Python 2.7:奇怪的 Unicode 行为

Python 2.7: Strange Unicode behavior

我在 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'

这种行为的解释是什么?更具体地说:

  1. 如果语句 #5 为真,我希望两个字符串相等,而 #3 证明否则。
  2. 像在语句#6 中一样将两个代码点编码在一起产生的结果与在#7 和#8 中一个一个地编码时产生的结果不同。看起来这两个代码点被视为一个 4 字节代码点。但是,如果我真的希望将它们视为两个不同的代码点呢?
  3. 正如你从 #9 中看到的,a2 中的数字实际上是使用 UTF-16-BE 编码的 a1,但尽管它们在内部使用 \u 指定为 Unicode 代码点一个 Unicode 字符串 (!), Python 仍然可以在 #5 中以某种方式达到相等。怎么可能?

这里没有任何意义!怎么回事?

A Python 2 在这里违反了 Unicode 标准,因为它允许您对 U+D800 到 U+DFFF 范围内的代码点进行编码,至少在 UCS4 版本中是这样。来自 Wikipedia:

The Unicode standard permanently reserves these code point values for UTF-16 encoding of the high and low surrogates, and they will never be assigned a character, so there should be no reason to encode them. The official Unicode standard says that no UTF forms, including UTF-16, can encode these code points.

官方 UTF-8 标准 没有针对 UTF-16 代理项对代码点的编码,因此 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 构建中,您的变量是 equal:

>>> import sys
>>> sys.maxunicode
65535
>>> a1 = u'\U0001f04f'
>>> a2 = u'\ud83c\udc4f'
>>> a1 == a2
True

因为在这样的构建中,所有非 BMP 代码点都被编码为 UTF-16 代理对(在 UCS2 标准上扩展)。

因此,在 UCS2 构建中,您的两个值之间没有区别,并且选择编码为完整的非 BMP 代码点是 完全有效 当您假设您想要对 U+1F04F 和其他此类代码点进行编码。 UCS4 构建恰好符合该行为。