2.5. 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')
astro = Astronaut()
astro.firstname = 'Mark'
astro.lastname = 'Watney'
astro.mission = 'Ares 3'
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'mission'
2.5.1. Example¶
class Astronaut:
__slots__ = ()
astro = Astronaut()
astro.name = 'Mark Watney'
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'name'
class Astronaut:
__slots__ = ('name',)
astro = Astronaut()
astro.name = 'Mark Watney'
astro.mission = 'Ares 3'
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'mission'
2.5.2. __slots__
and __dict__
¶
Using
__slots__
will prevent from creating__dict__
class Astronaut:
__slots__ = ('name',)
astro = Astronaut()
astro.name = 'Mark Watney'
print(astro.__slots__)
# ('name',)
print(astro.__dict__)
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute '__dict__'
class Astronaut:
__slots__ = ('__dict__', 'name')
astro = Astronaut()
astro.name = 'Mark Watney' # will use __slots__
astro.mission = 'Ares 3' # will use __dict__
print(astro.__slots__)
# ('__dict__', 'name')
print(astro.__dict__)
# {'mission': 'Ares 3'}
2.5.3. Slots and Methods¶
class Astronaut:
__slots__ = ('name',)
def say_hello(self):
print(f'My name... {self.name}')
astro = Astronaut()
astro.name = 'Mark Watney'
astro.say_hello()
2.5.4. Slots and Init¶
class Astronaut:
__slots__ = ('name',)
def __init__(self, name)
self.name = name
astro = Astronaut('Mark Watney')
print(astro.name)
# Mark Watney
class Astronaut:
__slots__ = ('name',)
def __init__(self, name, mission):
self.name = name
self.mission = mission
astro = Astronaut('Mark Watney', 'Ares 3')
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'mission'
2.5.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
astro = Astronaut()
astro.name = 'Mark Watney'
astro.mission = 'Ares 3'
print(astro.mission)
# Ares 3
class Pilot:
__slots__ = ('name',)
class Astronaut(Pilot):
__slots__ = ('name', 'mission')
astro = Astronaut()
astro.firstname = 'Mark Watney'
astro.mission = 'Ares 3'
astro.rank = 'Senior'
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'rank'
class Pilot:
__slots__ = ('name',)
class Astronaut(Pilot):
__slots__ = ('mission',)
astro = Astronaut()
astro.name = 'Mark Watney'
astro.mission = 'Ares 3'
astro.rank = 'Senior'
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'rank'
2.5.6. Use Case - 0x01¶
class Astronaut:
__slots__ = ('firstname', 'lastname')
astro = Astronaut()
astro.firstname = 'Mark'
astro.lastname = 'Watney'
print(astro.firstname)
# Mark
print(astro.lastname)
# Watney
print(astro.__slots__)
# ('firstname', 'lastname')
print(astro.__dict__)
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute '__dict__'
result = {attr: getattr(astro, attr)
for attr in astro.__slots__}
print(result)
# {'firstname': 'Mark', 'lastname': 'Watney'}
2.5.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:
...