dataclasses --- 數(shù)據(jù)類?

源碼: Lib/dataclasses.py


這個模塊提供了一個裝飾器和一些函數(shù),用于自動添加生成的 special method,例如 __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

將在添加的內(nèi)容中包括如下所示的 __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 新版功能.

模塊內(nèi)容?

@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)?

這個函數(shù)是 decorator ,用于將生成的 special method 添加到類中,如下所述。

dataclass() 裝飾器會檢查類以查找 field。 field 被定義為具有 類型標注 的類變量。 除了下面描述的兩個例外,在 dataclass() 中沒有什么東西會去檢查在變量標注中所指定的類型。

所有生成的方法中的字段順序是它們在類定義中出現(xiàn)的順序。

dataclass() 裝飾器將向類中添加各種“dunder”方法,如下所述。 如果所添加的方法已存在于類中,則行為將取決于下面所列出的參數(shù)。 裝飾器會返回調(diào)用它的類本身;不會創(chuàng)建新的類。

如果 dataclass() 僅用作沒有參數(shù)的簡單裝飾器,它就像它具有此簽名中記錄的默認值一樣。也就是說,這三種 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() 的參數(shù)有:

  • init: 如果為真值(默認),將生成一個 __init__() 方法。

    如果類已定義 __init__() ,則忽略此參數(shù)。

  • repr :如果為真值(默認),將生成一個 __repr__() 方法。 生成的 repr 字符串將具有類名以及每個字段的名稱和 repr ,按照它們在類中定義的順序。不包括標記為從 repr 中排除的字段。 例如:InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10)

    如果類已定義 __repr__() ,則忽略此參數(shù)。

  • eq :如果為true(默認值),將生成 __eq__() 方法。此方法將類作為其字段的元組按順序比較。比較中的兩個實例必須是相同的類型。

    如果類已定義 __eq__() ,則忽略此參數(shù)。

  • order :如果為真值(默認為 False ),則 __lt__() 、 __le__() 、 __gt__()__ge__() 方法將生成。 這將類作為其字段的元組按順序比較。比較中的兩個實例必須是相同的類型。如果 order 為真值并且 eq 為假值 ,則引發(fā) ValueError

    如果類已經(jīng)定義了 __lt__() 、 __le__()__gt__() 或者 __ge__() 中的任意一個,將引發(fā) TypeError 。

  • unsafe_hash :如果為 False (默認值),則根據(jù) eqfrozen 的設(shè)置方式生成 __hash__() 方法。

    __hash__() 由內(nèi)置的 hash() 使用,當對象被添加到散列集合(如字典和集合)時。有一個 __hash__() 意味著類的實例是不可變的??勺冃允且粋€復(fù)雜的屬性,取決于程序員的意圖, __eq__() 的存在性和行為,以及 dataclass() 裝飾器中 eqfrozen 標志的值。

    默認情況下, dataclass() 不會隱式添加 __hash__() 方法,除非這樣做是安全的。 它也不會添加或更改現(xiàn)有的明確定義的 __hash__() 方法。 設(shè)置類屬性 __hash__ = None 對 Python 具有特定含義,如 __hash__() 文檔中所述。

    如果 __hash__() 沒有顯式定義,或者它被設(shè)為 None,則 dataclass() 可能 會添加一個隱式 __hash__() 方法。 雖然并不推薦,但你可以用 unsafe_hash=True 來強制 dataclass() 創(chuàng)建一個 __hash__() 方法。 如果你的類在邏輯上不可變但卻仍然可被修改那么可能就是這種情況。 這是一個特殊用例并且應(yīng)當被仔細地考慮。

    以下是隱式創(chuàng)建 __hash__() 方法的規(guī)則。請注意,你不能在數(shù)據(jù)類中都使用顯式的 __hash__() 方法并設(shè)置 unsafe_hash=True ;這將導(dǎo)致 TypeError

    如果 eqfrozen 都是 true,默認情況下 dataclass() 將為你生成一個 __hash__() 方法。如果 eq 為 true 且 frozen 為 false ,則 __hash__() 將被設(shè)置為 None ,標記它不可用(因為它是可變的)。如果 eq 為 false ,則 __hash__() 將保持不變,這意味著將使用超類的 __hash__() 方法(如果超類是 object ,這意味著它將回到基于id的hash)。

  • frozen: 如為真值 (默認值為 False),則對字段賦值將會產(chǎn)生異常。 這模擬了只讀的凍結(jié)實例。 如果在類中定義了 __setattr__()__delattr__() 則將會引發(fā) TypeError。 參見下文的討論。

  • match_args: 如果為真值 (默認值為 True),則將根據(jù)傳給生成的 __init__() 方法的形參列表來創(chuàng)建 __match_args__ 元組 (即使沒有生成 __init__() 也會創(chuàng)建,見上文)。 如果為假值,或者如果 __match_args__ 已在類中定義,則將不生成 __match_args__。

