2009-05-01 14 views
95

Wir haben bereits unsere Code-Basis unter Python 2.6 laufen. Um für Python vorzubereiten 3.0, haben wir begonnen, und fügte hinzu:Irgendwelche Fehler mit Unicode_literals in Python 2.6?

 
from __future__ import unicode_literals 

in unsere .py Dateien (wie wir sie ändern). Ich frage mich, ob jemand anderes dies getan hat und auf irgendwelche nicht offensichtlichen Fallstricke gestoßen ist (vielleicht nachdem ich viel Zeit mit Debugging verbracht habe).

Antwort

99

Die Hauptquelle der Probleme, die ich mit Unicode-Strings arbeiten hätte ist, wenn Sie utf-8 kodierten Strings mischen mit Unicode-Einsen.

Betrachten Sie zum Beispiel die folgenden Skripts.

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 

Der Ausgang python one.py des Laufens ist:

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 diesem Beispiel two.name ist ein UTF-8-codierter String (nicht Unicode), da unicode_literals undnicht importiert wurdenist eine Unicode-Zeichenfolge. Wenn Sie beide mischen, versucht Python, die kodierte Zeichenkette zu dekodieren (vorausgesetzt, es ist ascii) und konvertiert sie in Unicode und schlägt fehl. Es würde funktionieren, wenn Sie print name + two.name.decode('utf-8') taten.

Das Gleiche kann passieren, wenn Sie eine Zeichenfolge codieren und versuchen, sie später zu mischen. Zum Beispiel funktioniert das:

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

Ausgang:

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

aber nach dem import unicode_literals Hinzufügen es nicht:

# 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 

Ausgang:

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) 

Es schlägt fehl, weil 'DEBUG: %s' eine Unicode-Zeichenfolge ist und daher versucht Python, html zu decodieren. Ein paar Möglichkeiten, um den Druck zu beheben, sind entweder print str('DEBUG: %s') % html oder print 'DEBUG: %s' % html.decode('utf-8').

Ich hoffe, dies hilft Ihnen, die potenziellen Fehler zu verstehen, wenn Sie Unicode-Strings verwenden.

+11

Ich würde vorschlagen, mit den 'decode()' Lösungen statt der 'str()' oder 'encode()' Lösungen zu gehen: Je öfter Sie Unicode-Objekte verwenden, desto klarer ist der Code, da was Sie wollen ist manipulieren Zeichenketten, nicht Arrays von Bytes mit einer extern implizierten Codierung. – EOL

+7

Bitte korrigieren Sie Ihre Terminologie.'Wenn Sie utf-8-kodierte Strings mit Unicode-Einsen mischen, gibt es in UTF-8 und Unicode nicht zwei verschiedene Kodierungen; Unicode ist ein Standard und UTF-8 ist eine der Codierungen, die es definiert. – Kos

+10

@Kos: Ich denke, er meint "mix" utf-8 codierte Strings "* Objekte * mit Unicode (also dekodierten) * Objekten *. Ersteres ist vom Typ "str", letzteres ist vom Typ "Unicode". Da es sich um verschiedene Objekte handelt, kann es zu Problemen kommen, wenn Sie versuchen, sie zu summieren/zu verketten/zu interpolieren. – MestreLion

13

Ich habe das finden, wenn Sie die unicode_literals Direktive etwas hinzufügen, wie Sie sollte auch hinzufügen:

# -*- coding: utf-8 

die erste oder zweite Zeile Ihrer .py-Datei. Ansonsten Zeilen wie:

foo = "barré" 

Ergebnis in einem ein Fehler wie:

 
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. Aber solltest du das nicht unter * allen * Umständen tun? –

+5

@IanMackinnon: Python 3 geht davon aus, dass Dateien standardmäßig UTF8 sind – endolith

+3

@endolith: Aber Python 2 nicht, und es wird den Syntaxfehler geben, wenn Sie Nicht-ASCII-Zeichen verwenden * sogar in Kommentaren *! Also IMHO '# - * - Codierung: utf-8' ist eine praktisch obligatorische Aussage, unabhängig davon, ob Sie' unicode_literals' verwenden oder nicht – MestreLion

16

Auch in 2.6 (vor Python 2.6.5 RC1 +) Unicode-Literale nicht spielen schön mit Schlüsselwort-Argumente (issue4978):

Der folgende Code beispielsweise arbeitet ohne unicode_literals, doch irgendwie Typeerror: keywords must be string wenn unicode_literals verwendet wird.

>>> 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

Just FYI, Python 2.6.5 RC1 + hat dies behoben. –

8

auch berücksichtigen, dass unicode_literaleval() beeinflussen aber nicht repr() (ein asymmetrisches Verhalten, das imho ein Fehler ist), das heißt eval(repr(b'\xa4')) nicht gleich zu b'\xa4' (wie wäre es mit Python 3).

Idealerweise ist der folgende Code eine Invariante sein, die für alle Kombinationen von unicode_literals und Python immer, sollte {2.7, 3.x} Nutzung:

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+ 

Die zweite Behauptung arbeiten geschieht, da repr('\xa4') wertet u'\xa4' in Python 2.7 aus.

+1

Ich fühle mich wie das größere Problem hier ist, dass Sie "Rep" verwenden, um ein Objekt zu regenerieren. Die ['repr' Dokumentation] (https://docs.python.org/2/library/functions.html#func-repr) besagt eindeutig, dass dies * keine * Voraussetzung ist. Meiner Meinung nach wird das "Repr" nur für das Debuggen verwendet. – jpmc26

5

Es gibt mehr.

Es gibt Bibliotheken und Builtins, die Zeichenfolgen erwarten, die Unicode nicht tolerieren.

Zwei Beispiele:

builtin:

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

(leicht esotic) funktioniert nicht mit unicode_literals: type() erwartet einen String.

Bibliothek:

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

funktioniert nicht: die wx PubSub Bibliothek erwartet einen String Nachrichtentyp.

Ersteres ist geheim und leicht behoben mit

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

aber letztere ist verheerend, wenn Ihr Code voller Anrufe pub.sendMessage ist() (die Mine).

Dang es, was?!?

+3

Und der Typ stuff dringt auch in Metaklassen ein - also sollten in Django alle Zeichenfolgen, die du in Klasse Meta deklarierst, 'b'field_name'' sein. –

+2

Yeah ... in meinem Fall wurde mir klar, dass es die Mühe wert war zu suchen und Ersetzen Sie alle sendMessage-Strings durch b-Versionen. Wenn Sie die gefürchtete "Dekodieren" -Ausnahme vermeiden möchten, gibt es nichts, was Sie in Ihrem Programm verwenden würden, wenn Sie Input und Output bei Bedarf konvertieren (das "Unicode-Sandwich", auf das ich in einem Artikel zu diesem Thema Bezug nehme). Alles in allem war unicode_literals ein großer Gewinn für mich ... – GreenAsJade