美文网首页计算机@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