实现自定义格式化打印的最佳方法
自定义 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 个回答
如果你想修改默认的美化打印功能,而不想通过子类化来实现,可以使用
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'))))")))))
这个问题可能和以下内容重复:
使用 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
的内部实现。这种方式明确且简洁。
缺点是你需要手动处理缩进。
我的解决办法是用一个简单的包装器来替代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)