打印对象和Unicode背后的机制是什么?有什么好的指南?
我在处理打印和Unicode转换时遇到了一些困难。这是一些在2.5版本的Windows解释器中执行的代码。
>>> import sys
>>> print sys.stdout.encoding
cp850
>>> print u"é"
é
>>> print u"é".encode("cp850")
é
>>> print u"é".encode("utf8")
├®
>>> print u"é".__repr__()
u'\xe9'
>>> class A():
... def __unicode__(self):
... return u"é"
...
>>> print A()
<__main__.A instance at 0x0000000002AEEA88>
>>> class B():
... def __repr__(self):
... return u"é".encode("cp850")
...
>>> print B()
é
>>> class C():
... def __repr__(self):
... return u"é".encode("utf8")
...
>>> print C()
├®
>>> class D():
... def __str__(self):
... return u"é"
...
>>> print D()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 0: ordinal not in range(128)
>>> class E():
... def __repr__(self):
... return u"é"
...
>>> print E()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 0: ordinal not in range(128)
当打印一个Unicode字符串时,并不是调用并打印它的__repr__()
函数。
而当打印一个对象时,会调用__str__()
或者__repr__()
(如果没有实现__str__
的话),而不是__unicode__()
。这两个方法都不能返回Unicode字符串。
那么,为什么会这样呢?如果__repr__()
或__str__()
返回一个Unicode字符串,为什么打印时的行为就不一样呢?换句话说,为什么print D()
和print D().__str__()
会有不同的结果?
我是不是漏掉了什么?
这些示例还表明,如果你想打印一个用Unicode字符串表示的对象,你必须把它编码成一个对象字符串(类型为str)。但是为了让打印效果更好(避免出现“├®”这样的字符),这取决于sys.stdout
的编码。
那么,我是否需要在每个__str__
或__repr__
方法中都加上u"é".encode(sys.stdout.encoding)
?或者返回repr(u"é")
?如果我使用管道呢?那样的编码和sys.stdout
是一样的吗?
我主要的问题是让一个类“可打印”,也就是说,print A()
能打印出完全可读的内容(而不是带有\x***这样的Unicode字符)。这里是需要修改的错误行为/代码:
class User(object):
name = u"Luiz Inácio Lula da Silva"
def __repr__(self):
# returns unicode
return "<User: %s>" % self.name
# won't display gracefully
# expl: print repr(u'é') -> u'\xe9'
return repr("<User: %s>" % self.name)
# won't display gracefully
# expl: print u"é".encode("utf8") -> print '\xc3\xa9' -> ├®
return ("<User: %s>" % self.name).encode("utf8")
谢谢!
2 个回答
我猜你的 sys.getdefaultencoding()
现在还是 'ascii'。这意味着每当你使用 str() 或 repr() 来处理一个对象时,都会用到这个编码。你可以通过 sys.setdefaultencoding()
来改变它。不过,一旦你开始写入一个流,比如标准输出(STDOUT)或者文件,你就得遵循那个流的编码方式。我觉得在命令行中使用管道的时候也是这样。我认为 'print' 会遵循标准输出的编码,但出现问题的地方是在调用 'print' 之前,也就是在构造它的参数时。
Python对函数和方法的类型限制不多,但还是有一些,比如在Python 2.*中,__str__
方法必须返回一个字节字符串。通常情况下,如果需要字节字符串的地方出现了unicode对象,Python会使用当前的默认编码(通常是'ascii'
)来尝试将这个unicode对象转换成字节字符串。
在这个过程中,任何给定文件对象的编码都不重要,因为__str__
返回的内容可能会被打印出来,或者会被用于完全不同的处理。你调用__str__
的目的对这个调用和结果本身并没有影响;一般来说,Python不会考虑操作的“未来上下文”(即你在操作完成后打算如何使用结果)来决定操作的含义。
这是因为Python并不总是“知道”你未来的意图,它会尽量减少意外情况的发生。比如,print str(x)
和s = str(x); print s
(一次性执行和分两步执行)必须产生相同的效果;如果在第二种情况下,str(x)
不能有效地生成字节字符串(例如,x.__str__()
不能),那么在第一种情况下也应该出现异常。
自从2.4版本以来,print
在处理unicode对象时,会考虑目标流的.encoding
属性(如果有的话,默认是sys.stdout
);而其他操作(与任何给定目标流无关的操作)则不会这样做,str(x)
(即x.__str__()
)就是这样一种操作。
希望这能帮助你理解让你感到困扰的行为原因……
编辑:提问者现在澄清了“我主要的问题是让一个类变得‘可打印’,也就是说,打印A()时能输出完全可读的内容(而不是带有\x***的unicode字符)。”我认为以下方法最适合这个特定目标:
import sys
DEFAULT_ENCODING = 'UTF-8' # or whatever you like best
class sic(object):
def __unicode__(self): # the "real thing"
return u'Pel\xe9'
def __str__(self): # tries to "look nice"
return unicode(self).encode(sys.stdout.encoding or DEFAULT_ENCODING,
'replace')
def __repr__(self): # must be unambiguous
return repr(unicode(self))
也就是说,这种方法将__unicode__
作为类实例自我格式化的主要方式——但由于(在Python 2中)print
调用的是__str__
,所以它会尽量通过__unicode__
来处理编码。虽然不完美,但Python 2的print
语句本身就远非完美;-)。
而__repr__
则必须努力做到不含糊,也就是说,不能为了“好看”而冒着含糊的风险(理想情况下,如果可行,它应该返回一个字节字符串,如果传递给eval
,会使一个实例等于当前实例……这并不总是可行,但消除含糊是__str__
和__repr__
之间区别的绝对核心,我强烈建议尊重这个区别!)。