【Python】Python 中的字符与字节及规范化Unicode字符串

323 阅读3分钟

「这是我参与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é'

字节

bytesbytearray 对象的各个元素是介于 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')

构建 bytesbytearray 实例可以调用各自的构造方法,传入下述参数:

  • 一个 str 对象和一个 encoding 关键字参数。
  • 一个可迭代对象,提供 0~255 之间的数值。
  • 一个实现了缓冲协议的对象(如 bytesbytearraymemoryviewarray.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 可能会损失或曲解信息,但是可以为搜索和索引提供便利的中间表述。