卖兜搞IT

2024彻底搞懂Python的Dataclass(1)

本文较长,会分为几期,目标是给大家讲解Python的dataclass该怎么用,以及它是如何工作的,而不只是和大家一起创建一个dataclass

本文基于Python的版本是3.11,不同的版本的python,dataclass可能会有不同的差异,这是因为dataclass是一个正在不断演进的python特性,变化较大。

Dataclass是什么?

一句话,dataclass是一个代码生成器code generator, 一个用来生成python class的代码生成器。如果你用过Python的collection模块namedtuple,就会比较容易理解代码生成器的概念。

class Student:

    def __init__(self, name, age):
        self.name = name
        self.age = age

s1 = Student('Jack', 30)
s2 = Student('Tim', 20)

print(s1.name)
print(s2.age)

# namedtuple可以简化一个class的创建

from collections import namedtuple

Student = namedtuple('Student',['name', 'age'])

s3 = Student('Paul', 55)
s4 = Student('Nico', 40)

print(s3.name)
print(s4.age)

Dataclass基础

如名字dataclasses所示,dataclasses 是用于数据结构的类(在某些方面类似于namedtuple,但dataclasses 提供了更多的可能,因为它们归根结底还是常规的 Python 类,而不是元组)

让我们看看如何使用 dataclasses 生成标准的 Python 类,看看它可以帮我们简化多少工作量。

作为一个例子,我们先创建一个标准的position类,这个类有两个实例属性,x和y,都是float类型。

class Position:
    def __init__(self, x: float = 0, y: float = 0,):
        self.x = x
        self.y = y

创建一个实例

>>> p = Position()
>>> p
<__main__.Position object at 0x10459ae40>
>>> print(p)
<__main__.Position object at 0x10459ae40>
>>>

那为了让它打印出来的内容更好看一些,我们一般可以给这个Class添加__str__和__repr__方法

class Position:
    def __init__(self, x: float = 0, y: float = 0,):
        self.x = x
        self.y = y
    def __str__(self):
        return f"{self.__class__.__qualname__}(x={self.x}, y={self.y})"
    def __repr__(self):
        return f"{self.__class__.__qualname__}(x={self.x}, y={self.y})"
>>> p = Position()
>>> p
Position(x=0, y=0)
>>> print(p)
Position(x=0, y=0)

拿上面这个Python类,如果用dataclass实现,就会简单很多,我们看一下。

from dataclasses import dataclass

@dataclass
class Position:
    x: float = 0
    y: float = 0

>>> p = Position()
>>> p
Position(x=0, y=0)
>>> print(p)
Position(x=0, y=0)

就这么简单几行代码,实现了前面的一个传统的Python类。

那为什么说dataclass是一个代码生成器code generator呢?因为它实际上是通过一个装饰器 @dataclass 对我们原始定义的类进行了一个修改,然后返回了这个修改后的同一个类,有点拗口,我们证明一下

首先,我们定一个没有装饰器的传统python类

class Position:
    x: float = 0
    y: float = 0

通过id可以查看这个类在内存中的位置

>>> id(Position)
409177792

然后我们把这个Position类作为参数传递给dataclass, 返回一个新类 NewPosition ,然后查看它内存的位置

>>> NewPosition = dataclass(Position)
>>> id(NewPosition)
409177792

在内存中的位置完全一样,这说明dataclass装饰器并没有创建一个新类,而是在原先类的基础上进行修改。