dataclasses– 数据类

源代码: Lib / dataclasses.py


这个模块提供了一个装饰器和函数来自动添加生成的特殊方法__init__()__repr__()到用户定义的类。它最初是在PEP 557 .

在这些生成的方法中使用的成员变量是使用PEP 526 类型注释。例如这个代码:

@dataclassclass 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

这个函数是装饰器用于将生成的特殊方法 s添加到类中,如下所述.

dataclass()装饰师检查班级找到field秒。一个 field被定义为具有类型注释。使用下面描述的两个例外,dataclass()中没有任何内容检查变量注释中指定的类型.

所有生成的方法中的字段顺序是它们在类定义中出现的顺序。

dataclass()装饰者会在课堂上添加各种“dunder”方法,如下所述。如果在类上添加了任何alreadyexist方法,则行为取决于参数,如下所述。装饰器返回被调用的同一个类;没有创建新类.

如果dataclass()仅用作没有参数的简单装饰器,它就像它具有此签名中记录的默认值一样。也就是说,dataclass()的这三个用法是等价的:

@dataclassclass C:    ...@dataclass()class C:    ...@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)class C:   ...

dataclass()的参数是:

  • init:如果是(默认值),则__init__()方法将生成

    如果类已定义__init__(),则此参数为isignored。

  • repr:如果为true(默认值),则__repr__()方法将生成。生成的repr字符串将具有类名以及每个字段的名称和repr,按照它们在类中定义的顺序。标记为从reprare中排除的字段不包括在内。例如:InventoryItem(name="widget", unit_price=3.0, quantity_on_hand=10).

    如果类已定义__repr__(),则此参数为isignored。

  • eq:如果为true(默认值),将生成__eq__()方法。此方法按顺序将类比较为其字段的元组。比较中的两个实例都必须是相同的类型.

    如果类已经定义了__eq__(),则此参数为isignored。

  • order:如果为true(默认为False),__lt__(),__le__(), __gt__()__ge__()将生成方法。这些按顺序比较该类,就好像它是其字段的元组一样。比较中的两个实例必须是相同的类型。如果order为真且eq为假,则ValueError被提出

    如果该类已经定义了__lt__(),__le__(), __gt__()中的任何一个,或者__ge__(),然后TypeError被抬起来

  • unsafe_hash:如果False(默认),__hash__()按照eqfrozen设置了

    __hash__()由内置的hash()使用,并且当对象被添加到散列集合(如字典和集合)时使用。有一个__hash__()暗示类的实例是不可变的。可变性是一个复杂的属性,取决于程序员的意图,__eq__(),以及eqfrozen中的标志dataclass()decorator.

    默认情况下, dataclass()不会暗中添加__hash__()方法,除非这样做是安全的。也不会添加或更改现有的明确定义__hash__()方法。设置classattribute __hash__ = None具有Python的特定含义,如__hash__()文档中所述

    如果__hash__()没有明确定义,或者是否已设置到None,然后dataclass() may添加一个隐含的__hash__()方法。虽然不推荐,你可以用dataclass()强制__hash__()创建一个unsafe_hash=True。如果您的类在逻辑上是不可变的但可能会发生变异,这可能就是这种情况。这是一个专门的用例,应该仔细考虑.

    以下是管理__hash__()方法的隐式创建的规则。请注意,您不能在数据类中都有明确的__hash__()方法并设置unsafe_hash=True;这将导致TypeError.

    如果eqfrozen两者都是真的,默认情况下dataclass()将为你生成一个__hash__()方法。如果eq为真且frozen为假,则__hash__()将设为None,标记它不可用(因为它是可变的)。如果eq为false,__hash__()将保持不变意味着将使用超类的__hash__()方法(如果超类是object,这意味着它将回退到基于id的散列).

  • frozen:如果为true(默认值为False),分配给字段将生成异常。这模拟了只读的冻结实例。如果__setattr__()__delattr__()在类中定义,然后TypeError被引发。请参阅下面的讨论.

fields可以选择使用normalPython语法指定默认值:

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

TypeError如果没有默认值的字段将使用默认值的字段,则会引发此字段。无论是在单个类中出现还是作为类继承的结果都是如此.

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

对于常见和简单的用例,不需要其他功能。但是,有些数据类功能需要额外的每个字段信息。为了满足这种需要的附加信息,您可以通过调用提供的field()功能。例如:

@dataclassclass C:    mylist: List[int] = field(default_factory=list)c = C()c.mylist += [1, 2, 3]

