dataclasses
— 資料類¶
原始碼: Lib/dataclasses.py
此模組提供一個裝飾器和函式,用於自動向使用者定義的類新增生成的特殊方法,例如 __init__()
和 __repr__()
。它最初在 PEP 557 中描述。
這些生成方法中使用的成員變數是使用 PEP 526 型別註解定義的。例如,以下程式碼
from dataclasses import dataclass
@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
將新增,除其他外,一個看起來像這樣的 __init__()
def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0):
self.name = name
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand
請注意,此方法會自動新增到類中:它不會在上面顯示的 InventoryItem
定義中直接指定。
3.7 版本新增。
模組內容¶
- @dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False)¶
-
@dataclass
裝飾器會檢查類以查詢field
。field
定義為具有型別註解的類變數。除了下面描述的兩個例外情況外,@dataclass
中沒有任何內容會檢查變數註解中指定的型別。所有生成方法中欄位的順序是它們在類定義中出現的順序。
@dataclass
裝飾器會將各種 “dunder” 方法新增到類中,如下所述。 如果任何新增的方法已存在於類中,則其行為取決於引數,如下所述。裝飾器返回它被呼叫的同一個類;不會建立新類。如果
@dataclass
只是作為一個沒有引數的簡單裝飾器使用,它的行為就像它具有此簽名中記錄的預設值一樣。 也就是說,以下三種@dataclass
的用法是等效的@dataclass class C: ... @dataclass() class C: ... @dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False) class C: ...
@dataclass
的引數為init:如果為 true (預設值),則會生成
__init__()
方法。如果類已定義
__init__()
,則忽略此引數。repr:如果為 true (預設值),則會生成
__repr__()
方法。 生成的 repr 字串將具有類名以及每個欄位的名稱和 repr,其順序與它們在類中定義的順序相同。標記為從 repr 中排除的欄位不包括在內。 例如:InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10)
.如果類已定義
__repr__()
,則忽略此引數。eq:如果為 true (預設值),則會生成
__eq__()
方法。 此方法將類比較為就像它是其欄位的元組一樣,按順序排列。比較中的兩個例項必須是相同的型別。如果類已定義
__eq__()
,則忽略此引數。order:如果為 true (預設值為
False
),則會生成__lt__()
、__le__()
、__gt__()
和__ge__()
方法。這些方法將類比較為就像它是其欄位的元組一樣,按順序排列。比較中的兩個例項必須是相同的型別。如果 order 為 true 且 eq 為 false,則會引發ValueError
。如果類已定義
__lt__()
、__le__()
、__gt__()
或__ge__()
中的任何一個,則會引發TypeError
。unsafe_hash:如果為
False
(預設值),則會根據 eq 和 frozen 的設定方式生成__hash__()
方法。__hash__()
方法被內建的hash()
函式使用,並且當物件被新增到字典和集合等雜湊集合中時也會使用。擁有__hash__()
方法意味著該類的例項是不可變的。可變性是一個複雜的屬性,它取決於程式設計師的意圖、__eq__()
方法的存在和行為,以及@dataclass
裝飾器中 eq 和 frozen 標誌的值。預設情況下,
@dataclass
不會隱式新增__hash__()
方法,除非這樣做是安全的。它也不會新增或更改已顯式定義的__hash__()
方法。將類屬性__hash__ = None
設定為 Python 有特定含義,如__hash__()
文件中所述。如果
__hash__()
沒有顯式定義,或者設定為None
,則@dataclass
*可能* 會新增一個隱式的__hash__()
方法。雖然不推薦,但你可以使用unsafe_hash=True
強制@dataclass
建立一個__hash__()
方法。如果你的類在邏輯上是不可變的但仍然可以被修改,則可能需要這樣做。這是一個特殊的用例,應該仔細考慮。以下是管理隱式建立
__hash__()
方法的規則。請注意,你不能在資料類中同時擁有顯式的__hash__()
方法並設定unsafe_hash=True
;這將導致TypeError
。如果 *eq* 和 *frozen* 都為真,則預設情況下
@dataclass
將為你生成__hash__()
方法。如果 *eq* 為真,而 *frozen* 為假,則__hash__()
將被設定為None
,標記為不可雜湊(因為它是可變的)。如果 *eq* 為假,則__hash__()
將保持不變,這意味著將使用超類的__hash__()
方法(如果超類是object
,這意味著它將回退到基於 id 的雜湊)。frozen:如果為真(預設為
False
),則對欄位賦值將生成異常。這模擬了只讀的凍結例項。如果在類中定義了__setattr__()
或__delattr__()
,則會引發TypeError
。請參閱下面的討論。match_args:如果為真(預設為
True
),則__match_args__
元組將從生成的__init__()
方法的引數列表中建立(即使不生成__init__()
方法,請參見上文)。如果為假,或者在類中已經定義了__match_args__
,則不會生成__match_args__
。
在 3.10 版本中新增。
kw_only:如果為真(預設值為
False
),則所有欄位都將被標記為僅限關鍵字。如果一個欄位被標記為僅限關鍵字,那麼唯一的效果是,從僅限關鍵字欄位生成的__init__()
引數在呼叫__init__()
時必須使用關鍵字指定。這不會影響資料類的任何其他方面。有關詳細資訊,請參閱 parameter 詞彙條目。另請參閱KW_ONLY
部分。
在 3.10 版本中新增。
警告
在使用
slots=True
的資料類中呼叫無引數的super()
將導致引發以下異常:TypeError: super(type, obj): obj must be an instance or subtype of type
。 雙引數的super()
是一個有效的解決方法。 有關完整詳細資訊,請參閱 gh-90562。警告
在使用
slots=True
時,將引數傳遞給基類__init_subclass__()
將導致TypeError
。 要麼使用不帶引數的__init_subclass__
,要麼使用預設值作為解決方法。 有關完整詳細資訊,請參閱 gh-91126。在 3.10 版本中新增。
weakref_slot:如果為真(預設為
False
),則新增一個名為“__weakref__”的 slot,這是使例項可弱引用
所必需的。如果未同時指定slots=True
而指定weakref_slot=True
,則會出錯。
在 3.11 版本中新增。
field
可以選擇使用正常的 Python 語法指定預設值@dataclass class C: a: int # 'a' has no default value b: int = 0 # assign a default value for 'b'
在此示例中,
a
和b
都將包含在新增的__init__()
方法中,該方法將被定義為def __init__(self, a: int, b: int = 0):
如果一個沒有預設值的欄位跟在具有預設值的欄位之後,則會引發
TypeError
。無論這種情況是在單個類中發生,還是由於類繼承而發生,都是如此。
- dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING)¶
對於常見和簡單的用例,不需要其他功能。然而,有些資料類特性需要每個欄位的額外資訊。為了滿足對額外資訊的需求,你可以將預設欄位值替換為對提供的
field()
函式的呼叫。例如:@dataclass class C: mylist: list[int] = field(default_factory=list) c = C() c.mylist += [1, 2, 3]
如上所示,
MISSING
值是一個哨兵物件,用於檢測使用者是否提供了一些引數。使用這個哨兵是因為None
對於某些引數來說是一個具有不同含義的有效值。任何程式碼都不應直接使用MISSING
值。field()
的引數如下:default:如果提供,這將是該欄位的預設值。這是必需的,因為
field()
呼叫本身會替換預設值的正常位置。default_factory:如果提供,它必須是一個零引數的可呼叫物件,當需要此欄位的預設值時將被呼叫。除其他用途外,這可用於指定具有可變預設值的欄位,如下所述。同時指定 default 和 default_factory 是錯誤的。
init:如果為 true(預設值),則此欄位將作為引數包含在生成的
__init__()
方法中。repr:如果為 true(預設值),則此欄位將包含在生成的
__repr__()
方法返回的字串中。hash:這可以是布林值或
None
。如果為 true,則此欄位將包含在生成的__hash__()
方法中。如果為None
(預設值),則使用 compare 的值:這通常是預期的行為。如果一個欄位用於比較,則應將其視為雜湊的一部分。不鼓勵將此值設定為除None
之外的任何值。將
hash=False
但compare=True
的一個可能原因是,如果某個欄位計算雜湊值的開銷很大,而該欄位是等式測試所必需的,並且還有其他欄位會影響型別的雜湊值。即使一個欄位從雜湊中排除,它仍然用於比較。compare:如果為 true(預設值),則此欄位將包含在生成的相等性和比較方法中(
__eq__()
、__gt__()
等)。metadata:這可以是對映或
None
。None
被視為一個空字典。此值被包裝在MappingProxyType()
中使其變為只讀,並在Field
物件上公開。資料類完全不使用它,它作為第三方擴充套件機制提供。多個第三方可以各自擁有自己的鍵,用作元資料中的名稱空間。kw_only:如果為 true,則此欄位將被標記為僅限關鍵字。這在計算生成的
__init__()
方法的引數時使用。
在 3.10 版本中新增。
如果欄位的預設值由對
field()
的呼叫指定,則此欄位的類屬性將被指定的 default 值替換。如果未提供 default,則將刪除類屬性。目的是在@dataclass
裝飾器執行後,類屬性將全部包含欄位的預設值,就像指定了預設值本身一樣。例如,之後:@dataclass class C: x: int y: int = field(repr=False) z: int = field(repr=False, default=10) t: int = 20
類屬性
C.z
將為10
,類屬性C.t
將為20
,類屬性C.x
和C.y
將不會設定。
- class dataclasses.Field¶
Field
物件描述每個定義的欄位。這些物件在內部建立,並由fields()
模組級方法(見下文)返回。使用者永遠不應該直接例項化Field
物件。其文件化的屬性為:name
:欄位的名稱。type
:欄位的型別。default
、default_factory
、init
、repr
、hash
、compare
、metadata
和kw_only
具有與field()
函式中相同的含義和值。
可能存在其他屬性,但它們是私有的,不得檢查或依賴。
- dataclasses.fields(class_or_instance)¶
返回一個
Field
物件元組,該元組定義此資料類的欄位。接受資料類或資料類的例項。如果未傳遞資料類或資料類的例項,則引發TypeError
。不返回作為ClassVar
或InitVar
的偽欄位。
- dataclasses.asdict(obj, *, dict_factory=dict)¶
將資料類 obj 轉換為字典(透過使用工廠函式 dict_factory)。每個資料類都轉換為其欄位的字典,形式為
name: value
對。資料類、字典、列表和元組會遞迴處理。其他物件使用copy.deepcopy()
複製。以下是在巢狀資料類上使用
asdict()
的示例:@dataclass class Point: x: int y: int @dataclass class C: mylist: list[Point] p = Point(10, 20) assert asdict(p) == {'x': 10, 'y': 20} c = C([Point(0, 0), Point(10, 4)]) assert asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}
要建立淺複製,可以使用以下解決方法:
{field.name: getattr(obj, field.name) for field in fields(obj)}
如果 obj 不是資料類例項,則
asdict()
會引發TypeError
。
- dataclasses.astuple(obj, *, tuple_factory=tuple)¶
將資料類 *obj* 轉換為元組(透過使用工廠函式 *tuple_factory*)。每個資料類都轉換為其欄位值的元組。資料類、字典、列表和元組會被遞迴轉換。其他物件會使用
copy.deepcopy()
進行復制。繼續前面的例子
assert astuple(p) == (10, 20) assert astuple(c) == ([(0, 0), (10, 4)],)
要建立淺複製,可以使用以下解決方法:
tuple(getattr(obj, field.name) for field in dataclasses.fields(obj))
如果 *obj* 不是資料類例項,
astuple()
會引發TypeError
。
- dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False, module=None)¶
建立一個新的資料類,其名稱為 *cls_name*,欄位定義在 *fields* 中,基類在 *bases* 中給出,並使用 *namespace* 中給出的名稱空間進行初始化。*fields* 是一個可迭代物件,其元素可以是
name
,(name, type)
或(name, type, Field)
。如果僅提供name
,則type
使用typing.Any
。*init*、*repr*、*eq*、*order*、*unsafe_hash*、*frozen*、*match_args*、*kw_only*、*slots* 和 *weakref_slot* 的值與在@dataclass
中的含義相同。如果定義了 *module*,則資料類的
__module__
屬性將設定為該值。預設情況下,它設定為呼叫者的模組名稱。此函式不是嚴格必需的,因為任何使用
__annotations__
建立新類的 Python 機制都可以應用@dataclass
函式將該類轉換為資料類。提供此函式是為了方便。例如C = make_dataclass('C', [('x', int), 'y', ('z', int, field(default=5))], namespace={'add_one': lambda self: self.x + 1})
等價於
@dataclass class C: x: int y: 'typing.Any' z: int = 5 def add_one(self): return self.x + 1
- dataclasses.replace(obj, /, **changes)¶
建立一個與 *obj* 型別相同的新物件,並使用 *changes* 中的值替換欄位。如果 *obj* 不是資料類,則會引發
TypeError
。如果 *changes* 中的鍵不是給定資料類的欄位名稱,則會引發TypeError
。新返回的物件是透過呼叫資料類的
__init__()
方法建立的。這確保瞭如果存在__post_init__()
,也會被呼叫。如果存在沒有預設值的僅初始化變數,則必須在呼叫
replace()
時指定它們,以便可以將它們傳遞給__init__()
和__post_init__()
。如果 *changes* 包含任何定義為
init=False
的欄位,則會出錯。在這種情況下會引發ValueError
。請注意
init=False
欄位在呼叫replace()
期間如何工作。它們不是從源物件複製的,而是在__post_init__()
中初始化的(如果它們被初始化的話)。預計init=False
欄位很少會被謹慎地使用。如果使用了它們,最好有備用的類建構函式,或者可能有一個自定義的replace()
(或類似名稱的)方法來處理例項複製。泛型函式
copy.replace()
也支援資料類例項。
- dataclasses.is_dataclass(obj)¶
如果其引數是資料類(包括資料類的子類)或資料類的例項,則返回
True
,否則返回False
。如果您需要知道一個類是否是資料類的例項(而不是資料類本身),則需要新增進一步的檢查:
not isinstance(obj, type)
def is_dataclass_instance(obj): return is_dataclass(obj) and not isinstance(obj, type)
- dataclasses.MISSING¶
一個哨兵值,表示缺少預設值或 default_factory。
- dataclasses.KW_ONLY¶
用作型別註釋的哨兵值。型別為
KW_ONLY
的偽欄位之後的任何欄位都標記為僅關鍵字欄位。請注意,型別為KW_ONLY
的偽欄位在其他方面會被完全忽略。這包括此類欄位的名稱。按照慣例,_
的名稱用於KW_ONLY
欄位。僅關鍵字欄位表示在例項化類時必須指定為關鍵字的__init__()
引數。在此示例中,欄位
y
和z
將被標記為僅關鍵字欄位@dataclass class Point: x: float _: KW_ONLY y: float z: float p = Point(0, y=1.5, z=2.0)
在單個數據類中,指定多個型別為
KW_ONLY
的欄位是錯誤的。在 3.10 版本中新增。
- exception dataclasses.FrozenInstanceError¶
當在定義時使用了
frozen=True
的資料類上呼叫隱式定義的__setattr__()
或__delattr__()
時,會引發此異常。它是AttributeError
的子類。
初始化後處理¶
- dataclasses.__post_init__()¶
當在類上定義時,它將由生成的
__init__()
呼叫,通常為self.__post_init__()
。但是,如果定義了任何InitVar
欄位,它們也將按照它們在類中定義的順序傳遞給__post_init__()
。如果沒有生成__init__()
方法,則不會自動呼叫__post_init__()
。除其他用途外,這還允許初始化依賴於一個或多個其他欄位的欄位值。例如
@dataclass class C: a: float b: float c: float = field(init=False) def __post_init__(self): self.c = self.a + self.b
__init__()
方法由 @dataclass
生成,不會呼叫基類的 __init__()
方法。如果基類有一個必須呼叫的 __init__()
方法,通常在 __post_init__()
方法中呼叫此方法
class Rectangle:
def __init__(self, height, width):
self.height = height
self.width = width
@dataclass
class Square(Rectangle):
side: float
def __post_init__(self):
super().__init__(self.side, self.side)
但是請注意,通常情況下,資料類生成的 __init__()
方法不需要被呼叫,因為派生的資料類將負責初始化任何作為資料類本身的基類的所有欄位。
有關如何將引數傳遞給 __post_init__()
的方法,請參見下面關於僅限初始化的變數的部分。另請參閱關於 replace()
如何處理 init=False
欄位的警告。
類變數¶
@dataclass
實際檢查欄位型別的少數幾個地方之一是確定欄位是否為 PEP 526 中定義的類變數。它透過檢查欄位的型別是否為 typing.ClassVar
來實現。如果欄位是 ClassVar
,則將其排除在作為欄位的考慮範圍之外,並且資料類機制會忽略它。此類 ClassVar
偽欄位不會由模組級 fields()
函式返回。
僅限初始化的變數¶
@dataclass
檢查型別註釋的另一個地方是確定欄位是否為僅限初始化的變數。它透過檢視欄位的型別是否為 dataclasses.InitVar
型別來實現。如果欄位是 InitVar
,則將其視為一個名為僅限初始化的欄位的偽欄位。由於它不是真正的欄位,因此模組級 fields()
函式不會返回它。僅限初始化的欄位將作為引數新增到生成的 __init__()
方法,並傳遞給可選的 __post_init__()
方法。資料類不會以其他方式使用它們。
例如,假設如果建立類時未提供值,則將從資料庫初始化一個欄位
@dataclass
class C:
i: int
j: int | None = None
database: InitVar[DatabaseType | None] = None
def __post_init__(self, database):
if self.j is None and database is not None:
self.j = database.lookup('j')
c = C(10, database=my_database)
凍結例項¶
無法建立真正不可變的 Python 物件。但是,透過將 frozen=True
傳遞給 @dataclass
裝飾器,可以模擬不可變性。在這種情況下,資料類會將 __setattr__()
和 __delattr__()
方法新增到類中。當呼叫這些方法時,它們將引發 FrozenInstanceError
。
使用 frozen=True
時會產生很小的效能損失:__init__()
不能使用簡單的賦值來初始化欄位,而必須使用 object.__setattr__()
。
繼承¶
當 @dataclass
裝飾器建立資料類時,它會以反向 MRO 順序(即從 object
開始)遍歷類的所有基類,對於它找到的每個資料類,它都會將該基類的欄位新增到欄位的有序對映中。在添加了所有基類欄位後,它會將其自己的欄位新增到有序對映中。所有生成的方法都將使用此組合的、計算出的欄位的有序對映。由於欄位是按插入順序排列的,因此派生類會覆蓋基類。一個例子
@dataclass
class Base:
x: Any = 15.0
y: int = 0
@dataclass
class C(Base):
z: int = 10
x: int = 15
最終的欄位列表按順序為 x
、y
、z
。x
的最終型別是 int
,如類 C
中指定的那樣。
為 C
生成的 __init__()
方法將如下所示
def __init__(self, x: int = 15, y: int = 0, z: int = 10):
__init__()
中僅限關鍵字的引數的重新排序¶
在計算出 __init__()
所需的引數後,任何僅限關鍵字的引數都會被移動到所有常規(非僅限關鍵字)引數之後。這是 Python 中實現僅限關鍵字的引數的方式的要求:它們必須位於非僅限關鍵字的引數之後。
在此示例中,Base.y
、Base.w
和 D.t
是僅限關鍵字的欄位,而 Base.x
和 D.z
是常規欄位
@dataclass
class Base:
x: Any = 15.0
_: KW_ONLY
y: int = 0
w: int = 1
@dataclass
class D(Base):
z: int = 10
t: int = field(kw_only=True, default=0)
為 D
生成的 __init__()
方法將如下所示
def __init__(self, x: Any = 15.0, z: int = 10, *, y: int = 0, w: int = 1, t: int = 0):
請注意,引數的順序已與它們在欄位列表中出現的順序不同:從常規欄位派生的引數後面是僅從關鍵字欄位派生的引數。
僅限關鍵字的引數的相對順序在重新排序的 __init__()
引數列表中保持不變。
預設工廠函式¶
如果 field()
指定了 default_factory,則當需要該欄位的預設值時,將使用零個引數呼叫它。例如,要建立列表的新例項,請使用
mylist: list = field(default_factory=list)
如果一個欄位從 __init__()
中排除(使用 init=False
),並且該欄位還指定了 default_factory,那麼預設工廠函式將始終從生成的 __init__()
函式中呼叫。 發生這種情況是因為沒有其他方法可以為該欄位賦予初始值。
可變預設值¶
Python 將預設成員變數值儲存在類屬性中。考慮這個不使用資料類的例子
class C:
x = []
def add(self, element):
self.x.append(element)
o1 = C()
o2 = C()
o1.add(1)
o2.add(2)
assert o1.x == [1, 2]
assert o1.x is o2.x
請注意,類 C
的兩個例項共享同一個類變數 x
,正如預期的那樣。
使用資料類,如果 這段程式碼有效
@dataclass
class D:
x: list = [] # This code raises ValueError
def add(self, element):
self.x.append(element)
它將生成類似於以下的程式碼
class D:
x = []
def __init__(self, x=x):
self.x = x
def add(self, element):
self.x.append(element)
assert D().x is D().x
這與使用類 C
的原始示例存在相同的問題。也就是說,類 D
的兩個例項在建立類例項時沒有為 x
指定值,它們將共享 x
的相同副本。因為資料類只是使用普通的 Python 類建立,它們也共享此行為。資料類沒有通用的方法來檢測這種情況。相反,如果 @dataclass
裝飾器檢測到不可雜湊的預設引數,則會引發 ValueError
。假設如果一個值是不可雜湊的,那麼它是可變的。這是一個部分解決方案,但它可以防止許多常見錯誤。
使用預設工廠函式是一種為欄位建立可變型別的新例項作為預設值的方法
@dataclass
class D:
x: list = field(default_factory=list)
assert D().x is not D().x
描述符型別的欄位¶
被分配了 描述符物件 作為其預設值的欄位具有以下特殊行為
傳遞給資料類的
__init__()
方法的欄位值將傳遞給描述符的__set__()
方法,而不是覆蓋描述符物件。同樣,在獲取或設定欄位時,將呼叫描述符的
__get__()
或__set__()
方法,而不是返回或覆蓋描述符物件。為了確定欄位是否包含預設值,
@dataclass
將使用其類訪問形式呼叫描述符的__get__()
方法:descriptor.__get__(obj=None, type=cls)
。 如果描述符在這種情況下返回一個值,它將被用作欄位的預設值。另一方面,如果描述符在這種情況下引發AttributeError
,則不會為該欄位提供預設值。
class IntConversionDescriptor:
def __init__(self, *, default):
self._default = default
def __set_name__(self, owner, name):
self._name = "_" + name
def __get__(self, obj, type):
if obj is None:
return self._default
return getattr(obj, self._name, self._default)
def __set__(self, obj, value):
setattr(obj, self._name, int(value))
@dataclass
class InventoryItem:
quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)
i = InventoryItem()
print(i.quantity_on_hand) # 100
i.quantity_on_hand = 2.5 # calls __set__ with 2.5
print(i.quantity_on_hand) # 2
請注意,如果一個欄位使用描述符型別進行註釋,但沒有為其預設值分配描述符物件,則該欄位的行為將類似於普通欄位。