如何在Python中让我的类可打印?

22

Python有一个漂亮的打印机(pprint(...))。我想让我的类漂亮地可打印。如果我提供某个接口,漂亮的打印会以更好的方式打印我的实例吗?

Python文档在第8.11节中展示了不同的示例,但没有展示如何使用户定义的类漂亮地可打印的示例。

那么我的类需要提供什么接口呢?
还有其他(可能更好的)格式化程序吗?


使用案例:

我想要美化打印ConfigParser的内容,因此我创建了一个扩展版本ExtendenConfigParser。这样我就可以添加更多功能或添加匹配的漂亮打印界面。


“pretty printed”类是什么意思?像字典吗? - exprosic
pprint(config) 只会输出 <lib.ExtendedConfigParser.ExtendedConfigParser object at 0x0000000003569940>。内部数据结构是两个嵌套的有序字典。我想将它们打印为2个嵌套的字典。我可以编写一个函数来完成这项工作,但我希望有一种方法和/或pprint兼容的类。 - Paebbels
我不认为pprint提供了那个功能。不过,你可以给你的类添加一个__format__方法(除了__repr____str__方法之外),使它在传递给format内置函数或str.format方法时打印得更漂亮。 - PM 2Ring
相关:https://dev59.com/zHA75IYBdhLWcg3wf5RV - Ciro Santilli OurBigBook.com
4个回答

15

pprint不寻找任何钩子。相反,pprint.PrettyPrinter使用一种调度模式,即在类上键入的一系列方法,这些方法与class.__repr__引用相关联。

您可以创建pprint.PrettyPrinter的子类来教它了解您的类:

class YourPrettyPrinter(pprint.PrettyPrinter):
    _dispatch = pprint.PrettyPrinter._dispatch.copy()

    def _pprint_yourtype(self, object, stream, indent, allowance, context, level):
        stream.write('YourType(')
        self._format(object.foo, stream, indent, allowance + 1,
                     context, level)
        self._format(object.bar, stream, indent, allowance + 1,
                     context, level)
        stream.write(')')

    _dispatch[YourType.__repr__] = _pprint_yourtype

然后直接使用类来漂亮地打印包含“YourType”实例的数据。请注意,这取决于类型是否具有自己的自定义“__repr__”方法!
您还可以直接将函数插入到“PrettyPrinter._dispatch”字典中;“self”被明确传递进去。这可能是第三方库的更好选择。
from pprint import PrettyPrinter

if isinstance(getattr(PrettyPrinter, '_dispatch'), dict):
     # assume the dispatch table method still works
     def pprint_ExtendedConfigParser(printer, object, stream, indent, allowance, context, level):
         # pretty print it!
     PrettyPrinter._dispactch[ExtendedConfigParser.__repr__] = pprint_ExtendedConfigParser

请查看pprint模块源代码了解其他调度方法的编写方式。

_dispatch这样的单下划线名称始终是内部实现细节,可以在将来的版本中更改。然而,在这里,它是您最好的选择。调度表在Python 3.5及以上版本中添加(Python 3.5)

您可能想要查看第三方库{{link3:rich}},它具有一些很棒的漂亮打印功能,并支持钩子(__rich_repr__);请参阅有关自定义漂亮打印的文档


你提出的解决方案似乎对我的类已经使用(多)继承没有影响,是吗? - Paebbels
1
@Paebbels:确保有一个唯一的__repr__方法作为键,否则我看不到任何潜在的危害。 - Martijn Pieters
ConfigParser 类是对 INI 配置文件的封装。如果我想要打印出 INI 的表示格式,那么应该使用 __format__,对吗?因为 pprint(...) 的目标是 Python 可读的表示形式。 - Paebbels
1
@Paebbels,你需要尝试一下如何将其与pprint输出集成(例如给定缩进和允许值)。 - Martijn Pieters
抱歉,我的第一条评论(已删除)是不正确的。Python 3.6、3.7和3.8具有_dispatch,而2.7没有。 - Rick Graves
显示剩余4条评论

2

这并不是一个真正的解决方案,但我通常会将对象序列化并像这样漂亮地打印出来:

