Есть ли какие-нибудь ошибки при использовании unicode_literals в Python 2.6?
-
03-07-2019 - |
Вопрос
Мы уже запустили нашу кодовую базу под Python 2.6.Чтобы подготовиться к Python 3.0, мы начали добавлять:
from __future__ import unicode_literals
в наш .py
файлы (по мере их изменения).Мне интересно, делал ли кто-нибудь еще это и сталкивался ли с какими-либо неочевидными ошибками (возможно, после того, как потратил много времени на отладку).
Решение
Основным источником проблем, с которыми я столкнулся при работе со строками в Юникоде, является смешивание строк в кодировке utf-8 со строками в Юникоде.
Например, рассмотрим следующие сценарии.
два.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
представляет собой строку Юникода.Когда вы смешиваете оба, 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')
.
Надеюсь, это поможет вам понять потенциальные ошибки при использовании строк Юникода.
Другие советы
Также в версии 2.6 (до Python 2.6.5 RC1+) литералы Юникода не очень хорошо работают с аргументами ключевых слов (выпуск 4978):
Например, следующий код работает без 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
Я обнаружил, что если вы добавите unicode_literals
директиву, вы также должны добавить что-то вроде:
# -*- coding: utf-8
в первую или вторую строку вашего .py-файла.В противном случае такие строки, как:
foo = "barré"
привести к ошибке, например:
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
Также учтите, что unicode_literal
повлияет eval()
но нет repr()
(асимметричное поведение, которое, по моему мнению, является ошибкой), т.е. eval(repr(b'\xa4'))
не будет равен b'\xa4'
(как и в Python 3).
В идеале следующий код должен быть инвариантом, который всегда должен работать для всех комбинаций unicode_literals
и использование Python {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.
Есть и другие.
Существуют библиотеки и встроенные функции, которые ожидают строки, не поддерживающие Юникод.
Два примера:
встроенный:
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() (как у меня).
Черт возьми, а?!?
Щелчок вызовет повсюду исключения Юникода. если какой-либо модуль, имеющий from __future__ import unicode_literals
импортируется там, где вы используете click.echo
.Это кошмар…