为什么unicode()在没有指定编码的情况下只对我的对象使用str()?

7 投票
2 回答
1138 浏览
提问于 2025-04-11 09:16

我先创建了一个字符串变量,里面有一些非ASCIIutf-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

为什么呢?为什么会有这种不一致的表现?这是个bug吗?还是故意的?真让人困惑。

2 个回答

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__(),这样一切就会正常工作。

8

这个行为看起来有点让人困惑,但其实是有意为之。下面我把Python内置函数文档中关于unicode的内容完整地复制过来(这是针对2.5.2版本的):

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

这个函数会返回对象的Unicode字符串版本,使用以下几种方式:

如果提供了编码和/或错误参数,unicode()会使用指定的编码来解码对象,这个对象可以是一个8位字符串或字符缓冲区。编码参数是一个字符串,表示编码的名称;如果这个编码不被识别,就会抛出LookupError错误。错误处理根据errors参数来进行;这个参数指定了如何处理输入编码中无效的字符。如果errors是'strict'(默认值),在出现错误时会抛出ValueError,而如果是'ignore',错误会被悄悄忽略;如果是'replace',则会用官方的Unicode替代字符U+FFFD来替换那些无法解码的输入字符。更多信息可以查看codecs模块。

如果没有提供可选参数,unicode()的行为会模仿str(),但返回的是Unicode字符串,而不是8位字符串。更准确地说,如果对象本身就是一个Unicode字符串或其子类,它会直接返回这个Unicode字符串,而不进行额外的解码。

对于提供了__unicode__()方法的对象,它会调用这个方法来创建一个Unicode字符串。对于其他对象,会请求其8位字符串版本或表示形式,然后使用默认编码的codec在'strict'模式下转换为Unicode字符串。

在2.0版本中新增。在2.2版本中更改:增加了对__unicode__()的支持。

所以,当你调用unicode(r, 'utf-8')时,它需要一个8位字符串或字符缓冲区作为第一个参数,因此它会使用__str__()方法来强制转换你的对象,并尝试用utf-8编码来解码。如果没有utf-8,那么unicode()函数会在你的对象上查找__unicode__()方法,如果找不到,就会调用__str__()方法,正如你所提到的,尝试使用默认编码来转换为Unicode。

撰写回答