Python dataclasses 是 Python 3.7 引入的一个模块,它提供了一个装饰器 @dataclass
,可以自动为类生成一些特殊方法,例如 __init__
, __repr__
, __eq__
等。这使得创建只用于存储数据的类变得非常方便,大大减少了模板代码。
dataclass 介绍
在没有 dataclasses
之前,如果想创建一个简单的数据类,可能需要手动定义这些方法:
class Point:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
def __eq__(self, other):
if not isinstance(other, Point):
return False
return self.x == other.x and self.y == other.y
使用 dataclasses
后,同样的功能可以简化为:
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
可以看到,dataclasses
自动处理了初始化、表示和比较等常用方法,让代码更加简洁、可读性更强,也更不容易出错。
在 Python 3.11 之后,dataclasses
模块内置了一个名为 dataclass_transform
的装饰器,而对于早于 3.11 的 Python 版本,则需要从 typing_extensions
库中导入。
dataclass_transform
的作用是帮助静态类型检查工具(如 Mypy
, Pyright
等)更好地理解并推断出那些行为类似于 dataclass
的自定义装饰器或基类。
简单来说,当你在开发一个框架或库时,如果你的某个装饰器或基类能像 @dataclass
一样自动为类添加 __init__
, __repr__
等方法,那么 dataclass_transform
就可以告诉类型检查工具:嘿,这个装饰器产生的结果,你要把它当作
dataclass 一样来处理,这样我就能正确地进行类型检查了。
这解决了在自定义数据类转换器时,类型检查工具无法正确识别自动生成的方法和属性的问题。
dataclass 使用示例
下面是一些 dataclasses
的常用特性和使用示例。
1. 基本使用
只需要在类定义前加上 @dataclass
装饰器,并使用类型提示来定义属性。
from dataclasses import dataclass
@dataclass
class Book:
title: str
author: str
year: int
book1 = Book("The Hitchhiker's Guide to the Galaxy", "Douglas Adams", 1979)
book2 = Book("Dune", "Frank Herbert", 1965)
# 自动生成 __repr__
print(book1)
# 输出: Book(title="The Hitchhiker's Guide to the Galaxy", author='Douglas Adams', year=1979)
# 自动生成 __eq__
print(book1 == book2)
# 输出: False
2. 默认值
可以像普通类一样为属性设置默认值。
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int = 18 # 设置默认值
is_active: bool = True
user1 = User("Alice")
user2 = User("Bob", 30)
print(user1)
# 输出: User(name='Alice', age=18, is_active=True)
print(user2)
# 输出: User(name='Bob', age=30, is_active=True)
注意: 如果一个属性有默认值,所有它后面的属性都必须有默认值。
3. 不包含在 __init__
中的字段 (field
)
有时候可能希望某个字段不出现在 __init__
方法中,例如一个只读的计算属性。可以使用 dataclasses.field
函数来配置字段。
from dataclasses import dataclass, field
@dataclass
class Circle:
radius: float
# 这个字段不出现在 __init__ 中,且是只读的
area: float = field(init=False)
def __post_init__(self):
# __post_init__ 方法在 __init__ 之后被调用,常用于初始化那些不包含在 __init__ 中的字段
self.area = 3.14159 * self.radius ** 2
c = Circle(5.0)
print(c)
# 输出: Circle(radius=5.0, area=78.53975)
field
函数的其他常用参数:
repr=False
: 不将该字段包含在 __repr__
的输出中。
compare=False
: 不将该字段用于比较(__eq__
)。
kw_only=True
: 强制该字段只能通过关键字参数传递。
default_factory
: 用于为可变类型(如列表、字典)提供默认值,以避免共享同一个默认对象。
4. 不可变的数据类 (frozen
)
如果想创建一个不可变(Immutable)的数据类,可以在装饰器中设置 frozen=True
。这意味着一旦对象被创建,它的属性就不能再被修改。尝试修改会引发 FrozenInstanceError
。
from dataclasses import datacass
@dataclass(frozen=True)
class Coordinate:
x: float
y: float
c = Coordinate(10.0, 20.0)
try:
c.x = 15.0 # 这行代码会报错
except Exception as e:
print(e)
# 输出: cannot assign to field 'x'
5. 继承
dataclasses
支持继承。子类会继承父类的所有字段,并在其后添加自己的字段。
from dataclasses import dataclass
@dataclass
class Employee:
name: str
employee_id: int
@dataclass
class Manager(Employee):
department: str
team_size: int
m = Manager("Charlie", 101, "Engineering", 5)
print(m)
# 输出: Manager(name='Charlie', employee_id=101, department='Engineering', team_size=5)
总结
总而言之,dataclasses 是一个功能强大且易于使用的工具,特别适合用于创建那些主要用于存储和传递数据的轻量级对象。
假设你正在编写一个框架,并想创建一个自己的装饰器 @my_record
,它的功能和 @dataclass
类似,用于创建简单的、带有 __init__
和 __repr__
的数据记录类。
在没有 dataclass_transform
的情况下,当你使用 Mypy
进行类型检查时,它会认为 __init__
方法不存在,从而报错。
没有 dataclass_transform
的情况:
# 假设这是你的自定义装饰器
def my_record(cls):
# 模拟自动添加 __init__ 和 __repr__ 的逻辑
def __init__(self, x, y):
self.x = x
self.y = y
cls.__init__ = __init__
def __repr__(self):
return f"Record(x={self.x}, y={self.y})"
cls.__repr__ = __repr__
return cls
@my_record
class Point:
x: int
y: int
p = Point(1, 2) # Mypy 可能会报错:Type 'Point' has no '__init__'
print(p.x) # Mypy 可能会报错:'Point' has no attribute 'x'
使用 dataclass_transform
的情况:
为了解决上述问题,我们引入 dataclass_transform
,它是一个类型注释装饰器,专门用于告诉类型检查器如何处理被装饰的类。
from typing_extensions import dataclass_transform
@dataclass_transform()
def my_record(cls):
# 实际的运行时逻辑,可以和上面一样
def __init__(self, x, y):
self.x = x
self.y = y
cls.__init__ = __init__
def __repr__(self):
return f"Record(x={self.x}, y={self.y})"
cls.__repr__ = __repr__
return cls
@my_record
class Point:
x: int
y: int
p = Point(1, 2) # 现在 Mypy 知道 `Point` 拥有一个 `__init__` 方法,因此不会报错。
print(p.x) # Mypy 也能正确识别 `p` 有 `x` 属性。
通过 @dataclass_transform()
,我们有效地向类型检查器传达了 my_record
的意图,使其能够正确地对 Point
类进行类型推断。
dataclass_transform
还有几个可选参数,可以更精确地控制类型检查器的行为:
kw_only_default
: 如果设置为 True
,则所有由装饰器生成的 __init__
方法的参数都将被视为只接受关键字参数。
field_specifiers
: 指定哪些函数被视为字段(类似 dataclasses.field
)。
这些参数对于开发复杂的、定制化的数据类系统非常有用,可以确保你的自定义转换器与 Python 的类型系统无缝集成。