这里涉及到两个不同的字符。一个是键盘上的
微符号,另一个是
希腊小写字母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*">
我们的两个字符,MICRO SIGN和GREEK SMALL LETTER MU,都是
Ll
Unicode组(小写字母)的一部分,因此它们都可以在标识符的任何位置使用。现在请注意,标识符的定义实际上是指
xid_start
和
xid_continue
,这些被定义为各自非-x定义中所有字符的NFKC归一化结果,其结果为标识符的有效字符序列。
显然,Python只关心标识符的规范化形式。这在下面得到了确认:
在解析时,所有标识符都被转换为正常形式NFKC;标识符的比较基于NFKC。
NFKC是
Unicode normalization,将字符分解为单独的部分。MICRO SIGN分解为GREEK SMALL LETTER MU,这正是正在发生的事情。
还有很多其他字符也受到这种规范化的影响。另一个例子是欧姆符号,它分解成希腊大写字母欧米伽。使用它作为标识符会得到类似的结果,这里使用本地语言显示:
>>> Ω = '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'
最终,这只是Python的一种行为。不幸的是,没有真正好的方法来检测这种行为,导致了像所示的错误。通常,在标识符仅被称为标识符时,即它被用作实际变量或属性时,一切都会很好:规范化每次运行,并找到标识符。
唯一的问题在于基于字符串的访问。字符串只是字符串,当然没有规范化发生(那将是一个坏主意)。而这里展示的两种方式,
getattr
和
locals
,都操作字典。
getattr()
通过对象的
__dict__
访问对象的属性,而
locals()
返回一个字典。在字典中,键可以是任何字符串,因此在其中具有微符号或欧姆符号是完全可以的。
在这些情况下,你需要记得自己进行规范化。我们可以利用unicodedata.normalize
来实现这一点,这样我们也可以正确地从locals()
(或使用getattr
)中获取我们的值:
>>> normalized_ohm = unicodedata.normalize('NFKC', 'Ω')
>>> locals()[normalized_ohm]
'bar'
class Test: mu = 'foo'
- Galax