Именованный кортеж со строкой юникода в качестве имени
У меня проблемы с назначением юникодных строк в качестве имен для именованного кортежа. Это работает:
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. Идея Мартино Фернандеса по предварительному номинированию имен.