Python - Dataclass
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 装饰器的传统类定义。