如上所示,MISSING值是一个用于在提供defaultdefault_factory参数时检测的标记对象。使用这个哨兵是因为Nonedefault的有效值。没有代码应该直接使用MISSINGvalue.

field()的参数是:

  • default:如果提供,这将是此字段的默认值。这是必需的,因为field()调用本身代替默认值的正常位置.

  • default_factory:如果提供,它必须是零参数可调用,当需要该字段的默认值时将调用它。除了其他目的之外,这可以用于指定具有可变默认值的字段,如下所述。指定defaultdefault_factory.

  • init都是错误的:如果为true(默认值),则该字段作为参数包含在生成的__init__()方法中

  • repr:如果为true(默认值),则此字段包含在生成的__repr__()方法返回的字符串中

  • compare//:如果为true(默认值),则此字段包含在生成的等式和比较方法中(__eq__(),__gt__(),等人).

  • hash:这可以是一个bool或None。如果为true,则该字段包含在生成的__hash__()方法中。如果None(默认值),请使用compare:这通常是预期的行为。应该在用于比较的hashif中考虑一个字段。不建议将此值设置为None以外的其他任何内容.

    设置hash=False但是compare=True的可能原因是,如果字段计算哈希是昂贵的对于等式测试,需要该字段的值,并且还有其他字段对类型的哈希值有贡献。即使从散列中排除了一个字段,它仍将用于比较.

  • metadata:这可以是映射或无。没有人被视为空的dict。这个值包含在MappingProxyType()中,使其成为只读,并暴露在Field对象中。DataClasses根本不使用它,它是作为第三方扩展机制提供的。多个第三方都可以拥有自己的密钥,用作元数据中的anamespace

如果默认值为通过调用field()指定字段,然后该字段的class属性将被指定的default值替换。如果不 default是提供的,那么class属性将被删除。意图是在dataclass()如果指定了默认值,则decorator将运行,classattributes将包含字段的默认值。例如,在:

@dataclassclass 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将不会被设置.

class dataclasses.Field

Field对象描述每个定义的字段。这些对象在内部创建,并由fields()模块级方法返回(见下文)。用户永远不应该实例化Field对象直接。其记录的属性是:

其他属性可能存在,但它们是私密的,不得被视察或依赖.

dataclasses.fields (class_or_instance)

返回一个元组Field定义thisdataclass字段的对象。接受数据类或数据类的实例.Raises TypeError如果没有传递一个数据类或一个实例。不返回伪字段ClassVarInitVar.

dataclasses.asdictinstance, *, dict_factory=dict

转换数据类instance到dict(使用thefactory函数dict_factory)。每个数据类都转换为其字段的字典,如name: value对。数据类,dicts,列表和元组被递归。例如:

@dataclassclass Point:     x: int     y: int@dataclassclass 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}]}

如果TypeError提高instance不是数据类实例.

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

将数据类instance转换为元组(使用thefactory函数tuple_factory)。每个数据类都转换为其字段值的元组。数据类,词组,列表和元组被递归到.

继续前面的例子:

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

如果TypeError引起instance不是数据类实例.

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

创建一个名为cls_name的新数据类,字段定义为fields,base在bases中给出的类,并使用namespace. fields是aiterable,其元素分别是name, (name, type)(name, type, Field)。如果只提供nametyping.Any用于typeinit,repr, eq, order, unsafe_hashfrozen的值与dataclass().

这个函数并不是严格要求的,因为使用__annotations__ canthen创建一个新类的任何Python机制都适用于dataclass()用于将该类转换为数据类的函数。提供此功能是为了方便。例如:

C = make_dataclass("C",                   [("x", int),                     "y",                    ("z", int, field(default=5))],                   namespace={"add_one": lambda self: self.x + 1})

相当于:

@dataclassclass C:    x: int    y: "typing.Any"    z: int = 5    def add_one(self):        return self.x + 1
dataclasses.replaceinstance, **changes

创建一个相同类型的新对象instance,用changes中的值替换字段。如果instance不是DataClass,则引发TypeError。如果changes中的值没有指定字段,则引发TypeError.

通过调用__init__()数据类的方法。这样可以确保__post_init__()(如果存在的话)也被调用.

Init-只有没有默认值的变量,如果存在,必须在调用replace()这样它们就可以传递到__init__()__post_init__().

这是changes包含任何被定义为具有init=False。一个 ValueError在这种情况下会被提出来

