概述

为了实现让编写类重获乐趣的雄心勃勃的目标,attrs 提供了一个类装饰器,并以声明式的方式定义该类的属性:

>>> from attrs import asdict, define, make_class, Factory

>>> @define
... class SomeClass:
...     a_number: int = 42
...     list_of_numbers: list[int] = Factory(list)
...
...     def hard_math(self, another_number):
...         return self.a_number + sum(self.list_of_numbers) * another_number


>>> sc = SomeClass(1, [1, 2, 3])
>>> sc
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])

>>> sc.hard_math(3)
19
>>> sc == SomeClass(1, [1, 2, 3])
True
>>> sc != SomeClass(2, [3, 2, 1])
True

>>> asdict(sc)
{'a_number': 1, 'list_of_numbers': [1, 2, 3]}

>>> SomeClass()
SomeClass(a_number=42, list_of_numbers=[])

>>> C = make_class("C", ["a", "b"])
>>> C("foo", "bar")
C(a='foo', b='bar')

声明了属性之后,attrs 为你提供:

  • 简洁明了的类属性概览,

  • 人类可读的 __repr__

  • 等值检查方法,

  • 初始化器,

  • 以及更多功能,

不需要重复编写无聊的样板代码,并且不会带来运行时性能的损失。


这个例子使用了 attrs 现代 API,它在 20.1.0 版本中引入,而 attrs 包的导入名称则在 21.3.0 版本中加入。 经典 API(如 @attr.sattr.ib 以及它们的正式别名)和 attr 包的导入名称将无限期保留。

查看 关于核心 API 名称 了解详细解释!

理念

一切围绕常规类。

attrs 用于创建具有类型、属性、方法等完整特性的良好行为类。虽然它可以用于像 namedtupletypes.SimpleNamespace 这样的仅包含数据的容器,但它们只是 attrs 所擅长的子类型之一。

类属于用户。

你定义一个类,attrs 根据你声明的属性向该类添加静态方法。就这些。 它不会添加元类,也不会往继承树中塞入一些你从未听说过的类。 在运行时,一个 attrs 类与常规类毫无区别:因为它就是一个附加了少量样板方法的常规类。

轻量化 API 影响。

尽管一开始看似方便,attrs 不会往你的类中添加任何方法,除了 双下划线方法。 因此,所有随 attrs 提供的有用工具都存在于基于实例操作的函数中。 由于它们将 attrs 实例作为第一个参数,你可以用一行代码将它们附加到你的类中。

性能至关重要。

attrs 的运行时影响几乎为零,因为所有的工作都在类定义时完成。一旦你实例化它,attrs 就完全退出了场景。

无意外。

attrs 创建的类可以说是按 Python 初学者合理预期的方式工作。 它不试图猜测你的意图,因为显式优于隐式。 它不试图“耍聪明”,因为软件不应当如此。

如果你想了解它如何实现上述目标,请查看 它是如何工作的?(How Does It Work?)

attrs 不是什么

attrs 不发明任何神奇的系统,不会通过元类、运行时自省或不稳定的依赖关系从魔术帽中拉出类。

attrs 所做的一切就是:

  1. 获取你的声明,

  2. 根据这些信息编写 双下划线方法 (dunder methods),

  3. 并将它们附加到你的类上。

它在运行时完全没有动态行为,因此没有任何运行时开销。 它仍然是你的类,随意处理它吧。


attrs不是一个功能齐全的序列化库。 虽然它带有转换器和验证器等功能,但它旨在成为一个用于构建你自己编写类的工具包——只不过少了样板代码。 如果你正在寻找强大但不打扰的序列化和验证工具,请查看我们的姐妹项目 cattrs 或我们的第三方扩展

这种将创建类与序列化分离的设计是经过深思熟虑的。我们不认为你的业务逻辑和序列化格式应该耦合在一起。