美文网首页计算机@linux_python_R 技术帖
提升类编写效率的库——attr

提升类编写效率的库——attr

作者: 井底蛙蛙呱呱呱 | 来源:发表于2018-04-18 21:23 被阅读328次

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.sattr库的核心API之一,他是一个类装饰器。他的参数如下:

attr.s(these=None, repr_ns=None, repr=True, cmp=True, 
       hash=None, init=True,slots=False, frozen=False, str=False)

在开头的例子中我们见到的借助于attr实现的类编写如此简洁,就是因为装饰器中的默认参数repr=Truecmp=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__方法由frozencmp,这两个参数决定。具体见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.OrderedDictretain_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 程序员都该用的一个库

相关文章

网友评论

    本文标题:提升类编写效率的库——attr

    本文链接:https://www.haomeiwen.com/subject/phcvkftx.html