概述¶
为了实现让编写类重获乐趣的雄心勃勃的目标,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.s
、attr.ib
以及它们的正式别名)和 attr
包的导入名称将无限期保留。
查看 关于核心 API 名称 了解详细解释!
理念¶
- 一切围绕常规类。
attrs 用于创建具有类型、属性、方法等完整特性的良好行为类。虽然它可以用于像
namedtuple
或types.SimpleNamespace
这样的仅包含数据的容器,但它们只是 attrs 所擅长的子类型之一。- 类属于用户。
你定义一个类,attrs 根据你声明的属性向该类添加静态方法。就这些。 它不会添加元类,也不会往继承树中塞入一些你从未听说过的类。 在运行时,一个 attrs 类与常规类毫无区别:因为它就是一个附加了少量样板方法的常规类。
- 轻量化 API 影响。
尽管一开始看似方便,attrs 不会往你的类中添加任何方法,除了 双下划线方法。 因此,所有随 attrs 提供的有用工具都存在于基于实例操作的函数中。 由于它们将 attrs 实例作为第一个参数,你可以用一行代码将它们附加到你的类中。
- 性能至关重要。
attrs 的运行时影响几乎为零,因为所有的工作都在类定义时完成。一旦你实例化它,attrs 就完全退出了场景。
- 无意外。
attrs 创建的类可以说是按 Python 初学者合理预期的方式工作。 它不试图猜测你的意图,因为显式优于隐式。 它不试图“耍聪明”,因为软件不应当如此。
如果你想了解它如何实现上述目标,请查看 它是如何工作的?(How Does It Work?)。
attrs 不是什么¶
attrs 不发明任何神奇的系统,不会通过元类、运行时自省或不稳定的依赖关系从魔术帽中拉出类。
attrs 所做的一切就是:
获取你的声明,
根据这些信息编写 双下划线方法 (dunder methods),
并将它们附加到你的类上。
它在运行时完全没有动态行为,因此没有任何运行时开销。 它仍然是你的类,随意处理它吧。
attrs 也不是一个功能齐全的序列化库。 虽然它带有转换器和验证器等功能,但它旨在成为一个用于构建你自己编写类的工具包——只不过少了样板代码。 如果你正在寻找强大但不打扰的序列化和验证工具,请查看我们的姐妹项目 cattrs 或我们的第三方扩展。
这种将创建类与序列化分离的设计是经过深思熟虑的。我们不认为你的业务逻辑和序列化格式应该耦合在一起。