Jakieś problemy z używaniem unicode_literals w Pythonie 2.6?

101

Mamy już naszą bazę kodu działającą pod Pythonem 2.6. Aby przygotować się do Pythona 3.0, zaczęliśmy dodawać:

z __future__ importuj unicode_literals

do naszych .pyplików (w miarę ich modyfikacji). Zastanawiam się, czy ktoś inny to robił i napotkał nieoczywiste problemy (być może po spędzeniu dużo czasu na debugowaniu).

Jacob Gabrielson
źródło

Odpowiedzi:

101

Głównym źródłem problemów, które miałem podczas pracy z ciągami znaków Unicode, jest mieszanie ciągów zakodowanych w utf-8 z ciągami znaków Unicode.

Na przykład rozważ następujące skrypty.

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

Wynik działania python one.pyto:

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)

W tym przykładzie two.namejest to ciąg zakodowany w formacie UTF-8 (nie Unicode), ponieważ nie został zaimportowany unicode_literals, i one.namejest to ciąg znaków Unicode. Kiedy mieszasz oba, Python próbuje zdekodować zakodowany ciąg (zakładając, że jest to ascii) i przekonwertować go na Unicode, ale kończy się to niepowodzeniem. Zadziałałoby, gdybyś to zrobił print name + two.name.decode('utf-8').

To samo może się zdarzyć, jeśli zakodujesz ciąg i spróbujesz go później zmiksować. Na przykład to działa:

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

Wynik:

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

Ale po dodaniu import unicode_literalsNIE:

# 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

Wynik:

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)

Nie udaje się, ponieważ 'DEBUG: %s'jest to ciąg znaków Unicode i dlatego Python próbuje zdekodować html. Kilka sposobów na naprawienie wydruku to robi print str('DEBUG: %s') % htmllub print 'DEBUG: %s' % html.decode('utf-8').

Mam nadzieję, że pomoże ci to zrozumieć potencjalne problemy podczas używania ciągów Unicode.

Koba
źródło
11
Sugerowałbym, aby wybrać decode()rozwiązania zamiast rozwiązań str()lub encode(): im częściej używasz obiektów Unicode, tym jaśniejszy jest kod, ponieważ to, czego chcesz, to manipulowanie ciągami znaków, a nie tablicami bajtów z zewnętrznie implikowanym kodowaniem.
Eric O Lebigot,
8
Popraw terminologię. when you mix utf-8 encoded strings with unicode onesUTF-8 i Unicode mają dwa różne kodowania; Unicode jest standardem, a UTF-8 jest jednym z kodowań, które definiuje.
Kos
11
@Kos: Myślę, że oznacza on mix „UTF-8 ciągi” obiektów z Unicode (stąd dekodowane) sprzeciwia . To pierwsze jest typem str, drugie jest typem unicode. Będąc różnymi obiektami, problem może się pojawić, jeśli spróbujesz je zsumować /
połączyć
Czy dotyczy to python>=2.6lub python==2.6?
joar
16

Również w 2.6 (przed Pythonem 2.6.5 RC1 +) literały Unicode nie działają dobrze z argumentami słów kluczowych ( problem4978 ):

Na przykład poniższy kod działa bez unicode_literals, ale kończy się niepowodzeniem z TypeError: keywords must be stringjeśli jest używany 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
mfazekas
źródło
17
FYI, python 2.6.5 RC1 + to naprawił.
Mahmoud Abdelkader
13

Zauważyłem, że jeśli dodasz unicode_literalsdyrektywę, powinieneś również dodać coś takiego:

 # -*- coding: utf-8

do pierwszej lub drugiej linii pliku .py. W przeciwnym razie linie takie jak:

 foo = "barré"

powoduje błąd, taki jak:

Błąd składni: znak spoza ASCII '\ xc3' w pliku mumble.py w wierszu 198,
 ale nie zadeklarowano kodowania; zobacz http://www.python.org/peps/pep-0263.html
 dla szczegółów
Jacob Gabrielson
źródło
5
@IanMackinnon: Python 3 zakłada, że ​​pliki są domyślnie UTF8
endolith
3
@endolith: Ale Python 2 tego nie robi i da błąd składni, jeśli użyjesz znaków innych niż ASCII nawet w komentarzach ! Więc IMHO # -*- coding: utf-8jest praktycznie obowiązkowym oświadczeniem, niezależnie od tego, czy używasz, unicode_literalsczy nie
MestreLion
Nie -*-jest wymagane; jeśli wybierasz sposób zgodny z emacsem, myślę, że będziesz potrzebować -*- encoding: utf-8 -*-(zobacz również -*-na końcu). Wszystko, czego potrzebujesz, to coding: utf-8(lub nawet =zamiast : ).
Chris Morgan,
2
Otrzymujesz ten błąd niezależnie od tego, czy ty from __future__ import unicode_literals.
Flimm
3
Zgodność z Emacsem wymaga # -*- coding: utf-8 -*- „kodowania” (nie „kodowania”, „kodowania plików” ani czegokolwiek innego - Python po prostu szuka „kodowania” bez względu na prefiks).
Alex Dupuy
7

Weź również pod uwagę, że unicode_literalwpłynie, eval()ale nie repr()(asymetryczne zachowanie, które imho jest błędem), tj. eval(repr(b'\xa4'))Nie będzie równe b'\xa4'(tak jak w przypadku Pythona 3).

Idealnie byłoby, gdyby poniższy kod był niezmiennikiem, który zawsze powinien działać dla wszystkich kombinacji unicode_literalsi użycia Pythona {2.7, 3.x}:

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+

Drugie twierdzenie dzieje się z pracy, ponieważ repr('\xa4')ma wartość u'\xa4'w Pythonie 2.7.

hvr
źródło
2
Czuję, że większym problemem jest to, że używasz reprdo regeneracji obiektu. reprDokumentacja wyraźnie stwierdza, że to nie wymóg. Moim zdaniem sprowadza się to reprdo czegoś przydatnego tylko do debugowania.
jpmc26
5

Jest ich więcej.

Istnieją biblioteki i elementy wbudowane, które oczekują ciągów znaków, które nie tolerują kodu Unicode.

Dwa przykłady:

wbudowany:

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

(nieco esotyczny) nie działa z unicode_literals: type () oczekuje łańcucha.

biblioteka:

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

nie działa: biblioteka wx pubsub oczekuje komunikatu typu string.

Ten pierwszy jest ezoteryczny i można go łatwo naprawić

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

ale to drugie jest druzgocące, jeśli twój kod jest pełen wywołań funkcji pub.sendMessage () (czyli mojego).

Cholera, co?!?

GreenAsJade
źródło
3
Typy również przeciekają do metaklas - więc w Django wszystkie deklarowane ciągi znaków class Meta:powinny byćb'field_name'
Hamish Downer
2
Tak ... w moim przypadku zdałem sobie sprawę, że warto było szukać i zastępować wszystkie ciągi sendMessage wersjami b '. Jeśli chcesz uniknąć przerażającego wyjątku „dekodowania”, nie ma nic lepszego niż ścisłe używanie Unicode w programie, konwertowanie danych wejściowych i wyjściowych w razie potrzeby („kanapka Unicode”, o której mowa w pewnym artykule, który przeczytałem na ten temat). Ogólnie rzecz biorąc, unicode_literals był dla mnie wielką wygraną ...
GreenAsJade