Python Data Classes 怎么用?数据类定义和应用详解?

文章导读
Previous Quiz Next dataclass 是 Python 中的一项功能,可帮助为用户定义的 class 自动添加特殊方法。例如,dataclass 可以用于自动为你的 class 生成 constructor 方法,从而让你轻松创建 class 的实例
📋 目录
  1. 什么是 Dataclass?
  2. 创建 Dataclass
  3. 不使用 Dataclass 的示例
  4. Dataclass 中的默认值
  5. 具有可变默认值的 Dataclass
  6. 不可变/冻结 Dataclass
  7. 设置后初始化
  8. 将 Dataclass 转换为字典
  9. 结论
A A

Python - Dataclass



Previous
Quiz
Next

dataclass 是 Python 中的一项功能,可帮助为用户定义的 class 自动添加特殊方法。例如,dataclass 可以用于自动为你的 class 生成 constructor 方法,从而让你轻松创建 class 的实例。本章中,我们将通过示例解释 dataclass 模块的所有功能。

什么是 Dataclass?

dataclass 是使用 dataclasses 模块中的 @dataclass 装饰器标记的 Python class。它用于根据 class body 中定义的 class 属性自动生成特殊方法,如 constructor 方法 __init__()、字符串表示方法 __repr__()、相等性方法 __eq__() 等。这简单意味着使用 dataclasses 可以减少 class 定义中的样板代码。

Dataclass 的语法

定义 dataclass 的语法如下 −

@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

每个参数接受一个布尔值,表示是否自动生成相应的特殊方法。

@dataclass 装饰器可以接受以下选项:

  • init (默认: True) − 自动创建一个 __init__() 方法,用于初始化 class 实例。
  • repr (默认: True) − 自动创建一个 __repr__() 方法,为对象提供可读的字符串表示。
  • eq (默认: True) − 生成一个 __eq__() 方法,用于使用 == 运算符比较对象。
  • order (默认: False) − 如果设置为 True,则会生成比较方法(如 < 和 >),用于对对象进行排序。
  • unsafe_hash (默认: False) − 如果为 False,则会根据相等性和可变性定义生成默认的 __hash__() 方法。
  • frozen (默认: False) − 创建不可变实例(创建后无法更改)。

创建 Dataclass

在下面的代码中,我们定义了一个名为 Student 的简单 dataclass,包含三个属性:name, age,percent。即使我们没有使用 constructor 或其他特殊方法,我们仍然可以创建 Student class 的实例并使用其属性。这是因为 dataclass 装饰器会自动为我们生成这些方法。

from dataclasses import dataclass  

@dataclass  
class Student:  
    name: str  
    age: int  
    percent: float

s1 = Student("Alice", 20, 90.0)
s2 = Student("Bob", 22, 85.5)

print(s1)         
print(s1 == s2)   

上述代码的输出将为 −

Student(name='Alice', age=20, percent=90.0)
False

不使用 Dataclass 的示例

上述代码等价于不使用 dataclass 的传统 class 定义中的以下代码 −

class Student:  
    def __init__(self, name: str, age: int, percent: float):  
        self.name = name  
        self.age = age  
        self.percent = percent  
    def __repr__(self):  
        return f"Student(name={self.name}, age={self.age}, percent={self.percent})"  
    def __eq__(self, other):  
        if not isinstance(other, Student):  
            return NotImplemented  
        return (self.name == other.name and self.age == other.age and self.percent == other.percent)
s1 = Student("Alice", 20, 90.0)
s2 = Student("Bob", 22, 85.5)
print(s1)
print(s1 == s2)

此代码的输出与上述相同 −

Student(name='Alice', age=20, percent=90.0)
False

Dataclass 中的默认值

你也可以为 dataclass 中的属性提供默认值。如果在创建实例时没有提供值,则将使用默认值。在下面的代码中,我们为 percent 属性提供了 0.0 的默认值。

from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int
    percent: float = 0.0  # percent 的默认值

s1 = Student("Alice", 20)
s2 = Student("Bob", 22, 85.5)
print(s1)
print(s2)

上述代码的输出将是 −

Student(name='Alice', age=20, percent=0.0)
Student(name='Bob', age=22, percent=85.5)

