Python slots 的用法笔记

Python 是一个动态语言,可以动态的给实例或类增减属性或方法,给类添加的属性会影响到前后所有创建的实例。但是使用 __slots__ 属性可以限定类或实例属性和方法,如果没有 __slots__ 的话实例的属性和方法包含在实例的 __dict__ 字典中,类的属性和方法包含在类的 __dict__ 字典中。


在使用 __slots__ 按常规写法可能会出现的问题大概有
  1. AttributeError: 'Xxx' object has no attribute 'yyy'
  2. AttributeError: 'Xxx' object attribute 'yyy' is read-only
  3. ValueError: 'yyy' in __slots__ conflicts with class variable

我们来看下面的例子
 1>>> class Cat:
 2...     lags = 4
 3...     def __init__(self):
 4...         self.eyes = 2
 5...
 6...     def walk(self):
 7...         pass
 8...
 9...
10>>> Cat.__dict__
11mappingproxy({'__module__': '__main__', 'lags': 4, '__init__': <function Cat.__init__ at 0x106816700>, 'walk': <function Cat.walk at 0x106817920>, '__dict__': <attribute
12'__dict__' of 'Cat' objects>, '__weakref__': <attribute '__weakref__' of 'Cat' objects>, '__doc__': None})
13>>> c1 = Cat()
14>>> c1.__dict__
15{'eyes': 2}
16>>> c1.miaow = lambda: 'hello'
17>>> c1.__dict__
18{'eyes': 2, 'miaow': <function <lambda> at 0x106d6fce0>}
19>>> Cat.ears = 2
20>>> c1.ears
212
22>>> c1.__dict__
23{'eyes': 2, 'miaow': <function <lambda> at 0x106d6fce0>}
24>>> Cat.__dict__
25mappingproxy({'__module__': '__main__', 'lags': 4, '__init__': <function Cat.__init__ at 0x106816700>, 'walk': <function Cat.walk at 0x106817920>, '__dict__': <attribute
26'__dict__' of 'Cat' objects>, '__weakref__': <attribute '__weakref__' of 'Cat' objects>, '__doc__': None, '__getattribute__': <slot wrapper '__getattribute__' of 'object'
27 objects>, 'ears': 2})

类或实例可以随意的添加属性和方法

