PrettyPrinter 是 Python 3.6 及以上版本中的一个功能强大, 支持语法高亮, 描述性的美化打印包. 它使用了改进的 Wadler-Leijen 布局算法, 和 Haskell 打印美化库中的 prettyprinter 以及 anti-wl-pprint, JavaScript 的 Prettier,Ruby 的 prettypreinter.rb 以及 IPython 的 Ipython.lib.pretty 类似. Python 的 PrettyPrinter 集以上众家之所长, 并在此基础上继续改进, 因此也成为目前 Python 最强大的美化输出工具.
以下是使用 PrettyPrinter 输出结果的截图:
为什么 Python 还需要额外的美化打印包呢?
无论是 IDE 还是开发者手动运行命令, 将数据打印到屏幕上是程序运行过程中程序员和数值交互的最基础的界面. 改进该界面有助于提升开发体验和生产效率. Python 本身和第三方库都提供了一些工具来达到此目的:
__repr__和__str__两个下划线方法返回普通字符串.__repr__应该尽可能返回语法正确的 Python 表达式, 断言判断失败及控制台计算结果打印最常用的就是该方法. 由于其完全基于字符串格式化, 因此并不具备美化打印的功能.
标准库中的 pprint 模块为 dicts, lists, tuples, sets, and frozensets 等内置数据类型提供了美化打印的功能. 它将__repr__方法应用在用户自定义的类实例上. 然而, 它使用了非常贪婪的布局算法, 导致在很多情况下的美化打印出现问题. 由于自定义的美化打印受__repr__所限制, pprint 的作用也就限制于内置数据类型了.
第三方库 pprintpp 是对 pprint 的改进及替代方案, 也可以对输出进行优化, 不过和 pprint 一样受限于__repr__使用的代码美化定义.
IPython 中默认的打印模块 IPython.lib.pretty 的目标是 pprint 更进阶的替代方案. 和 pprint 相比, 它在很多方面都表现得更好: 大多数情况下算法都能对输出进行美化, 而且提供了针对用户自定义类型美化输出的定义工具, 能和输出的其他部分实现比较好的结合. 不过, 为了实现你自己的美化打印方式, 你需要对布局算法有所了解.
另外, 该 API 也有一些与生俱来的副作用: 调用美化打印工具将数据直接推送至布局缓冲区, 不允许原始布局对数据进行初步检测.
以上所有工具都达不到我对美化打印体验的要求, 因此我开始做以下几点改进:
实现一个能尽可能多的美化打印的算法, 即便在效率上做出一些牺牲. 花十分之一秒对输出结果进行美化是非常划算的, 因为当你需要在结果中寻找自己需要的数据时它将为你节约两秒钟的时间.
实现一个超级简单, 描述性的接口来实现用户自定义的美化打印工具. Python 成员几乎不会重写__repr__方法, 因为这很痛苦; 几乎没有人愿意为用户定义的类型编写整齐打印规则, 除非类型非常简单.
实现不会在无效 Python 语法上中断的语法高亮显示. 并不是所有__repr__方法都会返回有效的语法, 一旦发生语法错误会打断正常的语法高亮.
新的代码美化包的使用体验令我非常惊讶. 算法运行的很出色, 效率也满足需求. 而用户自定义美化规则的方法也很简单, 仅仅需要了解两个描述性的函数 register_pretty 和 pretty_call 即可. 语法高亮看上去非常漂亮, 且不会被无效语法处中断. 特别是语法高亮, 会使你很难再回到普通的美化打印工具, 它大大提升了程序员的开发体验.
最有趣的改进是描述性 API, 下面是它的工作原理.
简单, 描述性的 API
在 PrettyPrinter 中定义输出美化方法主要基于 (创建) 函数调用. 所有非字符的 Python 值都需要用函数结果表示. 该库的主力函数是 pretty_call, 它允许你来描述 PrettyPrinter 应该输出何种类型的函数调用. 下面就是 pretty_call 调用的一个例子:
- from prettyprinter import pretty_call
- # ctx is available in pretty printer definitions
- layout_primitive = pretty_call(ctx, sorted, [5, 3, 6, 1], reverse=True)
PrettyPrinter 处理原始布局的过程类似于以下语句:
- sorted([5, 3, 6, 1], reverse=True)
- (第一个参数 ctx 允许用户控制案例中 [5,3,6,1] 列表中嵌套的数据, reverse 参数的 True 值依据此进行渲染. 大部分情况都直接使用默认值即可.)
上面介绍了如何使用 Pretty_call, 接下来定义我们自己的类型.
- class MyClass:
- def __init__(self, one, two):
- self.one = one
- self.two = two
使用 register_pretty 修饰符, 可以为 MyClass 类定义美化方式:
- from prettyprinter import register_pretty, pretty_call
- @register_pretty(MyClass)
- def pretty_myclass(value, ctx):
- return pretty_call(ctx, MyClass, one=value.one, two=value.two)
cpprint 的输出如下:
- >>> from prettyprinter import cpprint
- >>> cpprint(MyClass(1, 2))
- MyClass(one=1, two=2)
带状态实例的表示
调用函数的一个缺陷是无法很好的表示带状态的实例. 通常你想要额外输出一些信息来表示实例的状态. PrettyPrinter 使用解释性评论解决了这一问题, 我对这一强大的特性颇为满意. 使用评论来标注 Python 值(或者表示 Python 值的原始布局), 该评论将神奇的出现在输出的结果中.
假如我们定义了一个包含其连接与断开两个状态的 Connection 类:
- class Connection:
- def __init__(self, hostname):
- self.hostname = hostname
- self.is_open = False
- def open(self):
- self.is_open = True
- def close(self):
- self.is_open = False
如果想得到以下输出:
Connection('http://example.com') # Status: Open
可以通过如下定义来实现:
- from prettyprinter import register_pretty, pretty_call, comment
- @register_pretty(Connection)
- def pretty_connection(connection, ctx):
- status_text = (
- 'Status: Open'
- if connection.is_open
- else 'Status: Closed'
- )
- return comment(
- pretty_call(
- ctx,
- Connection,
- connection.hostname,
- ),
- status_text
- )
结论
我非常享受将 PrettyPrinter 作为开发工具包的一部分. 单独一篇文章只能粗略分享一些点, 还有很多有趣的部分等待你去探索, 强烈推荐大家尝试一下! 在 IPython 中使用效果更佳, 因为交互式解释器环境中的所有结果都可以自动使用 PrettyPrinter 打印输出. 文档中有对该命令的设置的说明.
点击 source code on GitHub 查看该项目的源码, 文档在 documentation on readthedocs.io(目前可能还略显简陋). 包中内置了针对 Django 模型, QuerySets 以及使用 attrs 包创建的所有类的现成的定义. 因此如果你恰好也用到了其中的某个, 毫无疑问你会想马上试试它的!
来源: https://yq.aliyun.com/articles/679940