问题描述
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()
)
注意事项:
- prettyprinter 不是 Python 标准库的一部分
- default values aren't printed,at all 并且从 2021 年开始就没有办法解决这个问题
我们可以使用 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',# },# )
这应该足以涵盖大多数情况。希望这会有所帮助!