python數據類


前言

之前有寫過一篇python元類的筆記,元類主要作用就是在要創建的類中使用參數metaclass=YourMetaclass調用自定義的元類,這樣就可以為所有調用了這個元類的類添加相同的屬性了。

有需要查看鏈接:https://mp.weixin.qq.com/s/gmR_pggxeitPWKmnbo1usw

本篇筆記主要是對dataclass的特性作了解和對參考文章的總結摘要,完整文章地址:https://realpython.com/python-data-classes/

python數據類初識

用docker拉個python:3.7的鏡像作為實驗環境

  1. 使用dataclass裝飾器創建數據類
>>> from dataclasses import dataclass
>>> @dataclass
... class DataClassTest:
...     first_name: str
...     last_name: str
... 
>>> p = DataClassTest('vickey', 'wu')
>>> p.first_name
'vickey'
>>> p.last_name
'wu'
>>> p
DataClassTest(first_name='vickey', last_name='wu')
>>> p == DataClassTest('vickey', 'wu')
True
>>> p.first_name = 'wiki'
>>> p
DataClassTest(first_name='wiki', last_name='wu')

從上面例子可以看到,如果使用dataclass裝飾器來定義數據類,則必須聲明參數類型,數據類默認可以修改參數的值類型,如果不希望更改則使用@dataclass(frozen=True)即可,這樣上面的 參數值就不可更改了,更改會報錯dataclasses.FrozenInstanceError: cannot assign to field 'first_name'

當不確定參數到底用哪種類型,或可以是多種類型時則可以用下面的Any來聲明

>>> from dataclasses import dataclass
>>> from typing import Any
>>> @dataclass
... class W:
...     n:Any
...     v: float = 18
... 
>>> w = W('vickey')
>>> w
W(n='vickey', v=18)
>>> w = W(19)
>>> w
W(n=19, v=18)
  1. 不使用dataclass裝飾器的普通類
>>> class RegularClassTest:
...     def __init__(self, first_name, last_name):
...         self.first_name = first_name
...         self.last_name = last_name
... 
>>> pp = RegularClassTest('vickey', 'wu')
>>> pp.first_name
'vickey'
>>> pp.last_name
'wu'
>>> pp
<__main__.RegularClassTest object at 0x7f5f66a49550>
>>> pp == RegularClassTest('vickey', 'wu')
False

從1和2兩個例子對比可以看出,使用@dataclass后有幾個優勢(不限於此):

  • 無需定義__init__函數,只需定義參數及參數類型即可。
  • 打印出來的對象描述信息更清晰了。而未使用dataclass的類需要再添加__repr__函數顯示才友好。(看下面的例子)
  • 實例化后的實例可以用==判斷出是否與類實例相等,而未使用dataclass的類需要再添加__eq__函數才能判斷。(看下面的例子)

3 不使用dataclass裝飾器實現數據類相同功能

>>> class RegularClassTest2:
...     def __init__(self, first_name, last_name):
...         self.first_name = first_name
...         self.last_name = last_name
...     def __repr__(self):
...         return (f'{self.__class__.__name__}'
...                 f'(first_name={self.first_name!r}, last_name={self.last_name!r})')
...     def __eq__(self, other):
...         if other.__class__ is not self.__class__:
...             return NotImplemented
...         return (self.first_name, self.last_name) == (other.first_name, other.last_name)
... 
>>> r = RegularClassTest2('2', '1')
>>> r
RegularCard(first_name='2', last_name='1')
>>> r == RegularClassTest2('2', '1')
True
>>> 

通過在普通類中添加__repr____eq__就可以具有上面提到的數據類的第2,3個優勢,但還是需要__init__函數。雖然上面提到不使用dataclass也可以達到部分效果,參考文章作者也說明了各自的好處與不足,感興趣的童鞋查看原文,這里就不記錄了。

數據類參數調用函數賦值

from dataclasses import dataclass, field
from typing import List

