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

除其他事情外,將添加 __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 新版功能.

模塊級裝飾器、類和函數(shù)?

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

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

dataclass() 裝飾器檢查類以找到 fieldfield 被定義為具有 類型標注 的類變量。除了下面描述的兩個例外,在 dataclass() 中沒有任何內(nèi)容檢查變量標注中指定的類型。

所有生成的方法中的字段順序是它們在類定義中出現(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)
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__() 方法。雖然不推薦,但你可以強制 dataclass()unsafe_hash=True 創(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。 參見下文的討論。

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, repr=True, hash=None, init=True, compare=True, metadata=None)?

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

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

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

如上所示, MISSING 值是一個 sentinel 對象,用于檢測是否提供了 defaultdefault_factory 參數(shù)。 使用此 sentinel 是因為 Nonedefault 的有效值。沒有代碼應(yīng)該直接使用 MISSING 值。

field() 參數(shù)有:

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

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

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

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

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

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

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

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

如果通過調(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 :字段的類型。

  • defaultdefault_factoryinitreprhashcompare 以及 metadata 與具有和 field() 聲明中相同的意義和值。

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

dataclasses.fields(class_or_instance)?

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

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

將數(shù)據(jù)類 instance 轉(zhuǎn)換為字典(使用工廠函數(shù) dict_factory )。每個數(shù)據(jù)類都轉(zhuǎn)換為其字段的字典,如 name: value 對。數(shù)據(jù)類、字典、列表和元組被遞歸。例如:

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

引發(fā) TypeError 如果 instance 不是數(shù)據(jù)類實例。

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

將數(shù)據(jù)類 instance 轉(zhuǎn)換為元組(通過使用工廠函數(shù) tuple_factory )。每個數(shù)據(jù)類都轉(zhuǎn)換為其字段值的元組。數(shù)據(jù)類、字典、列表和元組被遞歸。

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

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

引發(fā) TypeError 如果 instance 不是數(shù)據(jù)類實例。

dataclasses.make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)?

創(chuàng)建一個名為 cls_name 的新數(shù)據(jù)類,字段為 fields 中定義的字段,基類為 bases 中給出的基類,并使用 namespace 中給出的命名空間進行初始化。 fields 是一個可迭代的元素,每個元素都是 name(name, type)(name, type, Field) 。 如果只提供``name`` , typetyping.Anyinitrepreqorderunsafe_hashfrozen 的值與它們在 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(instance, **changes)?

創(chuàng)建一個 instance 相同類型的新對象,用 changes 中的值替換字段。如果 instance 不是數(shù)據(jù)類,則引發(fā) TypeError 。如果 changes 中的值沒有指定字段,則引發(fā) 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(class_or_instance)?

如果其形參為 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)

初始化后處理?

生成的 __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

有關(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

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

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

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

默認工廠函數(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

這與使用類 C 的原始示例具有相同的問題。也就是說,在創(chuàng)建類實例時沒有為 x 指定值的類 D 的兩個實例將共享相同的 x 副本。由于數(shù)據(jù)類只使用普通的 Python 類創(chuàng)建,因此它們也會共享此行為。數(shù)據(jù)類沒有通用的方法來檢測這種情況。相反,如果數(shù)據(jù)類檢測到類型為 listdictset 的默認參數(shù),則會引發(fā) TypeError 。這是一個部分解決方案,但它可以防止許多常見錯誤。

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

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

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

異常?

exception dataclasses.FrozenInstanceError?

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