深入理解Python @dataclass的内部原理

 更新时间:2025年01月06日 09:53:16   作者:wang_yb  
文章介绍了Python中dataclass的实现原理,通过自定义装饰器实现了__init__和__repr__方法,并解释了__annotations__属性和exec函数在其中的作用,感兴趣的朋友跟随小编一起看看吧

之前写过一篇介绍Pythondataclass的文章:《掌握python的dataclass,让你的代码更简洁优雅》。

那篇侧重于介绍dataclass的使用,今天想探索一下这个有趣的特性是如何实现的。

表面上看,dataclass就是一个普通的装饰器,但是它又在class上实现了很多神奇的功能,

为我们在Python中定义和使用class带来了极大的便利。

如果你也好奇它在幕后是如何工作的,本篇我们就一同揭开Pythondataclass的神秘面纱,

深入探究一下其内部原理。

1. dataclass简介

dataclass为我们提供了一种简洁而高效的方式来定义类,特别是那些主要用于存储数据的类。

它能自动为我们生成一些常用的方法,如__init____repr__等,大大减少了样板代码的编写。

例如,我在量化中经常用的一个K线数据,用dataclass来定义的话,如下所示:

from dataclasses import dataclass
from datetime import datetime
@dataclass
class KLine:
    name: str = "BTC"
    open_price: float = 0.0
    close_price: float = 0.0
    high_price: float = 0.0
    low_price: float = 0.0
    begin_time: datetime = datetime.now()
if __name__ == "__main__":
    kl = KLine()
    print(kl)

这样,我们无需手动编写__init__方法来初始化对象,就可以轻松创建KLine类的实例,

并且直接打印对象也可以得到清晰,易于阅读的输出。

$  python.exe .\kline.py
KLine(name='BTC', open_price=0.0, close_price=0.0, 
high_price=0.0, low_price=0.0, 
begin_time=datetime.datetime(2025, 1, 2, 17, 45, 53, 44463))

但这背后究竟发生了什么呢?

2. 核心概念

dataclassPython3.7版本开始,已经加入到标准库中了。

代码就在Python安装目录中的Lib/dataclasses.py文件中。

实现这个装饰器功能的核心有两个:__annotations__属性和exec函数。

2.1. __annotations__属性

__annotations__是 Python 中一个隐藏的宝藏,它以字典的形式存储着变量、属性以及函数参数或返回值的类型提示。

对于dataclass来说,它就像是一张地图,装饰器通过它来找到用户定义的字段。

比如,在上面的KLine类中,__annotations__会返回字段的相关信息。

这使得dataclass装饰器能够清楚地知道类中包含哪些字段以及它们的类型,为后续的操作提供了关键信息。

if __name__ == "__main__":
    print(KLine.__annotations__)
# 运行结果:
{'name': <class 'str'>, 'open_price': <class 'float'>, 
'close_price': <class 'float'>, 'high_price': <class 'float'>, 
'low_price': <class 'float'>, 'begin_time': <class 'datetime.datetime'>}

2.2. exec 函数

exec函数堪称dataclass实现的魔法棒,它能够将字符串形式的代码转换为 Python 对象。

dataclass的世界里,它被用来创建各种必要的方法。

我们可以通过构建函数定义的字符串,然后使用exec将其转化为真正的函数,并添加到类中。

这就是dataclass装饰器能够自动生成__init____repr__等方法的秘密所在。

下面的代码通过exec,将一个字符串代码转换成一个真正可使用的函数。

# 定义一个存储代码的字符串
code_string = """
def greet(name):
    print(f"Hello, {name}!")
"""
# 使用 exec 函数执行代码字符串
exec(code_string)
# 调用通过 exec 生成的函数
greet("Alice")

3. 自定义dataclass装饰器

掌握了上面的核心概念,我们就可以开始尝试实现自己的dataclass装饰器。

当然,这里只是简单实现个雏形,目的是为了了解Python标准库中dataclass的原理。

下面主要实现两个功能__init____repr__

通过这两个功能来理解dataclass的实现原理。

3.1. 定义架构

我们首先定义一个dataclass装饰器,它的结构如下:

def dataclass(cls=None, init=True, repr=True):
    def wrap(cls):
        # 这里将对类进行修改
        return cls
    if cls is None:
        return wrap
    return wrap(cls)

接下来,我们在这个装饰器中实现__init____repr__

3.2. 初始化:init

init参数为True时,我们为类添加__init__方法。

通过_init_fn函数来实现,它会根据类的字段生成__init__方法的函数定义字符串,然后使用_create_fn函数将其转换为真正的方法并添加到类中。

def _create_fn(cls, name, fn):
    ns = {}
    exec(fn, None, ns)
    method = ns[name]
    setattr(cls, name, method)
def _init_fn(cls, fields):
    args = ", ".join(fields)
    lines = [f"self.{field} = {field}" for field in fields]
    body = "\n".join(f"  {line}" for line in lines)
    txt = f"def __init__(self, {args}):\n{body}"
    _create_fn(cls, "__init__", txt)

3.3. 美化输出:repr

__repr__方法让我们能够以一种清晰易读的方式打印出类的实例。

为了实现这个功能,我们创建_repr_fn函数,它生成__repr__方法的定义字符串。

