跳转至

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 属性后:

  1. 一旦实例化,所有字段都不能被修改。
  2. 由于是不可变的,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}