以 unicode 字符串为名称的 namedtuple

namedtuple with unicode string as name

我在将 unicode 字符串指定为命名元组的名称时遇到问题。这有效:

a = collections.namedtuple("test", "value")

而这不是:

b = collections.namedtuple("βαδιζόντων", "value")

我收到错误

Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "/usr/lib64/python3.4/collections/__init__.py", line 370, in namedtuple
        result = namespace[typename]
KeyError: 'βαδιζόντων'

为什么会这样?文档说,"Python 3 also supports using Unicode characters in identifiers," 密钥是有效的 unicode?

ó就是U+1F79 ɢʀᴇᴇᴋ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴏᴍɪᴄʀᴏɴ ᴡɪᴛʜ ᴏxɪᴀ。 Python标识符规范化为NFKC,NFKC中的U+1F79变为U+03CC ɢʀᴇᴇᴋ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴏᴍɪᴄʀᴏɴ ᴡɪᴛʜ ᴛᴏɴᴏ=s].[=20

有趣的是,如果您使用 U+1F79 替换为 U+03CC 的相同字符串,它会起作用。

>>> b = collections.namedtuple("βαδιζ\u03CCντων", "value")
>>>

namedtuple 的文档声称 "Any valid Python identifier may be used for a fieldname"。这两个字符串都是有效的 Python 标识符,可以在解释器中轻松测试。

>>> βαδιζόντων = 0
>>> βαδιζόντων = 0
>>>

这绝对是实现中的错误。我在 namedtuple:

的实现中追踪到了这一点
namespace = dict(__name__='namedtuple_%s' % typename)
exec(class_definition, namespace)
result = namespace[typename] # here!

我猜想通过执行 class_definition 模板留在 namespace 字典中的类型名,作为 Python 标识符,将采用 NFKC 形式,因此不再匹配用于检索它的 typename 变量的实际值。我相信简单的预规范化 typename 应该可以解决这个问题,但我还没有测试过。

问题出在字母 (U+1F79 带有 oxia 的希腊小写字母 omicron)。这是一个“兼容性字符”:Unicode 宁愿您使用 ό 代替(U+03CC 希腊小写字母 omicron 和 tonos)。 U+1F79 仅存在于 Unicode 中,以便往返于区分 oxia 和 tonos 的旧字符集,后来证明这种区分是不正确的。

当您在标识符中使用兼容性字符时,Python 的源代码解析器会自动将它们规范化以形成 NFKC,因此您的 class 名称以 U+03CC 结尾。

很遗憾 collections.namedtuple 不知道这件事。它创建新的 class 实例的方式是将给定的名称插入到字符串中的一堆 Python 代码中,然后 exec 对其进行处理(糟糕,对吗?),然后提取class 来自使用其名称的结果 locals dict...原始名称,而不是规范化版本 Python 实际上已编译,因此失败。

这是 collections 中的一个错误,可能值得归档,但现在您应该使用规范字符 U+03CC ό

虽然已经有一个被接受的答案让我提供一个

问题修复

# coding: utf-8
import collections
import unicodedata


def namedtuple_(typename, field_names, verbose=False, rename=False):
    ''' just like collections.namedtuple(), but does unicode nomalization
        on names
    '''

    if isinstance(field_names, str):
        field_names = field_names.replace(',', ' ').split()
    field_names = [
        unicodedata.normalize('NFKC', name) for name in field_names]
    typename = unicodedata.normalize('NFKC', typename)

    return collections.namedtuple(
        typename, field_names, verbose=False, rename=False)


βαδιζόντων = namedtuple_('βαδιζόντων', 'value')

a = βαδιζόντων(1)

print(a)
# βαδιζόντων(value=1)
print(a.value == 1)
# True

它有什么作用?

使用此 namedtuple_() 实现规范化了之前的名称 将它们交给 collections.namedtuple(),这样就可以使用一致的名称。

这是对@R 的阐述。 Martinho Fernandes 对名称进行预规范化的想法。