3.10 新版功能.

  • kw_only: If true (the default value is False), then all fields will be marked as keyword-only. If a field is marked as keyword-only, then the only effect is that the __init__() parameter generated from a keyword-only field must be specified with a keyword when __init__() is called. There is no effect on any other aspect of dataclasses. See the parameter glossary entry for details. Also see the KW_ONLY section.

3.10 新版功能.

  • slots: 如果為真值 (默認值為 False),則將生成 __slots__ 屬性并將返回一個新類而非原來的類。 如果 __slots__ 已在類中定義,則會引發(fā) TypeError。

3.10 新版功能.

在 3.11 版更改: If a field name is already included in the __slots__ of a base class, it will not be included in the generated __slots__ to prevent overriding them. Therefore, do not use __slots__ to retrieve the field names of a dataclass. Use fields() instead. To be able to determine inherited slots, base class __slots__ may be any iterable, but not an iterator.

  • weakref_slot: If true (the default is False), add a slot named "__weakref__", which is required to make an instance weakref-able. It is an error to specify weakref_slot=True without also specifying slots=True.

3.11 新版功能.

fields 可以選擇使用普通的 Python 語法指定默認值:

@dataclass
class C:
    a: int       # 'a' has no default value
    b: int = 0   # assign a default value for 'b'

在這個例子中, ab 都將包含在添加的 __init__() 方法中,它們將被定義為:

def __init__(self, a: int, b: int = 0):

如果具有默認值的字段之后存在沒有默認值的字段,將會引發(fā) TypeError。 無論此情況是發(fā)生在單個類中還是作為類繼承的結(jié)果,都是如此。

dataclasses.field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None, kw_only=MISSING)?

對于常見和簡單的用例,不需要其他功能。但是,有些數(shù)據(jù)類功能需要額外的每字段信息。為了滿足這種對附加信息的需求,你可以通過調(diào)用提供的 field() 函數(shù)來替換默認字段值。例如:

@dataclass
class C:
    mylist: list[int] = field(default_factory=list)

c = C()
c.mylist += [1, 2, 3]

As shown above, the MISSING value is a sentinel object used to detect if some parameters are provided by the user. This sentinel is used because None is a valid value for some parameters with a distinct meaning. No code should directly use the MISSING value.

field() 參數(shù)有:

  • default :如果提供,這將是該字段的默認值。這是必需的,因為 field() 調(diào)用本身會替換一般的默認值。

  • default_factory :如果提供,它必須是一個零參數(shù)可調(diào)用對象,當該字段需要一個默認值時,它將被調(diào)用。除了其他目的之外,這可以用于指定具有可變默認值的字段,如下所述。 同時指定 defaultdefault_factory 將產(chǎn)生錯誤。

  • init :如果為true(默認值),則該字段作為參數(shù)包含在生成的 __init__() 方法中。

  • repr :如果為true(默認值),則該字段包含在生成的 __repr__() 方法返回的字符串中。

  • hash :這可以是布爾值或 None 。如果為true,則此字段包含在生成的 __hash__() 方法中。如果為 None (默認值),請使用 compare 的值,這通常是預(yù)期的行為。如果字段用于比較,則應(yīng)在 hash 中考慮該字段。不鼓勵將此值設(shè)置為 None 以外的任何值。

    設(shè)置 hash=Falsecompare=True 的一個可能原因是,如果一個計算 hash 的代價很高的字段是檢驗等價性需要的,但還有其他字段可以計算類型的 hash 。 即使從 hash 中排除某個字段,它仍將用于比較。

  • compare :如果為true(默認值),則該字段包含在生成的相等性和比較方法中( __eq__() , __gt__() 等等)。

  • metadata :這可以是映射或 None 。 None 被視為一個空的字典。這個值包含在 MappingProxyType() 中,使其成為只讀,并暴露在 Field 對象上。數(shù)據(jù)類根本不使用它,它是作為第三方擴展機制提供的。多個第三方可以各自擁有自己的鍵值,以用作元數(shù)據(jù)中的命名空間。

  • kw_only: 如果為真值,則此字段將被標記為僅限關(guān)鍵字。 這將在當計算出所生成的 __init__() 方法的形參時被使用。

