68、Python之函数高级:一文搞懂dataclass,快速定义数据类

引言

Python中有不少实用的内置装饰器实现,前面已经介绍了缓存、函数重载等方面的应用,相信能在实际工作中,帮助我们大大地简化一些样板化代码的编写,从而提高开发的效率,以及代码的可维护性。

@dataclass也是一个比较实用的内置装饰器,由于它的使用细节比较多,所以,今天这篇文章重点来聊聊@dataclass的使用。

本文的主要内容有:

1、dataclass的定义及基本使用

2、field的使用

3、ClassVar的使用

dataclass的定义及基本使用

dataclass的特性是在Python3.7之后开始引入的,如果自己的Python环境是相对较老的版本需要注意一下。

在开始使用dataclass之前,我们先来看下dataclass的定义:

dab59ae36697b3a75ba433ce28eddd83.jpeg

从定义中,我们应该有如下认知:

1、dataclass是一个函数装饰器,这个函数装饰器用于对类进行装饰增强。

2、这个装饰器的实现是基于类中定义的字段,自动生成相应的方法。

3、dataclass()函数中定义了一组默认的参数,这些参数用于控制第2点中所提及的自动生成的方法,比如__init__()、__repr__()等。

4、由于函数中的参数都带有默认值,所以,我们可以使用无参的装饰器,实现大部分场景中的装饰、动态扩充的需求。也可以,通过通过关键字参数的形式传入需要的参数,来更加灵活地使用该函数装饰器。

5、装饰器的核心实现机制在_process_class()函数中,感兴趣的可以自行查看其具体实现过程。

接下来,我们先来用一下无参的dataclass装饰器,直接看代码:

from dataclasses import dataclass


@dataclass
class Point:
    x: int
    y: int


if __name__ == '__main__':
    p1 = Point(0, 0)
    p2 = Point(10, 20)
    p3 = Point(0, 0)
    print(p1)
    print(p2)
    print(p3)
    print(p1 == p2)
    print(p1 == p3)
    print(p1 > p2)

执行结果:

513adcefcfb42b0146762d8a4e9e8bfb.jpeg

结合上面dataclass定义中,默认值为True的参数,以及当前的执行结果,可以得知:

1、参数init默认为True,则@dataclass装饰的类,会基于我们在类中定义的字段,自动生成实例对象的初始化方法:__init__()。

2、参数repr默认值为True,表示@dataclass装饰的类,会基于我们在类中定义的字段,自动生成实例对象的__repr__()方法。

3、参数eq默认值为True,表示@dataclass装饰的类,会基于我们在类中定义的字段,自动生成__eq__()方法,从而该类支持相等的判断。但是,仅限于=或者!=的操作,当我们尝试>、<等比较时,则会报错提示该类不支持这种操作。

4、参数match_args是在Python3.10中添加的新的参数,默认值为True,主要用于配合match...case...模式化匹配的新特性的使用。当该参数为True时,会自动生成__match_args__属性,该属性是一个包含字段名的元组,这样可以在模式匹配中进行数据类的自动化解包。

前3点是比较简单的,我们来举例说明一下match_args的作用:

from dataclasses import dataclass


@dataclass
class Point:
    x: int
    y: int


if __name__ == '__main__':
    p1 = Point(0, 0)
    print(p1.__match_args__)
    p2 = Point(10, 20)
    match p2:
        case Point(0, 0):
            print('该点是原点')
        case Point(x, y):
            print(f'该点({x},{y})不是原点')

执行结果:

59ca917b3f1b79a292cfd7102c719a8d.jpeg

可以看到,在match...case...模式匹配中,自动将实例对象的字段解包到对应的x和y变量中了。

回到上面提到的第3点,@dataclass默认不支持>、<等的比较运算的,因为order参数默认为False,我们修改该参数,可以让数据类自动支持这些比较运算。

from dataclasses import dataclass


@dataclass(order=True)
class Point:
    x: int
    y: int


if __name__ == '__main__':
    p1 = Point(0, 0)
    p2 = Point(10, 20)
    p3 = Point(15, 0)
    p4 = Point(9, 100)
    print(p1 < p2)
    print(p3 <= p4)
    ps = [p1, p2, p3, p4]
    print(ps)
    ps.sort()
    print(ps)

执行结果:

5d013c7eafdf95ffb1bc1a10b75258e6.jpeg

需要说明的是,当order为True时,比较运算的规则是:将在类中定义的字段按照定义的顺序组装为元组,然后进行元组的比较,也就是按照元组中的元素顺序进行比较。所以,这种情况下,如果需要自定义比较、排序的规则,只需要调整类中字段定义的顺序即可。