这个方法会获取实例的__dict__属性中的所有变量,并使用 f-string 进行格式化输出。

def _repr_fn(cls, fields):
    txt = (
        "def __repr__(self):\n"
        "    fields = [f'{key}={val!r}' for key, val in self.__dict__.items()]\n"
        "    return f'{self.__class__.__name__}({\"\\n \".join(fields)})'"
    )
    _create_fn(cls, "__repr__", txt)

3.4. 合在一起

最终的代码如下,代码中使用的是自己的dataclass装饰器,而不是标准库中的dataclass

from datetime import datetime
def dataclass(cls=None, init=True, repr=True):
    def wrap(cls):
        fields = cls.__annotations__.keys()
        if init:
            _init_fn(cls, fields)
        if repr:
            _repr_fn(cls, fields)
        return cls
    if cls is None:  # 如果装饰器带参数
        return wrap
    return wrap(cls)
def _create_fn(cls, name, fn):
    ns = {}
    exec(fn, None, ns)
    method = ns[name]
    setattr(cls, name, method)
def _init_fn(cls, fields):
    args = ", ".join(fields)
    lines = [f"self.{field} = {field}" for field in fields]
    body = "\n".join(f"  {line}" for line in lines)
    txt = f"def __init__(self, {args}):\n{body}"
    _create_fn(cls, "__init__", txt)
def _repr_fn(cls, fields):
    txt = (
        "def __repr__(self):\n"
        "    fields = [f'{key}={val!r}' for key, val in self.__dict__.items()]\n"
        "    return f'{self.__class__.__name__}({\"\\n \".join(fields)})'"
    )
    _create_fn(cls, "__repr__", txt)
@dataclass
class KLine:
    name: str = "BTC"
    open_price: float = 0.0
    close_price: float = 0.0
    high_price: float = 0.0
    low_price: float = 0.0
    begin_time: datetime = datetime.now()
if __name__ == "__main__":
    kl = KLine(
        name="ETH",
        open_price=1000.5,
        close_price=3200.5,
        high_price=3400,
        low_price=200,
        begin_time=datetime.now(),
    )
    print(kl)

运行的效果如下:

可以看出,我们自己实现的dataclass装饰器也可以实现类的初始化和美化输出,这里输出时每个属性占一行。

4. 总结

通过自定义dataclass装饰器的构建过程,我们深入了解了 Python 中dataclass的内部原理。

利用__annotations__获取字段信息,借助exec创建各种方法,从而实现简洁高效的dataclass定义。

不过,实际的 Python标准库中的dataclass还有更多的功能和优化,了解了其原理之后,可以参考它的源码再进一步学习。

到此这篇关于探索Python @dataclass的内部原理的文章就介绍到这了,更多相关Python @dataclass原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • python 如何通过执行脚本安装库或卸载库

    python 如何通过执行脚本安装库或卸载库

    通过执行Python脚本,用户可以轻松地安装或卸载所需的库,本文介绍了该过程的具体步骤,包括如何编写用于安装或卸载库的脚本,无论是需要添加新功能还是清理环境,通过这种方式,开发者都能有效管理其工作环境中的库
    2024-11-11
  • python防止程序超时的实现示例

    python防止程序超时的实现示例

    因为某个需求,需要在程序运行的时候防止超时,本文主要介绍了python防止程序超时的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • OpenCV半小时掌握基本操作之边界填充

    OpenCV半小时掌握基本操作之边界填充

    这篇文章主要介绍了OpenCV基本操作之边界填充,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • Python迭代器的实现原理

    Python迭代器的实现原理

    这篇文章主要介绍了Python迭代器的实现原理,文章基于python的相关资料展开对Python迭代器的详细介绍,需要的小伙伴可以参考一下
    2022-05-05
  • python docx如何修改word表格内容

    python docx如何修改word表格内容

    使用Python-docx库,可以方便地修改Word文档中的表格内容,首先需要安装python-docx库,然后使用该库打开Word文档,遍历文档中的表格并修改指定单元格内容,最后另存为新文档
    2024-09-09
  • Python无权点文件转化成邻接矩阵方式

    Python无权点文件转化成邻接矩阵方式

    这篇文章主要介绍了Python无权点文件转化成邻接矩阵方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • python3.7实现云之讯、聚合短信平台的短信发送功能

    python3.7实现云之讯、聚合短信平台的短信发送功能

    这篇文章主要介绍了python3.7实现云之讯、聚合短信平台的短信发送功能,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • python多线程socket编程之多客户端接入

    python多线程socket编程之多客户端接入

    这篇文章主要为大家详细介绍了python多线程socket编程之多客户端接入,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-09-09
  • python 利用文件锁单例执行脚本的方法

    python 利用文件锁单例执行脚本的方法

    今天小编就为大家分享一篇python 利用文件锁单例执行脚本的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-02-02
  • 使用Python进行接口测试的代码演示

    使用Python进行接口测试的代码演示

    在软件开发过程中,接口测试是一个至关重要的环节,Python作为一种简洁、灵活且易于学习的编程语言,为接口测试提供了强大的支持,本文将介绍如何使用Python进行接口测试,并通过代码实例演示,需要的朋友可以参考下
    2025-05-05

最新评论