2015-05-28 14 views
8

Sto riscontrando problemi nell'assegnare stringhe unicode come nomi per un namedtuple. Questo funziona:namedtuple con stringa unicode come nome

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

e questo non lo fa:

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

ottengo l'errore

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

Perché è questo il caso? La documentazione dice, "Python 3 supporta anche l'uso di caratteri Unicode negli identificatori" e la chiave è unicode valida?

+1

Qualcosa che ho notato: Funziona bene se lascio fuori il '' ó''. Sembra un bug per me. – pmos

+0

Interessante - avrei dovuto provarlo anch'io. ó è l'unico carattere del blocco Unicode "Greco esteso", quindi potrebbe essere rilevante. Ma sarebbe ancora in disaccordo con ciò che dice la documentazione. – Thomas

+0

A un esame più attento, ciò che accade è che, per qualche ragione, '' 'ó''' è ''' \ xe1 \ xbd \ xb9''' nel file sorgente codificato UTF-8, ma si trasforma in '' '\ xcf \ x8c''' nel codice generato da '' namedtuple'' per generare la sua classe. Questo sicuramente sembra un bug. – pmos

risposta

4

Il problema è specificamente con la lettera (U + 1F79 lettera minuscola greca omicron con oxia). Questo è un 'carattere di compatibilità': Unicode preferirebbe invece usare ό (U + 03CC lettera minuscola greca omicron con toni). U + 1F79 esiste solo in Unicode al fine di arrotondare ai vecchi set di caratteri che distinguono tra oxia e tonos, una distinzione che in seguito si è rivelata errata.

Quando si utilizzano i caratteri di compatibilità in un identificatore, il parser del codice sorgente di Python li normalizza automaticamente per formare NFKC, quindi il nome della classe termina con U + 03CC.

Purtroppo lo collections.namedtuple non lo sa. Il modo in cui crea la nuova istanza di classe è inserendo il nome dato in un gruppo di codice Python in una stringa, quindi exec uting it (yuck, giusto?) Ed estraendo la classe dai locali risultanti dict usando il suo nome ... il nome originale, non la versione normalizzata che Python ha effettivamente compilato, quindi fallisce.

Questo è un bug in collections che potrebbe valere la pena di essere archiviato, ma per ora è necessario utilizzare il carattere canonico U + 03CC ό.

+0

Arrg, ora capisco! Sono stato morso da questi caratteri di compatibilità per le lettere accentate greche un certo numero di volte. Almeno questo mi permette di aggirare il problema. Grazie per la tua spiegazione! – Thomas

+0

Un riferimento al codice sorgente sarà utile https://hg.python.org/cpython/file/661cdbd617b8/Lib/collections/__init__.py#l332 – Kasramvd

2

Che ó sia U + 1F79 ɢʀᴇᴇᴋ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴏᴍɪᴄʀᴏɴ ᴡɪᴛʜ ᴏxɪᴀ. Gli identificatori Python sono normalizzati come NFKC e U + 1F79 in NFKC diventa U + 03CC ɢʀᴇᴇᴋ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴏᴍɪᴄʀᴏɴ ᴡɪᴛʜ ᴛᴏɴᴏs.

È interessante notare che se si utilizza la stessa stringa con U + 1F79 sostituita da U + 03CC, funziona.

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

La documentazione per namedtuple afferma che "Qualsiasi identificatore Python valido può essere utilizzato per un nome di campo". Entrambe le stringhe sono identificatori Python validi, come possono essere facilmente testati nell'interprete.

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

Questo è sicuramente un bug nell'implementazione. Ho rintracciato a questo bit in attuazione della namedtuple:

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

Credo che il typename lasciato nel dizionario namespace dal exec'ing il modello class_definition, essendo un identificatore Python, sarà in forma NFKC, e quindi non più lungo corrisponde al valore effettivo della variabile typename utilizzata per recuperarlo. Credo che semplicemente la pre-normalizzazione di typename dovrebbe risolvere questo problema, ma non l'ho ancora testato.

+0

Grazie, sanifico il mio input e spero per il meglio! – Thomas

1

Althoug c'è già una risposta accettata Permettetemi di offrire un

Fix del problema

# 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 

Che cosa fa?

utilizzare questo namedtuple_() implementazione normalizzato i nomi prima di di consegnarli a collections.namedtuple(), rendendo possibile avere nomi congruenti.

Questa è un'elaborazione su @R. L'idea di Martinho Fernandes di pre-nominare i nomi.

+0

Grazie, è estremamente utile! Sospetto che non risolva il mio particolare caso d'uso (che comporta l'estrazione di un elenco di parole da un file di testo e il confronto con un elenco di parole conosciute), ma è molto bello avere! – Thomas

+0

potrebbe essere d'aiuto, dipende da come/perché lo paragoni ... potresti spogliare combinando chracters dal modulo NFKC con un'espressione regolare e potresti completare l'intera nomalizzazione con un valore inferiore() – knitti