python3.7 的dataclass新特性大大簡化了定義類對象的代碼量,代碼簡潔明晰。經過使用@dataclass裝飾器來修飾類的設計,例如python
from dataclasses import dataclass @dataclass class DataClassCard: rank: str suit: str #生成實例 queen_of_hearts = DataClassCard('Q', 'Hearts') print(queen_of_hearts.rank) print(queen_of_hearts) print(queen_of_hearts == DataClassCard('Q', 'Hearts'))
運行結果數據結構
Q DataClassCard(rank='Q', suit='Hearts') True
而常規的類,按照3.7以前的語法相似於這樣ide
class RegularCard def __init__(self, rank, suit): self.rank = rank self.suit = suit
雖然這種寫法並無使用更多的代碼量,可是咱們很容易看到爲了初始化,僅僅只是爲了初始化一個對象,rank和suit已經重複了三次。此外,若是你試圖使用這個RegularCard類,你會注意到對象的表示不是很具描述性,而且已有的類與新聲明的類是沒法比較是否相同的。由於每次聲明都會使用一個新的內存地址,而「==」不止比較類存儲的信息,還比較內存地址是否相同。函數
具體請看下面代碼post
queen_of_hearts = RegularCard('Q', 'Hearts') print(queen_of_hearts.rank) print(queen_of_hearts) print(queen_of_hearts == RegularCard('Q', 'Hearts'))
運行結果ui
'Q' <__main__.RegularCard object at 0x7fb6eee35d30> False
dataclass還在底層給咱們作了更多的有用的封裝。默認狀況下dataclass實現了repr方法,能夠很好的提供字符串表示;也是了eq方法,能夠作基本的對象比較。而若是RegularCard想實現上面的功能須要寫大量的聲明,代碼量多的嚇人spa
class RegularCard(object): def __init__(self, rank, suit): self.rank = rank self.suit = suit def __repr__(self): #能夠將類的信息打印出來 return (f'{self.__class__.__name__}' f'(rank={self.rank!r}, suit={self.suit!r})') #你們能夠試着將「!r」去掉或者將其中的r改變爲s或a,看看輸出結果會有什麼變化 #conversion character: expected 's', 'r', or 'a' def __eq__(self, other): #能夠比較類是否相同(不考慮內存地址) if other.__class__ is not self.__class__: return NotImplemented return (self.rank, self.suit) == (other.rank, other.suit
在本教程中,您將準確瞭解dataclass類所帶來的便利性。除了很好的表示(當使用print時能夠在很好的打印出來)和比較(是否相同),你會看到:設計
如何給dataclass對象添加默認的字段(field)code
如何讓dataclass容許對象進行排序對象
如何讓dataclass表示不可更改數據
dataclass類的替代方案
可能你接觸過nametuple,它經常用來創造可讀的輕量級數據結構。實際上咱們能夠經過nametuple重複創造數據實例:
from collections import namedtuple NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
NamedTupleCard能夠作到DataClassCard能過的事情:
queen_of_hearts = NamedTupleCard('Q', 'Hearts') print(queen_of_hearts.rank) print(queen_of_hearts) print(queen_of_hearts == NamedTupleCard('Q', 'Hearts'))
運行結果跟 DataClassCard同樣
'Q' NamedTupleCard(rank='Q', suit='Hearts') True
可是nametuple也有一些限制和不足。例如,咱們不能對nametuple實例的屬性值進行更改,由於從根本上將nametuple是元組類,是不可更改數據類型。在某些應用中,這多是很棒的功能,但在其餘應用場景中,擁有更多靈活性會更好:
card = NamedTupleCard('7', 'Diamonds') card.rank = '9'
因爲nametuple不可更改性,運行結果報錯以下
AttributeError: can't set attribute
dataclass基礎
如今返回到dataclass,咱們要建立一個Position類,包含名字和經緯度信息的地理位置信息類:
from dataclasses import dataclass @dataclass class Position: name: str lon: float lat: float pos= Position('Oslo', 10.8, 59.9) print(pos) print(pos.lat) print(f'{pos.name} is at {pos.lat}°N, {pos.lon}°E')
咱們解讀下Position類代碼含義。首先使用@dataclass放在Position上方起到裝飾器語法做用。
經過@dataclass裝飾後的Position,咱們能夠給Position增長一些默認的字段,而且聲明這些字段的類型。
運行結果以下:
Position(name='Oslo', lon=10.8, lat=59.9) 59.9 Oslo is at 59.9°N, 10.8°E
咱們也可使用相似於nametuple語法的make_dataclass來建立Position類。代碼以下
from dataclasses import make_dataclass
pos = make_dataclass('Position', ['name', 'lat', 'lon'])
print(pos)
#打印結果:<class 'types.Position.
dataclass實際上也是普通的python對象,只不過dataclass幫咱們將 init()、 repr()和eq()封裝,更簡潔的提供給咱們使用。
dataclass類的默認屬性值
在dataclass中很方便的給屬性值添加默認值
from dataclasses import dataclass @dataclass class Position: name: str lon: float = 0.0 lat: float = 0.0
dataclass默認值設置相似於init()方法
print(Position('Null Island')) print(Position('Greenwich', lat=51.8)) print(Position('Vancouver', -123.1, 49.3))
默認經緯度均爲0.運行結果以下
Position(name='Null Island', lon=0.0, lat=0.0) Position(name='Greenwich', lon=0.0, lat=51.8) Position(name='Vancouver', lon=-123.1, lat=49.3)
稍後咱們會講到默認工廠(default factory),從而爲咱們默認值設置提供了更多更復雜的功能。
類型提示(Type Hints)
您可能已經注意到咱們使用類型提示定義的字段:name:str表示名稱應該是文本字符串(str類型)。
實際上,在數據類中定義字段時,必須添加某種類型提示。 若是沒有類型提示,該字段將不是dataclass類的一部分。 可是,若是您不想向dataclass類添加顯式類型,請使用typing.Any:
from dataclasses import dataclass from typing import Any @dataclass class WithoutExplicitTypes: name: Any value: Any = 42
雖然在使用數據類時須要以某種形式添加類型提示,但這些類型在運行時不會強制執行。
withoutexplicittypes = WithoutExplicitTypes(name=38, value='29') print(withoutexplicittypes)
上面的代碼運行沒有任何問題,運行結果。咱們發現name是任意類型,而values也是任意類型,雖然默認設置爲整數42,可是在這裏咱們輸入的是字符串29,也能正常運行。
WithoutExplicitTypes(name=38, value='29')
添加方法
dataclass類就是普通的python類,因此咱們能夠像給類定義方法同樣給dataclass類定義方法。
這裏咱們定義距離計算方法,爲了方便演示,咱們這裏假設地球是二維平面,經緯度表明座標軸中的位置,使用歐幾里得方法計算距離便可。
from dataclasses import dataclass @dataclass class Position: name: str lon: float = 0.0 lat: float = 0.0 def distance(self, newpostion): return sqrt((newpostion.lon - self.lon)**2 + (newpostion.lat - self.lat)**2) pos1 = Position('A', 0, 0) pos2 = Position('B', 0.0, 2.0) pos3 = Position('C', 3.0, 4.0) print(pos1.distance(pos2)) print(pos1.distance(pos3))
A點是座標原點,B點(0, 2), C點(3, 4)。運行結果
2.0 #AB = 2 5.0 #AC = 5
靈活的dataclass
到如今位置,咱們已經瞭解了dataclass的基本特性,如今咱們接觸些dataclass的高級用法,如參數和field()函數。將這二者結合能讓咱們更方便的 控制咱們創造的類。
讓咱們回到您在本教程開頭看到的撲克牌示例,並在咱們處理時添加一個包含一副牌的類:
from dataclasses import dataclass from typing import List @dataclass class PlayingCard: rank: str suit: str @dataclass class Deck: #Deck:一副牌。cards參數傳入列表,該列表中含有多個PlayingCard類實例。 cards: List[PlayingCard] queen_of_hearts = PlayingCard('Q', 'Hearts') ace_of_spades = PlayingCard('A', 'Spades') two_cards = Deck([queen_of_hearts, ace_of_spades]) print(two_cards)
上面的two_cards是最簡單的一副牌(Deck類),運行結果以下
Deck(cards=[PlayingCard(rank='Q', suit='Hearts'), PlayingCard(rank='A', suit='Spades')])