attrs is the Python package that will bring back the joy of writing classes by relieving you from the drudgery of implementing object protocols (aka dunder methods).
attr可看作是一个类装饰器但又不仅仅是简单的装饰器,他使得我们编写类变得更加简单轻松。下面先通过一个简单的例子来见识下attr的强大吧。
现在我们需要编写一个类,有a
,b
,c
两个属性,正常的写法:
class A(object):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
看起来也没那么复杂嘛。好了,现在领导说类的打印输出不好看,又不能进行比较,行吧,有需求就加吧。
from functools import total_ordering
@total_ordering
class A(object):
def __init__(self, a, b, c):
self.a = a
self.b = b
def __repr__(self):
return "ArtisanalClass(a={}, b={})".format(self.a, self.b)
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return (self.a, self.b) == (other.a, other.b)
def __lt__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return (self.a, self.b) < (other.a, other.b)
虽然看起来有点复杂,但是借助total_ordering
,也算是以尽量少的代码实现了所需功能了吧。
但是,有没有更简洁的方式呢?让我们来看看借助于attr
的实现:
import attr
@attr.s
class A(object):
a = attr.ib()
b = attr.ib()
有没有觉得,用attr
,生活更清新?
下面我们来看看attr
的一些常用的方式或API。
1、attr.s
attr.s
是attr
库的核心API之一,他是一个类装饰器。他的参数如下:
attr.s(these=None, repr_ns=None, repr=True, cmp=True,
hash=None, init=True,slots=False, frozen=False, str=False)
在开头的例子中我们见到的借助于attr
实现的类编写如此简洁,就是因为装饰器中的默认参数repr=True
和cmp=True
帮助我们实现了美化打印以及比较的功能。
1.1 these
these(dict of str to attr.ib())
与其他的几个参数不同,这个参数是直接用来定义属性的,用的较少。
>>> class SomethingFromSomeoneElse(object):
... def __init__(self, x):
... self.x = x
>>> SomethingFromSomeoneElse = attr.s(
... these={
... "x": attr.ib()
... }, init=False)(SomethingFromSomeoneElse)
>>> SomethingFromSomeoneElse(1)
SomethingFromSomeoneElse(x=1)
1.2 repr_ns
在Python2中无法__repr__
无法正确显示出嵌套类,因此需要这个参数。在python3中则无此需求。
>>> @attr.s
... class C(object):
... @attr.s(repr_ns="C")
... class D(object):
... pass
>>> C.D()
C.D()
1.3 repr
这里的__repr__
参数与一般类中的__repr__
函数功能相同,都是使类的打印更加美观易理解和debug。
1.4 str
与一般类中的__str__
函数功能相同。
1.5 cmp
此参数为True时相当于创建了__eq__
, __ne__
, __lt__
, __le__
, __gt__
和__ge__
等方法。
1.6 hash
默认为None,此时__hash__
方法由frozen
和cmp
,这两个参数决定。具体见http://www.attrs.org/en/stable/api.html 。
1.7 init
创造一个初始化方法,相当于__init__
。除了在这里设置初始化外,还可使用__attrs_post_init__
方法来初始化类,只是__attrs_post_init__
方法是在__init__
执行完之后执行的。
1.8 slots
相当于创建属性受限的类,相当于__slots__
。
>>> @attr.s(slots=True)
... class Coordinates(object):
... x = attr.ib()
... y = attr.ib()
...
>>> c = Coordinates(x=1, y=2)
>>> c.z = 3
Traceback (most recent call last):
...
AttributeError: 'Coordinates' object has no attribute 'z'
1.9 frozen
创造在实例化后不可变的类。若为True,则上面的__attrs_post_init__
方法无法执行。可使用object.__setattr__(self, "attribution_name", value)
改变,也可通过attr.evolve()
方法进行改变。attr.evolve()
相当于重新执行了一次init,因此对于那些init=False
的参数是无法通过此方法进行改变的。
>>> @attr.s(frozen=True)
... class C(object):
... x = attr.ib()
>>> i = C(1)
>>> i.x = 2
Traceback (most recent call last):
...
attr.exceptions.FrozenInstanceError: can't set attribute
>>> i.x
1
>>> @attr.s(frozen=True)
... class C(object):
... x = attr.ib()
... y = attr.ib()
>>> i1 = C(1, 2)
>>> i1
C(x=1, y=2)
>>> i2 = attr.evolve(i1, y=3)
>>> i2
C(x=1, y=3)
>>> i1 == i2
False
1.10 auto_attribs
使用注释属性:
>>> import typing
>>> @attr.s(auto_attribs=True)
... class AutoC:
... cls_var: typing.ClassVar[int] = 5 # this one is ignored
... l: typing.List[int] = attr.Factory(list)
... x: int = 1
... foo: str = attr.ib(
... default="every attrib needs a type if auto_attribs=True"
... )
... bar: typing.Any = None
>>> attr.fields(AutoC).l.type
typing.List[int]
>>> attr.fields(AutoC).x.type
<class 'int'>
>>> attr.fields(AutoC).foo.type
<class 'str'>
>>> attr.fields(AutoC).bar.type
typing.Any
>>> AutoC()
AutoC(l=[], x=1, foo='every attrib needs a type if auto_attribs=True', bar=None)
>>> AutoC.cls_var
5
2、attr.ib
attr.s
是类装饰器,而attr.ib
则是快速对属性进行定义的方法。其中有很多参数与attr.s
的参数重合,可以在这里对每个属性进行特殊设置。
attr.ib(default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True,
convert=None, metadata=None, type=None, converter=None)
2.1 default
顾名思义,即为此属性的默认值。当无参数传入或无需传参或init=False
时需要对此参数进行设置。
2.2 validator
validator
是用来进行属性检验的的一个参数。attr
提供了多种属性检验的方法。
# 使用装饰器进行属性检验
>>> @attr.s
... class C(object):
... x = attr.ib()
... @x.validator
... def check(self, attribute, value):
... if value > 42:
... raise ValueError("x must be smaller or equal to 42")
>>> C(42)
C(x=42)
>>> C(43)
Traceback (most recent call last):
...
ValueError: x must be smaller or equal to 42
# 传入一个列表,需通过列表中所有的检验方为通过
>>> def x_smaller_than_y(instance, attribute, value):
... if value >= instance.y:
... raise ValueError("'x' has to be smaller than 'y'!")
>>> @attr.s
... class C(object):
... x = attr.ib(validator=[attr.validators.instance_of(int),
... x_smaller_than_y])
... y = attr.ib()
>>> C(x=3, y=4)
C(x=3, y=4)
>>> C(x=4, y=3)
Traceback (most recent call last):
...
ValueError: 'x' has to be smaller than 'y'!
# attr.validators.instance_of(type)
>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.instance_of(int))
>>> i = C(1)
>>> i.x = "1"
>>> attr.validate(i)
Traceback (most recent call last):
...
TypeError: ("'x' must be <type 'int'> (got '1' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, repr=True, cmp=True, hash=None, init=True, type=None), <type 'int'>, '1')
# attr.validators.optional(validator)
# validator (callable or list of callables.) – A validator (or a list of validators) that is used for non-`None` values.
>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(int)))
>>> C(42)
C(x=42)
>>> C("42")
Traceback (most recent call last):
...
TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
>>> C(None)
C(x=None)
# attr.validators.in_(options)
>>> import enum
>>> class State(enum.Enum):
... ON = "on"
... OFF = "off"
>>> @attr.s
... class C(object):
... state = attr.ib(validator=attr.validators.in_(State))
... val = attr.ib(validator=attr.validators.in_([1, 2, 3]))
>>> C(State.ON, 1)
C(state=<State.ON: 'on'>, val=1)
>>> C("on", 1)
Traceback (most recent call last):
...
ValueError: 'state' must be in <enum 'State'> (got 'on')
>>> C(State.ON, 4)
Traceback (most recent call last):
...
ValueError: 'val' must be in [1, 2, 3] (got 4)
2.3 repr
产生__repr__
方法。
2.4 cmp
此参数为True时相当于创建了__eq__
, __ne__
, __lt__
, __le__
, __gt__
和__ge__
等方法。
2.5 hash
可用来进行对类的实例进行去重,功能可通过下面这个例子进行领会:
def __hash__(self):
return hash((self.id, self.author_id, self.category_id, self.brand_id))
In : p1 = Product(1, 100001, 2003, 20, 1002393002, '这是一个测试商品1', 2000001, 100, None, 1)
In : p2 = Product(1, 100001, 2003, 20, 1002393002, '这是一个测试商品2', 2000001, 100, None, 2)
In : {p1, p2}
Out: {Product(id=1, author_id=100001, category_id=2003, brand_id=20)}
2.6 init
相当于__init__
方法。
2.7 converter
将实例化时传入的属性值转化为目的类型。需要注意的是这个参数是在validator参数检验之前进行的。
>>> @attr.s
... class C(object):
... x = attr.ib(converter=int)
>>> o = C("1")
>>> o.x
1
2.8 metadata
元数据,使属性带有元数据,目前还不知道这个参数具体干嘛用的。
>>> @attr.s
... class C(object):
... x = attr.ib(metadata={'my_metadata': 1})
>>> attr.fields(C).x.metadata
mappingproxy({'my_metadata': 1})
>>> attr.fields(C).x.metadata['my_metadata']
1
2.9 type
属性的类型?目前没发现用处。
3、attr.make_class
attr.make_class(name, attrs, bases=(<class 'object'>, ), **attributes_arguments)
这个函数可用来快速的创建类:
>>> C1 = attr.make_class("C1", ["x", "y"])
>>> C1(1, 2)
C1(x=1, y=2)
>>> C2 = attr.make_class("C2", {"x": attr.ib(default=42),
... "y": attr.ib(default=attr.Factory(list))})
>>> C2()
C2(x=42, y=[])
4、attr.fields(cls)
用来查看类的一些信息。
>>> @attr.s
... class C(object):
... x = attr.ib()
... y = attr.ib()
>>> attr.fields(C)
(Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None), Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None))
>>> attr.fields(C)[1]
Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None)
>>> attr.fields(C).y is attr.fields(C)[1]
True
5、attr.asdict
attr.asdict(inst, recurse=True, filter=None, dict_factory=<class 'dict'>,
retain_collection_types=False)
inst
是实例化对象名字,recurse
显示attrs装饰类的嵌套结构,filter
表示选择性的显示哪些属性,dict_factory
,字典类型,可设置为collections.OrderedDict
,retain_collection_types
,当属性类型(type)为tuple或set时不转化为list。
# 通过匿名函数的方式筛选属性
>>> @attr.s
... class UserList(object):
... users = attr.ib()
>>> @attr.s
... class User(object):
... email = attr.ib()
... password = attr.ib()
>>> attr.asdict(UserList([User("jane@doe.invalid", "s33kred"),
... User("joe@doe.invalid", "p4ssw0rd")]),
... filter=lambda attr, value: attr.name != "password")
{'users': [{'email': 'jane@doe.invalid'}, {'email': 'joe@doe.invalid'}]}
# 通过exclude或include这两个函数来选择属性
>>> @attr.s
... class User(object):
... login = attr.ib()
... password = attr.ib()
... id = attr.ib()
>>> attr.asdict(
... User("jane", "s33kred", 42),
... filter=attr.filters.exclude(attr.fields(User).password, int))
{'login': 'jane'}
>>> @attr.s
... class C(object):
... x = attr.ib()
... y = attr.ib()
... z = attr.ib()
>>> attr.asdict(C("foo", "2", 3),
... filter=attr.filters.include(int, attr.fields(C).x))
{'x': 'foo', 'z': 3}
6、attr.validate(inst)
在实例化后再次更改属性值时进行检验实例属性值的方法:
>>> @attr.s
... class C(object):
... x = attr.ib(validator=attr.validators.instance_of(int))
>>> i = C(1)
>>> i.x = "1"
>>> attr.validate(i)
Traceback (most recent call last):
...
TypeError: ("'x' must be <type 'int'> (got '1' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, validator=<instance_of validator for type <type 'int'>>, repr=True, cmp=True, hash=None, init=True, type=None), <type 'int'>, '1')
attr.set_run_validators(run), Set whether or not validators are run. By default, they are run.
attr.get_run_validators(), Return whether or not validators are run.
7、最后
Python3.7中新加入了一个标准库dataclass
,作用与attr
库类似。
参考:
attr官方文档
attrs 和 Python3.7 的 dataclasses
Python 程序员都该用的一个库