7

我首先创建一个带有一些非 ascii utf-8编码数据的字符串变量:

>>> text = 'á'
>>> text
'\xc3\xa1'
>>> text.decode('utf-8')
u'\xe1'

使用unicode()它会引发错误......

>>> unicode(text)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: 
                    ordinal not in range(128)

...但如果我知道编码,我可以将其用作第二个参数:

>>> unicode(text, 'utf-8')
u'\xe1'
>>> unicode(text, 'utf-8') == text.decode('utf-8')
True

现在,如果我有一个在方法中返回此文本的类__str__()

>>> class ReturnsEncoded(object):
...     def __str__(self):
...         return text
... 
>>> r = ReturnsEncoded()
>>> str(r)
'\xc3\xa1'

unicode(r)似乎使用str()它,因为它引发了与上面相同的错误unicode(text)

>>> unicode(r)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: 
                    ordinal not in range(128)

到目前为止,一切都按计划进行!

但正如没有人会想到的那样,unicode(r, 'utf-8')甚至不会尝试:

>>> unicode(r, 'utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: coercing to Unicode: need string or buffer, ReturnsEncoded found

为什么?为什么会出现这种不一致的行为?它是一个错误吗?是有意的吗?很尴尬。

4

2 回答 2

8

这种行为确实看起来令人困惑,但内涵丰富。我在这里复制了Python Built-In Functions 文档中的全部 unicode 文档(对于 2.5.2 版,在我写这篇文章时):

unicode([对象[,编码[,错误]]])

使用以下模式之一返回对象的 Unicode 字符串版本:

如果给出编码和/或错误,unicode() 将使用编解码器对对象进行解码,该对象可以是 8 位字符串或字符缓冲区。encoding 参数是一个给出编码名称的字符串;如果编码未知,则引发 LookupError。根据错误进行错误处理;这指定了对输入编码中无效字符的处理。如果errors是'strict'(默认值),一个ValueError会在错误时引发,而'ignore'值会导致错误被静默忽略,'replace'值会导致官方Unicode替换字符,U+FFFD,用于替换无法解码的输入字符。另请参阅编解码器模块。

如果没有给出可选参数,unicode() 将模仿 str() 的行为,只是它返回 Unicode 字符串而不是 8 位字符串。更准确地说,如果 object 是 Unicode 字符串或子类,它将返回该 Unicode 字符串,而无需应用任何额外的解码。

对于提供 __unicode__() 方法的对象,它将不带参数调用此方法来创建 Unicode 字符串。对于所有其他对象,请求 8 位字符串版本或表示,然后使用“严格”模式下的默认编码的编解码器将其转换为 Unicode 字符串。

2.0 版中的新功能。在 2.2 版更改:添加了对 __unicode__() 的支持。

因此,当您调用 时unicode(r, 'utf-8'),它需要一个 8 位字符串或字符缓冲区作为第一个参数,因此它使用该__str__()方法强制您的对象,并尝试使用编解码器对其进行utf-8解码。如果没有utf-8,该unicode()函数会__unicode__()在您的对象上查找方法,但没有找到它,会按照您的建议调用该__str__()方法,尝​​试使用默认编解码器转换为 unicode。

于 2008-09-20T01:32:09.070 回答
5

unicode不会猜测您的文本的编码。如果您的对象可以将自身打印为unicode,请定义__unicode__()返回 Unicode 字符串的方法。


秘诀在于它unicode(r)实际上并没有调用__str__()自己。相反,它正在寻找一种__unicode__()方法。默认实现__unicode__()将调用__str__()然后尝试使用ascii字符集对其进行解码。当您传递编码时,unicode()期望第一个对象是可以解码的东西——即basestring.


行为很奇怪,因为如果我不通过“utf-8”,它会尝试解码为 ascii。但是如果我通过 'utf-8' 它会给出一个不同的错误......

那是因为当您指定“utf-8”时,它将第一个参数视为要解码的类似字符串的对象。没有它,它将参数视为要强制转换为 unicode 的对象。

我不明白这种混乱。如果您知道对象的text属性将始终是 UTF-8 编码,只需定义__unicode__(),然后一切都会正常工作。

于 2008-09-20T00:58:52.303 回答