想更快的写完代码?dataclass 来帮你!

共 3511字,需浏览 8分钟

 ·

2021-10-26 21:33

Python 3.7 增加了一个标准库 dataclasses,里面有个装饰器叫 dataclass,非常实用,可以大大提升代码的可读性,最重要的是它让你少写很多代码,从而大大节省你的时间,今天就来说说为什么你需要 dataclass。

假如你正在为一个评论系统编写代码,你新建了一个类,定义了几个成员变量,并为其编写了 init、repr 等魔术方法,代码如下:

class Comment:
    def __init__(self, id: int, text: str):
        self.id: int = id
        self.text: str = text

    def __repr__(self):
        return "{}(id={}, text={})".format(self.__class__.__name__, self.id, self.text)

为了不能发表重复的评论,你为此编写了 __eq____ne__,为了支持评论的排序,你还编写了 __lt____gt____le____ge__,为了让对象可以被 hash,你还增加了 __hash__,代码是越来越长,每个函数都用到了成员变量,如下:

class Comment:
    def __init__(self, id: int, text: str):
        self.id: int = id
        self.text: str = text

    def __repr__(self):
        return "{}(id={}, text={})".format(self.__class__.__name__, self.id, self.text)

    def __eq__(self, other):
        if other.__class__ is self.__class__:
            return (self.id, self.text) == (other.id, other.text)
        else:
            return NotImplemented

    def __ne__(self, other):
        result = self.__eq__(other)
        if result is NotImplemented:
            return NotImplemented
        else:
            return not result

    def __hash__(self):
        return hash((self.__class__, self.id, self.text))

    def __lt__(self, other):
        if other.__class__ is self.__class__:
            return (self.id, self.text) < (other.id, other.text)
        else:
            return NotImplemented

    def __le__(self, other):
        if other.__class__ is self.__class__:
            return (self.id, self.text) <= (other.id, other.text)
        else:
            return NotImplemented

    def __gt__(self, other):
        if other.__class__ is self.__class__:
            return (self.id, self.text) > (other.id, other.text)
        else:
            return NotImplemented

    def __ge__(self, other):
        if other.__class__ is self.__class__:
            return (self.id, self.text) >= (other.id, other.text)
        else:
            return NotImplemented

现在,你突然想起还要加一个字段,就是评论者的 id:author_id,然后你不得不在每个函数里面都手动添加上这个字段,很麻烦,工作量相当于重写一个类了,如果不小心哪一个忘记添加了,这就是一个有 bug 的类。

问题是,后面还有可能增加字段或删除字段,有没有办法在我定义好类的成员变量之后,这些方法去自动更新?省的我改来改去?

有,这就是今天的 dataclass,借助于 dataclass,只需要这样就可以了:

from dataclasses import dataclass
@dataclass(frozen=True, order=True)
class Comment:
    id: int
    text: str = ""

代码简洁了许多,且字段后面可以加上类型提示,来增加可读性。

如果要加一个字段 author_id,直接加就可以了,一点也不麻烦:

from dataclasses import dataclass
@dataclass(frozen=True, order=True)
class Comment:
    id: int
    author_id: int
    text: str = "" # 带有默认值的字段要放在后面

来验证一下:

import inspect
from dataclasses import dataclass
from pprint import pprint

@dataclass(frozen=True, order=True)
class Comment:
    id: int
    author_id: int
    text: str = "" # 带有默认值的字段要放在后面

def main():
    comment = Comment(1,2,"I just subscribed!")
    print(comment)
    # frozen = True 表示这是不可变对象,初始化后不能重新赋值
    # comment.id = 3  # can't immutable
    print(dataclasses.astuple(comment))
    print(dataclasses.asdict(comment))
    # 如果非要修改,可以这样
    copy = dataclasses.replace(comment, id=3)
    print(copy)
    pprint(inspect.getmembers(Comment, inspect.isfunction))


if __name__ == '__main__':
    main()

运行结果如下所示:

从上面最后的结果可以看出,dataclass 自动给我们编写了很多魔术方法,省去了自己手动编写的麻烦。注意上述的 frozen = True 表示对象是不可变对象,初始化完成之后,不可对成员重新赋值,这一点可以应用在固定对象,不可变的配置信息等应用场景下,非常实用。

我们来看下官方文档的函数签名:

也就是说,默认情况下会为我们生成 __init__、__repr__、__eq__ 这样的魔术方法。对应的参数传入 True 或 False 来控制那些魔术方法是否自动生成,比如说:

  • 如果传入 order = True,则会生成__lt__(), __le__(), __gt__(), __ge__()方法。
  • 如果 eq 和 frozen 都是 True,则会生成 __hash__ 方法

如果你仍然要自己动手写这些函数也是可以的,比如说:当你自定义了 __init__()时,init = x 这个参数会被忽略。

不想全部的字段都参与?

看到这里,你已经知道 dataclass 能够自动生成<,=,>,<=和>=这些比较方法。但是这些比较方法的一个缺陷是,它们使用类中的所有字段进行比较,有没有办法让某些字段不参与比较呢?当然可以,比如说我们有一个 class,包含姓名、年龄、身高,我们不希望姓名参与比较,就可以这样写:

from dataclasses import dataclass,field

@dataclass(order = True)
class User:
    name: str = field(compare = False)
    age: int
    height: float

同样的,如果你不希望某个字段显示在 repr 中,那么可以可以指定 field(repr = False)

最后的话

本文分享了 dataclass 的基本用法,它可以大大节省我们编写或修改代码的时间,同时给予我们最大的灵活控制,不会对类产生什么副作用,推荐 Pythoneer 们用起来。如果有帮助的话,还请三连「点赞、在看、分享」支持一下,感谢阅读!

关注我,每天学习一个 Python 小技术。



浏览 23
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报