「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」
字符
Unicode 标准把字符的标识和具体的字节表述进行了如下的明确区分:
- 字符的标识,即码位,是 0~1 114 111 的数字(十进制),在 Unicode 标准中以 4~6 个十六进制数字表示,而且加前缀“U+”。例如,字母
A的码位是U+0041,欧元符号的码位是U+20AC。在 Unicode 6.3 中(这是 Python 3.4 使用的标准),约 10% 的有效码位有对应的字符。 - 字符的具体表述取决于所用的编码。编码是在码位和字节序列之间转换时使用的算法。在 UTF-8 编码中,
A(U+0041)的码位编码成单个字节\x41,而在 UTF-16LE 编码中编码成两个字节\x41\x00。
s = 'café' # Unicode 编码
len(s) # 4
b = s.encode('utf8') # b'caf\xc3\xa9'
len(b) # 5
b = b.decode('utf8') # 'café'
字节
bytes 或 bytearray 对象的各个元素是介于 0~255(含)之间的整数。
二进制序列的切片始终是同一类型的二进制序列,包括长度为 1 的切片。举个栗子:
cafe = bytes('café', encoding='utf_8') # b'caf\xc3\xa9'
cafe[0] # 99
cafe[:1] # b'c'
cafe_arr = bytearray(cafe) # bytearray(b'caf\xc3\xa9')
cafe_arr[-1:] # bytearray(b'\xa9')
构建 bytes 或 bytearray 实例可以调用各自的构造方法,传入下述参数:
- 一个
str对象和一个encoding关键字参数。 - 一个可迭代对象,提供 0~255 之间的数值。
- 一个实现了缓冲协议的对象(如
bytes、bytearray、memoryview、array.array);此时,把源对象中的字节序列复制到新建的二进制序列中
import array
numbers = array.array('h', [-2, -1, 0, 1, 2])
octets = bytes(numbers) # b'\xfe\xff\xff\xff\x00\x00\x01\x00\x02\x00'
虽然二进制序列其实是整数序列,但是它们的字面量表示法表明其中有 ASCII 文本。因此,各个字节的值可能会使用下列三种不同的方式显示:
- 可打印的 ASCII 范围内的字节(从空格到 ~),使用 ASCII 字符本身。
- 制表符、换行符、回车符和
\对应的字节,使用转义序列\t、\n、\r和\\。 - 其他字节的值,使用十六进制转义序列(例如,
\x00是空字节)。
规范化Unicode字符串
从上面的例子中可以看出, café 这个词可以使用两种方式构成,分别有 4 个( café )和 5 个( cafe\u0301)码位。
Unicode 标准中,é 和 e\u0301 这样的序列叫“标准等价物”,应用程序应该把它们视作相同的字符。但是,Python 看到的是不同的码位序列,因此判定二者不相等。
这个问题的解决方案是使用 unicodedata.normalize 函数提供的 Unicode 规范化。这个函数的第一个参数是这 4 个字符串中的一 个:'NFC'、'NFD'、'NFKC' 和 'NFKD':
- NFC:Normalization Form C。使用最少的码位构成等价的字符串。西方键盘通常能输出组合字符,因此用户输入的文本默认是 NFC 形式。不过,安全起见,保存文本之前,最好使用
normalize('NFC', user_text)清洗字符串。NFC 也是 W3C 的“Character Model for the World Wide Web: String Matching and Searching”规范。 - NFD 把组合字符分解成基字符和单独的组合字符。NFC 和 NFD 两种规范化方式都能让比较行为符合预期。
- 在另外两个规范化形式(NFKC 和 NFKD)的首字母缩略词中,字母 K 表示“compatibility”(兼容性)。这两种是较严格的规范化形式,对“兼容字符”有影响。例如二分之一 '½'(U+00BD)经过兼容分解后得到的是三个字符序列 '1/2';'4²' 会被转换成 '42' 。NFKC 或 NFKD 可能会损失或曲解信息,但是可以为搜索和索引提供便利的中间表述。