【问题标题】:Identifier normalization: Why is the micro sign converted into the Greek letter mu?标识符规范化:为什么将微符号转换为希腊字母 mu?
【发布时间】:2016-03-09 21:48:12
【问题描述】:

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

>>> 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')

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

【问题讨论】:

    标签: python python-3.x unicode identifier python-internals


    【解决方案1】:

    What Python does here 基于Unicode Standard Annex #31

    将规范化和大小写考虑在内的实现有两种选择:将变体视为等效,或禁止变体。

    本节的其余部分提供了更多详细信息,但基本上,这意味着如果一种语言允许您拥有一个名为 µ 的标识符,它应该处理两个 µ 字符 MICRO SIGN 和 GREEK SMALL LETTER MU相同,并且应该将它们都视为希腊小写字母 MU。


    允许非 ASCII 标识符的大多数其他语言遵循相同的标准;1 只有少数语言发明了自己的。2 所以,这条规则的优点是在各种语言中都是相同的(并且可能受到 IDE 和其他工具的支持)。

    有一种情况是,它在像 Python 这样反射繁重的语言中确实不能很好地工作,在 Python 中,字符串可以像编写 getattr(Test, 'µ') 一样简单地用作标识符。但是如果你能读到the python-3000 mailing list discussions,就在PEP 3131附近;唯一认真考虑的选项是坚持使用 ASCII、UAX-31 或 Java 在 UAX-31 上的微小变化;没有人想为 Python 发明一个新标准。

    解决此问题的另一种方法是添加一个 collections.identifierdict 类型,该类型已记录为应用与编译器在源代码中应用的标识符完全相同的查找规则,并在旨在用作的映射中使用该类型命名空间(例如,对象、模块、局部变量、类定义)。我依稀记得有人建议过,但没有任何好的激励例子。如果有人认为这是一个足够好的例子来重振这个想法,他们可以将其发布到 bugs.python.orgthe python-ideas list


    1。一些语言,如 ECMAScript 和 C#,使用“Java 标准”,它基于早期形式的 UAX-31 并添加了一些小的扩展,比如忽略 RTL 控制代码——但这已经足够接近了。

    2。例如,Julia 允许使用 Unicode 货币和数学符号,并且还具有用于在 LaTeX 和 Unicode 标识符之间映射的规则——但他们明确添加了将ɛµ 规范化到希腊后者的规则……

    【讨论】:

      【解决方案2】:

      这里涉及到两个不同的角色。一个是MICRO SIGN,也就是键盘上的那个,另一个是GREEK SMALL LETTER MU

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

      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 组(小写字母)的一部分,因此它们都可以在标识符中的任何位置使用。现在请注意,identifier 的定义实际上是指xid_startxid_continue,它们被定义为各自的非 x 定义中的所有字符,其 NFKC 规范化导致标识符的有效字符序列。

      Python 显然只关心 规范化 形式的标识符。这一点在下面得到了证实:

      所有的标识符在解析的时候都转换成正常形式的NFKC;标识符的比较基于NFKC。

      NFKC 是一个Unicode normalization,它将字符分解为单独的部分。 MICRO SIGN 分解成希腊小写字母 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'
      

      所以说到底,这只是 Python 所做的事情。不幸的是,实际上并没有一种很好的方法来检测这种行为,从而导致出现如图所示的错误。通常,当标识符仅被称为标识符时,即像真正的变量或属性一样使用时,一切都会好起来的:每次都运行规范化,并找到标识符。

      唯一的问题是基于字符串的访问。字符串只是字符串,当然不会发生标准化(这只是个坏主意)。这里显示的两种方式,getattrlocals,都对字典进行操作。 getattr() 通过对象的__dict__ 访问对象的属性,locals() 返回一个字典。而且在字典中,键可以是任何字符串,所以里面有一个 MICRO SIGN 或一个 OHM SIGN 是完全可以的。

      在这些情况下,您需要记住自己执行规范化。我们可以为此使用unicodedata.normalize,这也允许我们从locals()(或使用getattr)中正确获取我们的值:

      >>> normalized_ohm = unicodedata.normalize('NFKC', 'Ω')
      >>> locals()[normalized_ohm]
      'bar'
      

      【讨论】:

      • 那是非常清楚和彻底的。即使在字符串文字中,我仍然尽量避免使用非 ASCII 字符,更不用说变量名了。规范化只是一个问题,一些编辑器也会弄乱事情,复制和粘贴更改编码等。class Test: mu = 'foo'
      • 只要您对源文件使用 UTF-8(您确实应该这样做),在大多数情况下使用 Python 3 就可以了,尤其是在字符串文字中。如果你的编辑器会搞砸,你应该找一个更好的编辑器 ;) 至于标识符,你也可以在那里发挥创造力,除了显示的问题可能会给某些人造成问题或完全不被其他人注意 :)
      猜你喜欢
      • 1970-01-01
      • 2014-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多