Python 增强提案PEP
- Python中10个必读的PEP提案
- 理解Python数据类:Dataclass 的特征概述 (上)
- 理解Python数据类:Dataclass fields 的概述(下)
- Python3.7 dataclass使用指南小结
PEP 是 Python 增强提案(Python Enhancement Proposal)的缩写。社区通过PEP来给 Python 语言建言献策,每个版本你所看到的新特性和一些变化都是通过PEP提案经过社区决策层讨论、投票决议的。
PEP 557 数据类(data class)
dataclass的定义位于PEP-557,根据定义一个dataclass是指“一个带有默认值的可变的namedtuple”,广义的定义就是有一个类,它的属性均可公开访问,可以带有默认值并能被修改,而且类中含有与这些属性相关的类方法,那么这个类就可以称为dataclass,再通俗点讲,dataclass就是一个含有数据及操作数据方法的容器。
Dataclasses 是一些适合于存储数据对象(data object)的 Python 类。
下面是一个并不详尽的用于定义数据对象的特征列表:
• 他们存储并表示特定的数据类型。例如:一个数字。对于那些熟悉对象关系映射(Object Relational Mapping,简称 ORM)的人来说,一个模型实例就是一个数据对象。它表示了一种特定类型的实体。它存储了用于定义或表示那种实体的属性。
• 他们能够被用于和同类型的其他对象进行比较。例如,一个数字可能大于,小于或等于另一个数字。
• 当然数据对象还有更多的特征,但上述内容足以帮助你理解关键部分。
• 为了理解 Dataclases,我们将实现一个简单的类。它能够存储一个数字,并允许我们执行上面提到的各种运算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| 1.相比普通class,dataclass通常不包含私有属性,数据可以直接访问 2.dataclass的repr方法通常有固定格式,会打印出类型名以及属性名和它的值 3.dataclass拥有__eq__和__hash__魔法方法 4.dataclass有着模式单一固定的构造方式,或是需要重载运算符,而普通class通常无需这些工作
import random from typing import List from dataclasses import dataclass from dataclasses import field
def get_random_marks(): return [random.randint(1, 10) for _ in range(5)]
@dataclass(order=True) class Student: marks: List[int] = field(default_factory=get_random_marks) name: str = field(default='noname', compare=False) age: int = field(default=18, repr=False) verified: bool = field(repr=False, init=False, default=False)
student_1 = Student([random.randint(1, 10) for _ in range(5)], 'tom', 17) student_2 = Student(age=16, name='Rick')
print(student_1) print(f'student_1 == student_2: {student_1 == student_2}')
from dataclasses import asdict, astuple print(asdict(student_2))
print(f'isinstance(student_1, Student): {isinstance(student_1, Student)}')
>>> Student(marks=[7, 6, 7, 2, 3], name='tom') student_1 == student_2: False {'marks': [6, 4, 3, 5, 1], 'name': 'Rick', 'age': 16, 'verified': False} isinstance(student_1, Student): True
|
1 2 3
| 装饰器的原型如下: dataclasses.dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False) dataclass装饰器将根据类属性生成数据类和数据类需要的方法
|
key |
含义 |
init |
是否自动生成init,如果已经有定义同名方法则忽略这个值,也就是指定为True也不会自动生成 |
repr |
是否自动生成repr;自动生成的打印格式为class_name(arrt1:value1, attr2:value2, …) |
eq |
是否生成eq;按属性在类内定义时的顺序逐个比较,全部的值相同才会返回True |
order |
自动生成lt,le,gt,ge,比较方式与eq相同;如果order指定为True而eq指定为False,将引发ValueError;如果已经定义同名函数,将引发TypeError |
unsafehash |
如果是False,将根据eq和frozen参数来生成hash: 1. eq和frozen都为True,hash将会生成 2. eq为True而frozen为False,hash被设为None 3. eq为False,frozen为True,hash将使用超类(object)的同名属性(通常就是基于对象id的hash) 当设置为True时将会根据类属性自动生成hash,然而这是不安全的,因为这些属性是默认可变的,这会导致hash的不一致,所以除非能保证对象属性不可随意改变,否则应该谨慎地设置该参数为True |
frozen |
设为True时对field赋值将会引发错误,对象将是不可变的,如果已经定义了setattr和delattr将会引发TypeError |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| field的原型: dataclasses.field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, init=True, compare=True, metadata=None) 通常我们无需直接使用,装饰器会根据我们给出的类型注解自动生成field,但有时候我们也需要定制这一过程,这时dataclasses.field就显得格外有用了。
default和default_factory参数将会影响默认值的产生,它们的默认值都是None,意思是调用时如果为指定则产生一个为None的值。其中default是field的默认值,而default_factory控制如何产生值,它接收一个无参数或者全是默认参数的callable对象,然后用调用这个对象获得field的初始值,之后再将default(如果值不是MISSING)复制给callable返回的这个对象。 init参数如果设置为False,表示不为这个field生成初始化操作,dataclass提供了hook—— __post_init__供我们利用这一特性: repr参数表示该field是否被包含进repr的输出,compare和hash参数表示field是否参与比较和计算hash值。metadata不被dataclass自身使用,通常让第三方组件从中获取某些元信息时才使用,所以我们不需要使用这一参数。
如果指定一个field的类型注解为dataclasses.InitVar,那么这个field将只会在初始化过程中(__init__和__post_init__)可以被使用,当初始化完成后访问该field会返回一个dataclasses.Field对象而不是field原本的值,也就是该field不再是一个可访问的数据对象。举个例子,比如一个由数据库对象,它只需要在初始化的过程中被访问: @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)
|
dataclass继承
python3.7引入dataclass的一大原因就在于相比namedtuple,dataclass可以享受继承带来的便利。
dataclass装饰器会检查当前class的所有基类,如果发现一个dataclass,就会把它的字段按顺序添加进当前的class,随后再处理当前class的field。所有生成的方法也将按照这一过程处理,因此如果子类中的field与基类同名,那么子类将会无条件覆盖基类。子类将会根据所有的field重新生成一个构造函数,并在其中初始化基类。
几点注意事项:
dataclass通常情况下是unhashable的,因为默认生成的hash是None,所以不能用来做字典的key,如果有这种需求,那么应该指定你的数据类为frozen dataclass
小心当你定义了和dataclass生成的同名方法时会引发的问题
当使用可变类型(如list)时,应该考虑使用field的default_factory
数据类的属性都是公开的,如果你有属性只需要初始化时使用而不需要在其他时候被访问,请使用dataclasses.InitVar