实现自定义格式化打印的最佳方法

26 投票
4 回答
8942 浏览
提问于 2025-04-16 01:21

自定义 pprint.PrettyPrinter

pprint模块的文档提到,PrettyPrinter.format这个方法是用来让我们可以自定义格式的。

我了解到可以在一个子类中重写这个方法,但这似乎并不能让基础类的方法应用换行和缩进。

  • 我是不是漏掉了什么?
  • 有没有更好的方法来实现这个(比如其他模块)?

其他选择?

我查看了pretty模块,感觉挺有意思的,但似乎没有办法在不修改其他模块的情况下自定义格式。

我想我在寻找的东西是可以让我提供一个类型映射(或者可能是函数),把类型和处理节点的例程关联起来。这些处理节点的例程会接收一个节点,并返回它的字符串表示,以及一个子节点的列表,依此类推。

我为什么要研究漂亮打印

我的最终目标是紧凑地打印自定义格式的DocBook格式的xml.etree.ElementTree

(我很惊讶没有发现更多Python对DocBook的支持。也许我错过了什么。)

我在一个叫xmlearn的客户端中构建了一些基本功能,它使用了lxml。例如,要导出一个Docbook文件,你可以:

xmlearn -i docbook_file.xml dump -f docbook -r book

这功能有点简陋,但我得到了我想要的信息。

xmlearn还有其他功能,比如生成图像和展示XML文档中标签之间关系的导出。这些和这个问题基本上没什么关系。

你还可以导出到任意深度,或者指定一个XPath作为起始点。这些XPath的功能有点取代了Docbook特定的格式,所以那部分并没有很好地开发。

这仍然不是对问题的真正回答。我仍然希望能找到一个可以轻松自定义的漂亮打印工具。

4 个回答

2

如果你想修改默认的美化打印功能,而不想通过子类化来实现,可以使用

pprint.PrettyPrinter

类里的内部_dispatch表。你可以查看一些示例,看看如何为字典和列表等内部类型添加调度,具体可以在源代码中找到。

下面是我如何为MatchPy的Operation类型添加自定义美化打印的:

import pprint
import matchpy

def _pprint_operation(self, object, stream, indent, allowance, context, level):
    """
    Modified from pprint dict https://github.com/python/cpython/blob/3.7/Lib/pprint.py#L194
    """
    operands = object.operands
    if not operands:
        stream.write(repr(object))
        return
    cls = object.__class__
    stream.write(cls.__name__ + "(")
    self._format_items(
        operands, stream, indent + len(cls.__name__), allowance + 1, context, level
    )
    stream.write(")")


pprint.PrettyPrinter._dispatch[matchpy.Operation.__repr__] = _pprint_operation

现在,如果我对任何与matchpy.Operation有相同__repr__的对象使用pprint.pprint,它就会使用这个方法来进行美化打印。这也适用于子类,只要它们不重写__repr__,这其实是有道理的!如果你有相同的__repr__,那么美化打印的行为也会相同。

下面是一些MatchPy操作的美化打印示例:

ReshapeVector(Vector(Scalar('1')),
              Vector(Index(Vector(Scalar('0')),
                           If(Scalar('True'),
                              Scalar("ReshapeVector(Vector(Scalar('2'), Scalar('2')), Iota(Scalar('10')))"),
                              Scalar("ReshapeVector(Vector(Scalar('2'), Scalar('2')), Ravel(Iota(Scalar('10'))))")))))
3

这个问题可能和以下内容重复:


使用 pprint.PrettyPrinter

我查看了 pprint的源代码。看起来要增强 pprint(),你需要:

  • 创建一个 PrettyPrinter 的子类
  • 重写 _format() 方法
  • 检查是否是子类 issubclass()
  • 如果不是你的类,就把请求传回 _format()

替代方案

我觉得一个更好的方法是直接写你自己的 pprint(),当它不知道该怎么做的时候,就调用 pprint.pformat

比如:

'''Extending pprint'''

from pprint import pformat

class CrazyClass: pass

def prettyformat(obj):
    if isinstance(obj, CrazyClass):
        return "^CrazyFoSho^"
    else:
        return pformat(obj)

def prettyp(obj):
    print(prettyformat(obj))

# test
prettyp([1]*100)
prettyp(CrazyClass())

这样做的一个大好处是你不需要依赖 pprint 的内部实现。这种方式明确且简洁。

缺点是你需要手动处理缩进。

5

我的解决办法是用一个简单的包装器来替代pprint.PrettyPrinter,这个包装器会在调用原来的打印功能之前,先把它找到的所有浮点数进行格式化。

from __future__ import division
import pprint
if not hasattr(pprint,'old_printer'):
    pprint.old_printer=pprint.PrettyPrinter

class MyPrettyPrinter(pprint.old_printer):
    def _format(self,obj,*args,**kwargs):
        if isinstance(obj,float):
            obj=round(obj,4)
        return pprint.old_printer._format(self,obj,*args,**kwargs)
pprint.PrettyPrinter=MyPrettyPrinter

def pp(obj):
    pprint.pprint(obj)

if __name__=='__main__':
    x=[1,2,4,6,457,3,8,3,4]
    x=[_/17 for _ in x]
    pp(x)

撰写回答