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
就好了。
- 这是在某处指定的吗?
- 这是有意为之的行为吗?
- 这是否仅取决于 Python 版本,或者还取决于有关 UTF-16 与 UTF-32 的编译时标志?
- 有没有办法在不区分大小写的情况下获得一致的行为?
- 如果区分大小写是不可避免的,具体的条件是什么?
只需在输入字符串中使用 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 的前进方向。但窄构建的向后兼容性对旧版本构成了冲突的目标。
如果范围的一个或两个端点在 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
就好了。
- 这是在某处指定的吗?
- 这是有意为之的行为吗?
- 这是否仅取决于 Python 版本,或者还取决于有关 UTF-16 与 UTF-32 的编译时标志?
- 有没有办法在不区分大小写的情况下获得一致的行为?
- 如果区分大小写是不可避免的,具体的条件是什么?
只需在输入字符串中使用 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 的前进方向。但窄构建的向后兼容性对旧版本构成了冲突的目标。