具有可变默认值的 Dataclass

可变默认值指的是在实例创建后可以被修改的默认值,例如列表或字典。在 dataclass 中使用可变默认值时,推荐使用 dataclasses 模块中的 field() 函数,并使用 default_factory 参数。因为如果你直接使用可变对象作为默认值,它将在 dataclass 的所有实例之间共享。这可能导致安全问题和意外行为。

在下面的代码中,我们定义了一个名为 Course 的 dataclass,为 students 属性提供了可变默认值。

from dataclasses import dataclass, field
from typing import List
@dataclass
class Course:
    name: str
    students: List[str] = field(default_factory=list)  # 可变默认值

course1 = Course("Math")
course2 = Course("Science", ["Alice", "Bob"])
course1.students.append("Charlie")
print(course1)
print(course2)

上述代码的输出将是 −

Course(name='Math', students=['Charlie'])
Course(name='Science', students=['Alice', 'Bob'])

解释:如果你直接使用 students: List[str] = [],所有 Course 实例将获得同一个列表,因为默认值只在类创建时求值一次。通过使用 field(default_factory=list),Python 确保每个 Course 实例获得自己的独立列表,从而避免安全漏洞。

不可变/冻结 Dataclass

不可变或冻结 dataclass 表示 dataclass 的实例在创建后不能被修改。这可以通过在 @dataclass 装饰器中将 frozen 参数设置为 True 来实现。当 dataclass 被冻结时,任何尝试修改其属性的行为都会引发 FrozenInstanceError

冻结 dataclass 常用于通过防止未经授权的访问或修改敏感数据来保护应用程序。在下面的代码中,我们定义了一个冻结 dataclass,名为 Point,具有 x 和 y 两个属性。我们将在尝试修改 x 属性时捕获 FrozenInstanceError。

from dataclasses import dataclass, FrozenInstanceError
@dataclass(frozen=True)
class Point:
    x: int
    y: int
p = Point(1, 2)
print(p)

try:
    p.x = 10  # 这将引发错误
except FrozenInstanceError as e:
    print(e)

上述代码的输出将是 −

Point(x=1, y=2)
cannot assign to field 'x'

设置后初始化

post-Initialization 指的是在自动生成的 __init__() 方法调用后,可以添加到 dataclass 的额外初始化逻辑。这可以通过在 dataclass 中定义一个名为 __post_init__() 的特殊方法来实现。__post_init__() 方法会在实例创建并所有属性初始化完成后自动调用。

在下面的代码中,我们定义了一个名为 Rectangle 的 dataclass,为 area 设置默认值为 0.0。然后在 __post_init__() 方法中根据接收到的 width 和 height 值计算 area。

from dataclasses import dataclass
@dataclass
class Rectangle:
    width: float
    height: float
    area: float = 0.0  # 这将在 __post_init__ 中计算

    def __post_init__(self):
        self.area = self.width * self.height  # 初始化后计算面积
r = Rectangle(5.0, 10.0)
print(r)
print(f"Area of the rectangle: {r.area}")

上述代码的输出将是 −

Rectangle(width=5.0, height=10.0, area=50.0)
Area of the rectangle: 50.0

将 Dataclass 转换为字典

你可以使用 dataclasses 模块中的 asdict() 函数将 dataclass 实例转换为字典。该函数会递归地将 dataclass 及其字段转换为字典。这在你想要序列化 dataclass 实例或以字典格式处理其数据时非常有用。

from dataclasses import dataclass, asdict
from typing import List 

@dataclass
class Student:
    name: str
    age: int
    grades: List[float]  

student = Student("Alice", 20, [88.5, 92.0, 79.5])
student_dict = asdict(student)
print(student_dict)

上述代码的输出将是 −

{'name': 'Alice', 'age': 20, 'grades': [88.5, 92.0, 79.5]}

结论

在本章中,我们了解到 dataclass 是 Python 中引入的一项功能,用于减少类定义中的样板代码。我们已经了解了如何创建 dataclass、提供默认值、处理可变默认值等。需要注意的是,dataclasses 在 Python 3.7 及更高版本中可用。如果你使用的是较早版本的 Python,则需要使用没有 dataclass 装饰器的传统类定义。