pprint(obj.dict())

0

如果你要付出所有的努力,那么最好是将pprint超类化以接受钩子,这样你只需要编写所有代码一次。

在你用pp = pprint.PrettyPrinter(indent=4).pprint实例化pprint帮助程序之后定义类的情况下,它也可以更好地工作(这是我的一个坏习惯)。

然后,您可以通过使用以下任何一种方法中的任何一种来选择加入任何类[双关语不是故意的]

[编辑]:经过一些自我使用,我意识到有一个更简单的替代解决方案,即__pprint_repr__。而不是尝试创建自己的pprint函数,只需定义__pprint_repr__方法并返回标准python对象即可。如果您有一个复杂的类,可以在dict中组合多个对象。

[编辑#2]:我还意识到将所有_format变量传递给__pprint_repr__函数非常有用,因为这允许您做一些真正酷的事情--例如,如果您的项目在列表中(缩进> 0),则显示紧凑输出与完整输出(缩进== 0)。

这也意味着该解决方案与Python 2.7兼容,而不仅仅是Python ~> 3.3

class my_object(object):

    # produce pprint compatible object, easy as pie!
    def __pprint_repr__(self, **kwargs):
        return self.__dict__
    
    # make a multi-level object, easy as cheese-cake!
    def __pprint_repr__(self, **kwargs):
        _indent = kwargs['indent']
        if _indent:
            return self._toText()
        return { self._toText(): self.__dict__ }

    # to take total control (python 3) (requires __repr__ be defined)
    def __pprint__(self, object, stream, indent, allowance, context, level):
        stream.write('my_object(\n')
        self._format(object._data, stream, indent, allowance + 1, context, level)
        stream.write(')')
        pass

子类化非常简单--在Python 3.7和2.7中进行了测试:

        if _pprint_repr:
            return PrettyPrinter._format(self, _pprint_repr(object, stream=stream, 
                indent=indent, allowance=allowance, context=context, level=level), 
                    stream, indent, allowance, context, level)

        # else check for alternate _pprint method (if supported ~ python 3.3)
        if getattr(PrettyPrinter, '_dispatch', None):
            _repr = type(object).__repr__
            _pprint = getattr(type(object), '__pprint__', None)
            _exists = self._dispatch.get(_repr, None)
            if not _exists and _pprint:
                self._dispatch[_repr] = _pprint

        return PrettyPrinter._format(self, object, stream, indent, allowance, context, level)

不要使用双下划线(“dunder方法”)来命名自己的方法——这种惯例表示它是一种内置方法,具有某些透明的“魔法”。此外,两个前导下划线会无意中触发名称混淆。 - Dillon Davis
换句话说,__this__表示某些外部力量将以某种方式作用于属性,从而产生魔法。在这种情况下,外部力量是PrettyPrinter,魔法是格式化输出。虽然你的规则是正确的,但这是一个“特殊情况”。 - Orwellophile
@DillonDavis 两个前导下划线不会触发名称混淆,因为名称混淆只有在没有两个尾随下划线时才会触发,请参阅https://docs.python.org/3/reference/expressions.html#private-name-mangling - MarcellPerger
@MarcellPerger 这不是真的。如果你阅读了你引用的文档,"例如,在名为Ham的类中出现的标识符__spam将被转换为_Ham__spam。" 内置的魔术方法或者所谓的"dunder"方法在两边都有双下划线,但是前面有双下划线会触发名称混淆。 - Dillon Davis
是的,__method 会被混淆,所以不能在类外部使用。__method__(注意末尾有两个下划线)不会被混淆,可以在类外部使用。 - MarcellPerger
显示剩余3条评论

0
Martijn Pieters的子类解决方案对我有效,我通过不将foo和bar硬编码使它更通用。
取出:
    self._format(object.foo, stream, indent, allowance + 1,
                 context, level)
    self._format(object.bar, stream, indent, allowance + 1,
                 context, level)

替换(放入):

    for s in vars(object):
        stream.write( '\n%s: ' % s )
        self._format( object.__dict__[s],
                      stream, indent, allowance + 1, context, level )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接