причина
Как мы все знаем, UnicodeEncodeError и UnicodeDecodeError в Python 2 — относительно сложные проблемы, иногда, когда возникают такие проблемы, они всегда запутаны и необъяснимы. Даже автор «Fluent Python» также предложил что-то под названием «модель сэндвича», чтобы помочь решить такие проблемы (на самом деле это не должно быть так хлопотно, как описано ниже).
Я столкнулся с небольшой проблемой, связанной с этим сегодня в Интернете, и это было очень интересно, и я описал это в статье по гидрологии.
Когда жук пришел ко мне, я увидел, что явление естественное.UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
Такое странное напоминание. Затем переверните журнал и быстро найдите соответствующую строку кода, которая, вероятно, похожа на следующую:
thrift_obj = ThriftKeyValue(key=str(xx_obj.name)) # 出错行, xx_obj.name 是一个 str
В начале см.str(xx_obj.name)
, я не знаю, ошибка это или намеренно, во всяком случае, я не могу научиться такой операции (такой волшебный код должен быть в каждом проекте более или менее).
анализировать
Глядя на буквальное значение исключения, оно примерно таково: есть определенная строка, которая кодируется кодировщиком ASCII, но очевидно, что строка выходит за диапазон, указанный кодировщиком ASCII, поэтому возникает ошибка. Итак, предположим:
- Где должна быть строка Unicode (неважно какая строка, главное, чтобы она все равно превышала диапазон ASCII), здесь должна быть
xx_obj.name
. - происходит где-то
编码动作
, причем делает это тайно (самый надоедливый вид неявного преобразования, их много в Python 2), и из кода не очевидно, где оно находится.
Глядя налево и направо, это должна быть встроенная функция str(), поэтому я просто попробовал следующий код:
In [5]: u = u'中国'
In [6]: str(u)
---------------------------------------------------------------------------
UnicodeEncodeError Traceback (most recent call last)
<ipython-input-6-b3b94fb7b5a0> in <module>()
----> 1 str(u)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
In [7]: b = u.encode('utf-8')
In [8]: str(b)
Out[8]: '\xe4\xb8\xad\xe5\x9b\xbd'
Конечно же. Глядя на документацию, ценной информации нет, описание слишком расплывчатое:
class str(object='')
Return a string containing a nicely printable representation of an object. For strings, this returns the string itself. The difference with repr(object) is that str(object) does not always attempt to return a string that is acceptable to eval(); its goal is to return a printable string. If no argument is given, returns the empty string, ''.
For more information on strings see Sequence Types — str, unicode, list, tuple, bytearray, buffer, xrange which describes sequence functionality (strings are sequences), and also the string-specific methods described in the String Methods section. To output formatted strings use template strings or the % operator described in the String Formatting Operations section. In addition see the String Services section. See also unicode().
В нашем коде (Python 2) каждый файл py имеет эту строку:
from __future__ import unicode_literals, absolute_import
Итак, я предполагаю, что xx_obj.name должен дать строку Unicode, и я проверил журнал, и это правда.
решать
На этом этапе либо преобразуйте xx_obj.name во что-то, что может распознать str(), по крайней мере, здесь не юникод, это должны быть байты. Но я этого не делал, уж больно некрасиво, второе - поменять на это:
thrift_obj = ThriftKeyValue(key=xx_obj.name) # 这里没必要调用 str() ,估计前面能跑正常,是因为 name 恰好总是 ASCII 字符
Исправлены ошибки, другие функции работают нормально.
Суммировать
Как упоминалось ранее, таких неявных преобразований в Python 2 много, а документации нет, особенно при добавлении среды Windows и операций печати сообщение об ошибке еще больше запутывает. «Fluent Python» рассказывает о так называемой «сэндвич-модели» для решения этой проблемы, которая весьма поучительна.
Тем не менее, мое общее практическое правило таково: просто используйте Unicode, сделайте его Unicode везде. Способ следующий:
- Все файлы py должны иметь следующие заголовки:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
from __future__ import unicode_literals, absolute_import
- Байтовые строки, полученные из внешнего мира (из сети, из файлов и т. д.), сначала конвертируются в Unicode, но их лучше извлекать в функции, чтобы избежать повторного кодирования:
API 的起名优点冗余,主要是为了做到 “见名知义”
class UnicodeUtils(object):
@classmethod
def get_unicode_str(cls, bytes_str, try_decoders=('utf-8', 'gbk', 'utf-16')):
"""转换成字符串(一般是Unicode)"""
if not bytes_str:
return u''
if isinstance(bytes_str, (unicode,)):
return bytes_str
for decoder in try_decoders:
try:
unicode_str = bytes_str.decode(decoder)
except UnicodeDecodeError:
pass
else:
return unicode_str
raise DecodeBytesFailedException('decode bytes failed. tried decoders: %s' % list(try_decoders))
@classmethod
def encode_to_bytes(cls, unicode_str, encoder='utf-8'):
"""转换成字节串"""
if unicode_str is None:
return b''
if isinstance(unicode_str, unicode):
return unicode_str.encode(encoding=encoder)
else:
u = cls.get_unicode(unicode_str)
return u.encode(encoding=encoder)
- Все вещи, отправляемые во внешний мир, преобразуются в строки байтов в кодировке UTF-8, см. код выше.