zoukankan      html  css  js  c++  java
  • (转)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一定能成为提高生产力的利器。

    技术链接
  • 相关阅读:
    1105 Spiral Matrix (25分)(蛇形填数)
    1104 Sum of Number Segments (20分)(long double)
    1026 Table Tennis (30分)(模拟)
    1091 Acute Stroke (30分)(bfs,连通块个数统计)
    1095 Cars on Campus (30分)(排序)
    1098 Insertion or Heap Sort (25分)(堆排序和插入排序)
    堆以及堆排序详解
    1089 Insert or Merge (25分)
    1088 Rational Arithmetic (20分)(模拟)
    1086 Tree Traversals Again (25分)(树的重构与遍历)
  • 原文地址:https://www.cnblogs.com/liujiacai/p/15614579.html
Copyright © 2011-2022 走看看