(轉)python 數據類:dataclass


原文:https://www.cnblogs.com/dan-baishucaizi/p/14786600.html

1、dataclass簡介

​ dataclass是python3.7開始帶有的新屬性(類裝飾器),dataclass是指”一個帶有默認值的可變namedtuple“,本質還是一個類,它的屬性非特殊情況可以直接訪問,類中有與屬性相關的類方法。簡單地說就是一個含有數據及其操作方法的類。

dataclass與普通類的區別

  • 與普通類相比,dataclass通常不包含私有屬性,這些屬性可以直接訪問(也可以私有);
  • repr() 函數將對象轉化為供解釋器讀取的形式;dataclass的repr方法通常有其固定格式,會打印類名、屬性名、屬性值;
  • dataclass有__eq____hash__這些魔法方法;
  • dataclass有着模式單一固定的構造方式,根據需要有時需要重載運算符,而普通class通常無需這些工作。

注:namedtuple是tuple的子類,它的元素是有命名的!


Top  ---  Bottom

2、引入dataclass裝飾器

常見的類生成方式

class elfin: def __init__(self, name, age): self.name = name self.age = age 

使用dataclass裝飾器

@dataclass class elfin: name: str age: int 

我們使用@dataclass就可以實現與普通類的效果,這樣代碼更簡潔!

__post_init__方法

如果某個屬性需要在init后處理,就可以放置到__post_init__中!

@dataclass class elfin: name: str age: int def __post_init__(self): if type(self.name) is str: self.identity = identity_dict[self.name] 

測試上面的案例:

>>> from dataclasses import dataclass >>> identity_dict = { ... "firstelfin": "boss", ... "secondelfin": "master", ... "thirdelfin": "captain" ... } >>> @dataclass ... class Elfin: ... name: str ... age: int ... ... def __post_init__(self): ... if type(self.name) is str: ... self.identity = identity_dict[self.name] >>> print(Elfin) ... Out[1]: <class '__main__.Elfin'> >>> elfin_ins = Elfin("firstelfin", 23) >>> elfin_ins ... Out[2]: Elfin(name='firstelfin', age=23) >>> elfin_ins.identity ... Out[3]: 'boss' 

上面的案例向我們展示了即使init部分沒有生成identity屬性,實例也可以獲取到!

下面我們就分別展示dataclass裝飾器的一些知識點。


Top  ---  Bottom

3、dataclass裝飾器選項

使用dataclass類裝飾器的選項,我們可以定制我們想要的數據類,默認選項為:

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False) class Elfin: pass 

裝飾器的參數選項說明:

  • init控制是否生成__init__方法;
  • repr控制是否生成__repr__方法;
  • eq控制是否生成__eq__方法,它用於判斷實例是否相等;
  • order控制是否創建四種大小關系方法:__lt____le____gt____ge__;order為True,則eq不能為False,也不能自定義order方法。
  • unsafe_hash控制hash的生成方式。
    • 當unsafe_hash為False時,將根據eq、frozen參數來生成__hash__方法;
      1. eq、frozen都為True時,__hash__將會生成;
      2. eq為True,frozen為False,__hash__將被設置為None;
      3. eq為False,frozen為True,__hash__將使用object(超類)的同名屬性(通常就是對象id的hash)
    • 當unsafe_hash為True時,將會根據類的屬性生成__hash__。如其名,這是不安全的,因為屬性是可變的,這會導致hash的不一致。當然您能保證對象屬性不會變,你也可以設置為True。
  • frozen控制是否凍結對field賦值。設置為True時,對象將是不可變的,因為不可變,所以如果設置有__setattr____delattr__將會導致TypeError錯誤。

前兩個參數我們在上一章實際已經看了效果,下面我們查看參數eqorder

>>> @dataclass(init=True, repr=True, eq=True, order=True) ... class Elfin: ... name: str ... age: int ... ... def __post_init__(self): ... if type(self.name) is str: ... self.identity = identity_dict[self.name] >>> elfin_ins1 = Elfin("thirdelfin", 18) >>> elfin_ins2 = Elfin("secondelfin", 20) >>> elfin_ins1 == elfin_ins2 ... Out[4]: False >>> elfin_ins1 >= elfin_ins2 ... Out[5]: True >>> 

可以發現我們可以在實例之間進行大小的比較了!同時我們知道普通類是不同進行大小比較的:

>>> class A: ... def __init__(self, age): ... self.age = age >>> a1 = A(20) >>> a2 = A(30) >>> a1 > a2 ... TypeError Traceback (most recent call last) ... <ipython-input-24-854e76ddfa09> in <module> ... ----> 1 a1 > a2 ... ... TypeError: '>' not supported between instances of 'A' and 'A' 

上面我們提到了field,實際上,所有的數據類屬性,都是被field所控制,它代表一個數據的實體和它的元信息,下面我們了解一下dataclasses.field


Top  ---  Bottom

4、數據類的基石--dataclasses.field

field的定義如下:

