Dataclass是Python3.7新增的對象類型,若是你尚未使用Python3.7——這是最新發布的Python版本,請儘快到官方網站下載安裝,一邊隨本文一塊兒體會它的新發展。 簡介html
Dataclass是Python的類,但適合存儲數據對象。數據對象是什麼?下面列出這種對象類型的幾項特徵,雖然不全面:python
固然還有更多的特性,可是這裏列出的足以幫助你理解「數據對象」關鍵所在。bash
爲了理解Dataclass類,咱們將寫一個簡單的類,它包含一個數字,而且容許咱們執行上面提到的操做。框架
Python 3.7提供了一個裝飾器dataclass,用它能夠將一個類轉換爲「數據對象」的類,即dataclass。dom
from dataclasses import dataclass
@dataclass
class A:
pass
複製代碼
下面,對這種類型進行深刻研究。機器學習
下面建立一個類,注意初始化方法,主要是實現以該對象屬性存儲數字。函數
>>> class Number:
... def __init__(self, val):
... self.val = val
...
>>> one = Number(1)
>>> one.val
1
複製代碼
下面使用@dataclass裝飾器實現以上一樣的功能:post
>>> @dataclass
... class Number:
... val:int
...
>>> done = Number(1)
>>> done.val
1
複製代碼
概括使用dataclass裝飾器帶來的變化:學習
1.不須要在__init__
方法中給實例self的屬性賦值。網站
2.有了類型提示,提升了可讀性。如今咱們當即知道屬性val是整數型的——彷佛在吸取強類型語言的特徵。
若是還記得《Python之禪》中說過的「Readability counts」(重在可讀性),彷佛感受上面的作法吻合了這種要求。
還能夠這樣寫,設置默認值。
>>> @dataclass
... class Number:
... val:int = 0
...
>>> do = Number()
>>> do.val
0
>>> dt = Number(2)
>>> dt.val
2
複製代碼
對象說明應該是用有意義的字符串表示的,它在在程序調試中很是有用。
默認的Python對象表示法不是頗有意義:
>>> class Number:
... def __init__(self, val=0):
... self.val = val
...
>>> a = Number()
>>> a
<__main__.Number object at 0x10279bf60>
複製代碼
返回值沒有用有意義的字符串表示,咱們僅僅可以知道它在內存中的地址。
因此,若是你要自定義對象類型,最好要使用__repr__
方法,它是一個對解釋器友好的方法(詳見《跟老齊學Python:輕鬆入門》中的有關闡述),經過這個方法能夠對此對象給予有意義的說明。
>>> class Number:
... def __init__(self, val=0):
... self.val = val
... def __repr__(self):
... return "your object is: " + str(self.val)
...
>>> a = Number()
>>> a
your object is: 0
複製代碼
再來看調試結果,則告訴咱們,變量a引用的對象就是數字1——咱們獲得一個有意義的對象說明:
若是不用上面的方式,而是使用@dataclass裝飾器定義一個Dataclass類型的對象,則會自動添加一個__repr__
函數,這樣咱們就沒必要手動實現它。
>>> @dataclass
... class Number:
... val:int = 0
...
>>> a = Number()
>>> a
Number(val=0)
複製代碼
通常來講,數據對象須要相互比較。
兩個變量a和b所引用的對象,能夠進行以下各類比較操做:
a < b
a > b
a == b
a >= b
a <= b
複製代碼
在Python中,若是自定義對象類型實現上述各項比較功能,就必須定義相應的方法,好比要實現「==」和「<」比較,就要分別實現「__eq__
」和「__lt__
」兩個特殊方法(詳見《跟老齊學Python:輕鬆入門》中的說明)。下面是一個簡單的例子:
class Number:
def __init__( self, val = 0):
self.val = val
def __eq__(self, other):
return self.val == other.val
def __lt__(self, other):
return self.val < other.val
複製代碼
這是一種一般的自定義對象類型,並該類型對象可以實現比較「==」和「<」的比較運算。
若是使用@dataclass,則讓代碼簡單了許多。
@dataclass(order = True)
class Number:
val: int = 0
複製代碼
就這麼多代碼,驚訝了吧。
不須要定義__eq__
和__lt__
方法。只須要在@dataclass裝飾器的參數中設置order = True
便可,就自動在所定義的類中實現了這兩個特殊方法。
那麼,怎麼作到的呢?
當你使用@dataclass時,它會向類定義中添加函數__eq__
和__lt__
。這是咱們已經知道的。那麼,這些函數是如何知道檢查是否相等和進行比較的呢?
一個由dataclass生成的__eq__
函數將把屬性元組與相同類的另外一個實例的屬性的元組進行比較。在咱們的例子中,由__eq__
方法自動生成的對象將等同於:
def __eq__(self, other):
return (self.val,) == (other.val,)
複製代碼
讓咱們來看一個更詳細的例子:
寫一個數據類Person來保存姓名和年齡。
@dataclass(order = True)
class Person:
name: str
age:int = 0
複製代碼
這個自動生成的__eq__
方法將等價於:
def __eq__(self, other):
return (self.name, self.age) == ( other.name, other.age)
複製代碼
注意屬性的順序。它們老是按照你在數據類中定義的順序生成的。
同理,等效的__le__
函數將相似於這樣:
def __le__(self, other):
return (self.name, self.age) <= (other.name, other.age)
複製代碼
由於數據對象中已經默認實現了各類比較功能,所以就能夠實現排序。
>>> import random
>>> a = [Number(random.randint(1,10)) for _ in range(10)] #隨機數列表
>>> a
>>> [Number(val=2), Number(val=7), Number(val=6), Number(val=5), Number(val=10), Number(val=9), Number(val=1), Number(val=10), Number(val=1), Number(val=7)]
>>> sorted_a = sorted(a) #Sort Numbers in ascending order
>>> [Number(val=1), Number(val=1), Number(val=2), Number(val=5), Number(val=6), Number(val=7), Number(val=7), Number(val=9), Number(val=10), Number(val=10)]
>>> reverse_sorted_a = sorted(a, reverse = True) #降序排列
>>> reverse_sorted_a
>>> [Number(val=10), Number(val=10), Number(val=9), Number(val=7), Number(val=7), Number(val=6), Number(val=5), Number(val=2), Number(val=1), Number(val=1)]
複製代碼
在Python中,自定義不可變對象,其實有點麻煩。
>>> class Number:
... val: int = 0
...
>>> a = Number()
>>> a.val
0
>>> a.val = 10 #所謂不變對象,就是當用這種方式修改屬性值的時候,應該不容許,應該報異常
>>> a.val #固然,在這裏沒有報異常,而是實現了修改,由於此處的對象不是不可變的,是可變的。
10
複製代碼
在Python3.7中,有了dataclass,則能夠很輕鬆的實現上述設想。
在下面的代碼中,使用了@dataclass裝飾器,實現不可變對象。
>>> from dataclasses import dataclass
>>> @dataclass(frozen=True)
... class Book:
... name: str = "Learn Python with Laoqi"
...
>>> python_book = Book()
>>> python_book.name
'Learn Python with Laoqi'
>>> python_book.name = "other Python Book" #試圖進行修改,結果報異常,不容許修改
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'name'
複製代碼
用這種方式設置常熟,是否是更優雅。
__post_init__
進行初始化後處理什麼是初始化後處理?下面舉一個例子,說明之:
>>> import math
>>> class Float:
... def __init__(self, val=0):
... self.val = val
... self.process()
... def process(self):
... self.decimal, self.integer = math.modf(self.val)
...
>>> a = Float(2.5)
>>> a.decimal
0.5
>>> a.integer
2.0
複製代碼
在類Float中,定義了方法process,而且在初始化方法__init__
中調用,這就是要在初始化以後作的事情。
在Python3.7中,具備上述功能代碼可使用__post_init__
來完成。以下所示:
>>> @dataclass
... class FloatNumber:
... val: float = 0.0
... def __post_init__(self):
... self.decimal, self.integer = math.modf(self.val)
...
>>> b = FloatNumber(2.5)
>>> b.val
2.5
>>> b.integer
2.0
>>> b.decimal
0.5
複製代碼
是否是再次感覺到了「代碼的整潔」。
參考資料:
https://medium.com/mindorks/understanding-python-dataclasses-part-1-c3ccd4355c34
https://docs.python.org/3/library/dataclasses.html
複製代碼
以上書籍,各大網店有售。相關網站:itdiffer.com