前言

  作为 TA 工具人,已经用 Python 很长一段时间,很多内置库都使用得得心应手了。
  最近开始尝试了解一些第三方库,从而能够更好解决一些实际运用遇到的问题。
  attrs 这个库是一个非常不错的代码优化方案。
  可以简略了很多类描述的方法。

Github 地址
官方说明文档

attrs 解决的问题

  attrs 统一了类属性描述,让代码更加简洁可读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class ArtisanalClass(object):
def __init__(self, a, b):
self.a = a
self.b = b

def __repr__(self):
return "ArtisanalClass(a={}, b={})".format(self.a, self.b)

def __eq__(self, other):
if other.__class__ is self.__class__:
return (self.a, self.b) == (other.a, other.b)
else:
return NotImplemented

def __ne__(self, other):
result = self.__eq__(other)
if result is NotImplemented:
return NotImplemented
else:
return not result

def __lt__(self, other):
if other.__class__ is self.__class__:
return (self.a, self.b) < (other.a, other.b)
else:
return NotImplemented

def __le__(self, other):
if other.__class__ is self.__class__:
return (self.a, self.b) <= (other.a, other.b)
else:
return NotImplemented

def __gt__(self, other):
if other.__class__ is self.__class__:
return (self.a, self.b) > (other.a, other.b)
else:
return NotImplemented

def __ge__(self, other):
if other.__class__ is self.__class__:
return (self.a, self.b) >= (other.a, other.b)
else:
return NotImplemented

def __hash__(self):
return hash((self.__class__, self.a, self.b))

  如果要写一个完整的类需要加上很多内置方法的实现。
  这会导致大量的重复工作,而且代码会非常冗长。

1
2
3
4
5
import attr
@attr.s
class SmartClass(object):
a = attr.ib()
b = attr.ib()

代码例子来源于 文档

  attrs 只需要简单的描述就可以帮你完成上面一大堆的功能,非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

class SM_Controller(object):
def __init__(self,polyName,col,row,conPre,size,shape,color,shape):
self.start = start
self.polyName = polyName
self.col = col
self.row = row
self.conPre = conPre
self.size = size
self.shape = shape
self.color = color

import attr
@attr.s
class SM_Controller(object):
polyName = attr.ib()
col = attr.ib()
row = attr.ib()
conPre = attr.ib()
size = attr.ib()
shape = attr.ib()
color = attr.ib()
shape = attr.ib()

  上面就是实战中的例子

alt

  打印对象的时候使用 attrs 也会变得清晰。

attrs 介绍

不可变类构建

1
2
3
4
5
6
7
8
9
10
11
12
13
import attr
@attr.s(frozen=True)
class SmartClass(object):
a = attr.ib()
b = attr.ib()

smart = SmartClass(1,2)
smart.a = 3
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "c:\_thm\rez_local_cache\ext\attrs\21.4.0\site-packages\attr\_make.py", line 642, in _frozen_setattrs
# raise FrozenInstanceError()
# attr.exceptions.FrozenInstanceError

  加入 frozen 参数可以让类初始化之后不可改变。

验证数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@attr.s
class SmartClass(object):
a = attr.ib()
b = attr.ib()
@a.validator
def must_be_str(self,attribute,value):
if not isinstance(value,str):
raise ValueError("must be a string type")

smart = SmartClass(1,2)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<attrs generated init __main__.SmartClass-2>", line 5, in __init__
# File "<stdin>", line 8, in must_be_str
# ValueError: must be a string type

  支持对初始化的数据进行校验。

参数默认值

1
2
3
4
5
6
7
8
@attr.s
class SmartClass(object):
a = attr.ib(default=1)
b = attr.ib(factory=list)
smart = SmartClass()
print(smart) # SmartClass(a=1, b=[])
smart = SmartClass(b=2,a='a')
print(smart) # SmartClass(a='a', b=2)

  参数添加默认值之后可以不进行传参,也可以用键值进行传参。
  使用 attrs 构建类就变得更加灵活。

踩过的坑

mutable 容器

  构建 mutable 容器的时候,需要用 factory 参数或者 attr.ib(default=attr.factory(list))
  参数默认值使用数组和字典这类 mutable 容器会导致很多问题

1
2
3
4
5
6
7
8
def return_list(a=[]):
a.append(1)
return a

a = return_list()
print(a) # [1]
a = return_list()
print(a) # [1, 1] 这里的结果预期是 [1] 才对

  虽然我们想要进入函数的时候 a 的默认值都是这个数组。
  然而实际运行的时候赋值只发生了一次,并不会每次函数运行都重置默认值的。
  所以参数不能使用 [] {} 这些容器,而是推荐使用 None,然后再函数中初始化

1
2
3
4
5
6
7
8
def return_list(a=None):
a = a if a else []
a.append(1)
return a
a = return_list()
print(a) # [1]
a = return_list()
print(a) # [1]

  所以如果默认值想要为 数组 之类的,需要用 attrs 提供的 factory 方法。

hash 值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import attr

@attr.s
class SmartClass(object):
a = attr.ib(default=1)
b = attr.ib(factory=list)


smart = SmartClass()
a = {smart: 1}
# Traceback (most recent call last):
# File "f:/repo/_blog/source/_posts/Python/pacakge/01_attrs.py", line 11, in <module>
# a = {smart: 1}
# TypeError: unhashable type: 'SmartClass'

  attrs 默认处理 __hash__ 方法,所以无法把它当成键值使用。
  然而不使用 attrs 装饰的话是可以的。
  解决方法就是加上 `@attr.s(hash=False)` 就可以了。

参考文档 Hashing 的部分

init 初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
import attr

@attr.s
class SmartClass(object):
a = attr.ib(default=1)
b = attr.ib(factory=list)

def __init__(self,*args, **kwargs):
super(SmartClass,self).__init__(*args, **kwargs)
print(123)

smart = SmartClass() # 子类并没有打印数据输出

  依照文档 attrs 的装饰器会覆盖掉原有的 __init__
  但是如果我们确实要在 __init__ 进行数据处理,需要使用 __attrs_post_init__ 方法
  或者也可以使用继承的方式调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import attr


@attr.s
class SmartClass(object):
a = attr.ib(default=1)
b = attr.ib(factory=list)

def __attrs_post_init__(self):
print(123)


class SuperClass(SmartClass):
def __init__(self, *args, **kwargs):
super(SuperClass, self).__init__(*args, **kwargs)
print(456)


smart = SmartClass() # 打印 123
sup = SuperClass() # 打印 123 456

总结

  Python3.7 加入了 dataclass 内置模块,基本上涵盖 attrs 大多数功能。
  attrs 的好处在于老版本完美兼容 Python2 可以在 Maya 等 DCC 里面使用。
  让代码编写更加紧凑和简洁。