Python3.7 dataclass 介紹


Python3.7 加入了一個新的 module:dataclasses。可以簡單的理解成“支持默認值、可以修改的tuple”( “mutable namedtuples with defaults”)。其實沒什么特別的,就是你定義一個很普通的類,@dataclass 裝飾器可以幫你生成 __repr__ __init__ 等等方法,就不用自己寫一遍了。但是此裝飾器返回的依然是一個 class,這意味着並沒有帶來任何不便,你依然可以使用繼承、metaclass、docstring、定義方法等。

先展示一個 PEP 中舉的例子,下面的這段代碼(Python3.7):

@dataclass 會自動生成

 

引入dataclass的理念

Python 想簡單的定義一種容器,支持通過的對象屬性進行訪問。在這方面已經有很多嘗試了:

  1. 標准庫的 collections.namedtuple
  2. 標准庫的 typing.NamedTuple
  3. 著名的 attr 庫
  4. 各種 Snippet,問題和回答

那么為什么還需要 dataclass 呢?主要的好處有:

  1. 沒有使用 BaseClass 或者 metaclass,不會影響代碼的繼承關系。被裝飾的類依然是一個普通的類
  2. 使用類的 Fields 類型注解,用原生的方法支持類型檢查,不侵入代碼,不像 attr 這種庫對代碼有侵入性(要用 attr 的函數將一些東西處理)

dataclass 並不是要取代這些庫,作為標准庫的 dataclass 只是提供了一種更加方便使用的途徑來定義 Data Class。以上這些庫有不同的 feature,依然有存在的意義。

基本用法

dataclasses 的 dataclass 裝飾器的原型如下:

很明顯,這些默認參數可以控制是否生成魔術方法。通過本文開頭的例子可以看出,不用加括號也可以調用。

通過 field 可以對參數做更多的定制化,比如默認值、是否參與repr、是否參與hash等。比如文檔中的這個例子,由於 mylist 的缺失,就調用了 default_factory 。更多 field 能做的事情參考文檔吧。

此外,dataclasses 模塊還提供了很多有用的函數,可以將 dataclass 轉換成 tuple、dict 等形式。話說我自己重復過很多這樣的方法了……

 

Hook init

自動生成的 __init__ 可以被 hook。很簡單,自動生成的 __init__ 方法會調用 __post_init__

如果想傳給 __post_init__ 方法但是不傳給 __init__ ,可以使用一個特殊的類型 InitVar

 

不可修改的功能

Python 沒有 const 類似的東西,理論上任何東西都是可以修改的。如果非要說不能修改的實現呢,這里有個比較著名的實現。只有不到10行代碼。

但是有了 dataclass ,可以直接使用 @dataclass(frozen=True) 了。然后裝飾器會對 Class 添加上 __setattr__ 和 __delattr__ 。Raise 一個 FrozenInstanceError。缺點是會有一些性能損失,因為 __init__ 必須通過 object.__setattr__ 。

繼承

對於有繼承關系的 dataclass,會按照 MRO 的反順序(從object開始),對於每一個基類,將在基類找到的 fields 添加到順序的一個 mapping 中。所有的基類都找完了,按照這個 mapping 生成所有的魔術方法。所以方法中這些參數的順序,是按照找到的順序排的,先找到的排在前面。因為是先找的基類,所以相同 name 的話,后面子類的 fields 定義會覆蓋基類的。比如文檔中的這個例子:

那么最后生成的將會是:

注意 x y 的順序是 Base 中的順序,但是 C 的 x 是 int 類型,覆蓋了 Base 中的 Any。

可變對象的陷阱

在前面的“基本用法”一節中,使用了 default_factory 。為什么不直接使用 [] 作為默認呢?

老鳥都會知道 Python 這么一個坑:將可變對象比如 list 作為函數的默認參數,那么這個參數會被緩存,導致意外的錯誤。詳細的可以參考這里:Python Common Gotchas

考慮到下面的代碼:

將會生成:

這樣無論實例化多少對象,x 變量將在多個實例之間共享。dataclass 很難有一個比較好的辦法預防這種情況。所以這個地方做的設計是:如果默認參數的類型是 list dict 或 set ,就拋出一個 TypeError。雖然不算完美,但是可以預防很大一部分情況了。

如果默認參數需要是 list,那么就用上面提到的 default_factory 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM