您可以更改
pprint
的输出,但需要重新实现
saferepr()
函数,而不仅仅是子类化
pprint.PrettyPrinter()
类。
发生的情况是,所有对象都使用(内部版本的)
saferepr()
函数,并且该函数本身递归地处理将对象转换为表示形式(仅使用自身,而不是
PrettyPrinter()
实例),因此任何自定义都必须在这里发生。只有当
saferepr()
的结果变得太大(宽度超过配置的宽度)时,
PrettyPrinter
类才会开始将容器输出分解成组件以放置在单独的行上;然后为组件元素重复调用
saferepr()
的过程。
所以PrettyPrinter.format()
只负责处理顶层对象,以及每个递归对象,这些对象 a) 在支持的容器类型(字典、列表、元组、字符串和这些类型的标准库子类)内部,并且 b) 父容器的表示形式由.format()
产生的显示宽度超过了。
为了能够覆盖实现,我们需要了解.format()
方法和saferepr()
实现如何交互,它们接受什么参数以及需要返回什么。
PrettyPrinter.format()
还传递了额外的参数:context
、maxlevels
和level
:
context
用于检测递归 (默认实现如果 id(object) in context
为真,则返回 _recursion(object)
的结果。
- 当设置了
maxlevels
并且 level >= maxlevels
为真时,缺省实现将 ...
作为容器的内容返回。
该方法还应返回一个由3个值组成的元组;表示字符串和两个标志。您可以安全地 忽略 这些标志的含义,它们实际上在当前实现中 从未使用过。它们的作用是表示生成的表示形式是否“可读”(使用可以传递给 eval()
的 Python 语法)或是否递归(对象包含循环引用)。但是 PrettyPrinter.isreadable()
和 PrettyPrinter.isrecursive()
方法实际上完全绕过了 .format()
;这些返回值似乎是重构的遗留物,破坏了 .format()
和这两个方法之间的关系。因此,只需返回表示字符串和任何两个布尔值即可。
.format()
实际上只是委托给了内部实现的 saferepr()
,然后执行了几个操作
- 使用
context
处理递归检测和深度处理 maxlevels
和 level
- 递归处理字典、列表和元组(以及它们的子类,只要它们的
__repr__
方法仍然是默认实现)
- 对于字典,按键值对排序。这在 Python 3 中比看起来更加 棘手, 但是可以通过自定义的
_safe_tuple
排序键解决,它近似于 Python 2 的 sort everything 行为。我们可以重复使用它。
为了实现递归替换,我倾向于使用
@functools.singledispatch()
来委托处理不同类型。忽略自定义的
__repr__
方法、处理深度问题、递归和空对象,也可以通过装饰器来处理:
import pprint
from pprint import PrettyPrinter
from functools import singledispatch, wraps
from typing import get_type_hints
def common_container_checks(f):
type_ = get_type_hints(f)['object']
base_impl = type_.__repr__
empty_repr = repr(type_())
too_deep_repr = f'{empty_repr[0]}...{empty_repr[-1]}'
@wraps(f)
def wrapper(object, context, maxlevels, level):
if type(object).__repr__ is not base_impl:
return repr(object)
if not object:
return empty_repr
if maxlevels and level >= maxlevels:
return too_deep_repr
oid = id(object)
if oid in context:
return pprint._recursion(object)
context[oid] = 1
result = f(object, context, maxlevels, level)
del context[oid]
return result
return wrapper
@singledispatch
def saferepr(object, context, maxlevels, level):
return repr(object)
@saferepr.register
def _handle_int(object: int, *args):
return f'0x{object:X}'
@saferepr.register
@common_container_checks
def _handle_dict(object: dict, context, maxlevels, level):
level += 1
contents = [
f'{saferepr(k, context, maxlevels, level)}: '
f'{saferepr(v, context, maxlevels, level)}'
for k, v in sorted(object.items(), key=pprint._safe_tuple)
]
return f'{{{", ".join(contents)}}}'
@saferepr.register
@common_container_checks
def _handle_list(object: list, context, maxlevels, level):
level += 1
contents = [
f'{saferepr(v, context, maxlevels, level)}'
for v in object
]
return f'[{", ".join(contents)}]'
@saferepr.register
@common_container_checks
def _handle_tuple(object: tuple, context, maxlevels, level):
level += 1
if len(object) == 1:
return f'({saferepr(object[0], context, maxlevels, level)},)'
contents = [
f'{saferepr(v, context, maxlevels, level)}'
for v in object
]
return f'({", ".join(contents)})'
class HexIntPrettyPrinter(PrettyPrinter):
def format(self, *args):
return saferepr(*args), True, False
这个工具可以处理基本的
pprint
实现所能处理的任何内容,并且它可以在任何支持的容器中生成十六进制整数。只需创建一个
HexIntPrettyPrinter()
类的实例并调用
.pprint()
即可:
>>> sample = {66: 'far',
... 99: 'Bottles of the beer on the wall',
... '12': 4277009102,
... 'boo': 21,
... 'pprint': [16, 32, 48, 64, 80, 96, 112, 128]}
>>> pprinter = HexIntPrettyPrinter()
>>> pprinter.pprint(sample)
{0x42: 'far',
0x63: 'Bottles of the beer on the wall',
'12': 0xFEEDFACE,
'boo': 0x15,
'pprint': [0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80]}
顺便提一下:如果您使用的是Python 3.6或更早版本,则需要将@saferepr.registration
行替换为@saferepr.registration(<type>)
调用,其中<type>
重复注册函数第一个参数上的类型注释。
pprint
模块。重新实现整个该死的东西会更容易、更干净。他们应该重写文档,删除任何关于自定义的提及。 - Aran-Fey_safe_repr
。根据下面的工作,我可能最终会建议将模块重构为基于Python,因为使用singledispatch
将使得pprint
在开箱即用时更具可扩展性。我想第三方库可以通过此默认情况下向pprint
注册其类型,并且对于 OP 的使用案例来说,这将是微不足道的(只需直接使用模块中的类型注释处理程序进行注册)。 - Martijn Pieters_safe_repr
来处理您的情况,然后再调用原始函数,而不是重写整个函数。这并不是非常糟糕,但仍然很丑陋。但更大的问题是,您以这种方式挂钩了所有实例,而不仅仅是您创建的实例,这违背了拥有类的目的。如果您重构代码,您需要使其能够向实例注册类型(虽然能够向模块注册也很好,因为在实践中没有人真正使用单独的实例...)。 - abarnert_safe_repr()
会做的事情)。如果我重构,你可能会传入一个替代的saferepr()
函数,并有机会重用标准函数以进行扩展,或者注册到模块使用的函数。 - Martijn Pieters_safe_repr
的全局调用等效的操作,但你真正想要的是将分派器保存到实例中,以便让人们轻松地在他们想要的任何级别上挂钩它。 - abarnert