3.10 新版功能.

如果通過調(diào)用 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.xC.y 將不設(shè)置。

class dataclasses.Field?

Field 對象描述每個定義的字段。這些對象在內(nèi)部創(chuàng)建,并由 fields() 模塊級方法返回(見下文)。用戶永遠不應(yīng)該直接實例化 Field 對象。 其有文檔的屬性是:

  • name :字段的名字。

  • type :字段的類型。

  • default, default_factory, init, repr, hash, compare, metadatakw_only 具有與 field() 函數(shù)中對應(yīng)參數(shù)相同的含義和值。

可能存在其他屬性,但它們是私有的,不能被審查或依賴。

dataclasses.fields(class_or_instance)?

返回 Field 對象的元組,用于定義此數(shù)據(jù)類的字段。 接受數(shù)據(jù)類或數(shù)據(jù)類的實例。如果沒有傳遞一個數(shù)據(jù)類或?qū)嵗龑⒁l(fā) TypeError 。 不返回 ClassVarInitVar 的偽字段。

dataclasses.asdict(obj, *, dict_factory=dict)?

Converts the dataclass obj to a dict (by using the factory function dict_factory). Each dataclass is converted to a dict of its fields, as name: value pairs. dataclasses, dicts, lists, and tuples are recursed into. Other objects are copied with copy.deepcopy().

Example of using asdict() on nested dataclasses:

@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}]}

To create a shallow copy, the following workaround may be used:

dict((field.name, getattr(obj, field.name)) for field in fields(obj))

asdict() raises TypeError if obj is not a dataclass instance.

dataclasses.astuple(obj, *, tuple_factory=tuple)?

Converts the dataclass obj to a tuple (by using the factory function tuple_factory). Each dataclass is converted to a tuple of its field values. dataclasses, dicts, lists, and tuples are recursed into. Other objects are copied with copy.deepcopy().

繼續(xù)前一個例子:

assert astuple(p) == (10, 20)
assert astuple(c) == ([(0, 0), (10, 4)],)

To create a shallow copy, the following workaround may be used:

tuple(getattr(obj, field.name) for field in dataclasses.fields(obj))

astuple() raises TypeError if obj is not a dataclass instance.

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)?

Creates a new dataclass with name cls_name, fields as defined in fields, base classes as given in bases, and initialized with a namespace as given in namespace. fields is an iterable whose elements are each either name, (name, type), or (name, type, Field). If just name is supplied, typing.Any is used for type. The values of init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, and weakref_slot have the same meaning as they do in dataclass().

此函數(shù)不是嚴格要求的,因為用于任何創(chuàng)建帶有 __annotations__ 的新類的 Python 機制都可以應(yīng)用 dataclass() 函數(shù)將該類轉(zhuǎn)換為數(shù)據(jù)類。提供此功能是為了方便。例如:

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)?

Creates a new object of the same type as obj, replacing fields with values from changes. If obj is not a Data Class, raises TypeError. If values in changes do not specify fields, raises TypeError.

新返回的對象通過調(diào)用數(shù)據(jù)類的 __init__() 方法創(chuàng)建。這確保了如果存在 __post_init__() ,其也被調(diào)用。

如果存在沒有默認值的僅初始化變量,必須在調(diào)用 replace() 時指定,以便它們可以傳遞給 __init__()__post_init__() 。

changes 包含任何定義為 init=False 的字段是錯誤的。在這種情況下會引發(fā) ValueError

提前提醒 init=False 字段在調(diào)用 replace() 時的工作方式。如果它們完全被初始化的話,它們不是從源對象復(fù)制的,而是在 __post_init__() 中初始化。估計 init=False 字段很少能被正確地使用。如果使用它們,那么使用備用類構(gòu)造函數(shù)或者可能是處理實例復(fù)制的自定義 replace() (或類似命名的)方法可能是明智的。