如果我们引入 __slots__ 来限定属性或方法
 1>>> class Cat:
 2...     __slots__ = ('lags', 'eyes')
 3...     def __init__(self):
 4...         self.eyes = 2
 5...
 6...
 7...
 8>>> Cat.__dict__
 9mappingproxy({'__module__': '__main__', '__slots__': ('lags', 'eyes'), '__init__': <function Cat.__init__ at 0x10699ef20>, 'eyes': <member 'eyes' of 'Cat' objects>, 'lags
10': <member 'lags' of 'Cat' objects>, '__doc__': None})
11>>> c1 = Cat()
12>>> c1.__dict__
13Traceback (most recent call last):
14  File "<input>", line 1, in <module>
15    c1.__dict__
16AttributeError: 'Cat' object has no attribute '__dict__'
17>>> c1.lags
18Traceback (most recent call last):
19  File "<input>", line 1, in <module>
20    c1.lags
21AttributeError: 'Cat' object has no attribute 'lags'
22>>> c1.eyes
232
24>>> c1.lags = 4
25>>> c1.ears = 2
26Traceback (most recent call last):
27  File "<input>", line 1, in <module>
28    c1.ears = 2
29     ^^^^^^^
30AttributeError: 'Cat' object has no attribute 'ears'
31>>> c1.miaow = lambda: 'hello'
32Traceback (most recent call last):
33  File "<input>", line 1, in <module>
34    c1.miaow = lambda: 'hello'
35     ^^^^^^^^
36AttributeError: 'Cat' object has no attribute 'miaow'
37>>> c1.lags = lambda: 'hello'
38>>> c1.lags
39<function <lambda> at 0x106d6c7c0>

引入了 __slots__ 后实例不再有 __dict__ 属性,只能添加在 __slots__ 中列出的属性或方法。添加没在 __slots__ 中的属性或方法时会报错误
AttributeError: 'Xxx' object has no attribute 'yyy'
在初始化函数中 __init__(self) 中也是一样的,如
 1>>> class Cat:
 2...     __slots__ = ('lags', 'eyes')
 3...     def __init__(self):
 4...         self.ears = 2
 5...
 6...
 7...
 8>>> c1 = Cat()
 9Traceback (most recent call last):
10  File "<input>", line 1, in <module>
11    c1 = Cat()
12          ^^^^^
13  File "<input>", line 4, in __init__
14    self.ears = 2
15    ^^^^^^^^^
16AttributeError: 'Cat' object has no attribute 'ears'

__slots__ 中没有 ears, 所以不能在初始化方法或外部动态添加该属性

但是定义类时声明的方法不在 __slots__ 约束内
1>>> class Cat:
2...     __slots__ = ('lags', 'eyes')
3...     def walk(self):
4...         pass
5...
6...
7>>> c1 = Cat()
8>>> c1.walk()

__slots__ 也不约束通过类动态添加属性或方法
1>>> class Cat:
2...     __slots__ = ('lags', 'eyes')
3...
4...
5>>> Cat.ears = 2
6>>>

__slots__ 中不能包含类变量,比如
1>>> class Cat:
2...     __slots__ = ('lags')
3...     lags = 4
4...
5...
6Traceback (most recent call last):
7  File "<input>", line 1, in <module>
8    class Cat:
9ValueError: 'lags' in __slots__ conflicts with class variable

声明的没定义在 __slots__ 中的类变量对实例方法是只读的
 1>>> class Cat:
 2...     __slots__ = ('lags')
 3...     eyes = 2
 4...     def __init__(self):
 5...         self.eyes = 3
 6...
 7...
 8...
 9>>> c1 = Cat()
10Traceback (most recent call last):
11  File "<input>", line 1, in <module>
12    c1 = Cat()
13          ^^^^^
14  File "<input>", line 5, in __init__
15    self.eyes = 3
16    ^^^^^^^^^
17AttributeError: 'Cat' object attribute 'eyes' is read-only

但是声明在没定义在 __slots__ 中的类变量通过实例来修改也不行,但可以通过类属性来修改
 1>>> class Cat:
 2...     __slots__ = ('lags')
 3...     eyes = 2
 4...
 5...
 6>>> c1 = Cat()
 7>>> c1.eyes = 3
 8Traceback (most recent call last):
 9  File "<input>", line 1, in <module>
10    c1.eyes = 3
11     ^^^^^^^
12AttributeError: 'Cat' object attribute 'eyes' is read-only
13>>> Cat.eyes = 3
14>>> c1.eyes
153

定义在 __slots__ 中的属性或方法在 IDE 中会有智能提示。

__slots__ 只能作用在当前类中,不会影响到子类,子类需定义自己的 __slots__.
 1>>> class Animal:
 2...     __slots__ = ('lags')
 3...
 4...
 5>>> class Cat(Animal):
 6...     pass
 7...
 8>>> Animal.__slots__
 9'lags'
10>>> Cat.__slots__
11'lags'
12>>> c = Cat()
13>>> c.eyes = 2

__slots__ 对于实例是只读的,通过类的 __slots__ 属性可修改,但不改变原有的约束
 1>>> class Cat:
 2...     __slots__ = ('lags', 'eyes')
 3...
 4...
 5>>> c.__slots__ = ('lags', 'eyes', 'ears')
 6Traceback (most recent call last):
 7  File "<input>", line 1, in <module>
 8    c.__slots__ = ('lags', 'eyes', 'ears')
 9     ^^^^^^^^^^^
10AttributeError: 'Cat' object attribute '__slots__' is read-only
11>>> Cat.__slots__ = ('lags', 'eyes', 'ears')
12>>> c = Cat()
13>>> c.ears = 2
14Traceback (most recent call last):
15  File "<input>", line 1, in <module>
16    c.ears = 2
17     ^^^^^^
18AttributeError: 'Cat' object has no attribute 'ears'
19>>> c.__slots__
20('lags', 'eyes', 'ears')
21>>> Cat.__slots__
22('lags', 'eyes', 'ears')

__slots__ 还可声明为 list, 或省略圆括号的 tuple 形式
 1>>> class Cat:
 2...     __slots__ = ['lags', 'eyes']
 3...
 4...
 5>>> class Cat:
 6...     __slots__ = 'lags', 'eyes'
 7...
 8...
 9>>> type(Cat.__slots__)
10<class 'tuple'>

另外,用 __slots__ 避免了使用 __dict__ 记录实例属性和方法,可节约一些内存 永久链接 https://yanbin.blog/python-__slots__-notes/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。