标识符规范化:为什么微标志转换为希腊字母mu?

pok*_*oke 25 python unicode identifier python-3.x python-internals

我偶然发现了以下奇怪的情况:

>>> class Test:
        µ = 'foo'

>>> Test.µ
'foo'
>>> getattr(Test, 'µ')
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    getattr(Test, 'µ')
AttributeError: type object 'Test' has no attribute 'µ'
>>> 'µ'.encode(), dir(Test)[-1].encode()
(b'\xc2\xb5', b'\xce\xbc')
Run Code Online (Sandbox Code Playgroud)

我输入的字符始终是键盘上的μ符号,但由于某种原因它会被转换.为什么会这样?

pok*_*oke 27

这里涉及两个不同的角色.一个是MICRO SIGN,它是键盘上的一个,另一个是GREEK SMALL LETTER MU.

要了解发生了什么,我们应该看看Python如何在语言参考中定义标识符:

identifier   ::=  xid_start xid_continue*
id_start     ::=  <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>
id_continue  ::=  <all characters in id_start, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>
xid_start    ::=  <all characters in id_start whose NFKC normalization is in "id_start xid_continue*">
xid_continue ::=  <all characters in id_continue whose NFKC normalization is in "id_continue*">
Run Code Online (Sandbox Code Playgroud)

我们的字符MICRO SIGN和GREEK SMALL LETTER MU都是Llunicode组(小写字母)的一部分,因此它们都可以在标识符的任何位置使用.现在请注意,identifier实际引用的定义是xid_startxid_continue,并且它们被定义为相应的非x定义中的所有字符,其NFKC规范化导致标识符的有效字符序列.

Python显然只关心标准化的标准化形式.这有点如下:

解析时,所有标识符都转换为正常格式NFKC; 标识符的比较基于NFKC.

NFKC是一种Unicode规范化,可将字符分解为单个部分.MICRO SIGN分解为GREEK SMALL LETTER MU,这就是那里正在发生的事情.

还有很多其他角色也会受到此规范化的影响.另一个例子是OHM SIGN,它分解为GREEK CAPITAL LETTER OMEGA.使用它作为标识符给出了类似的结果,这里使用locals显示:

>>> ? = 'bar'
>>> locals()['?']
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    locals()['?']
KeyError: '?'
>>> [k for k, v in locals().items() if v == 'bar'][0].encode()
b'\xce\xa9'
>>> '?'.encode()
b'\xe2\x84\xa6'
Run Code Online (Sandbox Code Playgroud)

所以最后,这只是Python所做的事情.不幸的是,没有一种很好的方法可以检测到这种行为,从而导致错误,例如显示的错误.通常,当标识符仅被称为标识符时,即它像真实变量或属性一样使用时,一切都会正常:标准化每次运行,并找到标识符.

唯一的问题是基于字符串的访问.字符串只是字符串,当然没有规范化发生(这只是一个坏主意).这里显示的两种方式,getattr并且locals,无论在字典操作.getattr()通过对象访问对象的属性__dict__,并locals()返回一个字典.在字典中,键可以是任何字符串,因此在那里有一个MICRO SIGN或OHM SIGN是完全没问题的.

在这些情况下,您需要记住自己执行标准化.我们可以利用unicodedata.normalize它,这也允许我们从内部locals()(或使用getattr)正确地获取我们的价值:

>>> normalized_ohm = unicodedata.normalize('NFKC', '?')
>>> locals()[normalized_ohm]
'bar'
Run Code Online (Sandbox Code Playgroud)