2.7. Slots¶
Faster attribute access
Space savings in memory (overhead of dict for every object)
Prevents from adding new attributes
The space savings is from:
Store value references in slots instead of
__dict__
Denying
__dict__
and__weakref__
creation if parent classes deny them and you declare__slots__
class Astronaut:
__slots__ = ('firstname', 'lastname')
mark = Astronaut()
mark.firstname = 'Mark'
mark.lastname = 'Watney'
mark.mission = 'Ares 3'
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'mission'
2.7.1. Example¶
class Astronaut:
__slots__ = ()
mark = Astronaut()
mark.name = 'Mark Watney'
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'name'
class Astronaut:
__slots__ = ('name',)
mark = Astronaut()
mark.name = 'Mark Watney'
mark.mission = 'Ares 3'
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'mission'
2.7.2. __slots__
and __dict__
¶
Using
__slots__
will prevent from creating__dict__
class Astronaut:
__slots__ = ('name',)
mark = Astronaut()
mark.name = 'Mark Watney'
print(mark.__slots__)
# ('name',)
print(mark.__dict__)
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute '__dict__'
class Astronaut:
__slots__ = ('__dict__', 'name')
mark = Astronaut()
mark.name = 'Mark Watney' # will use __slots__
mark.mission = 'Ares 3' # will use __dict__
print(mark.__slots__)
# ('__dict__', 'name')
print(mark.__dict__)
# {'mission': 'Ares 3'}
2.7.3. Slots and Methods¶
class Astronaut:
__slots__ = ('name',)
def say_hello(self):
print(f'My name... {self.name}')
mark = Astronaut()
mark.name = 'Mark Watney'
mark.say_hello()
2.7.4. Slots and Init¶
class Astronaut:
__slots__ = ('name',)
def __init__(self, name)
self.name = name
mark = Astronaut('Mark Watney')
print(mark.name)
# Mark Watney
2.7.5. Inheritance¶
Slots do not inherit, unless they are specified in subclass
Slots are added on inheritance
class Pilot:
__slots__ = ('name',)
class Astronaut(Pilot):
pass
mark = Astronaut()
mark.name = 'Mark Watney'
mark.mission = 'Ares 3'
print(mark.mission)
# Ares 3
class Pilot:
__slots__ = ('name',)
class Astronaut(Pilot):
__slots__ = ('name', 'mission')
mark = Astronaut()
mark.firstname = 'Mark Watney'
mark.mission = 'Ares 3'
mark.rank = 'Senior'
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'rank'
class Pilot:
__slots__ = ('name',)
class Astronaut(Pilot):
__slots__ = ('mission',)
mark = Astronaut()
mark.name = 'Mark Watney'
mark.mission = 'Ares 3'
mark.rank = 'Senior'
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'rank'
2.7.6. Use Case - 0x01¶
class Astronaut:
__slots__ = ('firstname', 'lastname')
mark = Astronaut()
mark.firstname = 'Mark'
mark.lastname = 'Watney'
print(mark.firstname)
# Mark
print(mark.lastname)
# Watney
print(mark.__slots__)
# ('firstname', 'lastname')
print(mark.__dict__)
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute '__dict__'
result = {attr: getattr(mark, attr)
for attr in mark.__slots__}
print(result)
# {'firstname': 'Mark', 'lastname': 'Watney'}
2.7.7. Assignments¶
"""
* Assignment: OOP Slots Define
* Complexity: easy
* Lines of code: 11 lines
* Time: 13 min
English:
1. Define class `Iris` with attributes: `sepal_length, sepal_width, petal_length, petal_width, species`
2. All attributes must be in `__slots__`
3. Define method `__repr__` which prints class name and all values positionally, ie. `Iris(5.8, 2.7, 5.1, 1.9, 'virginica')`
4. Run doctests - all must succeed
Polish:
1. Zdefiniuj klasę `Iris` z atrybutami: `sepal_length, sepal_width, petal_length, petal_width, species`
2. Wszystkie atrybuty muszą być w `__slots__`
3. Zdefiniuj metodę `__repr__` wypisującą nazwę klasy i wszystkie wartości atrybutów pozycyjnie, np. `Iris(5.8, 2.7, 5.1, 1.9, 'virginica')`
4. Uruchom doctesty - wszystkie muszą się powieść
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> result = [Iris(*row) for row in DATA[1:]]
>>> result # doctest: +NORMALIZE_WHITESPACE
[Iris(5.8, 2.7, 5.1, 1.9, 'virginica'),
Iris(5.1, 3.5, 1.4, 0.2, 'setosa'),
Iris(5.7, 2.8, 4.1, 1.3, 'versicolor'),
Iris(6.3, 2.9, 5.6, 1.8, 'virginica'),
Iris(6.4, 3.2, 4.5, 1.5, 'versicolor'),
Iris(4.7, 3.2, 1.3, 0.2, 'setosa')]
>>> iris = result[0]
>>> iris
Iris(5.8, 2.7, 5.1, 1.9, 'virginica')
>>> iris.__slots__
('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species')
>>> [getattr(iris, x) for x in iris.__slots__]
[5.8, 2.7, 5.1, 1.9, 'virginica']
>>> {x: getattr(iris, x) for x in iris.__slots__}
{'sepal_length': 5.8, 'sepal_width': 2.7, 'petal_length': 5.1, 'petal_width': 1.9, 'species': 'virginica'}
>>> iris.__dict__
Traceback (most recent call last):
AttributeError: 'Iris' object has no attribute '__dict__'
>>> values = tuple(getattr(iris, x) for x in iris.__slots__)
>>> print(f'Iris{values}')
Iris(5.8, 2.7, 5.1, 1.9, 'virginica')
Hint:
* In `__repr__()` use tuple comprehension to get `self.__slots__` values
"""
DATA = [
('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
(5.8, 2.7, 5.1, 1.9, 'virginica'),
(5.1, 3.5, 1.4, 0.2, 'setosa'),
(5.7, 2.8, 4.1, 1.3, 'versicolor'),
(6.3, 2.9, 5.6, 1.8, 'virginica'),
(6.4, 3.2, 4.5, 1.5, 'versicolor'),
(4.7, 3.2, 1.3, 0.2, 'setosa'),
]
# type: type
class Iris:
...