4.6. Dataclass Mutable Attrs¶
problem with
dict
,list
,set
You should not set mutable objects as a default function argument
field()
creates new emptylist
for each objectIt does not reuse reference
Discussion: https://github.com/ericvsmith/dataclasses/issues/3
4.6.1. SetUp¶
>>> from dataclasses import dataclass, field
4.6.2. Problem¶
Note, You should not set mutable objects as a default function argument. More information in Argument Mutability. This is how all dynamically typed languages work (including PHP, Ruby, Perl etc).
>>> class User:
... def __init__(self, name, groups=[]):
... self.name = name
... self.groups = groups
>>>
>>>
>>> mark = User('Mark Watney')
>>> melissa = User('Melissa Lewis')
>>>
>>> mark.groups.append('user')
>>> mark.groups.append('staff')
>>> mark.groups.append('admin')
>>>
>>> print(f'Name: {mark.name}, Missions: {mark.groups}')
Name: Mark Watney, Missions: ['user', 'staff', 'admin']
>>>
>>> print(f'Name: {melissa.name}, Missions: {melissa.groups}')
Name: Melissa Lewis, Missions: ['user', 'staff', 'admin']
>>> class User:
... def __init__(self, name, groups=None):
... self.name = name
... self.groups = groups if groups else []
>>>
>>>
>>> mark = User('Mark Watney')
>>> melissa = User('Melissa Lewis')
>>>
>>> mark.groups.append('user')
>>> mark.groups.append('staff')
>>> mark.groups.append('admin')
>>>
>>> print(f'Name: {mark.name}, Missions: {mark.groups}')
Name: Mark Watney, Missions: ['user', 'staff', 'admin']
>>>
>>> print(f'Name: {melissa.name}, Missions: {melissa.groups}')
Name: Melissa Lewis, Missions: []
4.6.3. List of Strings¶
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... groups: list[str] = field(default_factory=list)
>>>
>>>
>>> mark = User('Mark', 'Watney')
>>> print(mark)
User(firstname='Mark', lastname='Watney', groups=[])
4.6.4. List of Objects¶
>>> @dataclass
... class Group:
... gid: int
... name: str
>>>
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... groups: list[Group] = field(default_factory=list)
>>>
>>>
>>> mark = User('Mark', 'Watney')
>>> print(mark)
User(firstname='Mark', lastname='Watney', groups=[])
4.6.5. Dict¶
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... groups: dict[int,str] = field(default_factory=dict)
>>>
>>>
>>> mark = User('Mark', 'Watney')
>>> print(mark)
User(firstname='Mark', lastname='Watney', groups={})
4.6.6. Default Values¶
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... groups: list[str] = field(default_factory=lambda: ['user', 'staff', 'admins'])
>>>
>>>
>>> mark = User('Mark', 'Watney')
>>> print(mark)
User(firstname='Mark', lastname='Watney', groups=['user', 'staff', 'admins'])
4.6.7. Assignments¶
"""
* Assignment: Dataclass Mutability list
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min
English:
1. Create dataclass `User`, with attributes:
a. `firstname: str` (required)
b. `lastname: str` (required)
c. `groups: list[str]` (optional)
2. Attributes must be set at the initialization from constructor arguments
3. Avoid mutable parameter problem
4. Run doctests - all must succeed
Polish:
1. Stwórz dataklasę `User`, z atrybutami:
a. `firstname: str` (wymagane)
b. `lastname: str` (wymagane)
c. `groups: list[str]` (opcjonalne)
2. Atrybuty mają być ustawiane przy inicjalizacji z parametrów konstruktora
3. Uniknij problemu mutowalnych parametrów
4. Uruchom doctesty - wszystkie muszą się powieść
Hints:
* field(default_factory=list)
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> assert isclass(User)
>>> assert hasattr(User, '__annotations__')
>>> assert 'firstname' in User.__dataclass_fields__
>>> assert 'lastname' in User.__dataclass_fields__
>>> assert 'groups' in User.__dataclass_fields__
>>> mark = User('mwatney', 'Ares3')
>>> assert mark.firstname == 'mwatney'
>>> assert mark.lastname == 'Ares3'
>>> assert mark.groups == []
>>> melissa = User('mlewis', 'Nasa1', groups=['user', 'staff', 'admin'])
>>> assert melissa.firstname == 'mlewis'
>>> assert melissa.lastname == 'Nasa1'
>>> assert melissa.groups == ['user', 'staff', 'admin']
>>> assert id(mark.groups) != id(melissa.groups)
"""
from dataclasses import dataclass, field
# Create class `User`, with attributes:
# - `firstname: str` (required)
# - `lastname: str` (required)
# - `groups: list[str]` (optional)
# type: type[User]
@dataclass
class User:
...
"""
* Assignment: Dataclass Mutability list[str]
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min
English:
1. Create dataclass `User`, with attributes:
a. `firstname: str` (required)
b. `lastname: str` (required)
c. `groups: list[str]` (optional), default: ['user', 'staff']
2. Attributes must be set at the initialization from constructor arguments
3. Avoid mutable parameter problem
4. Run doctests - all must succeed
Polish:
1. Stwórz dataklasę `User`, z atrybutami:
a. `firstname: str` (wymagane)
b. `lastname: str` (wymagane)
c. `groups: list[str]` (opcjonalne), domyślnie: ['user', 'staff']
2. Atrybuty mają być ustawiane przy inicjalizacji z parametrów konstruktora
3. Uniknij problemu mutowalnych parametrów
4. Uruchom doctesty - wszystkie muszą się powieść
Hints:
* field(default_factory=lambda: [...])
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> assert isclass(User)
>>> assert hasattr(User, '__annotations__')
>>> assert 'firstname' in User.__dataclass_fields__
>>> assert 'lastname' in User.__dataclass_fields__
>>> assert 'groups' in User.__dataclass_fields__
>>> mark = User('mwatney', 'Ares3')
>>> assert mark.firstname == 'mwatney'
>>> assert mark.lastname == 'Ares3'
>>> assert mark.groups == ['user', 'staff']
>>> melissa = User('mlewis', 'Nasa1', groups=['user', 'staff', 'admin'])
>>> assert melissa.firstname == 'mlewis'
>>> assert melissa.lastname == 'Nasa1'
>>> assert melissa.groups == ['user', 'staff', 'admin']
>>> assert id(mark.groups) != id(melissa.groups)
"""
from dataclasses import dataclass, field
# Create dataclass `User`, with attributes:
# - `firstname: str` (required)
# - `lastname: str` (required)
# - `groups: list[str]` (optional), default: ['user', 'staff']
# type: type[User]
@dataclass
class User:
...
"""
* Assignment: OOP AttributeMutability Randint Dataclass
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min
English:
1. Create class `Hero`, with attributes:
a. `name: str` (required)
b. `health: int` (optional), default: randint(50, 100)
2. Attributes must be set at the initialization from constructor arguments
3. Avoid mutable parameter problem
4. Użyj funkcji `randint()` z biblioteki `random`
5. Use `dataclass`
6. Run doctests - all must succeed
Polish:
1. Stwórz klasę `Hero`, z atrybutami:
a. `name: str` (wymagane)
b. `health: int` (opcjonalne), domyślnie: randint(50, 100)
2. Atrybuty mają być ustawiane przy inicjalizacji z parametrów konstruktora
3. Uniknij problemu mutowalnych parametrów
4. Użyj funkcji `randint()` z biblioteki `random`
5. Użyj `dataclass`
6. Uruchom doctesty - wszystkie muszą się powieść
Hints:
* field(default_factory=lambda:...)
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> from random import seed; seed(0)
>>> assert isclass(Hero)
>>> assert hasattr(Hero, '__annotations__')
>>> assert 'name' in Hero.__dataclass_fields__
>>> assert 'health' in Hero.__dataclass_fields__
>>> warrior = Hero('Warrior')
>>> assert warrior.name == 'Warrior'
>>> assert warrior.health == 74
>>> mage = Hero('Mage')
>>> assert mage.name == 'Mage'
>>> assert mage.health == 98
>>> rouge = Hero('Rouge')
>>> assert rouge.name == 'Rouge'
>>> assert rouge.health == 76
>>> cleric = Hero('Cleric')
>>> assert cleric.name == 'Cleric'
>>> assert cleric.health == 52
"""
from dataclasses import dataclass, field
from random import randint
# Create class `User`, with attributes:
# - `name: str` (required)
# - `health: int` (optional), default: randint(50, 100)
# Use `dataclass`
# type: type[Hero]
@dataclass
class Hero:
...