field的使用

在基本的使用中,在类中定义的所有字段都会出现在自动添加的方法中,我们如果需要控制字段在各个自动生成的方法中是否出现,可以通过filed来进行控制。

首先看一下field的定义:

a1050674b520b7c22877eab6656bee70.jpeg

从定义中,可以知道,我们可以通过field控制类中定义的字段的默认值、默认值工厂函数、是否出现在特定方法中等。

此外,如果只需要给定字段默认值,是不需要用到field的。

接下来,我们通过代码修改一下Point类的定义:

from dataclasses import dataclass, field


@dataclass(order=True)
class Point:
    x: int = field(compare=False)
    y: int = 10
    z: int = field(default=0, init=False, compare=False)


if __name__ == '__main__':
    p1 = Point(0, 0)
    p2 = Point(10, 20)
    p3 = Point(15, 0)
    p4 = Point(9, 100)
    print(p1 < p2)
    print(p3 <= p4)
    ps = [p1, p2, p3, p4]
    print(ps)
    ps.sort()
    print(ps)

代码中,我们做了如下的调整:

1、将一个二维平面的点,升级为了一个三维空间的点,增加了一个Z轴的坐标值,为了不影响用户端代码的使用,使用filed将z字段设置为默认值为1,不需要放到__init__()方法中,不参与比较运算。

2、我们修改了比较运算的规则,将x通过field设定为不参与比较运算。

3、给y设定了一个默认值为10。

用户侧(这里以if __name__ == '__main__'代码块为例)中的代码无需做任何修改。

执行结果:

fb0869fe74dbf3b59b055ba053340cef.jpeg

可以看到,比较和排序的规则已经变成了只比较y了。

ClassVar的使用

在定义类中,有时候我们还需要定义一些类变量,在@dataclass装饰器定义的数据类的语境中,我们可以通过ClassVar来进行类变量的定义:

c179b931660f2dd963832c16f02265f3.jpeg

其实,ClassVar是一个类型注解的相关函数,使用ClassVar包裹的注解表示该属性应当作为一个类属性存在。

简单看下ClassVar的使用:

from dataclasses import dataclass, field
from typing import ClassVar


@dataclass(order=True)
class Point:
    x: int = field(compare=False)
    y: int = 10
    z: int = field(default=0, init=False, compare=False)
    dimension: ClassVar[int] = 3
    version: ClassVar[str] = 'v2'


if __name__ == '__main__':
    p1 = Point(0, 0)
    p2 = Point(10, 20)
    print(f'Point当前的维度是:{Point.dimension}')
    print(f'当前Point类的版本是:{Point.version}')

执行结果:

3473c2a5d7e962518b5c8dea4fa3914e.jpeg

总结

本文详细介绍了@dataclass这个内部装饰器,在定义数据类中的各种用法,详细介绍了dataclass函数中几个比较重要的带默认值参数的使用。同时介绍了field()函数对@dataclass装饰器装饰效果的影响。此外,还介绍了在数据类中通过ClassVar[type]的类型注解,实现类变量的定义。

感谢您的拨冗阅读,希望对您有所帮助。

b23af37a72fbc2d41844705d206113f6.jpeg

Python中的concat函数用于合并数组或数据框。该函数可以通过numpy包或pandas包来实现。在numpy中,可以使用concatenate函数进行数组的拼接。而在pandas中,可以使用concat函数进行数据框的合并。 在numpy中,concatenate函数的语法如下: np.concatenate((array1, array2, ...), axis=0) 在pandas中,concat函数的语法如下: pd.concat(objs, axis=0, join='outer', join_axes=None, ignore_index=False, keys=None, levels=None, names=None, verify_integrity=False, copy=True) 其中,objs表示要合并的对象,可以是数组、数据框或者是一个对象列表。axis参数指定了合并的轴,0表示按行合并,1表示按列合并。join参数指定合并方式,'outer'表示并集,'inner'表示交集。其他参数用于进一步控制合并的方式和结果。 例如,使用concat函数合并两个数据框可以使用如下语法: pd.concat([test_DataFrame1, test_DataFrame2]) 这样可以将test_DataFrame1和test_DataFrame2按行合并成一个新的数据框。 需要注意的是,concat函数在两个包中的用法略有不同,具体使用时可以参考各自的文档。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Python数组拼接np.concatenate实现过程](https://download.csdn.net/download/weixin_38693311/14850802)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [一文详解 Python 拼接函数concat、merge参数详解(附代码操作展示)](https://blog.csdn.net/m0_59596937/article/details/127235933)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫理的日知录

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值