Python 數(shù)據類 dataclasses 實踐

簡介

Python3.7 版本開始鲁纠,引入了一個新的模塊 dataclasses,該模塊主要提供了一種數(shù)據類的數(shù)據類的實現(xiàn)方式廷臼。基于 PEP-557實現(xiàn)绝页。 所謂數(shù)據類荠商,類似 Java 語言中的 Bean。通過一個容器類(class)续誉,繼而使用對象的屬性訪問數(shù)據莱没。

如果你使用過標準庫中的 collections.namedtuple, 或者 typing.NamedTupledataclasses是與這兩者類似的酷鸦。

通過 dataclasses 我們可以更加方便的去定義一個數(shù)據類饰躲。并且可以通過原生的方式進行類型檢查。

一個基礎例子:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

基礎用法

dataclasses 提供一個模塊級的裝飾器 dataclass 用來將類轉化為數(shù)據類臼隔。該裝飾器的原型定義如下:

@dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

提供的默認參數(shù)用來控制是否生成相應的魔術方法嘹裂。如 reprTrue 時,將會自動生成 __repr__ 方法躬翁。

我們定義一個簡單的數(shù)據類焦蘑,用以實現(xiàn)一個使用對象的屬性存儲實體 Person 數(shù)據:

@dataclasses.dataclass
class Person:
    name: str
    age: int = 20

該類中定義了兩個屬性 nameage。分別表示名稱和年齡盒发,并且說明 name 屬性是一個字符串例嘱,age 屬性是一個數(shù)字(注意: 因為 Python 編譯器不會對此處的類型進行強制檢查)狡逢,并為 age 屬性設置了默認值 20

我們可以這樣去使用:

In [1]: person = Person('ikaros', 24)

In [2]: person.name
Out[2]: 'ikaros'

# 因為默認情況下 `repr` 是自動生成的拼卵,所以我們得到 `person` 的字符串表示奢浑。
In [3]: person
Out[3]: Person(name='ikaros', age=24)

通過使用 field 我們可以對參數(shù)做更多的定制化,如:

@dataclasses.dataclass
class Person:
    name: str
    age: int = dataclasses.field(default=20, repr=False)

此處我們?yōu)?age 屬性賦予了一個額外的 reprFalse 的參數(shù)腋腮。該參數(shù)說明雀彼,在調用 __repr__ 方法時,不展示 age 屬性:

In [4]: person
Out[4]: Person(name='ikaros')

更多的 field 說明即寡,可以查看 參考文檔徊哑。

實例說明

此處我們通過一個實際的例子展示 dataclasses 的用法.

現(xiàn)有一個數(shù)據實體內部的數(shù)據如下:

{
    "id": "20531316728",
    "about": "The Facebook Page celebrates how our friends inspire us, support us, and help us discover the world when we connect.",
    "birthday": "02/04/2004",
    "name": "Facebook",
    "username": "facebookapp",
    "fan_count": 214643503,
    "cover": {
        "cover_id": "10158913960541729",
        "offset_x": 50,
        "offset_y": 50,
        "source": "https://scontent.xx.fbcdn.net/v/t1.0-9/s720x720/73087560_10158913960546729_8876113648821469184_o.jpg?_nc_cat=1&_nc_ohc=bAJ1yh0abN4AQkSOGhMpytya2quC_uS0j0BF-XEVlRlgwTfzkL_F0fojQ&_nc_ht=scontent.xx&oh=2964a1a64b6b474e64b06bdb568684da&oe=5E454425",
        "id": "10158913960541729"
    }
}

我們通過定義一個對應的數(shù)據類來表示該數(shù)據實體:

@dataclass
class Page:
    id: str = None
    about: str = field(default=None, repr=False)
    birthday: str = field(default=None, repr=False)
    name: str = None
    username: str = None
    fan_count: int = field(default=None, repr=False)
    cover: dict = field(default=None, repr=False)

將數(shù)據傳入到數(shù)據類中:

# data 為 上述的數(shù)據
In [5]: p = Page(**data)

對數(shù)據進行操作:

# 獲取數(shù)據
In [6]: p.name
Out[6]: 'Facebook'

# 字符串展示
In [7]: p
Out[8]: Page(id='20531316728', name='Facebook', username='facebookapp')

In [9]: p.cover
Out[9]: 
{'cover_id': '10158913960541729',
 'offset_x': 50,
 'offset_y': 50,
 'source': 'https://scontent.xx.fbcdn.net/v/t1.0-9/s720x720/73087560_10158913960546729_8876113648821469184_o.jpg?_nc_cat=1&_nc_ohc=bAJ1yh0abN4AQkSOGhMpytya2quC_uS0j0BF-XEVlRlgwTfzkL_F0fojQ&_nc_ht=scontent.xx&oh=2964a1a64b6b474e64b06bdb568684da&oe=5E454425',
 'id': '10158913960541729'}