# 數據類rank參數為牌大小,suit為花色
@dataclass
class PlayingCard:
    rank: str
    suit: str


# 生成13牌的4種花色
RANKS = '2 3 4 5 6 7 8 9 10 J Q K A'.split()
SUITS = '♣ ♢ ♡ ♠'.split()

def make_french_deck():
    print([PlayingCard(r, s) for s in SUITS for r in RANKS])
    print('################## list generated by fuction make_french_deck')
    return [PlayingCard(r, s) for s in SUITS for r in RANKS]



# 參考源碼typing.List
# List(yourclass):https://docs.python.org/3/library/typing.html#typing.ForwardRef
# 使用field的default_factory調用參數名為make_french_deck的函數,這個函數會生成一個list,然后賦值參數cards
@dataclass
class Deck:
    cards: List[PlayingCard] = field(default_factory=make_french_deck)


print('################# called class Deck with para cards')
print(Deck())
  • output
################# called class Deck with para cards
[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), PlayingCard(rank='4', suit='♣'), PlayingCard(rank='5', suit='♣'), PlayingCard(rank='6', suit='♣'), PlayingCard(rank='7', suit='♣'), PlayingCard(rank='8', suit='♣'), PlayingCard(rank='9', suit='♣'), PlayingCard(rank='10', suit='♣'), PlayingCard(rank='J', suit='♣'), PlayingCard(rank='Q', suit='♣'), PlayingCard(rank='K', suit='♣'), PlayingCard(rank='A', suit='♣'), PlayingCard(rank='2', suit='♢'), PlayingCard(rank='3', suit='♢'), PlayingCard(rank='4', suit='♢'), PlayingCard(rank='5', suit='♢'), PlayingCard(rank='6', suit='♢'), PlayingCard(rank='7', suit='♢'), PlayingCard(rank='8', suit='♢'), PlayingCard(rank='9', suit='♢'), PlayingCard(rank='10', suit='♢'), PlayingCard(rank='J', suit='♢'), PlayingCard(rank='Q', suit='♢'), PlayingCard(rank='K', suit='♢'), PlayingCard(rank='A', suit='♢'), PlayingCard(rank='2', suit='♡'), PlayingCard(rank='3', suit='♡'), PlayingCard(rank='4', suit='♡'), PlayingCard(rank='5', suit='♡'), PlayingCard(rank='6', suit='♡'), PlayingCard(rank='7', suit='♡'), PlayingCard(rank='8', suit='♡'), PlayingCard(rank='9', suit='♡'), PlayingCard(rank='10', suit='♡'), PlayingCard(rank='J', suit='♡'), PlayingCard(rank='Q', suit='♡'), PlayingCard(rank='K', suit='♡'), PlayingCard(rank='A', suit='♡'), PlayingCard(rank='2', suit='♠'), PlayingCard(rank='3', suit='♠'), PlayingCard(rank='4', suit='♠'), PlayingCard(rank='5', suit='♠'), PlayingCard(rank='6', suit='♠'), PlayingCard(rank='7', suit='♠'), PlayingCard(rank='8', suit='♠'), PlayingCard(rank='9', suit='♠'), PlayingCard(rank='10', suit='♠'), PlayingCard(rank='J', suit='♠'), PlayingCard(rank='Q', suit='♠'), PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')]
################## list generated by fuction make_french_deck
Deck(cards=[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), PlayingCard(rank='4', suit='♣'), PlayingCard(rank='5', suit='♣'), PlayingCard(rank='6', suit='♣'), PlayingCard(rank='7', suit='♣'), PlayingCard(rank='8', suit='♣'), PlayingCard(rank='9', suit='♣'), PlayingCard(rank='10', suit='♣'), PlayingCard(rank='J', suit='♣'), PlayingCard(rank='Q', suit='♣'), PlayingCard(rank='K', suit='♣'), PlayingCard(rank='A', suit='♣'), PlayingCard(rank='2', suit='♢'), PlayingCard(rank='3', suit='♢'), PlayingCard(rank='4', suit='♢'), PlayingCard(rank='5', suit='♢'), PlayingCard(rank='6', suit='♢'), PlayingCard(rank='7', suit='♢'), PlayingCard(rank='8', suit='♢'), PlayingCard(rank='9', suit='♢'), PlayingCard(rank='10', suit='♢'), PlayingCard(rank='J', suit='♢'), PlayingCard(rank='Q', suit='♢'), PlayingCard(rank='K', suit='♢'), PlayingCard(rank='A', suit='♢'), PlayingCard(rank='2', suit='♡'), PlayingCard(rank='3', suit='♡'), PlayingCard(rank='4', suit='♡'), PlayingCard(rank='5', suit='♡'), PlayingCard(rank='6', suit='♡'), PlayingCard(rank='7', suit='♡'), PlayingCard(rank='8', suit='♡'), PlayingCard(rank='9', suit='♡'), PlayingCard(rank='10', suit='♡'), PlayingCard(rank='J', suit='♡'), PlayingCard(rank='Q', suit='♡'), PlayingCard(rank='K', suit='♡'), PlayingCard(rank='A', suit='♡'), PlayingCard(rank='2', suit='♠'), PlayingCard(rank='3', suit='♠'), PlayingCard(rank='4', suit='♠'), PlayingCard(rank='5', suit='♠'), PlayingCard(rank='6', suit='♠'), PlayingCard(rank='7', suit='♠'), PlayingCard(rank='8', suit='♠'), PlayingCard(rank='9', suit='♠'), PlayingCard(rank='10', suit='♠'), PlayingCard(rank='J', suit='♠'), PlayingCard(rank='Q', suit='♠'), PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')])

上面的例子是類Deck調用了類外的一個函數make_french_deck來生成一個類Deck的列表類型參數cards,這個列表由傳入類PlayingCard不同參數ranksuit而生成的類PlayingCard調用列表。這樣就生成了13牌的4種花色的所有值。

數據類的繼承

from dataclasses import dataclass

@dataclass
class Position:
    name: str
    lon: float = 0.0
    lat: float = 0.0

@dataclass
class Capital(Position):
    # 因為父類參數有默認值,所以子類的參數必須定義默認值,否則報錯
    # country: str
    country: str = 'Unknown'
    # 可以在子類重新定義父類的參數默認值
    lat: float = 40.0
  • 如果父類參數有默認值,子類的所有參數必須定義默認值,否則報錯:TypeError: non-default argument 'country' follows default argument。報錯原因相當於在子類初始化時def __init__(name: str, lon: float = 0.0, lat: float = 0.0, country: str):非默認參數沒有在默認參數前面,因為python規定非默認參數必須在默認參數前面
  • 參數的順序按照父類順序,然后子類參數順序。

總結

  • 數據類是Python3.7的新特性之一。使用數據類就不必編寫樣板代碼來為對象獲得適當的初始化__init__,表示__repr__,和比較__eq__
  • 數據類參數必須聲明參數類型,參數可以使用函數賦值。
  • 在繼承時如果父類參數有定義默認值,則子類參數必須也要定義默認值,繼承后的參數順序為父類參數,然后到子類參數。
  • 除此之外,數據類和普通類區別不大,數據類定義參數后像普通類一樣定義實例方法,一樣調用。

公眾號往期文章

python內置裝飾器

python裝飾器

scrapy-redis debug視頻

scrapy-redis源碼淺析

scrapy過濾重復數據和增量爬取

redis基礎筆記

scrapy電影天堂實戰(二)創建爬蟲項目

scrapy電影天堂實戰(一)創建數據庫

scrapy基礎筆記

在docker鏡像中加入環境變量

筆記 | mongodb 入門操作

筆記 | python元類

筆記 | python2和python3使用super()

那些你在python3中可能沒用到但應該用的東西

superset docker 部署

開機啟動容器里面的程序

博客 | 三步部署hitchhiker-api


免責聲明!

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



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