我在Python 2.7中遇到以下行为:
>>> a1 = u'\U0001f04f' #1
>>> a2 = u'\ud83c\udc4f' #2
>>> a1 == a2 #3
False
>>> a1.encode('utf8') == a2.encode('utf8') #4
True
>>> a1.encode('utf8').decode('utf8') == a2.encode('utf8').decode('utf8') #5
True
>>> u'\ud83c\udc4f'.encode('utf8') #6
'\xf0\x9f\x81\x8f'
>>> u'\ud83c'.encode('utf8') #7
'\xed\xa0\xbc'
>>> u'\udc4f'.encode('utf8') #8
'\xed\xb1\x8f'
>>> '\xd8\x3c\xdc\x4f'.decode('utf_16_be') #9
u'\U0001f04f'
对此行为的解释是什么?更具体地说:
a2
中的数字实际上是使用UTF-16-BE进行a1
编码的,但是尽管它们是在Unicode中使用\u
指定为Unicode代码点的字符串(!),Python仍然可以以某种方式在#5中达到相等。怎么可能?这里没有任何意义!发生了什么事?
答案 0 :(得分:4)
Python 2违反了Unicode标准,因为它允许您至少在UCS4构建中对U + D800到U + DFFF范围内的代码点进行编码。来自Wikipedia:
Unicode标准将这些代码点值永久保留为高和低替代项的UTF-16编码,并且永远不会分配给它们一个字符,因此应该没有理由对其进行编码。正式的Unicode标准说,没有UTF格式(包括UTF-16)可以对这些代码点进行编码。
UTF-16代理对代码点的官方UTF-8标准没有编码,因此当您尝试使用Python 3时会引发异常:
>>> '\ud83c\udc4f'.encode('utf8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'utf-8' codec can't encode characters in position 0-1: surrogates not allowed
但是Python 2对Unicode的支持更为基本,并且您观察到的行为随specific UCS2 / UCS4 build variant的不同而变化;在UCS2构建中,您的变量为等于:
>>> import sys
>>> sys.maxunicode
65535
>>> a1 = u'\U0001f04f'
>>> a2 = u'\ud83c\udc4f'
>>> a1 == a2
True
因为在这样的构建中,所有非BMP代码点都被编码为UTF-16代理对(在UCS2标准上扩展)。
因此,在UCS2构建中,两个值之间没有区别,并且当您假设要编码U + 1F04F时,选择编码为完全非BMP代码点是完全有效和其他此类代码点。 UCS4构建恰好符合该行为。