上述完整代碼參見 demo1

我們在上述的代碼發(fā)現(xiàn), 在調用 p.cover 屬性時,返回的是一個字典聪富,在正常的使用時莺丑,我們是想將 cover 屬性也聲明為一個數(shù)據類。則需要對上述的代碼進行修改墩蔓。

添加一個 Cover 的數(shù)據類實現(xiàn):

@dataclass
class Cover:
    id: str = None
    cover_id: str = None
    offset_x: str = field(default=None, repr=False)
    offset_y: str = field(default=None, repr=False)
    source: str = field(default=None, repr=False)

@dataclass
class Page:
    ...  # 此處不再復制上方的屬性
    cover: Cover = field(default=None, repr=False)  # 修改 `cover` 屬性

但是這時候梢莽,如果我們按照剛才的初始化方式,cover 屬性不會被識別到奸披。

我們可以通過添加一個額外的初始化的方法用來初始化到 cover 屬性.

def dicts_to_dataclasses(instance):
    """將所有的數(shù)據類屬性都轉化到數(shù)據類中"""
    cls = type(instance)
    for f in fields(cls):
        if not is_dataclass(f.type):
            continue

        value = getattr(instance, f.name)
        if not isinstance(value, dict):
            continue

        new_value = f.type(**value)
        setattr(instance, f.name, new_value)

并且修改上層數(shù)據類 Page 的代碼昏名,添加一個 __post_init__ 方法, 該方法會被自動生成的 __init__ 方法調用阵面,進而將 Cover 數(shù)據類進行初始化轻局。

@dataclass
class Page:
    ...  # 上方的屬性

    def __post_init__(self):
        dicts_to_dataclasses(self)

上述完整代碼參見 demo2

此時我們去初始化時,便可以將子數(shù)據類 Cover 也初始化了膜钓。

In [10]: p.cover
Out[10]: Cover(id='10158913960541729', cover_id='10158913960541729')

此外嗽交,dataclasses 還提供了對數(shù)據類到字典的轉化。

In [11]: from dataclasses import asdict
In [12]: asdict(p)
Out[12]:
{'id': '20531316728',
....
}

我們可以對上邊的代碼進行整合一下颂斜。將通用的一些函數(shù)放到一個 base 基類中。

完整代碼參見 demo3

第三方增強庫

上邊我們只是對含有嵌套字典的復雜數(shù)據進行了處理拾枣。事實上沃疮,生產中的數(shù)據的樣式會更加復雜。我們根據需求自行對 dicts_to_dataclasses 函數(shù)進行升級處理梅肤,或者使用第三方庫進行處理司蔬。

此處我們以第三方庫 dataclasses-json 來給出一個示例,詳細代碼參見 demo-with-dataclasses-json

參考資料

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末姨蝴,一起剝皮案震驚了整個濱河市俊啼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌左医,老刑警劉巖授帕,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件同木,死亡現(xiàn)場離奇詭異,居然都是意外死亡跛十,警方通過查閱死者的電腦和手機彤路,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芥映,“玉大人洲尊,你說我怎么就攤上這事∧纹” “怎么了坞嘀?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惊来。 經常有香客問我丽涩,道長,這世上最難降的妖魔是什么唁盏? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任内狸,我火速辦了婚禮,結果婚禮上厘擂,老公的妹妹穿的比我還像新娘昆淡。我一直安慰自己,他們只是感情好刽严,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布昂灵。 她就那樣靜靜地躺著,像睡著了一般舞萄。 火紅的嫁衣襯著肌膚如雪眨补。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天倒脓,我揣著相機與錄音撑螺,去河邊找鬼。 笑死崎弃,一個胖子當著我的面吹牛甘晤,可吹牛的內容都是我干的。 我是一名探鬼主播饲做,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼线婚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盆均?” 一聲冷哼從身側響起塞弊,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后游沿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饰抒,經...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年奏候,在試婚紗的時候發(fā)現(xiàn)自己被綠了循集。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡蔗草,死狀恐怖咒彤,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情咒精,我是刑警寧澤镶柱,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站模叙,受9級特大地震影響歇拆,放射性物質發(fā)生泄漏。R本人自食惡果不足惜范咨,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一故觅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧渠啊,春花似錦输吏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至躲查,卻和暖如春它浅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背镣煮。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工姐霍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人典唇。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓邮弹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蚓聘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內容