5.1. OOP Slots
Slots are faster and save memory
Slots prevent from adding new attributes
Slotted classes don't have __dict__ and __weakref__
Slotted classes have __slots__ and slotted attributes
Slots do not affect methods or __init__()
Slots do not inherit, unless subclass is also slotted
>>> class User:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> mark = User()
>>>
>>> mark.firstname = 'Mark'
>>> mark.lastname = 'Watney'
>>>
>>> mark.age = 41
Traceback (most recent call last):
AttributeError: 'User' object has no attribute 'age' and no __dict__ for setting new attributes
5.1.1. Definition
Add
__slots__
to class definitionIt could be
list[str]
ortuple[str]
ordict[str,str]
Format of
dict[str,str]
is used for docstrings of slotted fields
Slots as a tuple:
>>> class User:
... __slots__ = ('firstname', 'lastname')
Slots as a list:
>>> class User:
... __slots__ = ['firstname', 'lastname']
Slots as a dict:
>>> class User:
... __slots__ = {
... 'firstname': 'Docstring for firstname attribute',
... 'lastname': 'Docstring for lastname attribute',
... }
If a dictionary is used to assign __slots__
, the dictionary keys will
be used as the slot names. The values of the dictionary can be used to
provide per-attribute docstrings that will be recognised by
inspect.getdoc()
and displayed in the output of help()
[1]:
Mind, that single element tuple
must have a comma at the end:
>>> class User:
... __slots__ = ('fullname',)
5.1.2. Under the Hood
Unslotted classes will have
__dict__
and__weakref__
Slots will not have
__dict__
and__weakref__
Slots will have
__slots__
and slotted attributes
__slots__
are implemented at the class level by creating descriptors
for each variable name. As a result, class attributes cannot be used to set
default values for instance variables defined by __slots__
; otherwise,
the class attribute would overwrite the descriptor assignment
[1].
Unslotted class:
>>> class User:
... pass
>>>
>>>
>>> vars(User)
mappingproxy({'__module__': '__main__',
'__firstlineno__': 1,
'__static_attributes__': (),
'__dict__': <attribute '__dict__' of 'User' objects>,
'__weakref__': <attribute '__weakref__' of 'User' objects>,
'__doc__': None})
Slotted class:
>>> class User:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> vars(User)
mappingproxy({'__module__': '__main__',
'__firstlineno__': 1,
'__slots__': ('firstname', 'lastname'),
'__static_attributes__': (),
'firstname': <member 'firstname' of 'User' objects>,
'lastname': <member 'lastname' of 'User' objects>,
'__doc__': None})
5.1.3. Setattr
In unslotted classes you can add attributes dynamically
Slots disables adding attributes dynamically
Unslotted:
>>> class User:
... pass
>>>
>>>
>>> mark = User()
>>> mark.firstname = 'Mark'
>>> mark.lastname = 'Watney'
>>>
>>> mark.age = 41
Slotted:
>>> class User:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> mark = User()
>>> mark.firstname = 'Mark'
>>> mark.lastname = 'Watney'
>>>
>>> mark.age = 41
Traceback (most recent call last):
AttributeError: 'User' object has no attribute 'age' and no __dict__ for setting new attributes
Setting an attribute not listed in __slots__
will raise an error:
5.1.4. Getattr
You can access slotted attributes as normal
>>> class User:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> mark = User()
>>> mark.firstname = 'Mark'
>>> mark.lastname = 'Watney'
>>>
>>> print(mark.firstname)
Mark
5.1.5. Methods
Slots do not affect methods
You can define methods as usual
You can access slotted attributes inside methods
>>> class User:
... __slots__ = ('firstname', 'lastname')
...
... def fullname(self):
... return f'{self.firstname} {self.lastname}'
>>>
>>>
>>> mark = User()
>>> mark.firstname = 'Mark'
>>> mark.lastname = 'Watney'
>>>
>>> mark.fullname()
'Mark Watney'
5.1.6. Init
Slots do not affect
__init__()
You can define it as usual
You can set slotted attributes inside
__init__()
You cannot assign to not slotted attribute
>>> class User:
... __slots__ = ('firstname', 'lastname')
...
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
>>>
>>>
>>> mark = User('Mark', 'Watney')
Even inside of __init__
function you cannot assign
to not slotted attribute:
>>> class User:
... __slots__ = ('firstname', 'lastname')
...
... def __init__(self, firstname, lastname, age):
... self.firstname = firstname
... self.lastname = lastname
... self.age = age
>>>
>>>
>>> mark = User('Mark', 'Watney', age=41)
Traceback (most recent call last):
AttributeError: 'User' object has no attribute 'age' and no __dict__ for setting new attributes
5.1.7. Vars and __dict__
Slots will prevent from creating
__dict__
Builtin function
vars()
will not work on slotsvars()
requires__dict__
Without a __dict__
variable, instances cannot be assigned new
variables not listed in the __slots__
definition. Attempts to
assign to an unlisted variable name raises AttributeError
. If
dynamic assignment of new variables is desired, then add '__dict__'
to the sequence of strings in the __slots__
declaration
[1].
>>> class User:
... __slots__ = ('firstname', 'lastname')
...
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
>>>
>>>
>>> mark = User('Mark', 'Watney')
>>>
>>> mark.__dict__
Traceback (most recent call last):
AttributeError: 'User' object has no attribute '__dict__'. Did you mean: '__dir__'?
>>>
>>> vars(mark)
Traceback (most recent call last):
TypeError: vars() argument must have __dict__ attribute
5.1.8. Recreating Vars Behavior
To get values iterate over
self.__slots__
and usegetattr(self, x)
{x:getattr(mark,x) for x in mark.__slots__}
>>> class User:
... __slots__ = ('firstname', 'lastname')
...
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
>>>
>>>
>>> mark = User('Mark', 'Watney')
>>>
>>> {x:getattr(mark,x) for x in mark.__slots__}
{'firstname': 'Mark', 'lastname': 'Watney'}
5.1.9. Fallback
User.__slots__ = ('firstname', 'lastname', '__dict__')
>>> class User:
... __slots__ = ('firstname', 'lastname', '__dict__')
>>>
>>>
>>> mark = User()
>>> mark.firstname = 'Mark'
>>> mark.lastname = 'Watney'
>>> mark.age = 41
>>> mark.firstname
'Mark'
>>>
>>> mark.lastname
'Watney'
>>>
>>> mark.age
41
>>> vars(mark)
{'age': 41}
>>>
>>> mark.__dict__
{'age': 41}
5.1.10. Inheritance Problem
Slots do not inherit, unless they are specified in subclass
Slots are added on inheritance
If class does not specify slots, the
__dict__
will be added
The action of a __slots__
declaration is not limited to the class
where it is defined. __slots__
declared in parents are available in
child classes. However, child subclasses will get a __dict__
and
__weakref__
unless they also define __slots__
(which should only
contain names of any additional slots) [1].
Multiple inheritance with multiple slotted parent classes can be used, but
only one parent is allowed to have attributes created by slots (the other
bases must have empty slot layouts) - violations raise TypeError
[1].
If a class defines a slot also defined in a base class, the instance variable defined by the base class slot is inaccessible (except by retrieving its descriptor directly from the base class). This renders the meaning of the program undefined. In the future, a check may be added to prevent this.
Definition:
>>> class User:
... __slots__ = ('firstname', 'lastname')
>>>
>>> class Admin(User):
... pass
Result
>>> vars(User)
mappingproxy({'__module__': '__main__',
'__firstlineno__': 1,
'__slots__': ('firstname', 'lastname'),
'__static_attributes__': (),
'firstname': <member 'firstname' of 'User' objects>,
'lastname': <member 'lastname' of 'User' objects>,
'__doc__': None})
>>> vars(Admin)
mappingproxy({'__module__': '__main__',
'__firstlineno__': 1,
'__static_attributes__': (),
'__dict__': <attribute '__dict__' of 'Admin' objects>,
'__weakref__': <attribute '__weakref__' of 'Admin' objects>,
'__doc__': None})
5.1.11. Inheritance Solution
Definition:
>>> class User:
... __slots__ = ('firstname', 'lastname')
>>>
>>> class Admin(User):
... __slots__ = ()
Result:
>>> vars(User)
mappingproxy({'__module__': '__main__',
'__firstlineno__': 1,
'__slots__': ('firstname', 'lastname'),
'__static_attributes__': (),
'firstname': <member 'firstname' of 'User' objects>,
'lastname': <member 'lastname' of 'User' objects>,
'__doc__': None})
>>> vars(Admin)
mappingproxy({'__module__': '__main__',
'__firstlineno__': 1,
'__slots__': (),
'__static_attributes__': (),
'__doc__': None})
5.1.12. Dataclasses
Since Python 3.10
Parameter
@dataclass(slots=True)
will add slots to the class
Definition:
>>> from dataclasses import dataclass
>>>
>>> @dataclass(slots=True)
... class User:
... firstname: str
... lastname: str
Inheritance:
>>> from dataclasses import dataclass
>>>
>>> @dataclass(slots=True)
... class User:
... firstname: str
... lastname: str
>>>
>>>
>>> @dataclass(slots=True)
... class Admin(User):
... pass
5.1.13. Case Study
Deep Size
>>> from sys import getsizeof
>>> from itertools import chain
>>> from collections import deque
>>>
>>>
>>>
>>> def deepsizeof(o, handlers={}):
... """
... Returns the approximate memory footprint an object and all of its contents.
...
... Automatically finds the contents of the following builtin containers and
... their subclasses: tuple, list, deque, dict, set and frozenset
... """
... dict_handler = lambda d: chain.from_iterable(d.items())
... all_handlers = {tuple: iter,
... list: iter,
... deque: iter,
... dict: dict_handler,
... set: iter,
... frozenset: iter}
... all_handlers.update(handlers) # user handlers take precedence
... seen = set() # track which object id's have already been seen
... default_size = getsizeof(0) # estimate sizeof object without __sizeof__
...
... def sizeof(o):
... if id(o) in seen: # do not double count the same object
... return 0
... seen.add(id(o))
... s = getsizeof(o, default_size)
...
... for typ, handler in all_handlers.items():
... if isinstance(o, typ):
... s += sum(map(sizeof, handler(o)))
... break
... else:
... if not hasattr(o.__class__, '__slots__'):
... if hasattr(o, '__dict__'):
... # no __slots__ *usually* means a
... # __dict__, but some special builtin classes (such
... # as `type(None)`) have neither
... # else, `o` has no attributes at all, so sys.getsizeof()
... # actually returned the correct value
... s += sizeof(o.__dict__)
... else:
... s += sum(
... sizeof(getattr(o, x))
... for x in o.__class__.__slots__
... if hasattr(o, x))
... return s
... return sizeof(o)
Test:
>>> class User:
... __slots__ = ('firstname', 'lastname')
>>>
>>> mark = User()
>>> mark.firstname = 'Mark'
>>> mark.lastname = 'Watney'
>>>
>>> deepsizeof(mark)
140
>>> class User:
... pass
>>>
>>>
>>> mark = User()
>>> mark.firstname = 'Mark'
>>> mark.lastname = 'Watney'
>>>
>>> deepsizeof(mark)
535
5.1.14. Use Case - 1
>>> class User:
... __slots__ = ('firstname', 'lastname', 'age')
...
... def __init__(self, firstname, lastname, age):
... self.firstname = firstname
... self.lastname = lastname
... self.age = age
...
... def __repr__(self):
... clsname = self.__class__.__name__
... firstname = self.firstname
... lastname = self.lastname
... age = self.age
... return f'{clsname}({firstname=}, {lastname=}, {age=})'
>>>
>>>
>>> DATA = [
... ('Mark', 'Watney', 41),
... ('Melissa', 'Lewis', 40),
... ('Rick', 'Martinez', 39),
... ('Alex', 'Vogel', 40),
... ('Chris', 'Beck', 36),
... ('Beth', 'Johanssen', 29),
... ]
>>>
>>> result = [User(*row) for row in DATA]
Result:
>>> result
[User(firstname='Mark', lastname='Watney', age=41),
User(firstname='Melissa', lastname='Lewis', age=40),
User(firstname='Rick', lastname='Martinez', age=39),
User(firstname='Alex', lastname='Vogel', age=40),
User(firstname='Chris', lastname='Beck', age=36),
User(firstname='Beth', lastname='Johanssen', age=29)]
5.1.15. Use Case - 3
>>> class User:
... __slots__ = ('firstname', 'lastname', 'age')
...
... def __init__(self, firstname, lastname, age):
... self.firstname = firstname
... self.lastname = lastname
... self.age = age
...
... def __repr__(self):
... clsname = self.__class__.__name__
... firstname = self.firstname
... lastname = self.lastname
... age = self.age
... return f'{clsname}({firstname=}, {lastname=}, {age=})'
>>>
>>>
>>> DATA = [
... {'firstname': 'Mark', 'lastname': 'Watney', 'age': 41},
... {'firstname': 'Melissa', 'lastname': 'Lewis', 'age': 40},
... {'firstname': 'Rick', 'lastname': 'Martinez', 'age': 39},
... {'firstname': 'Alex', 'lastname': 'Vogel', 'age': 40},
... {'firstname': 'Chris', 'lastname': 'Beck', 'age': 36},
... {'firstname': 'Beth', 'lastname': 'Johanssen', 'age': 29},
... ]
>>>
>>> result = [User(**row) for row in DATA]
Result:
>>> result
[User(firstname='Mark', lastname='Watney', age=41),
User(firstname='Melissa', lastname='Lewis', age=40),
User(firstname='Rick', lastname='Martinez', age=39),
User(firstname='Alex', lastname='Vogel', age=40),
User(firstname='Chris', lastname='Beck', age=36),
User(firstname='Beth', lastname='Johanssen', age=29)]
5.1.16. Use Case - 3
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass(slots=True)
... class User:
... firstname: str
... lastname: str
... age: int
>>>
>>>
>>> DATA = [
... ('Mark', 'Watney', 41),
... ('Melissa', 'Lewis', 40),
... ('Rick', 'Martinez', 39),
... ('Alex', 'Vogel', 40),
... ('Chris', 'Beck', 36),
... ('Beth', 'Johanssen', 29),
... ]
>>>
>>> result = [User(*row) for row in DATA]
Result:
>>> result
[User(firstname='Mark', lastname='Watney', age=41),
User(firstname='Melissa', lastname='Lewis', age=40),
User(firstname='Rick', lastname='Martinez', age=39),
User(firstname='Alex', lastname='Vogel', age=40),
User(firstname='Chris', lastname='Beck', age=36),
User(firstname='Beth', lastname='Johanssen', age=29)]
5.1.17. Recap
Slots are faster and save memory
Slots prevent from adding new attributes
Slotted classes don't have
__dict__
and`` __weakref__``Slotted classes have`` __slots__`` and slotted attributes
Slots do not affect methods or
__init__()
Slots do not inherit, unless subclass is also slotted
5.1.18. Further Reading
5.1.19. References
5.1.20. Assignments
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP AttributeSlots Dataclass
# - Difficulty: easy
# - Lines: 4
# - Minutes: 3
# %% English
# 1. Define dataclass `User` with slots:
# - `firstname: str`
# - `lastname: str`
# 2. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj dataklasę `User` ze slotami:
# - `firstname: str`
# - `lastname: str`
# 2. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 10), \
'Python 3.10+ required'
>>> from dataclasses import is_dataclass
>>> assert hasattr(User, '__slots__')
>>> assert 'firstname' in User.__slots__
>>> assert 'lastname' in User.__slots__
>>> assert User is not Ellipsis, \
'Assign result to variable: `User`'
>>> assert type(User) is type, \
'Result must be a type'
>>> assert is_dataclass(User), \
'Class User has to be dataclass'
>>> result = User(firstname='Mark', lastname='Watney')
>>> assert not hasattr(result, '__dict__')
>>> assert not hasattr(result, '__weakref__')
"""
from dataclasses import dataclass
# Define dataclass `User` with slots:
# - `firstname: str`
# - `lastname: str`
# type: type[User]
@dataclass
class User:
...
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP AttributeSlots Define
# - Difficulty: easy
# - Lines: 2
# - Minutes: 2
# %% English
# 1. Define class `User` with slots:
# - `firstname: str`
# - `lastname: str`
# 2. Do not define `__init__()` method
# 3. Do not use dataclass
# 4. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `User` ze slotami:
# - `firstname: str`
# - `lastname: str`
# 2. Nie definiuj metody `__init__()`
# 3. Nie używaj dataclass
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 10), \
'Python 3.10+ required'
>>> from dataclasses import is_dataclass
>>> assert User is not Ellipsis, \
'Assign result to variable: `User`'
>>> assert type(User) is type, \
'Result must be a type'
>>> assert not is_dataclass(User), \
'Class User cannot be dataclass'
>>> assert hasattr(User, '__slots__')
>>> assert 'firstname' in User.__slots__
>>> assert 'lastname' in User.__slots__
>>> result = User()
>>> assert not hasattr(result, '__dict__')
>>> assert not hasattr(result, '__weakref__')
"""
# Define class `User` with slots:
# - `firstname: str`
# - `lastname: str`
# Do not define `__init__()` method
# Do not use dataclass
# type: type[User]
class User:
...
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP AttributeSlots Init
# - Difficulty: easy
# - Lines: 3
# - Minutes: 2
# %% English
# 1. Define class `User` with slots:
# - `firstname: str`
# - `lastname: str`
# 2. Define `__init__()` method
# 3. Do not use dataclass
# 4. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `User` z slotami:
# - `firstname: str`
# - `lastname: str`
# 2. Zdefiniuj metodę `__init__()`
# 3. Nie używaj dataclass
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 10), \
'Python 3.10+ required'
>>> from dataclasses import is_dataclass
>>> assert hasattr(User, '__slots__')
>>> assert 'firstname' in User.__slots__
>>> assert 'lastname' in User.__slots__
>>> assert User is not Ellipsis, \
'Assign result to variable: `User`'
>>> assert type(User) is type, \
'Result must be a type'
>>> assert not is_dataclass(User), \
'Class User cannot be dataclass'
>>> result = User(firstname='Mark', lastname='Watney')
>>> assert not hasattr(result, '__dict__')
>>> assert not hasattr(result, '__weakref__')
"""
# Define class `User` with slots:
# - `firstname: str`
# - `lastname: str`
# Define `__init__()` method
# type: type[User]
class User:
__slots__ = ('firstname', 'lastname')
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP AttributeSlots Init
# - Difficulty: easy
# - Lines: 2
# - Minutes: 3
# %% English
# 1. Define function `dump(obj) -> dict` accepting instance with slots
# 2. Function should return similar output to `vars()`, i.e.:
# {'firstname':'mwatney', 'lastname':'Ares3'}
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj funkcję `dump(obj) -> dict` przyjmującą instancję ze slotami
# 2. Funkcja powinna zwracać podobny wynik do `vars()`, np:
# {'firstname':'mwatney', 'lastname':'Ares3'}
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 10), \
'Python 3.10+ required'
>>> from dataclasses import is_dataclass
>>> class User:
... __slots__ = ('firstname', 'lastname')
...
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
>>>
>>>
>>> mark = User(firstname='Mark', lastname='Watney')
>>> result = dump(mark)
>>> assert result is not Ellipsis, \
'Assign result to variable: `result`'
>>> assert type(result) is dict, \
'Result must be a type'
>>> assert len(result) == 2, \
'Result length must be 2'
>>> assert all(type(x) is str for x in result.keys()), \
'All keys in result must be a str'
>>> assert all(type(x) is str for x in result.values()), \
'All values in result must be a str'
>>> result
{'firstname': 'Mark', 'lastname': 'Watney'}
"""
# Define function `dump(obj) -> dict` accepting instance with slots
# Function should return similar output to `vars()`
# type: Callable[[object], dict]
def dump(obj) -> dict:
...
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP AttributeSlots Repr
# - Difficulty: medium
# - Lines: 4
# - Minutes: 5
# %% English
# 1. Define method `__repr__` which prints class name and all values
# positionally, ie. `Iris(5.8, 2.7, 5.1, 1.9, 'virginica')`
# 2. Run doctests - all must succeed
# %% Polish
# 1. 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')`
# 2. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `self.__class__.__name__`
# - `tuple()`
# - `dict.values()`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> 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__} # doctest: +NORMALIZE_WHITESPACE
{'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__'. Did you mean: '__dir__'?
>>> values = tuple(getattr(iris, x) for x in iris.__slots__)
>>> print(f'Iris{values}')
Iris(5.8, 2.7, 5.1, 1.9, 'virginica')
"""
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'),
]
class Iris:
__slots__ = ('sepal_length', 'sepal_width', 'petal_length',
'petal_width', 'species')
def __init__(self, sepal_length, sepal_width,
petal_length, petal_width, species):
self.sepal_length = sepal_length
self.sepal_width = sepal_width
self.petal_length = petal_length
self.petal_width = petal_width
self.species = species
def _dump(self) -> dict:
return {x:getattr(self,x) for x in self.__slots__}
# Define method `__repr__` which prints class name and all values
# positionally, ie. `Iris(5.8, 2.7, 5.1, 1.9, 'virginica')`
# type: Callable[[Self], str]
def __repr__(self):
...