预先警告init=False字段在调用replace()时工作。它们不是从源对象复制的,而是在__post_init__()初始化,如果它们完全没有初始化的话。预计init=False很少和明智地使用字段。如果使用它们,它可能会有替代类构造函数,或者可能是一个习惯replace()(或类似命名的)处理instancecopying的方法.

dataclasses.is_dataclassclass_or_instance

如果参数是一个数据类或一个实例,则返回True,否则返回False .

如果你需要知道一个类是否是数据类的实例(而不是数据类本身),那么再添加一个检查notisinstance(obj, type)

def is_dataclass_instance(obj):    return is_dataclass(obj) and not isinstance(obj, type)

postinit处理

生成的__init__()代码将调用名为__post_init__()的方法,如果在类上定义了__post_init__()。它通常被称为self.__post_init__()。但是,如果定义了InitVar字段,它们也会被通过__post_init__()按照他们在课堂上定义的顺序。如果没有生成__init__()方法,那么__post_init__()将不会被自动调用.

在其他用途​​中,这允许初始化依赖于一个或多个其他字段的字段值。例如:

@dataclassclass C:    a: float    b: float    c: float = field(init=False)    def __post_init__(self):        self.c = self.a + self.b

有关将参数传递给__post_init__()。还可以看一下警告replace()如何处理init=False fields.

类变量

dataclass()实际上检查一个字段的类型是确定一个字段是否是一个类定义的变量PEP 526 。它是通过检查字段的类型是否typing.ClassVar。如果一个字段是ClassVar,它被排除在考虑范围之外,并被数据类机制忽略。这样的ClassVar伪字段不会被模块级返回fields()功能

只能变量

的另一个地方dataclass()检查类型注释是为了确定一个字段是否是一个init-only变量。它是通过查看字段的类型为dataclasses.InitVar来实现的。如果一个字段是InitVar,则它被认为是一个称为init-onlyfield的伪字段。因为它不是真正的字段,所以它不是由模块级fields()函数返回的。仅初始化字段作为生成的__init__()方法的参数添加,并传递给可选的__post_init__()方法。它们不是由数据类使用的.

例如,假设一个字段将从数据库初始化,如果在创建类时没有提供avalue:

@dataclassclass 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()将为Fieldi返回j对象,但不是database.

冻结的实例

真的无法创造不可变的Python对象。然而,通过将frozen=True传递给dataclass()装饰者,你可以使用不可变性。在这种情况下,数据类将__setattr__()__delattr__()方法添加到类中。这些方法会在调用时引起FrozenInstanceError.

使用时frozen=True__init__()不能使用简单赋值来初始化字段,并且必须使用object.__setattr__().

继承

创建数据类时,性能损失很小通过dataclass()装饰器,它查看反向MRO中的所有类的基类(即,从object开始),并且对于它找到的每个数据类,添加该基类的字段在添加了所有基类字段之后,它将自己的字段添加到有序映射中。所有生成的方法都将使用此组合的,计算的有序字段映射。由于字段按插入顺序排列,因此派生类会覆盖基类。例如:

@dataclassclass Base:    x: Any = 15.0    y: int = 0@dataclassclass C(Base):    z: int = 10    x: int = 15

最后的字段列表依次是x, y, zx的最终类型是int,如类C.

中指定的__init__()生成的C方法如下:

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

默认工厂函数

如果field()指定default_factory,当需要字段的默认值时,它被称为withzero参数。例如,要创建列表的新实例,请使用:

mylist: list = field(default_factory=list)

如果从__init__()中排除某个字段(使用init=False)并且该字段还指定了default_factory,然后将始终从生成的__init__()函数调用defaultfactory函数。发生这种情况是因为没有其他方法可以给字段赋一个初始值.

可变默认值

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,如预期的那样

使用数据类,if此代码有效:

@dataclassclass 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 += elementassert D().x is D().x

的代码这与使用类C的原始示例有同样的问题。也就是说,类的两个实例D在创建类实例时没有指定x的值将共享x。因为数据类只使用普通的Python类创建,所以它们也分享这种行为。DataClasses没有通用的方法来检测这种情况。相反,如果数据类检测到类型为TypeErrorlist,dict的默认参数,则会引发set。这是一个部分解决方案,但它确实保护了许多常见错误.

使用默认工厂函数是一种创建新的可变类型实例的方法,作为字段的默认值

@dataclassclass D:    x: list = field(default_factory=list)assert D().x is not D().x

例外

exception dataclasses.FrozenInstanceError

在使用__setattr__()文章导航__delattr__()定义的数据类上调用隐式定义的frozen=True.

评论被关闭。