使用十六进制数字的pprint

3
我使用一些类似 JSON 的字典。pprint 可以方便地对它们进行结构化处理。有没有办法让 pprint 输出中的所有整数以十六进制而非十进制打印?
{66: 'far',
 99: 'Bottles of the beer on the wall',
 '12': 4277009102,
 'boo': 21,
 'pprint': [16, 32, 48, 64, 80, 96, 112, 128]}

我宁愿看到:
{0x42: 'far',
 0x63: 'Bottles of the beer on the wall',
 '12': 0xFEEDFACE,
 'boo': 0x15,
 'pprint': [0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80]}

我曾尝试定制 PrettyPrinter,但是没有成功,让PrettyPrinter.format() 仅处理整数似乎只对部分整数有效:
class MyPrettyPrinter(PrettyPrinter):
    def format(self, object, context, maxlevels, level):
        if isinstance(object, int):
            return '0x{:X}'.format(object), True, False
        return super().format(object, context, maxlevels, level)

上述类别产生
{0x42: 'far',
 0x63: 'Bottles of the beer on the wall',
 '12': 0xFEEDFACE,
 'boo': 0x15,
 'pprint': [16, 32, 48, 64, 80, 96, 112, 128]}

列表内容格式不正确。

我放弃了。相信我:不要尝试自定义pprint模块。重新实现整个该死的东西会更容易、更干净。他们应该重写文档,删除任何关于自定义的提及。 - Aran-Fey
@Aran-Fey:我只重写了 _safe_repr。根据下面的工作,我可能最终会建议将模块重构为基于Python,因为使用 singledispatch 将使得 pprint 在开箱即用时更具可扩展性。我想第三方库可以通过此默认情况下向 pprint 注册其类型,并且对于 OP 的使用案例来说,这将是微不足道的(只需直接使用模块中的类型注释处理程序进行注册)。 - Martijn Pieters
@MartijnPieters 您可以简单地包装 _safe_repr 来处理您的情况,然后再调用原始函数,而不是重写整个函数。这并不是非常糟糕,但仍然很丑陋。但更大的问题是,您以这种方式挂钩了所有实例,而不仅仅是您创建的实例,这违背了拥有类的目的。如果您重构代码,您需要使其能够向实例注册类型(虽然能够向模块注册也很好,因为在实践中没有人真正使用单独的实例...)。 - abarnert
@abarnert: 我宁愿不在这里设置全局策略(这就是猴子补丁_safe_repr()会做的事情)。如果我重构,你可能会传入一个替代的saferepr()函数,并有机会重用标准函数以进行扩展,或者注册到模块使用的函数。 - Martijn Pieters
@MartijnPieters 是的,这就是我所说的违背了拥有类的目的。你在这里的回答通过将一个单独的模块专门用于子类来解决这个问题,以便它仍然可以执行与 _safe_repr 的全局调用等效的操作,但你真正想要的是将分派器保存到实例中,以便让人们轻松地在他们想要的任何级别上挂钩它。 - abarnert
也许将调度集成到类中(弃用saferepr)会更好。如果可以看作是实用的话,这样可以让您使用每种类型的单个调度程序来处理两种模式(单行和多行处理)。 - Martijn Pieters
2个回答

6
您可以更改pprint的输出,但需要重新实现saferepr()函数,而不仅仅是子类化pprint.PrettyPrinter()类。
发生的情况是,所有对象都使用(内部版本的)saferepr()函数,并且该函数本身递归地处理将对象转换为表示形式(仅使用自身,而不是PrettyPrinter()实例),因此任何自定义都必须在这里发生。只有当saferepr()的结果变得太大(宽度超过配置的宽度)时,PrettyPrinter类才会开始将容器输出分解成组件以放置在单独的行上;然后为组件元素重复调用saferepr()的过程。

所以PrettyPrinter.format()只负责处理顶层对象,以及每个递归对象,这些对象 a) 在支持的容器类型(字典、列表、元组、字符串和这些类型的标准库子类)内部,并且 b) 父容器的表示形式由.format()产生的显示宽度超过了。

为了能够覆盖实现,我们需要了解.format()方法和saferepr()实现如何交互,它们接受什么参数以及需要返回什么。

PrettyPrinter.format()还传递了额外的参数:contextmaxlevelslevel

  • context 用于检测递归 (默认实现如果 id(object) in context 为真,则返回 _recursion(object) 的结果。
  • 当设置了 maxlevels 并且 level >= maxlevels 为真时,缺省实现将 ... 作为容器的内容返回。

该方法还应返回一个由3个值组成的元组;表示字符串和两个标志。您可以安全地 忽略 这些标志的含义,它们实际上在当前实现中 从未使用过。它们的作用是表示生成的表示形式是否“可读”(使用可以传递给 eval() 的 Python 语法)或是否递归(对象包含循环引用)。但是 PrettyPrinter.isreadable()PrettyPrinter.isrecursive() 方法实际上完全绕过了 .format();这些返回值似乎是重构的遗留物,破坏了 .format() 和这两个方法之间的关系。因此,只需返回表示字符串和任何两个布尔值即可。

.format() 实际上只是委托给了内部实现的 saferepr(),然后执行了几个操作

  • 使用 context 处理递归检测和深度处理 maxlevelslevel
  • 递归处理字典、列表和元组(以及它们的子类,只要它们的 __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:  # subclassed repr
            return repr(object)
        if not object:                              # empty, short-circuit
            return empty_repr
        if maxlevels and level >= maxlevels:        # exceeding the max depth
            return too_deep_repr
        oid = id(object)
        if oid in context:                          # self-reference
            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):
    # uppercase hexadecimal representation with 0x prefix
    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):
        # it doesn't matter what the boolean values are here
        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>重复注册函数第一个参数上的类型注释。


0

更新

我已经用代码实现了我的概念。这似乎运行得相当不错。

只需使用 pph 进行“漂亮地打印十六进制”或“ppf”进行“漂亮地打印十六进制(格式)”(返回结果)。

from pprint import PrettyPrinter
pp = PrettyPrinter(indent=4).pprint
pf = PrettyPrinter(indent=4).pformat
def pph(o):
    print(re.sub(r"((?:, +|: +|\( *|\[ *|\{ *)-?)(\d\d+)(?=[,)}\]])", lambda m: m.group(1) + hex(m.group(2)), pf(o)))
def pfh(o):
    return re.sub(r"((?:, +|: +|\( *|\[ *|\{ *)-?)(\d\d+)(?=[,)}\]])", lambda m: m.group(1) + hex(m.group(2)), pf(o))

原始帖子

哇,听起来真的很复杂。我可以问一下,为什么不直接这样做呢?

d = pprint.pformat(data)
print re.sub(r'(\b\d+)L', lambda x: "0x{:x}".format(int(x.group(1))), d)

它在我的数据上运行良好,我承认这些数据全部是long而不是int(提供了方便的L锚点),并且没有引用文字数字的情况 - 但这种情况可以轻松处理。

re.split(r"('[^']+')", d)

我承认这不是一种优美的解决方案,但考虑到其他选择,至少它也不复杂。

{'funcStartRanges': [],
 'noCodeRanges': [],
 'noOwnerRanges': [{'last': 0x140ce1332, 'length': 0x12, 'start': 0x140ce1321},
                   {'last': 0x140ce1332, 'length': 0x12, 'start': 0x140ce1321}],
 'otherOwnerRanges': [{'last': 0x140ce1332,
                       'length': 0x12,
                       'start': 0x140ce1321}],
 'weOwnItRanges': []}

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