(转)python 数据类:dataclass


原文: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的子类,它的元素是有命名的!


Top  ---  Bottom

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装饰器的一些知识点。


Top  ---  Bottom

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__方法;
      1. eq、frozen都为True时,__hash__将会生成;
      2. eq为True,frozen为False,__hash__将被设置为None;
      3. eq为False,frozen为True,__hash__将使用object(超类)的同名属性(通常就是对象id的hash)
    • 当unsafe_hash为True时,将会根据类的属性生成__hash__。如其名,这是不安全的,因为属性是可变的,这会导致hash的不一致。当然您能保证对象属性不会变,你也可以设置为True。
  • frozen控制是否冻结对field赋值。设置为True时,对象将是不可变的,因为不可变,所以如果设置有__setattr____delattr__将会导致TypeError错误。

前两个参数我们在上一章实际已经看了效果,下面我们查看参数eqorder

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


Top  ---  Bottom

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“,但是我们访问不到。


Top  ---  Bottom

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 

Top  ---  Bottom

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定义。


Top  ---  Bottom

7、小结

​ 合理使用dataclass将会大大减轻开发中的负担,将我们从大量的重复劳动中解放出来,这既是dataclass的魅力,不过魅力的背后也总是有陷阱相伴,最后我想提几点注意事项:

  • dataclass通常情况下是unhashable的,因为默认生成的__hash__None,所以不能用来做字典的key,如果有这种需求,那么应该指定你的数据类为frozen dataclass
  • 小心当你定义了和dataclass生成的同名方法时会引发的问题
  • 当使用可变类型(如list)时,应该考虑使用fielddefault_factory
  • 数据类的属性都是公开的,如果你有属性只需要初始化时使用而不需要在其他时候被访问,请使用dataclasses.InitVar

​ 只要避开这些陷阱,dataclass一定能成为提高生产力的利器。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM