• Python中的数据类dataclass详解


    1.为什么需要数据类

    1.1 ☹️内置数据类型的局限

    假设我们现在遇到一个场景, 需要一个数据对象来保存一些运动员信息.

    可以选择使用基本的数据类型tuple或者dict实现. 如:创建一个球员jordan, 信息包括球员姓名, 号码, 位置, 年龄.

    使用tuple

    In [1]: jordan = ('Micheal Jordan', 23, 'PG', 29)
    In [2]: jordan[0]
    Out[2]: 'Micheal Jordan'
    

    劣势: 创建和取值基于位置, 需要记住坐标对应的信息.

    使用dict

    In [3]: jordan = {'name': 'Micheal Jordan', 'number': 23, 'position': 'PG', 'age': 29}
    In [4]: jordan['position']
    Out[4]: 'PG'
    

    使用字典之后, 获取信息时会相对直观, 但是相较于字典的括号语法 jordan["position"] 我们更希望可以用类似获取属性一样使用 jordan.postion.

    劣势: 无法对数据属性名进行控制,

    少值或者错值,如, jordan = {'name': 'Micheal Jordan', 'NUMBER': 23} 一样可以创建成功.

    1.2 使用命名元组 namedtuple

    为了解决这种问题, python 中的 collections 模块提供一个命名元组, 可以使用点表示法和字段名称访问给定命名元组中的值. 使用namedtuple代码如下:

    In [5]: from collections import namedtuple
     
    In [6]: Player = namedtuple('Player', ['name', 'number', 'position', 'age', 'grade'])
    In [7]: jordan = Player('Micheal Jordan', 23, 'PG', 29, 'S+')
     
    In [8]: jordan
    Out[8]: Player(name='Micheal Jordan', number=23, position='PG', age=29, grade='S+')
    

    使用namedtuple之后

    ① 可以使用 '.'语法获取数据的属性, 可以限制数据的属性名称,

    ② 创建对象时数据不匹配会报错.

    In [9]: jordan.number
    Out[9]: 23
     
    In [10]: bryant = Player('Kobe Bryant', 24, 'PG')
    ---------------------------------------------------------------------------
    TypeError: __new__() missing 2 required positional arguments: 'age' and 'grade'
    

    1.2.1 namedtuple的不足

    对于一些字段比较少的数据结构, namedtuple是一个非常好的解决方案. 但面对一些复杂的数据的时候, 需要更多的功能时, namedtuple就无法满足了.

    In [11]: bryant = Player('Kobe Bryant', 24, 'PG', 22, 'S')
    In [12]: bryant.age=23
    ---------------------------------------------------------------------------
    AttributeError: can't set attribute
    

    劣势:① 数据无法修改

    ​ ② 无法自定义数据比较, 没有默认值, 没有函数支持.

    1.3 自定义类 Class

    为了支持数据修改, 默认值, 比较等功能. 更加好一些的方法是, 使用自定义类来实现数据类. 一个最简单的数据类代码如下:

    In [13]: class Player:
        ...:     def __init__(self, name, number, position, age, grade):
        ...:         self.name = name
        ...:         self.number = number
        ...:         self.position = position
        ...:         self.age = age
        ...:         self.grade = grade
     
    In [14]: bryant = Player(name='Kobe Bryant', number=24, position='PG', age=22, grade='S')
    In [15]: jordan = Player('Micheal Jordan', 23, 'PG', 29, 'S+')
    

    可以使用位置参数或者键值参数创建对象

    In [16]: bryant.position='SF'
    In [17]: bryant.position
    Out[17]: 'SF'
    

    可以看到, 数据类可以支持对属性的修改,

    In [18]: bryant
    Out[18]: <__main__.Player at 0x29446d401c8>
    

    问题①:目前的实现 对于对象的描述不太友好,

     
    In [19]: jordan > bryant
    ---------------------------------------------------------------------------
    TypeError: '>' not supported between instances of 'Player' and 'Player'
    

    问题②:数据还不支持比较.

    为了解决上面两个问题,可以通过实现 repr 方法来自定义描述, 实现 gt 方法来支持比较的功能. 更新代码如下:

    In [20]: class Player:
        ...:     def __init__(self, name, number, position, age, grade):
        ...:         self.name = name
        ...:         self.number = number
        ...:         self.position = position
        ...:         self.age = age
        ...:         self.grade = grade
        ...:     def __repr__(self):
        ...:         return f'Player: \n {self.name}\t #{self.number}\t @{self.position}\t <{self.grade}>'
        ...:     def __eq__(self, other):
        ...:         return self.age == other.age
        ...:     def __gt__(self, other):
        ...:         return self.age > other.age
        ...:     def swing(self, pos):
        ...:         self.position = pos
     
    In [21]: jordan = Player('Micheal Jordan', 23, 'PG', 29, 'S+')
    In [22]: bryant = Player('Kobe Bryant', 24, 'PG', 22, 'S')
     
    In [23]: jordan
    Out[23]:
    Player:
     Micheal Jordan  #23     @PG     <S+>
     
    In [24]: jordan > bryant
    Out[24]: True
     
    In [25]: jordan.swing('SF')
    In [26]: jordan
    Out[26]:
    Player:
     Micheal Jordan  #23     @SF     <S+>
    

    可以看到数据对象有了更直观的描述, 支持了对比 (若要支持 >= 的对比, 还需要自定义 __ge__方法). 还可以自定义方法swing来改变球员打的位置.

    劣势:① __init__方法中重复代码 (示例中每个属性都需要写3遍)

        ② 需要自己实现__repr__方法, 和比较方法__eq__, __gt__等
    

    1.4 数据类 dataclass

    主角出场了, 数据类是Python3.7 开始引入的一个新功能, 数据类提供了开箱即用的方法来创建自定义数据, 可以直接实例化、打印和比较数据类实例.

    In [1]: from dataclasses import dataclass
     
    In [2]: @dataclass
       ...: class Player:
       ...:     name: str
       ...:     number: int
       ...:     position: str
       ...:     age: int
       ...:     grade: str
     
    In [3]: james = Player('Lebron James', 23, 'SF', 25, 'S')
    In [4]: james
    Out[4]: Player(name='Lebron James', number=23, position='SF', age=25, grade='S')
    

    2. dataclass 的使用

    2.1 类型提示和默认值

    dataclass 可以认为是提供了一个简写__init__方法的语法糖. 类型注释是必填项 (不限制数据类型时, 添加typing.Any为类型注释), 默认值的传递方式和__init__方法的参数格式一致.

    In [1]: from dataclasses import dataclass
    In [2]: from typing import Any
     
    In [3]: @dataclass
       ...: class Data:
       ...:     name: Any
       ...:     value: Any = 42
    

    2.2 数据嵌套

    数据类可以嵌套为其他数据类的字段, 可以简单创建一个有2个队员的球队.lal包含两名球员 james和davis

    In [1]: from dataclasses import dataclass
    In [2]: from typing import List
     
    In [3]: @dataclass
       ...: class Player:
       ...:     name: str
       ...:     number: int
       ...:     position: str
       ...:     age: int
       ...:     grade: str
     
    In [4]: @dataclass
       ...: class Team:
       ...:     name: str
       ...:     players: List[Player]
     
    In [5]: james = Player('Lebron James', 23, 'SF', 25, 'S')
    In [6]: davis = Player('Anthony Davis', 3, 'PF', 21, 'S-')
     
    In [7]: lal = Team('Los Angeles Lakers', [james, davis])
    In [8]: lal
    Out[8]: Team(name='Los Angeles Lakers', players=[Player(name='Lebron James', number=23, position='SF', age=25, grade='S'), Player(name='Anthony Davis', number=3, position='PF', age=21, grade='S-')])
    

    2.3 dataclasses中的field

    当我们尝试使用可变的数据类型, 给数据类中做默认值时, 触发了python中的大坑之一 使用可变默认参数, 导致多个实例公用一个数据从而引发bug. dataclass 默认阻止使用可变数据做默认值

    In [9]: @dataclass
       ...: class Team:
       ...:     name: str
       ...:     players: List[Player] = [james]
    ---------------------------------------------------------------------------
    ValueError: mutable default <class 'list'> for field players is not allowed: use default_factory
    

    就像错误提示中的, 处理此种场景时, 需要使用 field 中的 default_factory .

    In [10]: from dataclasses import field
     
    In [11]: @dataclass
        ...: class Team:
        ...:     name: str
        ...:     players: List[Player] = field(default_factory=lambda :[james])
     
    In [12]: nyk = Team('New York Knicks')
    In [13]: nyk
    Out[13]: Team(name='New York Knicks', players=[Player(name='Lebron James', number=23, position='SF', age=25, grade='S')])
    

    image-20220922172843786

    2.4 不可变数据类

    要使数据类不可变,需要在创建类时设置frozen=True。

    In [1]: from dataclasses import dataclass
    In [2]: from typing import Any
     
    In [3]: @dataclass(frozen=True)
       ...: class Data:
       ...:     name: Any
       ...:     value: Any = 42
     
    In [4]: data = Data('myname', 99)
    In [4]: data.name = 'other'
    ---------------------------------------------------------------------------
    FrozenInstanceError: cannot assign to field 'name'
     
    

    总结:

    dataclass 提供一个简便的方式创建数据类, 默认实现__init__(), repr(), eq()方法.

    dataclass支持数据类型的嵌套

    支持将数据设置为不可变

    ————————————————
    版权声明:本文为CSDN博主「be5yond」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/be5yond/article/details/119545119

  • 相关阅读:
    面试收集—hello,world 也有新花样
    div+css优缺点
    css固定textarea文本域尺寸
    Python if __name__ == "__main__":
    ActionScript3.0基础教程
    【转】Flex代码生成器 (FCG)
    手机第一博文
    TabNavigator只初始化第一个TAB 引发的未初始化对象错误
    如何对待懒惰的小孩
    对孩子真诚就是尊重孩子,不要随意表扬
  • 原文地址:https://www.cnblogs.com/hanfe1/p/16720178.html
Copyright © 2020-2023  润新知