1. Unicode 标准与 utf-8 编码 介绍

1.1 Unicode 标准

  • Unicode 是一种国际化规范,它为每个字符定义了一个数字编码,与平台,程序,和语言无关,它包含世界上所有语言和各种领域内的符号
  • Unicode 分为 17 代码平面(Code plan),目前只使用了,平面0、平面1、平面2、平面14、平面15、平面16
  • 最常用的在基本多语言平面(Basic Multilingual Plane,BMP。简称零平面 plane 0)里的所有字符,大部分常用的字符都坐落在这个平面内,比如ASCII字符,汉字等
  • 其他平面是辅助平面(缩写SMP)
  • 其中平面15和平面16上只是定义了两个各占65534个码位的专用区(Private Use Area),分别是0xF0000-0xFFFFD和0x100000-0x10FFFD。所谓专用区,就是保留给大家放自定义字符的区域,可以简写为PUA



深入了解 Unicode 可参见


1.2 编码字符集

编码是将字符串转换为字节序列的过程,计算机能保存,传输,处理的编码后的字节序列,下面是常用的一些编码标准

编码 说明
ASCII 最早的字符集,包含 127 个字符
LANTIN1 英语和西欧编码标准,也被称作 iso-8859-1
gb2312 中国最早提出的一套汉字编码标准,用双字节表示文字符。65536=3755(一级汉字)+3088(二级汉字)等
GBK gb2312 的替代,添加了一些繁体等
GBK2K GBK 的再次扩充,添加的少数名族的文字
UTF-8 是UNICODE的实现方式之一,便于网路传输的编码字体


1.3 utf-8 编码

UTF-8 以字节为单位对 Unicode 进行编码。从 Unicode 到 UTF-8 的编码方式如下

Unicode编码(十六进制) UTF-8 字节流(二进制)
000000-00007F 0xxxxxxx
000080-0007FF 110xxxxx 10xxxxxx
000800-00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000-1FFFFF 11110xxx10xxxxxx10xxxxxx10xxxxxx


2 Python 3 中的 Unicode 字符串

2.1 Python3 与 Python2 字符串区别

  • Python2 中字符串存在两种字符串,字节字符串(ascii,utf-8,gbk 等)或 Unicode 字符串。
  • Python3 中字符串只有 Unicode 字符串

2.2 Python 3 Unicode 表示方式

  • 用 \u 及 4 个十六进制数字表示零平面的编码字符
  • 用 \U 及 8 个十六进制数字表示更高平面的字符
  • 用 \N{name} 来引用一个字符,name 为该字符在 unicode 标准中的名称,http://www.unicode.org/charts/charindex.html可以查到字符的标准名称



Python 中的 unicodedata 提供了名称和 unicode 编码的转换方法

  • lookup() 通过使用不区分大小写的标准名称,返回一个 Unicode 字符
  • name() 通过 Unicode 字符,返回标准名称
In [10]: unicodedata.name('A')
Out[10]: 'LATIN CAPITAL LETTER A'

In [11]: unicodedata.lookup('LATIN CAPITAL LETTER A')
Out[11]: 'A'


In [12]: unicodedata.name('$')
Out[12]: 'DOLLAR SIGN'

In [13]: unicodedata.lookup('DOLLAR SIGN')
Out[13]: '$'


In [14]: unicodedata.name('¥')
Out[15]: 'FULLWIDTH YEN SIGN'

In [15]: unicodedata.lookup('FULLWIDTH YEN SIGN')
Out[15]: '¥'


2.3 字符串与 unicode 值互相转换

a = '你好中国'


# 从字符串转换到 Unicode 整数值
uni_hex = [hex(ord(i)) for i in a]
print(uni_hex)

# 输出如下
# [20320, 22909, 20013, 22269]


# 打印 16 进制 unicode 值
print([hex(i) for i in uni_hex])
# 输入如下
['0x4f60', '0x597d', '0x4e2d', '0x56fd']


# 从 unicode 数值获得字符
uni_chr = [chr(i) for i in uni_hex]
print(uni_chr)

# 输出如下
# ['你', '好', '中', '国']


3. Python 3 中 utf-8 编码使用

python 3 中使用 utf-8 编码保存或传输数据的时候,需要做以下操作

  • 字符串编码为字节(bytes)
  • 字节解码成字符串

从下面的例子来了解编码和解码的方式
注意不同类型编码的长度不同:unicode 字符串, utf-8 字节数组,gbk 字节数组

#!/usr/bin/env python
# coding: utf-8
'''
python 字符串编码,解码使用实例
'''

a = '每一天都有好心情'
print('unicode str 长度: ', len(a))

# 编码成 utf-8 字节数组
utf_bytes = a.encode('utf-8')
print(type(utf_bytes))
print('utf8 字节数组长度: ', len(utf_bytes))
print(utf_bytes)


# 从 utf-8 解码成 unicode 字符串
uni_chr = utf_bytes.decode()
print(type(uni_chr))
print(uni_chr)


# 编码成 gbk
gbk_bytes = a.encode('gbk')
print(type(gbk_bytes))
print('gbk 字节数组长度: ', len(utf_bytes))
print(gbk_bytes)

# 从 gbk 解码到 unicode 字符串
uni_chr = gbk_bytes.decode('gbk')
print(type(uni_chr))
print(uni_chr)


4. Python 中编码,解码过程中的一些错误

4.1 编码错误

在编码的过程中,如果原始的字符串有目标编码无法表示的字符时,会出现错误。
例如:原始字符串中有非 ascii 字符,这时候选择使用 ascii 进行编码会出现错误

my_str = 'hello 中文字符'
my_str.encode('ascii')

错误输出

Traceback (most recent call last):
  File "utf_test.py", line 36, in <module>
      my_str.encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 6-9: ordinal not in range(128)

这就需要更改正确的编码来使用,或者忽略错误的字符,或者把无法编码的字符替换“?”, 还可以替换成 unicode-escape 类似的 unicode 字符串

my_str = 'hello 中文字符'
print(my_str)

# 忽略无法编码的字符
new_str = my_str.encode('ascii', 'ignore')
print(new_str)

# 替换成 ?
new_str = my_str.encode('ascii', 'replace')
print(new_str)

# 替换成 unicode-escape 类似的 unicode 字符串
new_str = my_str.encode('ascii', 'backslashreplace')
print(new_str)

# 还可以编码成网页中使用的实体字符串
new_str = my_str.encode('ascii', 'xmlcharrefreplace')
print(new_str)

输出

hello 中文字符
b'hello '
b'hello ????'
b'hello \\u4e2d\\u6587\\u5b57\\u7b26'
b'hello &#20013;&#25991;&#23383;&#31526;'


4.2 解码错误

解码的时候必须选择与字节数组对应编码,原来用 ascii 编码的字节数组就用 ascii 解码,
原来用 utf-8 编码的字节数组,就用 utf-8 解码,不然会出现错误。
例如用 utf-8 编码的字节数组用 ascii 解码, 因为 ascii 编码范围较小,无法争取解码就会出错

my_str = 'hello 中文字符'
print(my_str)

utf8_bytes = my_str.encode('utf-8')
utf8_bytes.decode('ascii')

输出的错误信息

Traceback (most recent call last):
  File "utf_test.py", line 56, in <module>
      utf8_bytes.decode('ascii')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 6: ordinal not in range(128)


5. 总结

  • Python 3 中字符串统一使用 unicode 编码
  • 在字符串保存,传输的时候尽可能使用 utf-8 编码,它兼容性好,可以表达全部的 unicode 字符
  • 编码和解码使用相同的方式
right