Gibt es Fallstricke, die unicode_literals in Python 2.6 verwenden?

101

Wir haben unsere Codebasis bereits unter Python 2.6 zum Laufen gebracht. Um sich auf Python 3.0 vorzubereiten, haben wir Folgendes hinzugefügt:

aus __future__ importiere unicode_literals

in unsere .pyDateien (wie wir sie ändern). Ich frage mich, ob jemand anderes dies getan hat und auf nicht offensichtliche Fallstricke gestoßen ist (möglicherweise nachdem er viel Zeit mit dem Debuggen verbracht hat).

Jacob Gabrielson
quelle

Antworten:

101

Die Hauptursache für Probleme bei der Arbeit mit Unicode-Zeichenfolgen ist das Mischen von utf-8-codierten Zeichenfolgen mit Unicode-Zeichenfolgen.

Betrachten Sie beispielsweise die folgenden Skripte.

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

Die Ausgabe von running python one.pyist:

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.namehandelt es sich um eine utf-8-codierte Zeichenfolge (kein Unicode), da sie nicht importiert wurde unicode_literals, und one.nameum eine Unicode-Zeichenfolge. Wenn Sie beide mischen, versucht Python, die codierte Zeichenfolge zu dekodieren (vorausgesetzt, es handelt sich um ASCII), und konvertiert sie in Unicode. Es würde funktionieren, wenn Sie es tun würden print name + two.name.decode('utf-8').

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

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

Ausgabe:

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

Aber nach dem Hinzufügen der import unicode_literalstut 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

Ausgabe:

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'es sich um eine Unicode-Zeichenfolge handelt und Python daher versucht, sie zu dekodieren html. Ein paar Möglichkeiten , um den Druck zu beheben sind entweder tun print str('DEBUG: %s') % htmloder print 'DEBUG: %s' % html.decode('utf-8').

Ich hoffe, dies hilft Ihnen dabei, die möglichen Fallstricke bei der Verwendung von Unicode-Zeichenfolgen zu verstehen.

Koba
quelle
11
Ich würde vorschlagen, die decode()Lösungen anstelle der str()oder encode()-Lösungen zu verwenden: Je häufiger Sie Unicode-Objekte verwenden, desto klarer ist der Code, da Sie Zeichenfolgen und keine Byte-Arrays mit einer extern implizierten Codierung bearbeiten möchten.
Eric O Lebigot
8
Bitte korrigieren Sie Ihre Terminologie. when you mix utf-8 encoded strings with unicode onesUTF-8 und Unicode sind nicht zwei verschiedene Codierungen. Unicode ist ein Standard und UTF-8 ist eine der von ihm definierten Codierungen.
Kos
11
@Kos: Ich glaube , er bedeutet mischen „utf-8 kodierten Strings“ Objekte mit Unicode (daher decodiert) Objekte . Ersteres ist vom Typ str, letzteres ist vom Typ unicode. Da es sich um verschiedene Objekte handelt, kann ein Problem auftreten, wenn Sie versuchen, diese zu summieren / zu verketten / zu interpolieren
MestreLion
Gilt das für python>=2.6oder python==2.6?
Joar
16

Auch in 2.6 (vor Python 2.6.5 RC1 +) spielen Unicode-Literale nicht gut mit Schlüsselwortargumenten ( Ausgabe 4978 ):

Der folgende Code funktioniert beispielsweise ohne unicode_literals, schlägt jedoch mit TypeError fehl: keywords must be stringWenn 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
mfazekas
quelle
17
Nur zu Ihrer Information, Python 2.6.5 RC1 + hat dies behoben.
Mahmoud Abdelkader
13

Ich habe festgestellt, dass Sie, wenn Sie die unicode_literalsDirektive hinzufügen, auch Folgendes hinzufügen sollten:

 # -*- coding: utf-8

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

 foo = "barré"

führen zu einem Fehler wie:

SyntaxError: Nicht-ASCII-Zeichen '\ xc3' in der Datei mumble.py in Zeile 198,
 aber keine Kodierung deklariert; Siehe http://www.python.org/peps/pep-0263.html
 für Details
Jacob Gabrielson
quelle
5
@ IanMackinnon: Python 3 geht davon aus, dass Dateien standardmäßig UTF8 sind
Endolith
3
@endolith: Aber Python 2 tut es nicht und es wird den Syntaxfehler geben, wenn Sie Nicht-ASCII-Zeichen auch in Kommentaren verwenden ! IMHO # -*- coding: utf-8ist also eine praktisch obligatorische Aussage, unabhängig davon, ob Sie sie verwenden unicode_literalsoder nicht
MestreLion
Das -*-ist nicht erforderlich; Wenn Sie sich für den Emacs-kompatiblen Weg entscheiden würden, würden Sie ihn wahrscheinlich brauchen -*- encoding: utf-8 -*-(siehe auch -*-am Ende). Alles was Sie brauchen ist coding: utf-8(oder sogar =statt : ).
Chris Morgan
2
Sie erhalten diesen Fehler, unabhängig davon, ob Sie from __future__ import unicode_literals.
Flimm
3
Emacs-Kompatibilität erfordert # -*- coding: utf-8 -*- "Codierung" (nicht "Codierung" oder "Dateicodierung" oder irgendetwas anderes - Python sucht nur nach "Codierung", unabhängig von einem Präfix).
Alex Dupuy
7

Berücksichtigen Sie auch, dass dies unicode_literalAuswirkungen hat, eval()aber nicht repr()(ein asymmetrisches Verhalten, das imho ein Fehler ist), dh eval(repr(b'\xa4'))nicht gleich b'\xa4'(wie bei Python 3).

Im Idealfall ist der folgende Code eine Invariante, die für alle Kombinationen von unicode_literalsPython {2.7, 3.x} immer funktionieren sollte :

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 funktioniert zufällig, da repr('\xa4')sie u'\xa4'in Python 2.7 ausgewertet wird .

hvr
quelle
2
Ich denke, das größere Problem hier ist, dass Sie reprein Objekt regenerieren. Aus der reprDokumentation geht eindeutig hervor, dass dies keine Voraussetzung ist. Meiner Meinung reprnach bedeutet dies nur etwas Nützliches zum Debuggen.
jpmc26
5

Da sind mehr.

Es gibt Bibliotheken und integrierte Funktionen, die Zeichenfolgen erwarten, die keinen Unicode tolerieren.

Zwei Beispiele:

eingebaut:

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

(leicht esotisch) 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 esoterisch und leicht zu reparieren

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

Letzteres ist jedoch verheerend, wenn Ihr Code voller Aufrufe von pub.sendMessage () ist (was meins ist).

Verdammt, was?!?

GreenAsJade
quelle
3
Und das Typ-Zeug dringt auch in Metaklassen ein - also sollten in Django alle Zeichenfolgen, in denen Sie sich deklarieren, class Meta:seinb'field_name'
Hamish Downer
2
Ja ... in meinem Fall wurde mir klar, dass es sich gelohnt hat, alle sendMessage-Zeichenfolgen zu durchsuchen und durch b'-Versionen zu ersetzen. Wenn Sie die gefürchtete "Dekodierungs" -Ausnahme vermeiden möchten, gibt es nichts Schöneres als die strikte Verwendung von Unicode in Ihrem Programm, die bei Bedarf bei Eingabe und Ausgabe konvertiert wird (das "Unicode-Sandwich", auf das in einem Artikel Bezug genommen wird, den ich zu diesem Thema gelesen habe). Insgesamt war unicode_literals ein großer Gewinn für mich ...
GreenAsJade