• (转)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一定能成为提高生产力的利器。

    技术链接
  • 相关阅读:
    Web开发人员需知的Web缓存知识
    SQLServer常见性能问题
    C#面试常见题
    SQL Server数据库大型应用解决方案总结
    asp.net 缓存
    asp.net 的页面几种传值方式
    C# 连接SQL数据库以及操作数据库
    变量命名规则
    C# 委托
    删除文件
  • 原文地址:https://www.cnblogs.com/liujiacai/p/15614579.html
Copyright © 2020-2023  润新知