2015-05-28 11 views
8

Ich habe Probleme beim Zuweisen von Unicode-Strings als Namen für ein namedtuple. Dies funktioniert:namedtuple mit Unicode-String als Name

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

und dies nicht:

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

Ich erhalte den Fehler

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: 'βαδιζόντων' 

Warum das der Fall ist? Die Dokumentation sagt, "Python 3 unterstützt auch die Verwendung von Unicode-Zeichen in Bezeichnern" und der Schlüssel ist Unicode gültig?

+1

Etwas, was mir aufgefallen ist: Es funktioniert gut, wenn ich das '' ó'' weglassen. Scheint mir wie ein Käfer. – pmos

+0

Interessant - ich hätte das selbst testen sollen. ó ist das einzige Zeichen aus dem Unicode-Block "Greek Extended", daher könnte dies relevant sein. Aber es würde immer noch nicht damit übereinstimmen, was die Dokumentation sagt. – Thomas

+0

Bei genauerem Hinsehen passiert, dass '' '' '' '' \ xe1 \ xbd \ xb9''' in der UTF-8 kodierten Quelldatei ist, aber in '' \ xcf \ x8c''' in dem von '' namedtuple'' generierten Code zum Generieren seiner Klasse. Das scheint definitiv ein Fehler zu sein. – pmos

Antwort

4

Das Problem ist speziell mit dem Buchstaben (U + 1F79 griechischen Kleinbuchstaben Omicron mit Oxia). Dies ist ein 'Kompatibilitätszeichen': Unicode würde stattdessen lieber ό verwenden (U + 03CC griechischer Kleinbuchstabe Omicron mit Tonos). U + 1F79 existiert nur in Unicode, um zu alten Zeichensätzen zu springen, die zwischen Oxia und Tonos unterscheiden, eine Unterscheidung, die sich später als falsch herausstellte.

Wenn Sie Kompatibilitätszeichen in einem Bezeichner verwenden, normalisiert der Quellcode-Parser von Python diese automatisch zu NFKC, sodass Ihr Klassenname mit U + 03CC endet.

Leider collections.namedtuple weiß nicht darüber. Die Art, wie die neue Klasseninstanz erstellt wird, besteht darin, den gegebenen Namen in eine Menge Python-Code in einer Zeichenfolge einzufügen, dann exec (yuck, richtig?) Und die Klasse aus dem resultierenden locals dict mit seinem Namen zu extrahieren ... der ursprüngliche Name, nicht die normalisierte Version, die Python tatsächlich kompiliert hat, so dass es fehlschlägt.

Dies ist ein Fehler in collections, die Einreichung wert sein kann, aber für jetzt sollten Sie das kanonische Zeichen U + 03CC ό verwenden.

+0

Arrg, jetzt verstehe ich! Ich wurde mehrmals von diesen Kompatibilitätszeichen für griechische Akzentbuchstaben gebissen. Zumindest erlaubt mir das, das Problem zu umgehen. Ich danke Ihnen für Ihre Erklärung! – Thomas

+0

Ein Verweis auf Quellcode wird nützlich sein https://hg.python.org/cpython/file/661cdbd617b8/Lib/collections/__init__.py#l332 – Kasramvd

2

Das ó ist U + 1F79 ɢʀᴇᴇᴋ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴏᴍɪᴄʀᴏɴ ᴡɪᴛʜ ᴏxɪᴀ. Python-Bezeichner werden wie NFKC normalisiert und U + 1F79 in NFKC wird zu U + 03CC ɢʀᴇᴇᴋ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴏᴍɪᴄʀᴏɴ ᴡɪᴛʜ ᴛᴏɴᴏs.

Interessanterweise funktioniert es, wenn Sie die gleiche Zeichenfolge mit U + 1F79 durch U + 03CC ersetzt verwenden.

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

Die Dokumentation für namedtuple behauptet, dass „Jede gültige Python-Kennung für einen Feldnamen verwendet werden kann“. Beide Strings sind gültige Python-Bezeichner, die im Interpreter einfach getestet werden können.

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

Dies ist definitiv ein Fehler in der Implementierung. Ich verfolgen sie auf dieses Bit bei der Umsetzung von namedtuple:

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

Ich denke, dass der Typname im namespace Wörterbuch links von exec'ing die class_definition Vorlage, ein Python-Bezeichner ist, in NFKC Form sein wird und somit keine länge den tatsächlichen Wert der Variablen typename, die zum Abrufen verwendet wird, nicht mehr überein. Ich glaube, einfach vor-Normalisierung typename sollte dies beheben, aber ich habe es nicht getestet.

+0

Danke, ich werde meinen Beitrag sanieren und auf das Beste hoffen! – Thomas

1

Althoug gibt es bereits eine akzeptierte Antwort lassen Sie mich ein

# 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 

Fix des Problems bieten Was macht es?

diese namedtuple_() Implementierung mit normalisiert die Namen vor sie an collections.namedtuple() Gabe, die es ermöglichen, kongruent Namen zu haben.

Dies ist eine Weiterentwicklung von @R. Martinho Fernandes 'Idee, die Namen vorherzusagen.

+0

Vielen Dank, das ist sehr hilfreich! Ich vermute, dass es meinen speziellen Anwendungsfall (der das Extrahieren einer Liste von Wörtern aus einer Textdatei und Vergleichen mit einer Liste von bekannten Wörtern beinhaltet) nicht lösen würde, aber es ist sehr gut zu haben! – Thomas

+0

es könnte helfen, hängt davon ab, wie/warum Sie es vergleichen ... Sie könnten die Kombination von Zeichen aus der NFKC-Form mit einer Regexp streichen und die gesamte Nominalisierung mit einer niedrigeren() – knitti

Verwandte Themen