原文:https://www.cnblogs.com/dan-baishucaizi/p/14786600.html
1、dataclass简介
dataclass是python3.7开始带有的新属性(类装饰器),dataclass是指”一个带有默认值的可变namedtuple“,本质还是一个类,它的属性非特殊情况可以直接访问,类中有与属性相关的类方法。简单地说就是一个含有数据及其操作方法的类。
dataclass与普通类的区别
- 与普通类相比,dataclass通常不包含私有属性,这些属性可以直接访问(也可以私有);
- repr() 函数将对象转化为供解释器读取的形式;dataclass的repr方法通常有其固定格式,会打印类名、属性名、属性值;
- dataclass有
__eq__、__hash__这些魔法方法; - dataclass有着模式单一固定的构造方式,根据需要有时需要重载运算符,而普通class通常无需这些工作。
注:namedtuple是tuple的子类,它的元素是有命名的!
2、引入dataclass装饰器
常见的类生成方式
class elfin: def __init__(self, name, age): self.name = name self.age = age
使用dataclass装饰器
@dataclass class elfin: name: str age: int
我们使用@dataclass就可以实现与普通类的效果,这样代码更简洁!
__post_init__方法
如果某个属性需要在init后处理,就可以放置到__post_init__中!
@dataclass class elfin: name: str age: int def __post_init__(self): if type(self.name) is str: self.identity = identity_dict[self.name]
测试上面的案例:
>>> from dataclasses import dataclass >>> identity_dict = { ... "firstelfin": "boss", ... "secondelfin": "master", ... "thirdelfin": "captain" ... } >>> @dataclass ... class Elfin: ... name: str ... age: int ... ... def __post_init__(self): ... if type(self.name) is str: ... self.identity = identity_dict[self.name] >>> print(Elfin) ... Out[1]: <class '__main__.Elfin'> >>> elfin_ins = Elfin("firstelfin", 23) >>> elfin_ins ... Out[2]: Elfin(name='firstelfin', age=23) >>> elfin_ins.identity ... Out[3]: 'boss'
上面的案例向我们展示了即使init部分没有生成identity属性,实例也可以获取到!
下面我们就分别展示dataclass装饰器的一些知识点。
3、dataclass装饰器选项
使用dataclass类装饰器的选项,我们可以定制我们想要的数据类,默认选项为:
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False) class Elfin: pass
装饰器的参数选项说明:
- init控制是否生成
__init__方法; - repr控制是否生成
__repr__方法; - eq控制是否生成
__eq__方法,它用于判断实例是否相等; - order控制是否创建四种大小关系方法:
__lt__、__le__、__gt__、__ge__;order为True,则eq不能为False,也不能自定义order方法。 - unsafe_hash控制hash的生成方式。
- 当unsafe_hash为False时,将根据eq、frozen参数来生成
__hash__方法;- eq、frozen都为True时,
__hash__将会生成; - eq为True,frozen为False,
__hash__将被设置为None; - eq为False,frozen为True,
__hash__将使用object(超类)的同名属性(通常就是对象id的hash)
- eq、frozen都为True时,
- 当unsafe_hash为True时,将会根据类的属性生成
__hash__。如其名,这是不安全的,因为属性是可变的,这会导致hash的不一致。当然您能保证对象属性不会变,你也可以设置为True。
- 当unsafe_hash为False时,将根据eq、frozen参数来生成
- frozen控制是否冻结对field赋值。设置为True时,对象将是不可变的,因为不可变,所以如果设置有
__setattr__、__delattr__将会导致TypeError错误。
前两个参数我们在上一章实际已经看了效果,下面我们查看参数eq、order:
>>> @dataclass(init=True, repr=True, eq=True, order=True) ... class Elfin: ... name: str ... age: int ... ... def __post_init__(self): ... if type(self.name) is str: ... self.identity = identity_dict[self.name] >>> elfin_ins1 = Elfin("thirdelfin", 18) >>> elfin_ins2 = Elfin("secondelfin", 20) >>> elfin_ins1 == elfin_ins2 ... Out[4]: False >>> elfin_ins1 >= elfin_ins2 ... Out[5]: True >>>
可以发现我们可以在实例之间进行大小的比较了!同时我们知道普通类是不同进行大小比较的:
>>> class A: ... def __init__(self, age): ... self.age = age >>> a1 = A(20) >>> a2 = A(30) >>> a1 > a2 ... TypeError Traceback (most recent call last) ... <ipython-input-24-854e76ddfa09> in <module> ... ----> 1 a1 > a2 ... ... TypeError: '>' not supported between instances of 'A' and 'A'
上面我们提到了field,实际上,所有的数据类属性,都是被field所控制,它代表一个数据的实体和它的元信息,下面我们了解一下dataclasses.field。
4、数据类的基石--dataclasses.field
field的定义如下:
def field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, hash=None, compare=True, metadata=None): if default is not MISSING and default_factory is not MISSING: raise ValueError('cannot specify both default and default_factory') return Field(default, default_factory, init, repr, hash, compare, metadata)
一般情况下,我们无需直接使用,装饰器会根据我们给出的类型注解自动生成field,但有时候也需要定制这个过程,所以dataclasses.field就特别重要了!
参数说明:
-
default:如果调用时没有指定,则默认为None,它控制的是field的默认值;
-
default_factory:控制如何产生值,它接收一个无参数或者全是默认参数的
callable对象,然后调用该对象field的初始值,再将default复制给callable对象。 -
init:控制是否在init中生成此参数。在前面章节的案例中,我们要生成
self.identity属性,但是不想在init中传入,就可以使用field了。>>> @dataclass(init=True, repr=True, eq=True, order=True) ... class Elfin: ... name: str ... age: int ... identity: str = field(init=False) ... ... def __post_init__(self): ... if type(self.name) is str: ... self.identity = identity_dict[self.name] >>> elfin_ins3 = Elfin("firstelfin", 20) >>> elfin_ins3 ... Out[6]: Elfin(name='firstelfin', age=20, identity='boss') -
repr:表示该field是否被包含进repr的输出,默认要输出,如上面的案例。
-
compare:是否参与比较和计算hash值。
-
hash:是否参与比较和计算hash值。
-
metadata不被dataclass自身使用,通常让第三方组件从中获取某些元信息时才使用,所以我们不需要使用这一参数。
只能初始化调用的属性
如果指定一个field的类型注解为dataclasses.InitVar,那么这个field将只会在初始化过程中(__init__和__post_init__)可以被使用,当初始化完成后访问该field会返回一个dataclasses.Field对象而不是field原本的值,也就是该field不再是一个可访问的数据对象。
>>> from dataclasses import InitVar >>> @dataclass(init=True, repr=True, eq=True, order=True) ... class Elfin: ... name: str ... age: int ... identity: InitVar[str] = None ... ... def __post_init__(self, identity): ... if type(self.name) is str: ... self.identity = identity_dict[self.name] >>> elfin_ins3 = Elfin("firstelfin", 20) >>> elfin_ins3 ... Out[7]: Elfin(name='firstelfin', age=20) >>> elfin_ins3.identity >>>
注意这里elfin_ins3.identity说明都没有返回,实际上应该是”boss“,但是我们访问不到。
5、dataclass的常用函数
5.1 转换数据为字典 dataclasses.asdict
>>> from dataclasses import asdict >>> asdict(elfin_ins3) ... Out[8]: {'name': 'firstelfin', 'age': 20}
5.2 转换数据为元组 dataclasses.astuple
>>> from dataclasses import astuple >>> astuple(elfin_ins3) ... Out[9]: ('firstelfin', 20)
5.3 判断是否是dataclass类
>>> from dataclasses import is_dataclass >>> is_dataclass(Elfin) ... Out[10]: True >>> is_dataclass(elfin_ins3) ... Out[11]: True
6、dataclass继承
python3.7引入dataclass的一大原因就在于相比namedtuple,dataclass可以享受继承带来的便利。
dataclass装饰器会检查当前class的所有基类,如果发现一个dataclass,就会把它的属性按顺序添加进当前的class,随后再处理当前class的field。所有生成的方法也将按照这一过程处理,因此如果子类中的field与基类同名,那么子类将会无条件覆盖基类。子类将会根据所有的field重新生成一个构造函数,并在其中初始化基类。
案例:
>>> @dataclass(init=True, repr=True, eq=True, order=True) ... class Elfin: ... name: str = "firstelfin" ... age: int = 20 ... identity: InitVar[str] = None ... ... def __post_init__(self, identity): ... if type(self.name) is str: ... self.identity = identity_dict[self.name] >>> @dataclass ... class Wude(Elfin): ... age: int = 68 >>> Wude() ... Out[11]: Wude(name='firstelfin', age=68) >>>
上述可见,Wude类继承了Elfin类的name属性,而实例中的age覆盖了Elfin中的age定义。
7、小结
合理使用dataclass将会大大减轻开发中的负担,将我们从大量的重复劳动中解放出来,这既是dataclass的魅力,不过魅力的背后也总是有陷阱相伴,最后我想提几点注意事项:
- dataclass通常情况下是unhashable的,因为默认生成的
__hash__是None,所以不能用来做字典的key,如果有这种需求,那么应该指定你的数据类为frozen dataclass - 小心当你定义了和
dataclass生成的同名方法时会引发的问题 - 当使用可变类型(如list)时,应该考虑使用
field的default_factory - 数据类的属性都是公开的,如果你有属性只需要初始化时使用而不需要在其他时候被访问,请使用
dataclasses.InitVar
只要避开这些陷阱,dataclass一定能成为提高生产力的利器。
