2009-05-01 10 views
95

Abbiamo già ottenuto il nostro codice base in esecuzione in Python 2.6. Al fine di prepararsi per Python 3.0, abbiamo iniziato l'aggiunta di:Qualche trucco con unicode_literals in Python 2.6?

 
from __future__ import unicode_literals 

nelle nostre file .py (come noi modifichiamo loro). Mi chiedo se qualcun altro ha fatto questo e si è imbattuto in trucchi non ovvi (forse dopo aver passato un sacco di tempo a debug).

risposta

99

La principale fonte di problemi che ho avuto che lavorano con le stringhe Unicode è quando si mescolano stringhe UTF-8 codificati con quelli unicode.

Ad esempio, considerare i seguenti script.

two.py

# encoding: utf-8 
name = 'helló wörld from two' 

one.py

# encoding: utf-8 
from __future__ import unicode_literals 
import two 
name = 'helló wörld from one' 
print name + two.name 

L'uscita di esecuzione python one.py è:

Traceback (most recent call last): 
    File "one.py", line 5, in <module> 
    print name + two.name 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 4: ordinal not in range(128) 

In questo esempio, two.name corda è un utf-8 codificato (non unicode) poiché non importava unicode_literals e one.name è una stringa unicode. Quando si mescolano entrambi, python prova a decodificare la stringa codificata (assumendo che sia ascii) e convertirla in unicode e fallisce. Funzionerebbe se lo facessi print name + two.name.decode('utf-8').

La stessa cosa può succedere se si codifica una stringa e si prova a mescolarli in un secondo momento. Per esempio, questo funziona:

# encoding: utf-8 
html = '<html><body>helló wörld</body></html>' 
if isinstance(html, unicode): 
    html = html.encode('utf-8') 
print 'DEBUG: %s' % html 

uscita:

DEBUG: <html><body>helló wörld</body></html> 

Ma dopo l'aggiunta del import unicode_literals non è così:

# encoding: utf-8 
from __future__ import unicode_literals 
html = '<html><body>helló wörld</body></html>' 
if isinstance(html, unicode): 
    html = html.encode('utf-8') 
print 'DEBUG: %s' % html 

uscita:

Traceback (most recent call last): 
    File "test.py", line 6, in <module> 
    print 'DEBUG: %s' % html 
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 16: ordinal not in range(128) 

Fallisce perché 'DEBUG: %s' è una stringa unicode e quindi python prova a decodificare html. Un paio di modi per risolvere la stampa stanno facendo print str('DEBUG: %s') % html o print 'DEBUG: %s' % html.decode('utf-8').

Spero che questo ti aiuti a capire i potenziali trucchi quando usi stringhe Unicode.

+11

Suggerirei di andare con le soluzioni 'decode()' invece delle soluzioni 'str()' o 'encode()': più spesso usi gli oggetti Unicode, più chiaro è il codice, poiché ciò che vuoi è manipola stringhe di caratteri, non matrici di byte con una codifica implicita esternamente. – EOL

+7

Si prega di correggere la terminologia.'quando si mischiano le stringhe codificate utf-8 con quelle unicode' UTF-8 e Unicode e non 2 diverse codifiche; Unicode è uno standard e UTF-8 è una delle codifiche che definisce. – Kos

+10

@Kos: Penso che voglia dire mixare "utf-8 encoded stringhe" * objects * con unicode (quindi decodificato) * oggetti *. Il primo è di tipo 'str', il secondo è tipo' unicode'. Essendo oggetti diversi, può sorgere un problema se si tenta di sommare/concatenare/interpolare loro – MestreLion

13

Ho trovato che se si aggiunge la direttiva unicode_literals si dovrebbe anche aggiungere qualcosa come:

# -*- coding: utf-8 

alla prima o seconda linea il file .py. In caso contrario, linee come:

foo = "barré" 

risultato in un un errore come:

 
SyntaxError: Non-ASCII character '\xc3' in file mumble.py on line 198, 
but no encoding declared; see http://www.python.org/peps/pep-0263.html 
for details 
+5

+1. Anche se non dovresti farlo in * tutte * le circostanze? –

+5

@IanMackinnon: Python 3 presuppone che i file siano UTF8 per impostazione predefinita – endolith

+3

@endolith: Ma Python 2 non lo fa e darà l'errore di sintassi se si utilizzano caratteri non ascii * anche nei commenti *! Quindi IMHO '# - * - coding: utf-8' è un'istruzione praticamente obbligatoria indipendentemente se si usa' unicode_literals' o no – MestreLion

16

Anche in 2.6 (prima di python 2.6.5 RC1 letterali +) unicode non viene riprodotto bello, con argomenti a parola chiave (issue4978):

Il seguente codice di esempio funziona senza unicode_literals, ma fallisce con TypeError: keywords must be string se viene utilizzato unicode_literals.

>>> def foo(a=None): pass 
    ... 
    >>> foo(**{'a':1}) 
    Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
     TypeError: foo() keywords must be strings 
+16

Solo FYI, Python 2.6.5 RC1 + ha risolto questo problema. –

8

anche tener conto che unicode_literal interesserà eval() ma non repr() (un comportamento asimmetrico che IMHO è un insetto), cioè eval(repr(b'\xa4')) non sarà uguale a b'\xa4' (come farebbe con Python 3).

Idealmente, il seguente codice sarebbe un invariante, che dovrebbe sempre funzionare, per tutte le combinazioni di unicode_literals e Python {2.7, 3.x} Utilizzo:

from __future__ import unicode_literals 

bstr = b'\xa4' 
assert eval(repr(bstr)) == bstr # fails in Python 2.7, holds in 3.1+ 

ustr = '\xa4' 
assert eval(repr(ustr)) == ustr # holds in Python 2.7 and 3.1+ 

La seconda affermazione succede a lavorare, poiché repr('\xa4') restituisce u'\xa4' in Python 2.7.

+1

Mi sembra che il problema più grande qui è che stai usando 'repr' per rigenerare un oggetto. La ['repr' documentation] (https://docs.python.org/2/library/functions.html#func-repr) afferma chiaramente che questo * non * è un requisito. A mio parere, questo relega 'repr' a qualcosa di utile solo per il debug. – jpmc26

5

Ce ne sono di più.

Esistono librerie e built-in che prevedono le stringhe che non tollerano l'unicode.

Due esempi:

incorporati:

myenum = type('Enum',(), enum) 

(leggermente esotic) non funziona con unicode_literals: type() si aspetta una stringa.

libreria:

from wx.lib.pubsub import pub 
pub.sendMessage("LOG MESSAGE", msg="no go for unicode literals") 

non funziona: la libreria WX PubSub si aspetta un tipo di messaggio di stringa.

Il primo è esoterica e facilmente fissato con

myenum = type(b'Enum',(), enum) 

ma il secondo è devastante se il codice è pieno di chiamate a pub.sendMessage() (che il mio è).

Dang, eh?!?

+3

E anche il tipo di materiale passa in metaclassi - quindi in Django qualsiasi stringa dichiari in 'class Meta:' dovrebbe essere 'b'field_name'' –

+2

Sì ... nel mio caso ho capito che valeva la pena di cercare e sostituisci tutte le stringhe sendMessage con b 'versioni. Se si vuole evitare la temuta eccezione di "decodifica", non c'è niente come usare rigorosamente l'unicode nel proprio programma, convertendo in input e output se necessario (il "sandwich unicode" a cui si fa riferimento in alcuni articoli che ho letto sull'argomento). Nel complesso, unicode_literals è stata una grande vittoria per me ... – GreenAsJade

Problemi correlati