Python is a dynamic language, and you can add or subtract properties or methods to instances or classes dynamically. But using the __slots__
attribute can qualify class or instance properties and methods; without __slots__
the instance properties and methods are contained in the instance’s __dict__
dictionary, and the class properties and methods are contained in the class’s __dict__
dictionary.
The following problems may occur when using __slots__
as written in the normal way.
- AttributeError: ‘Xxx’ object has no attribute ‘yyy’
- AttributeError: ‘Xxx’ object attribute ‘yyy’ is read-only
- ValueError: ‘yyy’ in
__slots__
conflicts with class variable
Let’s look at the following example.
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
|
>>> class Cat:
... lags = 4
... def __init__(self):
... self.eyes = 2
...
... def walk(self):
... pass
...
...
>>> Cat.__dict__
mappingproxy({'__module__': '__main__', 'lags': 4, '__init__': <function Cat.__init__ at 0x106816700>, 'walk': <function Cat.walk at 0x106817920>, '__dict__': <attribute
'__dict__' of 'Cat' objects>, '__weakref__': <attribute '__weakref__' of 'Cat' objects>, '__doc__': None})
>>> c1 = Cat()
>>> c1.__dict__
{'eyes': 2}
>>> c1.miaow = lambda: 'hello'
>>> c1.__dict__
{'eyes': 2, 'miaow': <function <lambda> at 0x106d6fce0>}
>>> Cat.ears = 2
>>> c1.ears
2
>>> c1.__dict__
{'eyes': 2, 'miaow': <function <lambda> at 0x106d6fce0>}
>>> Cat.__dict__
mappingproxy({'__module__': '__main__', 'lags': 4, '__init__': <function Cat.__init__ at 0x106816700>, 'walk': <function Cat.walk at 0x106817920>, '__dict__': <attribute
'__dict__' of 'Cat' objects>, '__weakref__': <attribute '__weakref__' of 'Cat' objects>, '__doc__': None, '__getattribute__': <slot wrapper '__getattribute__' of 'object'
objects>, 'ears': 2})
|
Classes or instances can add properties and methods at will.
If we introduce __slots__
to qualify properties or methods.
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
|
>>> class Cat:
... __slots__ = ('lags', 'eyes')
... def __init__(self):
... self.eyes = 2
...
...
...
>>> Cat.__dict__
mappingproxy({'__module__': '__main__', '__slots__': ('lags', 'eyes'), '__init__': <function Cat.__init__ at 0x10699ef20>, 'eyes': <member 'eyes' of 'Cat' objects>, 'lags
': <member 'lags' of 'Cat' objects>, '__doc__': None})
>>> c1 = Cat()
>>> c1.__dict__
Traceback (most recent call last):
File "<input>", line 1, in <module>
c1.__dict__
AttributeError: 'Cat' object has no attribute '__dict__'
>>> c1.lags
Traceback (most recent call last):
File "<input>", line 1, in <module>
c1.lags
AttributeError: 'Cat' object has no attribute 'lags'
>>> c1.eyes
2
>>> c1.lags = 4
>>> c1.ears = 2
Traceback (most recent call last):
File "<input>", line 1, in <module>
c1.ears = 2
^^^^^^^
AttributeError: 'Cat' object has no attribute 'ears'
>>> c1.miaow = lambda: 'hello'
Traceback (most recent call last):
File "<input>", line 1, in <module>
c1.miaow = lambda: 'hello'
^^^^^^^^
AttributeError: 'Cat' object has no attribute 'miaow'
>>> c1.lags = lambda: 'hello'
>>> c1.lags
<function <lambda> at 0x106d6c7c0>
|
With the introduction of __slots__
, instances no longer have the __dict__
attribute and can only add attributes or methods listed in __slots__
. Adding a property or method that is not listed in __slots__
will result in an error.
1
|
AttributeError: 'Xxx' object has no attribute 'yyy'
|
The same is true in the initialization function __init__(self)
, as in
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
>>> class Cat:
... __slots__ = ('lags', 'eyes')
... def __init__(self):
... self.ears = 2
...
...
...
>>> c1 = Cat()
Traceback (most recent call last):
File "<input>", line 1, in <module>
c1 = Cat()
^^^^^
File "<input>", line 4, in __init__
self.ears = 2
^^^^^^^^^
AttributeError: 'Cat' object has no attribute 'ears'
|
There are no ears
in __slots__
, so you cannot add this property inside the initialization method or dynamically.
But the methods declared when defining the class are not bound by __slots__
.
1
2
3
4
5
6
7
8
|
>>> class Cat:
... __slots__ = ('lags', 'eyes')
... def walk(self):
... pass
...
...
>>> c1 = Cat()
>>> c1.walk()
|
__slots__
also does not constrain the dynamic addition of properties or methods via classes.
1
2
3
4
5
6
|
>>> class Cat:
... __slots__ = ('lags', 'eyes')
...
...
>>> Cat.ears = 2
>>>
|
Class variables cannot be included in __slots__
, for example
1
2
3
4
5
6
7
8
9
|
>>> class Cat:
... __slots__ = ('lags')
... lags = 4
...
...
Traceback (most recent call last):
File "<input>", line 1, in <module>
class Cat:
ValueError: 'lags' in __slots__ conflicts with class variable
|
Declared class variables that are not defined in __slots__
are read-only for instance methods.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
>>> class Cat:
... __slots__ = ('lags')
... eyes = 2
... def __init__(self):
... self.eyes = 3
...
...
...
>>> c1 = Cat()
Traceback (most recent call last):
File "<input>", line 1, in <module>
c1 = Cat()
^^^^^
File "<input>", line 5, in __init__
self.eyes = 3
^^^^^^^^^
AttributeError: 'Cat' object attribute 'eyes' is read-only
|
However, class variables declared in classes that are not defined in __slots__
cannot be modified by instances, but can be modified by class attributes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
>>> class Cat:
... __slots__ = ('lags')
... eyes = 2
...
...
>>> c1 = Cat()
>>> c1.eyes = 3
Traceback (most recent call last):
File "<input>", line 1, in <module>
c1.eyes = 3
^^^^^^^
AttributeError: 'Cat' object attribute 'eyes' is read-only
>>> Cat.eyes = 3
>>> c1.eyes
3
|
Properties or methods defined in __slots__
will have smart hints in the IDE.
__slots__
only works in the current class and does not affect subclasses, which need to define their own __slots__
.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
>>> class Animal:
... __slots__ = ('lags')
...
...
>>> class Cat(Animal):
... pass
...
>>> Animal.__slots__
'lags'
>>> Cat.__slots__
'lags'
>>> c = Cat()
>>> c.eyes = 2
|
__slots__
is read-only for instances, and can be modified via the class’s __slots__
attribute without changing the original constraints.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
>>> class Cat:
... __slots__ = ('lags', 'eyes')
...
...
>>> c.__slots__ = ('lags', 'eyes', 'ears')
Traceback (most recent call last):
File "<input>", line 1, in <module>
c.__slots__ = ('lags', 'eyes', 'ears')
^^^^^^^^^^^
AttributeError: 'Cat' object attribute '__slots__' is read-only
>>> Cat.__slots__ = ('lags', 'eyes', 'ears')
>>> c = Cat()
>>> c.ears = 2
Traceback (most recent call last):
File "<input>", line 1, in <module>
c.ears = 2
^^^^^^
AttributeError: 'Cat' object has no attribute 'ears'
>>> c.__slots__
('lags', 'eyes', 'ears')
>>> Cat.__slots__
('lags', 'eyes', 'ears')
|
__slots__
can also be declared as a list, or as a tuple omitting the parentheses.
1
2
3
4
5
6
7
8
9
10
11
|
>>> class Cat:
... __slots__ = ['lags', 'eyes']
...
...
>>> class Cat:
... __slots__ = 'lags', 'eyes'
...
...
>>> type(Cat.__slots__)
<class 'tuple'>
|
Also, using __slots__
saves some memory by avoiding the use of __dict__
to record instance properties and methods.