我偶然发现了以下奇怪的情况:
>>> class Test: µ = 'foo' >>> Test.µ 'foo' >>> getattr(Test, 'µ') Traceback (most recent call last): File "", line 1, in getattr(Test, 'µ') AttributeError: type object 'Test' has no attribute 'µ' >>> 'µ'.encode(), dir(Test)[-1].encode() (b'\xc2\xb5', b'\xce\xbc')
我输入的字符始终是键盘上的μ符号,但由于某种原因它会被转换.为什么会这样?
这里涉及两个不同的角色.一个是MICRO SIGN,它是键盘上的一个,另一个是GREEK SMALL LETTER MU.
要了解发生了什么,我们应该看看Python如何在语言参考中定义标识符:
identifier ::= xid_start xid_continue* id_start ::=id_continue ::= xid_start ::= xid_continue ::=
我们的字符MICRO SIGN和GREEK SMALL LETTER MU都是Ll
unicode组(小写字母)的一部分,因此它们都可以在标识符的任何位置使用.现在请注意,identifier
实际引用的定义是xid_start
和xid_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 "", line 1, in locals()['?'] KeyError: '?' >>> [k for k, v in locals().items() if v == 'bar'][0].encode() b'\xce\xa9' >>> '?'.encode() b'\xe2\x84\xa6'
所以最后,这只是Python所做的事情.不幸的是,没有一种很好的方法可以检测到这种行为,从而导致错误,例如显示的错误.通常,当标识符仅被称为标识符时,即它像真实变量或属性一样使用时,一切都会正常:标准化每次运行,并找到标识符.
唯一的问题是基于字符串的访问.字符串只是字符串,当然没有规范化发生(这只是一个坏主意).这里显示的两种方式,getattr
并且locals
,无论在字典操作.getattr()
通过对象访问对象的属性__dict__
,并locals()
返回一个字典.在字典中,键可以是任何字符串,因此在那里有一个MICRO SIGN或OHM SIGN是完全没问题的.
在这些情况下,您需要记住自己执行标准化.我们可以利用unicodedata.normalize
它,这也允许我们从内部locals()
(或使用getattr
)正确地获取我们的价值:
>>> normalized_ohm = unicodedata.normalize('NFKC', '?') >>> locals()[normalized_ohm] 'bar'