Python 涉及星界的 unicode 范围的语义

Python semantics for unicode ranges involving astral planes

如果范围的一个或两个端点在 BMP 之外,正则表达式中字符范围的预期语义到底是什么?我观察到以下输入在 Python 2.7 和 3.5 中表现不同:

import re
bool(re.match(u"[\u1000-\U00021111]", "\u1234"))

在我的 2.7 中我得到 False,在 3.5 中我得到 True。后者对我来说很有意义。前者可能是由于 \U00021111 由代理对 \ud844\udd11 表示,但即便如此我还是不明白,因为 \u1000-\ud844 应该包括 \u1234 就好了。

只需在输入字符串中使用 u 前缀即可告诉 Python 这是一个 Unicode 字符串:

>>> bool(re.match(u"[\u1000-\U00021111]", u"\u1234")) # <= See u"\u1234"
True

在Python2.7中,每次处理字符串时都需要将字符串解码为Unicode。在Python 3中,所有字符串默认都是Unicode,在docs.

中说明

这是我到目前为止发现的。

PEP 261 which got accepted for Python 2.2 introduced a compile-time flag to build unicode support either using a narrow UTF-16 representation or a wide UTF-32 representation of characters. Check hex(sys.maxunicode) or len(u'\U00012345') to distinguish these at runtime: narrow builds will report a maximum of 0xffff and a length of 2, wide builds a maximum of 0x10ffff and a length of 1. PEP 393 for Python 3.3 隐藏了 unicode 字符串的实现细节,使所有字符串看起来像 UTF-32(除非必要,否则实际上不会浪费那么多 space)。因此,3.3 之前的窄版本会将星体平面上的代码点分解为代理项对,并独立对待各个代理项,以构建正则表达式和要匹配的字符串。或者至少我找不到相反的迹象。

正如 Wiktor 指出的那样,我的示例很愚蠢,因为我忘记了第二个字符串文字的 u 前缀。因此 Python 2 不会将其解析为转义序列,而是解析为字节字符串。这就解释了为什么即使在考虑了代理对之后,代码点似乎也没有包含在该范围内。

至于预期的行为:自 Python 3.3 起,基于构建类型的区别应该已过时。将每个代码点视为一个单元,无论平面如何,都应该是 Python 3 的前进方向。但窄构建的向后兼容性对旧版本构成了冲突的目标。