Именованный кортеж со строкой юникода в качестве имени

У меня проблемы с назначением юникодных строк в качестве имен для именованного кортежа. Это работает:

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 также поддерживает использование символов Unicode в идентификаторах", и ключ является действительным Unicode?

3 ответа

Решение

Проблема конкретно с письмом (U+1F79 греческая строчная буква омикрон с оксией). Это "символ совместимости": Unicode лучше использовать ό вместо этого (U+03CC греческая строчная буква omicron с тоннами). U+1F79 существует только в Юникоде для того, чтобы совершить круговую поездку к старым наборам символов, которые различают оксию и тонос, и это отличие позже оказалось неверным.

Когда вы используете символы совместимости в идентификаторе, синтаксический анализатор исходного кода Python автоматически нормализует их для формирования NFKC, поэтому имя вашего класса заканчивается в нем U + 03CC.

к несчастью collections.namedtuple не знает об этом. Он создает новый экземпляр класса, вставляя указанное имя в связку кода Python в строку, а затем execиспользуя его (да, верно?) и извлекая класс из результирующих локальных текстов, используя его имя... оригинальное имя, а не нормализованную версию, которую Python фактически скомпилировал, поэтому он терпит неудачу.

Это ошибка в collections что может стоить подать, но сейчас вы должны использовать канонический символ U + 03CC ό,

Тот ó это U+1F79 ɢʀᴇᴇᴋ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴏᴍɪᴄʀᴏɴ ᴡɪᴛʜ ᴏxɪᴀ. Идентификаторы Python нормализованы как NFKC, и U + 1F79 в NFKC становится U+03CC.

Интересно, что если вы используете ту же строку с U + 1F79, замененным на U+03CC, это работает.

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

Документация для namedtuple утверждает, что "Любой действительный идентификатор Python может быть использован для имени поля". Обе строки являются допустимыми идентификаторами Python, что легко проверяется в интерпретаторе.

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

Это определенно ошибка в реализации. Я проследил это в реализации namedtuple:

namespace = dict(__name__='namedtuple_%s' % typename)
exec(class_definition, namespace)
result = namespace[typename] # here!

Я думаю, что типовое имя осталось в namespace словарь, выполняя class_definition template, являющийся идентификатором Python, будет в форме NFKC и, следовательно, больше не будет соответствовать фактическому значению typename переменная используется для его получения. Я считаю, просто предварительно нормализуя typename должен исправить это, но я не проверял это.

Хотя уже есть принятый ответ, позвольте мне предложить

Исправление проблемы

# 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. Идея Мартино Фернандеса по предварительному номинированию имен.

Другие вопросы по тегам