UnicodeEncodeError, вызванный Python str()

Python Windows Unicode

причина

Как мы все знаем, 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, см. код выше.