def field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None): if default is not MISSING and default_factory is not MISSING: raise ValueError('cannot specify both default and default_factory') return Field(default, default_factory, init, repr, hash, compare, metadata) 

一般情況下,我們無需直接使用,裝飾器會根據我們給出的類型注解自動生成field,但有時候也需要定制這個過程,所以dataclasses.field就特別重要了!

參數說明:

  • default:如果調用時沒有指定,則默認為None,它控制的是field的默認值;

  • default_factory:控制如何產生值,它接收一個無參數或者全是默認參數的callable對象,然后調用該對象field的初始值,再將default復制給callable對象。

  • init:控制是否在init中生成此參數。在前面章節的案例中,我們要生成self.identity屬性,但是不想在init中傳入,就可以使用field了。

    >>> @dataclass(init=True, repr=True, eq=True, order=True) ... class Elfin: ... name: str ... age: int ... identity: str = field(init=False) ... ... def __post_init__(self): ... if type(self.name) is str: ... self.identity = identity_dict[self.name] >>> elfin_ins3 = Elfin("firstelfin", 20) >>> elfin_ins3 ... Out[6]: Elfin(name='firstelfin', age=20, identity='boss') 
  • repr:表示該field是否被包含進repr的輸出,默認要輸出,如上面的案例。

  • compare:是否參與比較和計算hash值。

  • hash:是否參與比較和計算hash值。

  • metadata不被dataclass自身使用,通常讓第三方組件從中獲取某些元信息時才使用,所以我們不需要使用這一參數。

只能初始化調用的屬性

如果指定一個field的類型注解為dataclasses.InitVar,那么這個field將只會在初始化過程中(__init____post_init__)可以被使用,當初始化完成后訪問該field會返回一個dataclasses.Field對象而不是field原本的值,也就是該field不再是一個可訪問的數據對象。

>>> from dataclasses import InitVar >>> @dataclass(init=True, repr=True, eq=True, order=True) ... class Elfin: ... name: str ... age: int ... identity: InitVar[str] = None ... ... def __post_init__(self, identity): ... if type(self.name) is str: ... self.identity = identity_dict[self.name] >>> elfin_ins3 = Elfin("firstelfin", 20) >>> elfin_ins3 ... Out[7]: Elfin(name='firstelfin', age=20) >>> elfin_ins3.identity >>> 

注意這里elfin_ins3.identity說明都沒有返回,實際上應該是”boss“,但是我們訪問不到。


Top  ---  Bottom

5、dataclass的常用函數

5.1 轉換數據為字典 dataclasses.asdict

>>> from dataclasses import asdict >>> asdict(elfin_ins3) ... Out[8]: {'name': 'firstelfin', 'age': 20} 

5.2 轉換數據為元組 dataclasses.astuple

>>> from dataclasses import astuple >>> astuple(elfin_ins3) ... Out[9]: ('firstelfin', 20) 

5.3 判斷是否是dataclass類

>>> from dataclasses import is_dataclass >>> is_dataclass(Elfin) ... Out[10]: True >>> is_dataclass(elfin_ins3) ... Out[11]: True 

Top  ---  Bottom

6、dataclass繼承

​ python3.7引入dataclass的一大原因就在於相比namedtuple,dataclass可以享受繼承帶來的便利。

dataclass裝飾器會檢查當前class的所有基類,如果發現一個dataclass,就會把它的屬性按順序添加進當前的class,隨后再處理當前class的field。所有生成的方法也將按照這一過程處理,因此如果子類中的field與基類同名,那么子類將會無條件覆蓋基類。子類將會根據所有的field重新生成一個構造函數,並在其中初始化基類。

案例:

>>> @dataclass(init=True, repr=True, eq=True, order=True) ... class Elfin: ... name: str = "firstelfin" ... age: int = 20 ... identity: InitVar[str] = None ... ... def __post_init__(self, identity): ... if type(self.name) is str: ... self.identity = identity_dict[self.name] >>> @dataclass ... class Wude(Elfin): ... age: int = 68 >>> Wude() ... Out[11]: Wude(name='firstelfin', age=68) >>> 

上述可見,Wude類繼承了Elfin類的name屬性,而實例中的age覆蓋了Elfin中的age定義。


Top  ---  Bottom

7、小結

​ 合理使用dataclass將會大大減輕開發中的負擔,將我們從大量的重復勞動中解放出來,這既是dataclass的魅力,不過魅力的背后也總是有陷阱相伴,最后我想提幾點注意事項:

  • dataclass通常情況下是unhashable的,因為默認生成的__hash__None,所以不能用來做字典的key,如果有這種需求,那么應該指定你的數據類為frozen dataclass
  • 小心當你定義了和dataclass生成的同名方法時會引發的問題
  • 當使用可變類型(如list)時,應該考慮使用fielddefault_factory
  • 數據類的屬性都是公開的,如果你有屬性只需要初始化時使用而不需要在其他時候被訪問,請使用dataclasses.InitVar

​ 只要避開這些陷阱,dataclass一定能成為提高生產力的利器。


免責聲明!

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



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