微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

漂亮的打印数据类更漂亮

如何解决漂亮的打印数据类更漂亮

Python Data Classes 实例还包含一个字符串表示方法,但当类具有多个字段和/或更长的字段值时,其结果不足以用于漂亮的打印目的。

基本上,我正在寻找一种方法自定义认的数据类字符串表示例程,或者寻找一种理解数据类并更漂亮地打印它们的漂亮打印机。

所以,这只是我想到的一个自定义:在每个字段之后添加一个换行符,同时在第一个字段之后缩进。

例如,代替

x = InventoryItem('foo',23)
print(x) # =>
InventoryItem(name='foo',unit_price=23,quantity_on_hand=0)

我想得到这样的字符串表示:

x = InventoryItem('foo',23)
print(x) # =>
InventoryItem(
    name='foo',quantity_on_hand=0
)

或类似的。也许漂亮的打印机可以变得更漂亮,例如对齐 = 赋值字符或类似的东西。

当然,它也应该以递归方式工作,例如也是数据类的字段应该缩进更多。

解决方法

截至 2021 年 (Python 3.9),Python 的标准 pprint doesn't support 数据类尚未推出。

然而,prettyprinter 包支持数据类并提供一些漂亮的打印功能。

示例:

[ins] In [1]: from dataclasses import dataclass
         ...:
         ...: @dataclass
         ...: class Point:
         ...:     x: int
         ...:     y: int
         ...:
         ...: @dataclass
         ...: class Coords:
         ...:     my_points: list
         ...:     my_dict: dict
         ...:
         ...: coords = Coords([Point(1,2),Point(3,4)],{'a': (1,(1,2): 'a'})

[nav] In [2]: import prettyprinter as pp

[ins] In [3]: pp.pprint(coords)
Coords(my_points=[Point(x=1,y=2),Point(x=3,y=4)],my_dict={'a': (1,2): 'a'})

默认情况下未启用数据类支持,因此:

[nav] In [4]: pp.install_extras()
[ins] In [5]: pp.pprint(coords)
Coords(
    my_points=[Point(x=1,2): 'a'}
)

或者强制缩进所有字段:

[ins] In [6]: pp.pprint(coords,width=1)
Coords(
    my_points=[
        Point(
            x=1,y=2
        ),Point(
            x=3,y=4
        )
    ],my_dict={
        'a': (
            1,2
        ),(
            1,2
        ): 'a'
    }
)

Prettyprinter 甚至可以高亮语法! (参见cpprint()


注意事项:

,

我们可以使用 dataclasses.fields 来递归嵌套数据类并漂亮地打印它们:

from collections.abc import Mapping,Iterable
from dataclasses import is_dataclass,fields

def pretty_print(obj,indent=4):
    """
    Pretty prints a (possibly deeply-nested) dataclass.
    Each new block will be indented by `indent` spaces (default is 4).
    """
    print(stringify(obj,indent))

def stringify(obj,indent=4,_indents=0):
    if isinstance(obj,str):
        return f"'{obj}'"

    if not is_dataclass(obj) and not isinstance(obj,(Mapping,Iterable)):
        return str(obj)

    this_indent = indent * _indents * ' '
    next_indent = indent * (_indents + 1) * ' '
    start,end = f'{type(obj).__name__}(',')'  # dicts,lists,and tuples will re-assign this

    if is_dataclass(obj):
        body = '\n'.join(
            f'{next_indent}{field.name}='
            f'{stringify(getattr(obj,field.name),indent,_indents + 1)},' for field in fields(obj)
        )

    elif isinstance(obj,Mapping):
        if isinstance(obj,dict):
            start,end = '{}'

        body = '\n'.join(
            f'{next_indent}{stringify(key,_indents + 1)}: '
            f'{stringify(value,' for key,value in obj.items()
        )

    else:  # is Iterable
        if isinstance(obj,list):
            start,end = '[]'
        elif isinstance(obj,tuple):
            start = '('

        body = '\n'.join(
            f'{next_indent}{stringify(item,' for item in obj
        )

    return f'{start}\n{body}\n{this_indent}{end}'

我们可以用嵌套的数据类来测试它:

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

@dataclass
class Coords:
    my_points: list
    my_dict: dict

coords = Coords([Point(1,2): 'a'})

pretty_print(coords)

# Coords(
#     my_points=[
#         Point(
#             x=1,#             y=2,#         ),#         Point(
#             x=3,#             y=4,#     ],#     my_dict={
#         'a': (
#             1,#             2,#         (
#             1,#         ): 'a',#     },# )

这应该足以涵盖大多数情况。希望这会有所帮助!

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。