(轉)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的子類,它的元素是有命名的!
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裝飾器的一些知識點。
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__
方法;- eq、frozen都為True時,
__hash__
將會生成; - eq為True,frozen為False,
__hash__
將被設定為None; - eq為False,frozen為True,
__hash__
將使用object(超類)的同名屬性(通常就是物件id的hash)
- eq、frozen都為True時,
- 當unsafe_hash為True時,將會根據類的屬性生成
__hash__
。如其名,這是不安全的,因為屬性是可變的,這會導致hash的不一致。當然您能保證物件屬性不會變,你也可以設定為True。
- 當unsafe_hash為False時,將根據eq、frozen引數來生成
- frozen控制是否凍結對field賦值。設定為True時,物件將是不可變的,因為不可變,所以如果設定有
__setattr__
、__delattr__
將會導致TypeError
錯誤。
前兩個引數我們在上一章實際已經看了效果,下面我們檢視引數eq
、order
:
>>> @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
。
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“,但是我們訪問不到。
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
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定義。
7、小結
合理使用dataclass將會大大減輕開發中的負擔,將我們從大量的重複勞動中解放出來,這既是dataclass的魅力,不過魅力的背後也總是有陷阱相伴,最後我想提幾點注意事項:
- dataclass通常情況下是unhashable的,因為預設生成的
__hash__
是None
,所以不能用來做字典的key,如果有這種需求,那麼應該指定你的資料類為frozendataclass - 小心當你定義了和
dataclass
生成的同名方法時會引發的問題 - 當使用可變型別(如list)時,應該考慮使用
field
的default_factory
- 資料類的屬性都是公開的,如果你有屬性只需要初始化時使用而不需要在其他時候被訪問,請使用
dataclasses.InitVar
只要避開這些陷阱,dataclass一定能成為提高生產力的利器。
技術連結