Python 2.7:奇怪的Unicode行为

时间:2018-11-04 12:22:24

标签: python unicode utf-8 character-encoding surrogate-pairs

我在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编码的,但是尽管它们是在Unicode中使用\u指定为Unicode代码点的字符串(!),Python仍然可以以某种方式在#5中达到相等。怎么可能?

这里没有任何意义!发生了什么事?

1 个答案:

答案 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构建恰好符合该行为。