Python 语言进阶 —— 数据类dataclass
介绍
Python 3.7 版本开始引入 dataclass
装饰器类。dataclass
是一个简化类定义的工具,特别适用于那些主要用于存储数据的类。
通过使用 dataclass
,可以自动生成一些常用的方法,比如 __init__
、__repr__
、__eq__
等,从而减少样板代码。
以学生类为例,在不使用 dataclass
类时,通常会这样定义:
class Student:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def __str__(self):
return f'Name = {self.name} and age = {self.age}'
if __name__ == "__main__":
stu = Student('Zhang San', 23)
print(stu)
执行上述代码将会输出:
<__main__.Student object at 0x0000034AC19FACA0>
直接输出的是类所在的地址信息,很多时候这并不是我们想要的,我们想要的是当调用 print
方法时,可以自动输出每个属性的值。要是按照传统定义类的方式,此时
需要在类中添加 __str__
方法。
class Student:
...
def __str__(self):
return f'Name = {self.name} and age = {self.age}'
这样当执行 print
方法时,才会显示类中属性的信息。
Name = Zhang San and age = 23
dataclass
就是为了解决上述定义类的繁琐过程。同样地使用 dataclass
的方式定义一个 Student 类:
@dataclass
class Student:
name: str
age: int
if __name__ == "__main__":
stu = Student('Zhang San', 23)
print(stu)
再次运行,可以得到相同的效果:
Name = Zhang San and age = 23
使用了 @dataclass
修饰的类,不需要编写 __init__
,也不需要 __str__
,通过 print
方法就可以打印对象的内容。
基础使用
默认值
在 Python 的 dataclass
中,可以通过多种方式设置默认值。以下是几种常见的方法:
使用简单的默认值
直接在类定义中指定默认值。
from dataclasses import dataclass
@dataclass
class Example:
name: str = "Unnamed" # 默认值为 "Unnamed"
age: int = 0 # 默认值为 0
使用 field
函数
如果需要更复杂的默认值或其他参数(如 init=False
),可以使用 field()
。
from dataclasses import dataclass, field
@dataclass
class Example:
name: str = "Unnamed"
values: list[int] = field(default_factory=list) # 使用 default_factory 创建一个空列表
使用 default_factory
当默认值是可变类型(如列表、字典等)时,使用 default_factory
可以避免多个实例共享同一对象。
from dataclasses import dataclass, field
from typing import List
@dataclass
class Example:
numbers: List[int] = field(default_factory=list) # 每个实例都有一个独立的空列表
使用自定义函数
你可以定义一个函数来生成默认值,并将其传递给 default_factory
。
def create_default_dict() -> dict:
return {"key": "value"}
@dataclass
class Example:
config: dict = field(default_factory=create_default_dict) # 使用函数生成默认字典
使用 lambda
表达式
对于简单的默认值,可以使用 lambda
表达式。
@dataclass
class Example:
items: List[int] = field(default_factory=lambda: [1, 2, 3]) # 默认值为 [1, 2, 3]
结合其他类的实例
可以将其他类的实例作为默认值。
class InnerClass:
def __init__(self):
self.value = 42
@dataclass
class Example:
inner: InnerClass = field(default_factory=InnerClass) # 默认创建一个 InnerClass 的实例
隐藏信息
在 Python 的 dataclass
中,如果希望某些字段在输出时被隐藏或不被包含在自动生成的方法中(例如 __repr__
),可以使用 field()
函数结合参数 repr=False
。这使得在打印对象或调用 repr()
时,这些字段不会显示。
@dataclass
class UserProfile:
username: str
email: str
password: str = field(repr=False) # 隐藏密码
api_key: str = field(repr=False) # 隐藏 API 密钥
# 示例
user = UserProfile(username="user123", email="user@example.com", password="securepass", api_key="apikey123")
print(user) # 输出: UserProfile(username='user123', email='user@example.com')
或者也可以自定义 __repr__
方法:
@dataclass
class Product:
name: str
price: float
secret_code: str = field(repr=False)
def __repr__(self):
return f"Product(name={self.name}, price={self.price})"
# 示例
product = Product(name="Gadget", price=99.99, secret_code="XYZ123")
print(product) # 输出: Product(name=Gadget, price=99.99)
初始化
如果希望某个字段在初始化时存在,不包含在 init 方法中,可以使用 init=False
。
@dataclass
class UserProfile:
username: str
email: str = field(init=False)
只读对象
在 Python 的 dataclass
中,使用 frozen=True
可以创建不可变(immutable)的数据类。这意味着一旦实例被创建,就不能修改其字段。frozen
数据类通常用于需要保护数据不被修改的场景,比如在多线程环境中或者作为配置对象。
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
# 创建一个点实例
p1 = Point(10, 20)
print(p1) # 输出: Point(x=10, y=20)
# 尝试修改字段会导致错误
# p1.x = 15 # 这一行会引发错误: FrozenInstanceError
Warning
使用了 frozen=True
属性后:
- 一旦实例化,所有字段都不能被修改。
- 由于是不可变的,frozen 数据类可以用作字典的键或放入集合中。
转换成元组和字典
在 Python 中,可以轻松地将 dataclass
实例转换为元组和字典。以下是如何实现这些转换的示例:
转换为元组
可以使用 dataclass
的内置方法 astuple
,或者利用 tuple()
函数结合 __dict__
属性来实现。
from dataclasses import dataclass, asdict, astuple
@dataclass
class Point:
x: int
y: int
# 创建一个点实例
p = Point(10, 20)
# 转换为元组
point_tuple = astuple(p)
print(point_tuple) # 输出: (10, 20)
转换为字典
可以使用 asdict
方法,将 dataclass
实例转换为字典。
from dataclasses import dataclass, asdict
@dataclass
class Point:
x: int
y: int
# 创建一个点实例
p = Point(10, 20)
# 转换为字典
point_dict = asdict(p)
print(point_dict) # 输出: {'x': 10, 'y': 20}