打印对象和Unicode背后的机制是什么?有什么好的指南?

6 投票
2 回答
3103 浏览
提问于 2025-04-16 03:13

我在处理打印和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 个回答

0

我猜你的 sys.getdefaultencoding() 现在还是 'ascii'。这意味着每当你使用 str() 或 repr() 来处理一个对象时,都会用到这个编码。你可以通过 sys.setdefaultencoding() 来改变它。不过,一旦你开始写入一个流,比如标准输出(STDOUT)或者文件,你就得遵循那个流的编码方式。我觉得在命令行中使用管道的时候也是这样。我认为 'print' 会遵循标准输出的编码,但出现问题的地方是在调用 'print' 之前,也就是在构造它的参数时。

8

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__之间区别的绝对核心,我强烈建议尊重这个区别!)。

撰写回答