dataclasses.is_dataclass(obj)?

如果其形參為 dataclass 或其實例則返回 True,否則返回 False。

如果你需要知道一個類是否是一個數(shù)據(jù)類的實例(而不是一個數(shù)據(jù)類本身),那么再添加一個 not isinstance(obj, type) 檢查:

def is_dataclass_instance(obj):
    return is_dataclass(obj) and not isinstance(obj, type)
dataclasses.MISSING?

一個表示缺失 default 或 default_factory 的監(jiān)視值。

dataclasses.KW_ONLY?

一個用作類型標注的監(jiān)視值。 任何在偽字段之后的類型為 KW_ONLY 的字段會被標記為僅限關(guān)鍵字字段。 請注意在其他情況下 KW_ONLY 類型的偽字段會被完全忽略。 這包括此類字段的名稱。 根據(jù)慣例,名稱 _ 會被用作 KW_ONLY 字段。 僅限關(guān)鍵字字段指明當類被實例化時 __init__() 形參必須以關(guān)鍵字形式來指定。

在這個例子中,字段 yz 將被標記為僅限關(guān)鍵字字段:

@dataclass
class Point:
  x: float
  _: KW_ONLY
  y: float
  z: float

p = Point(0, y=1.5, z=2.0)

在單個數(shù)據(jù)類中,指定一個以上 KW_ONLY 類型的字段將導(dǎo)致錯誤。

3.10 新版功能.

exception dataclasses.FrozenInstanceError?

在使用 frozen=True 定義的數(shù)據(jù)類上調(diào)用隱式定義的 __setattr__()__delattr__() 時引發(fā)。 這是 AttributeError 的一個子類。

初始化后處理?

生成的 __init__() 代碼將調(diào)用一個名為 __post_init__() 的方法,如果在類上已經(jīng)定義了 __post_init__() 。它通常被稱為 self.__post_init__() 。但是,如果定義了任何 InitVar 字段,它們也將按照它們在類中定義的順序傳遞給 __post_init__() 。 如果沒有 __init__() 方法生成,那么 __post_init__() 將不會被自動調(diào)用。

在其他用途中,這允許初始化依賴于一個或多個其他字段的字段值。例如:

@dataclass
class C:
    a: float
    b: float
    c: float = field(init=False)

    def __post_init__(self):
        self.c = self.a + self.b

dataclass() 所生成的 __init__() 方法不會調(diào)用基類的 __init__() 方法。 如果基類有需要被調(diào)用的 __init__() 方法,通常是在 __post_init__() 方法中調(diào)用此方法:

@dataclass
class Rectangle:
    height: float
    width: float

@dataclass
class Square(Rectangle):
    side: float

    def __post_init__(self):
        super().__init__(self.side, self.side)

但是請注意,一般來說 dataclass 生成的 __init__() 方法不需要被調(diào)用,因為派生的 dataclass 將負責初始化任何自身為 dataclass 的基類的所有字段。

有關(guān)將參數(shù)傳遞給 __post_init__() 的方法,請參閱下面有關(guān)僅初始化變量的段落。另請參閱關(guān)于 replace() 處理 init=False 字段的警告。

類變量?

兩個地方 dataclass() 實際檢查字段類型的之一是確定字段是否是如 PEP 526 所定義的類變量。它通過檢查字段的類型是否為 typing.ClassVar 來完成此操作。如果一個字段是一個 ClassVar ,它將被排除在考慮范圍之外,并被數(shù)據(jù)類機制忽略。這樣的 ClassVar 偽字段不會由模塊級的 fields() 函數(shù)返回。

僅初始化變量?

另一個 dataclass() 檢查類型注解地方是為了確定一個字段是否是一個僅初始化變量。它通過查看字段的類型是否為 dataclasses.InitVar 類型來實現(xiàn)。如果一個字段是一個 InitVar ,它被認為是一個稱為僅初始化字段的偽字段。因為它不是一個真正的字段,所以它不會被模塊級的 fields() 函數(shù)返回。僅初始化字段作為參數(shù)添加到生成的 __init__() 方法中,并傳遞給可選的 __post_init__() 方法。數(shù)據(jù)類不會使用它們。

