Python dataclasses 介绍

发布时间: 更新时间: 总字数:1822 阅读时间:4m 作者: IP上海 分享 网址

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 自动处理了初始化、表示和比较等常用方法,让代码更加简洁、可读性更强,也更不容易出错。

dataclass_transform 介绍

在 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 是一个功能强大且易于使用的工具,特别适合用于创建那些主要用于存储和传递数据的轻量级对象。

dataclass_transform 使用示例

假设你正在编写一个框架,并想创建一个自己的装饰器 @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 的类型系统无缝集成。

本文总阅读量 次 本站总访问量 次 本站总访客数
Home Archives Categories Tags Statistics