6.4. OOP Attribute 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
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
When inheriting from a class without __slots__
, the __dict__
and __weakref__
attribute of the instances will always be accessible.
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.
Without a __weakref__
variable for each instance, classes defining
__slots__
do not support weak references to its instances. If weak
reference support is needed, then add '__weakref__'
to the sequence
of strings in the __slots__
declaration.
__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.
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).
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.
Nonempty __slots__
does not work for classes derived from
'variable-length'
built-in types such as int
, bytes
and
tuple
.
Any non-string iterable may be assigned to __slots__
.
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()
.
__class__
assignment works only if both classes have the same
__slots__
.
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
.
If an iterator is used for __slots__
then a descriptor is created for
each of the iterator's values. However, the __slots__
attribute will
be an empty iterator.
Source: 1
6.4.1. Weakref¶
A weak reference to an object is not enough to keep the object alive: when the only remaining references to a referent are weak references, garbage collection is free to destroy the referent and reuse its memory for something else. However, until the object is actually destroyed the weak reference may return the object even if there are no strong references to it. A primary use for weak references is to implement caches or mappings holding large objects, where it's desired that a large object not be kept alive solely because it appears in a cache or mapping 3.
__weakref__
is just an opaque object that references all the weak
references to the current object. It's just an implementation detail that
allows the garbage collector to inform weak references that its referent
has been collected, and to not allow access to its underlying pointer
anymore. The weak reference can't rely on checking the reference count of
the object it refers to. This is because that memory may have been reclaimed
and is now being used by another object. Best case scenario the VM will
crash, worst case the weak reference will allow access to an object it
wasn't originally referring to. This is why the garbage collector must
inform the weak reference its referent is no longer valid. Weak references
form a stack. The top of that stack (the most recent weak reference to an
object) is available via __weakref__
. Weakrefs are re-used whenever
possible, so the stack is typically either empty or contains a single
element. 4
Garbage collection is simply the process of freeing memory when it is not used/reached by any reference/pointer anymore. Python performs garbage collection via a technique called reference counting (and a cyclic garbage collector that is used to detect and break reference cycles). Using reference counting, GC collects the objects as soon as they become unreachable which happens when the number of references to the object is 0. 2
The way with which weak references perform the task of NOT protecting the object from being collected by GC, or better to say the way with which they cause an object to be collected by GC is that (in case of a GC that uses reference counting rather than tracing technique) they just don't get to be counted as a reference. Otherwise, if counted, they will be called strong references 5.
6.4.2. Empty Slots¶
>>> class Astronaut:
... __slots__ = ()
>>>
>>>
>>> astro = Astronaut()
>>>
>>> astro.fullname = 'Mark Watney'
Traceback (most recent call last):
AttributeError: 'Astronaut' object has no attribute 'fullname'
6.4.3. One Slot¶
>>> class Astronaut:
... __slots__ = ('fullname',)
>>>
>>>
>>> astro = Astronaut()
>>>
>>> astro.fullname = 'Mark Watney'
>>> astro.role = 'Botanist'
Traceback (most recent call last):
AttributeError: 'Astronaut' object has no attribute 'role'
6.4.4. Many Slots¶
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> astro = Astronaut()
>>>
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
>>> astro.role = 'Botanist'
Traceback (most recent call last):
AttributeError: 'Astronaut' object has no attribute 'role'
6.4.5. Get Value¶
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
>>>
>>> print(astro.firstname)
Mark
>>> print(astro.lastname)
Watney
6.4.6. Slots and Methods¶
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
...
... def say_hello(self):
... print(f'My name... {self.firstname} {self.lastname}')
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
>>>
>>> astro.say_hello()
My name... Mark Watney
6.4.7. Slots and Init¶
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
...
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
>>>
>>>
>>> astro = Astronaut('Mark', 'Watney')
>>>
>>> print(astro.firstname)
Mark
>>> print(astro.lastname)
Watney
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
...
... def __init__(self, firstname, lastname, role):
... self.firstname = firstname
... self.lastname = lastname
... self.role = role
>>>
>>>
>>> astro = Astronaut('Mark', 'Watney', 'Botanist')
Traceback (most recent call last):
AttributeError: 'Astronaut' object has no attribute 'role'
6.4.8. Vars¶
Using
__slots__
will prevent from creating__dict__
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
>>>
>>> vars(astro)
Traceback (most recent call last):
TypeError: vars() argument must have __dict__ attribute
>>>
>>> print(astro.__dict__)
Traceback (most recent call last):
AttributeError: 'Astronaut' object has no attribute '__dict__'
6.4.9. Slots Internals¶
Slots are descriptors
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> vars(Astronaut)
mappingproxy({'__module__': '__main__',
'__slots__': ('firstname', 'lastname'),
'firstname': <member 'firstname' of 'Astronaut' objects>,
'lastname': <member 'lastname' of 'Astronaut' objects>,
'__doc__': None})
>>>
>>> Astronaut.firstname
<member 'firstname' of 'Astronaut' objects>
>>>
>>> type(Astronaut.firstname)
<class 'member_descriptor'>
6.4.10. Slots Dict¶
Docstring for slotted names
Used for documentation
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()
.
>>> class Astronaut:
... __slots__ = {
... 'firstname': 'Docstring for firstname attribute',
... 'lastname': 'Docstring for lastname attribute',
... }
>>>
>>>
>>> vars(Astronaut)
mappingproxy({'__module__': '__main__',
'__slots__': {'firstname': 'Docstring for firstname attribute',
'lastname': 'Docstring for lastname attribute'},
'firstname': <member 'firstname' of 'Astronaut' objects>,
'lastname': <member 'lastname' of 'Astronaut' objects>,
'__doc__': None})
6.4.11. Get Attributes and Values¶
To get values iterate over
self.__slots__
and usegetattr(self, x)
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
>>>
>>> print(astro.__slots__)
('firstname', 'lastname')
>>>
>>> {x: getattr(astro, x) for x in astro.__slots__}
{'firstname': 'Mark', 'lastname': 'Watney'}
6.4.12. Slots and Dunder Dict¶
Using
__slots__
will prevent from creating__dict__
Adding
__dict__
to__slots__
will combine both worlds
>>> class Astronaut:
... __slots__ = ('__dict__', 'firstname', 'lastname')
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark' # will use __slots__
>>> astro.lastname = 'Watney' # will use __slots__
>>> astro.role = 'Botanist' # will use __dict__
>>> astro.mission = 'Ares3' # will use __dict__
>>>
>>> print(astro.__slots__)
('__dict__', 'firstname', 'lastname')
>>>
>>> vars(astro)
{'role': 'Botanist', 'mission': 'Ares3'}
>>>
>>> {x:getattr(astro, x) for x in astro.__slots__ if x != '__dict__'} | vars(astro)
{'firstname': 'Mark',
'lastname': 'Watney',
'role': 'Botanist',
'mission': 'Ares3'}
6.4.13. Inheritance¶
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
>>> class Person:
... __slots__ = ('firstname', 'lastname')
>>>
>>> class Astronaut(Person):
... pass
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
>>> astro.role = 'Botanist'
>>>
>>> print(astro.firstname)
Mark
>>> print(astro.lastname)
Watney
>>> print(astro.role)
Botanist
>>>
>>>
>>> vars(astro)
{'role': 'Botanist'}
>>>
>>> vars(Astronaut)
mappingproxy({'__module__': '__main__',
'__dict__': <attribute '__dict__' of 'Astronaut' objects>,
'__weakref__': <attribute '__weakref__' of 'Astronaut' objects>,
'__doc__': None})
>>> class Person:
... __slots__ = ('firstname', 'lastname')
>>>
>>> class Astronaut(Person):
... __slots__ = ()
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
>>> astro.role = 'Botanist'
Traceback (most recent call last):
AttributeError: 'Astronaut' object has no attribute 'role'
>>>
>>>
>>> vars(astro)
Traceback (most recent call last):
TypeError: vars() argument must have __dict__ attribute
>>>
>>> vars(Astronaut)
mappingproxy({'__module__': '__main__', '__slots__': (), '__doc__': None})
>>> class Person:
... __slots__ = ('firstname', 'lastname')
>>>
>>> class Astronaut(Person):
... __slots__ = ('role',)
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
>>> astro.role = 'Botanist'
>>> astro.agency = 'NASA'
Traceback (most recent call last):
AttributeError: 'Astronaut' object has no attribute 'agency'
>>>
>>>
>>> vars(astro)
Traceback (most recent call last):
TypeError: vars() argument must have __dict__ attribute
>>>
>>> vars(Person)
mappingproxy({'__module__': '__main__',
'__slots__': ('firstname', 'lastname'),
'firstname': <member 'firstname' of 'Person' objects>,
'lastname': <member 'lastname' of 'Person' objects>,
'__doc__': None})
>>>
>>> vars(Astronaut)
mappingproxy({'__module__': '__main__',
'__slots__': ('role',),
'role': <member 'role' of 'Astronaut' objects>,
'__doc__': None})
6.4.14. Change Slots¶
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> astro = Astronaut()
>>>
>>> astro.__slots__
('firstname', 'lastname')
>>>
>>> astro.__slots__ = ('myslot1', 'myslot2')
Traceback (most recent call last):
AttributeError: 'Astronaut' object attribute '__slots__' is read-only
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> astro = Astronaut()
>>>
>>> Astronaut.__slots__ = ('myslot1', 'myslot2')
>>> Astronaut.__slots__
('myslot1', 'myslot2')
>>>
>>> vars(Astronaut)
mappingproxy({'__module__': '__main__',
'__slots__': ('myslot1', 'myslot2'),
'firstname': <member 'firstname' of 'Astronaut' objects>,
'lastname': <member 'lastname' of 'Astronaut' objects>,
'__doc__': None})
>>> class Astronaut:
... __slots__ = ('firstname', 'lastname')
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
>>>
>>> Astronaut.__slots__ = ('myslot1', 'myslot2')
>>> Astronaut.__slots__
('myslot1', 'myslot2')
>>>
>>>
>>> Astronaut.firstname
<member 'firstname' of 'Astronaut' objects>
>>>
>>> Astronaut.lastname
<member 'lastname' of 'Astronaut' objects>
>>>
>>> Astronaut.myslot1
Traceback (most recent call last):
AttributeError: type object 'Astronaut' has no attribute 'myslot1'
>>>
>>> Astronaut.myslot2
Traceback (most recent call last):
AttributeError: type object 'Astronaut' has no attribute 'myslot2'
>>>
>>> astro.firstname
'Mark'
>>>
>>> astro.lastname
'Watney'
>>>
>>> astro.myslot1
Traceback (most recent call last):
AttributeError: 'Astronaut' object has no attribute 'myslot1'
>>>
>>> astro.myslot2
Traceback (most recent call last):
AttributeError: 'Astronaut' object has no attribute 'myslot2'
6.4.15. Slots in Dataclasses¶
Since Python 3.10
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass(slots=True)
... class Iris:
... sl: float
... sw: float
... pl: float
... pw: float
... species: str
6.4.16. Use Case - 0x01¶
>>> from dataclasses import dataclass
>>> from itertools import starmap
>>>
>>>
>>> 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'),
... (7.0, 3.2, 4.7, 1.4, 'versicolor'),
... (7.6, 3.0, 6.6, 2.1, 'virginica'),
... (4.9, 3.0, 1.4, 0.2, 'setosa'),
... (4.9, 2.5, 4.5, 1.7, 'virginica'),
... (7.1, 3.0, 5.9, 2.1, 'virginica'),
... (4.6, 3.4, 1.4, 0.3, 'setosa'),
... (5.4, 3.9, 1.7, 0.4, 'setosa'),
... (5.7, 2.8, 4.5, 1.3, 'versicolor'),
... (5.0, 3.6, 1.4, 0.3, 'setosa'),
... (5.5, 2.3, 4.0, 1.3, 'versicolor'),
... (6.5, 3.0, 5.8, 2.2, 'virginica'),
... (6.5, 2.8, 4.6, 1.5, 'versicolor'),
... (6.3, 3.3, 6.0, 2.5, 'virginica'),
... (6.9, 3.1, 4.9, 1.5, 'versicolor'),
... (4.6, 3.1, 1.5, 0.2, 'setosa'),
... ]
>>>
>>> @dataclass(slots=True)
... class Iris:
... sl: float
... sw: float
... pl: float
... pw: float
... species: str
>>>
>>>
>>> result = starmap(Iris, DATA[1:])
>>>
>>> list(result)
[Iris(sl=5.8, sw=2.7, pl=5.1, pw=1.9, species='virginica'),
Iris(sl=5.1, sw=3.5, pl=1.4, pw=0.2, species='setosa'),
Iris(sl=5.7, sw=2.8, pl=4.1, pw=1.3, species='versicolor'),
Iris(sl=6.3, sw=2.9, pl=5.6, pw=1.8, species='virginica'),
Iris(sl=6.4, sw=3.2, pl=4.5, pw=1.5, species='versicolor'),
Iris(sl=4.7, sw=3.2, pl=1.3, pw=0.2, species='setosa'),
Iris(sl=7.0, sw=3.2, pl=4.7, pw=1.4, species='versicolor'),
Iris(sl=7.6, sw=3.0, pl=6.6, pw=2.1, species='virginica'),
Iris(sl=4.9, sw=3.0, pl=1.4, pw=0.2, species='setosa'),
Iris(sl=4.9, sw=2.5, pl=4.5, pw=1.7, species='virginica'),
Iris(sl=7.1, sw=3.0, pl=5.9, pw=2.1, species='virginica'),
Iris(sl=4.6, sw=3.4, pl=1.4, pw=0.3, species='setosa'),
Iris(sl=5.4, sw=3.9, pl=1.7, pw=0.4, species='setosa'),
Iris(sl=5.7, sw=2.8, pl=4.5, pw=1.3, species='versicolor'),
Iris(sl=5.0, sw=3.6, pl=1.4, pw=0.3, species='setosa'),
Iris(sl=5.5, sw=2.3, pl=4.0, pw=1.3, species='versicolor'),
Iris(sl=6.5, sw=3.0, pl=5.8, pw=2.2, species='virginica'),
Iris(sl=6.5, sw=2.8, pl=4.6, pw=1.5, species='versicolor'),
Iris(sl=6.3, sw=3.3, pl=6.0, pw=2.5, species='virginica'),
Iris(sl=6.9, sw=3.1, pl=4.9, pw=1.5, species='versicolor'),
Iris(sl=4.6, sw=3.1, pl=1.5, pw=0.2, species='setosa')]
6.4.17. Use Case - 0x02¶
Deep Size
>>> from sys import getsizeof
>>> from itertools import chain
>>> from collections import deque
>>> import logging
>>>
>>>
>>> logging.basicConfig(level='DEBUG')
>>> log = logging.getLogger('deepsizeof')
>>>
>>>
>>> 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)
...
... log.debug('Size: %s, Type: %s, Repr: %s', s, type(o), repr(o))
...
... 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)
>>>
>>>
>>>
... if __name__ == '__main__':
... class Astronaut:
... __slots__ = ('firstname', 'lastname')
...
... class Cosmonaut:
... pass
...
... a = Astronaut()
... a.firstname = 'Mark'
... a.lastname = 'Watney'
...
... c = Cosmonaut()
... c.firstname = 'Mark'
... c.lastname = 'Watney'
...
... print('Astronaut', deepsizeof(a))
... print('Cosmonaut', deepsizeof(c))
DEBUG:deepsizeof:Size: 48, Type: <class 'Astronaut'>, Repr: <Astronaut object at 0x10790b940>
DEBUG:deepsizeof:Size: 53, Type: <class 'str'>, Repr: 'Mark'
DEBUG:deepsizeof:Size: 55, Type: <class 'str'>, Repr: 'Watney'
DEBUG:deepsizeof:Size: 48, Type: <class 'Cosmonaut'>, Repr: <Cosmonaut object at 0x10790b9d0>
DEBUG:deepsizeof:Size: 104, Type: <class 'dict'>, Repr: {'firstname': 'Mark', 'lastname': 'Watney'}
DEBUG:deepsizeof:Size: 58, Type: <class 'str'>, Repr: 'firstname'
DEBUG:deepsizeof:Size: 53, Type: <class 'str'>, Repr: 'Mark'
DEBUG:deepsizeof:Size: 57, Type: <class 'str'>, Repr: 'lastname'
DEBUG:deepsizeof:Size: 55, Type: <class 'str'>, Repr: 'Watney'
Astronaut 156
Cosmonaut 375
6.4.18. Further Reading¶
6.4.19. References¶
- 1
van Rossum, G. et al. Data model. Customizing attribute access. Notes on using __slots__. Python documentation. Year: 2022. Retrieved: 2022-03-16. URL: https://docs.python.org/3/reference/datamodel.html#slots
- 2
van Rossum, G. et al. Data model. Objects, values and types. Python documentation. Year: 2022. Retrieved: 2022-04-01. URL: https://docs.python.org/3/reference/datamodel.html#objects-values-and-types
- 3
van Rossum, G. et al. Weak references. Python documentation. Year: 2022. Retrieved: 2022-03-16. URL: https://docs.python.org/3/library/weakref.html
- 4
Dunes. What exactly is __weakref__ in Python? Year: 2016. Retrieved: 2022-03-16. URL https://stackoverflow.com/a/36789779
- 5
Mazdak. What exactly is __weakref__ in Python? Year: 2016. Retrieved: 2022-03-16. URL https://stackoverflow.com/a/36788031
6.4.20. Assignments¶
"""
* Assignment: OOP AttributeSlots Define
* Complexity: easy
* Lines of code: 1 lines
* Time: 3 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. 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. Uruchom doctesty - wszystkie muszą się powieść
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> iris = Iris()
>>> assert not hasattr(iris, '__dict__')
>>> assert not hasattr(iris, '__weakref__')
>>> assert hasattr(iris, '__slots__')
>>> assert 'sepal_length' in iris.__slots__
>>> assert 'sepal_width' in iris.__slots__
>>> assert 'petal_length' in iris.__slots__
>>> assert 'petal_width' in iris.__slots__
>>> assert 'species' in iris.__slots__
"""
class Iris:
...
"""
* Assignment: OOP AttributeSlots Repr
* Complexity: easy
* Lines of code: 3 lines
* Time: 5 min
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ść
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__} # 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__'
>>> 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')]
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