例如,假設(shè)一個字段將從數(shù)據(jù)庫初始化,如果在創(chuàng)建類時未提供其值:

@dataclass
class C:
    i: int
    j: int = None
    database: InitVar[DatabaseType] = 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)

在這種情況下, fields() 將返回 ijField 對象,但不包括 database 。

凍結(jié)的實例?

無法創(chuàng)建真正不可變的 Python 對象。但是,通過將 frozen=True 傳遞給 dataclass() 裝飾器,你可以模擬不變性。在這種情況下,數(shù)據(jù)類將向類添加 __setattr__()__delattr__() 方法。 些方法在調(diào)用時會引發(fā) FrozenInstanceError

使用 frozen=True 時會有很小的性能損失: __ init__() 不能使用簡單的賦值來初始化字段,并必須使用 object.__setattr__()。

繼承?

當數(shù)組由 dataclass() 裝飾器創(chuàng)建時,它會查看反向 MRO 中的所有類的基類(即從 object 開始 ),并且對于它找到的每個數(shù)據(jù)類, 將該基類中的字段添加到字段的有序映射中。添加完所有基類字段后,它會將自己的字段添加到有序映射中。所有生成的方法都將使用這種組合的,計算的有序字段映射。由于字段是按插入順序排列的,因此派生類會重載基類。一個例子:

@dataclass
class Base:
    x: Any = 15.0
    y: int = 0

@dataclass
class C(Base):
    z: int = 10
    x: int = 15

最后的字段列表依次是 xy 、 zx 的最終類型是 int ,如類 C 中所指定的那樣。

C 生成的 __init__() 方法看起來像:

def __init__(self, x: int = 15, y: int = 0, z: int = 10):

__init__() 中僅限關(guān)鍵字字段的重新排序?

在計算出 __init__() 所需要的形參之后,任何僅限關(guān)鍵字形參會被移至所有常規(guī)(非僅限關(guān)鍵字)形參的后面。 這是 Python 中實現(xiàn)僅限關(guān)鍵字形參所要求的:它們必須位于非僅限關(guān)鍵字形參之后。

在這個例子中,Base.y, Base.w, and D.t 是僅限關(guān)鍵字字段,而 Base.xD.z 是常規(guī)字段:

@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):

請注意形參原來在字段列表中出現(xiàn)的位置已被重新排序:前面是來自常規(guī)字段的形參而后面是來自僅限關(guān)鍵字字段的形參。

僅限關(guān)鍵字形參的相對順序會在重新排序的 __init__() 形參列表中保持原樣。

默認工廠函數(shù)?

如果一個 field() 指定了一個 default_factory ,當需要該字段的默認值時,將使用零參數(shù)調(diào)用它。例如,要創(chuàng)建列表的新實例,請使用:

mylist: list = field(default_factory=list)

如果一個字段被排除在 __init__() 之外(使用 init=False )并且字段也指定 default_factory ,則默認的工廠函數(shù)將始終從生成的 __init__() 函數(shù)調(diào)用。發(fā)生這種情況是因為沒有其他方法可以為字段提供初始值。

可變的默認值?

Python 在類屬性中存儲默認成員變量值。思考這個例子,不使用數(shù)據(jù)類:

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 ,如預(yù)期的那樣。

使用數(shù)據(jù)類, 如果 此代碼有效:

@dataclass
class D:
    x: List = []
    def add(self, element):
        self.x += element

它生成的代碼類似于:

class D:
    x = []
    def __init__(self, x=x):
        self.x = x
    def add(self, element):
        self.x += element

assert D().x is D().x

This has the same issue as the original example using class C. That is, two instances of class D that do not specify a value for x when creating a class instance will share the same copy of x. Because dataclasses just use normal Python class creation they also share this behavior. There is no general way for Data Classes to detect this condition. Instead, the dataclass() decorator will raise a TypeError if it detects an unhashable default parameter. The assumption is that if a value is unhashable, it is mutable. This is a partial solution, but it does protect against many common errors.

使用默認工廠函數(shù)是一種創(chuàng)建可變類型新實例的方法,并將其作為字段的默認值:

@dataclass
class D:
    x: list = field(default_factory=list)

assert D().x is not D().x

在 3.11 版更改: Instead of looking for and disallowing objects of type list, dict, or set, unhashable objects are now not allowed as default values. Unhashability is used to approximate mutability.