Есть ли ошибки с использованием unicode_literals в Python 2.6?

101

У нас уже есть код, работающий под Python 2.6. Чтобы подготовиться к Python 3.0, мы начали добавлять:

from __future__ импортировать unicode_literals

в наши .pyфайлы (по мере их изменения). Мне интересно, делал ли кто-нибудь еще это и столкнулся с какими-либо неочевидными ошибками (возможно, потратив много времени на отладку).

Джейкоб Габриэльсон
источник

Ответы:

101

Основной источник проблем, с которыми я столкнулся при работе со строками Unicode, - это когда вы смешиваете строки в кодировке utf-8 со строками Unicode.

Например, рассмотрим следующие сценарии.

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

Результат работы 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)

В этом примере two.nameэто строка в кодировке utf-8 (не unicode), поскольку она не была импортирована unicode_literals, и one.nameявляется строкой unicode. Когда вы смешиваете оба, python пытается декодировать закодированную строку (при условии, что это ascii) и преобразовать ее в unicode и терпит неудачу. Если бы вы это сделали, это сработало бы print name + two.name.decode('utf-8').

То же самое может произойти, если вы закодируете строку и попытаетесь смешать их позже. Например, это работает:

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

Вывод:

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

Но после добавления import unicode_literalsНЕ:

# 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

Вывод:

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)

Это не удается, потому что 'DEBUG: %s'это строка в Юникоде, и поэтому python пытается декодировать html. Пару способов исправить отпечаток либо делаем, print str('DEBUG: %s') % htmlлибо print 'DEBUG: %s' % html.decode('utf-8').

Надеюсь, это поможет вам понять возможные проблемы при использовании строк в Юникоде.

Коба
источник
11
Я бы посоветовал использовать decode()решения вместо str()или encode(): чем чаще вы используете объекты Unicode, тем яснее будет код, поскольку вам нужно манипулировать строками символов, а не массивами байтов с внешне подразумеваемой кодировкой.
Эрик О Лебигот
8
Исправьте, пожалуйста, вашу терминологию. when you mix utf-8 encoded strings with unicode onesUTF-8 и Unicode - это не две разные кодировки; Юникод - это стандарт, а UTF-8 - одна из определяемых им кодировок.
Кос
11
@Kos: Я думаю , что он означает смешивать «UTF-8 закодированных строк» объекты с юникодом (отсюда декодированным) объектами . Первый тип str, второй тип unicode. Будучи разными объектами, могут возникнуть проблемы, если вы попытаетесь их суммировать /
объединить
Это относится к python>=2.6или python==2.6?
joar
16

Также в версии 2.6 (до Python 2.6.5 RC1 +) литералы юникода плохо сочетаются с аргументами ключевого слова ( issue4978 ):

Следующий код, например, работает без unicode_literals, но не работает с TypeError: keywords must be stringесли используется 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
Мфазекас
источник
17
К вашему сведению, python 2.6.5 RC1 + исправил это.
Махмуд Абделькадер
13

Я обнаружил, что если вы добавите unicode_literalsдирективу, вы также должны добавить что-то вроде:

 # -*- coding: utf-8

в первую или вторую строку вашего файла .py. В противном случае строки, такие как:

 foo = "barré"

приведет к ошибке, например:

SyntaxError: не-ASCII-символ '\ xc3' в файле mumble.py в строке 198,
 но кодировка не объявлена; см. http://www.python.org/peps/pep-0263.html
 для подробностей
Джейкоб Габриэльсон
источник
5
@IanMackinnon: Python 3 предполагает, что файлы по умолчанию имеют формат UTF8
endolith
3
@endolith: Но Python 2 этого не делает, и он выдаст синтаксическую ошибку, если вы используете символы, отличные от ascii, даже в комментариях ! Так что ИМХО # -*- coding: utf-8является практически обязательным заявлением, независимо от того, используете вы это unicode_literalsили нет
MestreLion
-*-Не требуется; если вы выбрали способ, совместимый с emacs, я думаю, вам понадобится -*- encoding: utf-8 -*-(см. также -*-в конце). Все, что вам нужно, это coding: utf-8(или даже =вместо : ).
Крис Морган
2
Вы получаете эту ошибку независимо от того, вы или нет from __future__ import unicode_literals.
Flimm
3
Для совместимости с Emacs требуется # -*- coding: utf-8 -*- «кодирование» (не «кодирование», «кодирование файлов» или что-то еще - Python просто ищет «кодирование» независимо от любого префикса).
Alex Dupuy
7

Также примите во внимание, что unicode_literalэто повлияет, eval()но не повлияет repr()(асимметричное поведение, которое imho является ошибкой), то есть eval(repr(b'\xa4'))не будет равно b'\xa4'(как в Python 3).

В идеале следующий код должен быть инвариантом, который всегда должен работать для всех комбинаций использования unicode_literalsPython {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+

Второе утверждение происходит на работу, так как имеет repr('\xa4')значение u'\xa4'в Python 2.7.

hvr
источник
2
Мне кажется, что более серьезная проблема здесь в том, что вы используете его reprдля регенерации объекта. В reprдокументации четко указано, что это не требование. На мой взгляд, это сводится reprк чему-то полезному только для отладки.
jpmc26
5

Есть еще.

Существуют библиотеки и встроенные функции, которые ожидают строк, которые не поддерживают Unicode.

Два примера:

встроенный:

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

(немного странно) не работает с unicode_literals: type () ожидает строку.

библиотека:

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

не работает: библиотека wx pubsub ожидает строкового типа сообщения.

Первый эзотерический и легко исправляется с помощью

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

но последний вариант разрушителен, если ваш код полон вызовов pub.sendMessage () (как и у меня).

Черт возьми, а?!?

GreenAsJade
источник
3
И типовой материал также просачивается в метаклассы - поэтому в Django любые строки, которые вы объявляете, class Meta:должны бытьb'field_name'
Хэмиш Даунер
2
Да ... в моем случае я понял, что поиск и замена всех строк sendMessage на версии b 'стоили того. Если вы хотите избежать пугающего исключения «декодирования», нет ничего лучше, чем строгое использование Unicode в вашей программе, преобразование при вводе и выводе по мере необходимости («бутерброд Unicode», упомянутый в некоторой статье, которую я читал по этой теме). В целом, unicode_literals стал для меня большой победой ...
GreenAsJade