天天看點

Python | 用PrettyPrinter,讓Python輸出更漂亮,你值得擁有

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 | 用PrettyPrinter,讓Python輸出更漂亮,你值得擁有
Python | 用PrettyPrinter,讓Python輸出更漂亮,你值得擁有

為什麼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包建立的所有類的現成的定義。是以如果你恰好也用到了其中的某個,毫無疑問你會想馬上試試它的!

原文釋出時間為:2018-12-16

本文作者:上海小胖

本文來自雲栖社群合作夥伴“

Python專欄

”,了解相關資訊可以關注“xpchuiit”微信公衆号