4.6. Dataclass Mutability
problem with
list,set,dictYou should not set mutable objects as a default function argument
field()creates new emptylistfor 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. Recap
Function/method definition is evaluated once, when the function is defined
Function/method body is evaluated each time, when the function is called
This is how all dynamically typed languages work (JavaScript, PHP, Ruby, Perl etc).
You should not set mutable objects as a default function argument
Problem with mutable data structures like:
list,set,dict
You should not set mutable objects as a default function argument.
Problem:
>>> class User:
... def __init__(self, firstname, lastname, groups=[]):
... self.firstname = firstname
... self.lastname = lastname
... self.groups = groups
>>>
>>>
>>> a = User('Alice', 'Apricot')
>>> b = User('Bob', 'Blackthorn')
>>>
>>> a.groups.append('users')
>>> a.groups.append('staff')
>>>
>>> a.groups
['users', 'staff']
>>>
>>> b.groups
['users', 'staff']
Note, you should not set mutable objects or data structures
(such as list, set, dict) as a default function argument.
Solution:
>>> class User:
... def __init__(self, firstname, lastname, groups=None):
... if groups is None:
... groups = []
... self.firstname = firstname
... self.lastname = lastname
... self.groups = groups
>>>
>>> a = User('Alice', 'Apricot')
>>> b = User('Bob', 'Blackthorn')
>>>
>>> a.groups.append('users')
>>> a.groups.append('staff')
>>>
>>> a.groups
['users', 'staff']
>>>
>>> b.groups
[]
Why:
>>> class User:
... def __init__(self, firstname, lastname, groups=print(1)):
... print(2)
... self.firstname = firstname
... self.lastname = lastname
... self.groups = groups
1
>>>
>>>
>>> a = User('Alice', 'Apricot')
2
>>>
>>> b = User('Bob', 'Blackthorn')
2
Function/method definition is evaluated once, when the function is defined. Function/method body is evaluated each time, when the function is called. This is how all dynamically typed languages work (including JavaScript, PHP, Ruby, Perl etc).
4.6.3. Problem
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... groups: list[str] = []
...
Traceback (most recent call last):
ValueError: mutable default <class 'list'> for field groups is not allowed: use default_factory
4.6.4. Solution
field(default_factory=lambda: [])
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... groups: list[str] = field(default_factory=lambda: [])
Usage:
>>> result = User('Alice', 'Apricot')
>>> print(result)
User(firstname='Alice', lastname='Apricot', groups=[])
4.6.5. Default Values
field(default_factory=lambda: ['user', 'staff', 'admins'])
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... groups: list[str] = field(default_factory=lambda: ['user', 'staff', 'admins'])
Usage:
>>> result = User('Alice', 'Apricot')
>>> print(result)
User(firstname='Alice', lastname='Apricot', groups=['user', 'staff', 'admins'])
4.6.6. Default Factory
field(default_factory=default_groups)
>>> def default_groups():
... return ['user', 'staff', 'admins']
>>>
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... groups: list[str] = field(default_factory=default_groups)
Usage:
>>> result = User('Alice', 'Apricot')
>>> print(result)
User(firstname='Alice', lastname='Apricot', groups=['user', 'staff', 'admins'])
4.6.7. Short Notation
field(default_factory=list)listis a callable object
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... groups: list[str] = field(default_factory=list)
4.6.8. Use Case - 1
SetUp:
>>> from random import randint, seed
>>> seed(0)
Problem:
>>> @dataclass
... class Hero:
... name: str
... health: int = randint(50, 100)
>>>
>>>
>>> a = Hero('Alice')
>>> b = Hero('Bob')
>>> c = Hero('Carol')
>>>
>>> a.health
74
>>> b.health
74
>>> c.health
74
Solution:
>>> @dataclass
... class Hero:
... name: str
... health: int = field(default_factory=lambda: randint(50, 100))
>>>
>>>
>>> a = Hero('Alice')
>>> b = Hero('Bob')
>>> c = Hero('Carol')
>>>
>>> a.health
98
>>> b.health
76
>>> c.health
52
4.6.9. Assignments
# %% About
# - Name: Dataclass Mutability list
# - Difficulty: easy
# - Lines: 3
# - Minutes: 3
# %% 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
# %% English
# 1. Create dataclass `User`, with attributes:
# - `firstname: str` (required)
# - `lastname: str` (required)
# - `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:
# - `firstname: str` (wymagane)
# - `lastname: str` (wymagane)
# - `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)`
# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'
>>> from inspect import isclass
>>> assert isclass(User), \
'Object `User` has an invalid type; expected: `class`.'
>>> assert hasattr(User, '__annotations__'), \
'Object `User` has an invalid attribute; expected: to have an attribute `__annotations__`.'
>>> assert 'firstname' in User.__dataclass_fields__
>>> assert 'lastname' in User.__dataclass_fields__
>>> assert 'groups' in User.__dataclass_fields__
>>> alice = User('Alice', 'Apricot')
>>> assert alice.firstname == 'Alice'
>>> assert alice.lastname == 'Apricot'
>>> assert alice.groups == []
>>> bob = User('Bob', 'Blackthorn')
>>> assert bob.firstname == 'Bob'
>>> assert bob.lastname == 'Blackthorn'
>>> assert bob.groups == []
>>> assert id(alice.groups) != id(bob.groups)
"""
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`
# %% Imports
from dataclasses import dataclass, field
# %% Types
User: type
firstname: str
lastname: str
groups: list[str]
# %% Data
# %% Result
@dataclass
class User:
...
# %% About
# - Name: Dataclass Mutability list[str]
# - Difficulty: easy
# - Lines: 3
# - Minutes: 3
# %% 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
# %% English
# 1. Create dataclass `User`, with attributes:
# - `firstname: str` (required)
# - `lastname: str` (required)
# - `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:
# - `firstname: str` (wymagane)
# - `lastname: str` (wymagane)
# - `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: [...])`
# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'
>>> from inspect import isclass
>>> assert isclass(User), \
'Object `User` has an invalid type; expected: `class`.'
>>> assert hasattr(User, '__annotations__'), \
'Object `User` has an invalid attribute; expected: to have an attribute `__annotations__`.'
>>> assert 'firstname' in User.__dataclass_fields__
>>> assert 'lastname' in User.__dataclass_fields__
>>> assert 'groups' in User.__dataclass_fields__
>>> alice = User('alice', 'secret')
>>> assert alice.firstname == 'alice'
>>> assert alice.lastname == 'secret'
>>> assert alice.groups == ['user', 'staff']
>>> bob = User('bob', 'qwerty')
>>> assert bob.firstname == 'bob'
>>> assert bob.lastname == 'qwerty'
>>> assert bob.groups == ['user', 'staff']
>>> assert id(alice.groups) != id(bob.groups)
"""
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`
# %% Imports
from dataclasses import dataclass, field
# %% Types
User: type
firstname: str
lastname: str
groups: list[str]
# %% Data
# %% Result
@dataclass
class User:
...
# %% About
# - Name: Dataclass Mutability Datetime
# - Difficulty: easy
# - Lines: 3
# - Minutes: 3
# %% 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
# %% English
# 1. Create dataclass `User`, with attributes:
# - `firstname: str` (required)
# - `lastname: str` (required)
# - `since: datetime` (optional), default: current time
# 2. Use `datetime.now()` as default value for `since` attribute
# 3. Attributes must be set at the initialization from constructor arguments
# 4. Avoid mutable parameter problem
# 5. Run doctests - all must succeed
# %% Polish
# 1. Stwórz dataklasę `User`, z atrybutami:
# - `firstname: str` (wymagane)
# - `lastname: str` (wymagane)
# - `since: datetime` (optional), domyślnie: obecny czas
# 2. Użyj `datetime.now()` jako domyślnej wartości dla atrybutu `since`
# 3. Atrybuty mają być ustawiane przy inicjalizacji z parametrów konstruktora
# 4. Uniknij problemu mutowalnych parametrów
# 5. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `field(default_factory=...)`
# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'
>>> from inspect import isclass
>>> assert isclass(User), \
'Object `User` has an invalid type; expected: `class`.'
>>> assert hasattr(User, '__annotations__'), \
'Object `User` has an invalid attribute; expected: to have an attribute `__annotations__`.'
>>> assert 'firstname' in User.__dataclass_fields__
>>> assert 'lastname' in User.__dataclass_fields__
>>> assert 'since' in User.__dataclass_fields__
>>> alice = User('alice', 'secret')
>>> assert alice.firstname == 'alice'
>>> assert alice.lastname == 'secret'
>>> assert isinstance(alice.since, datetime)
>>> bob = User('bob', 'qwerty')
>>> assert bob.firstname == 'bob'
>>> assert bob.lastname == 'qwerty'
>>> assert isinstance(bob.since, datetime)
>>> assert id(alice.since) != id(bob.since)
"""
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`
# %% Imports
from dataclasses import dataclass, field
from datetime import datetime
# %% Types
User: type
firstname: str
lastname: str
since: datetime
# %% Data
# %% Result
@dataclass
class User:
...
# %% About
# - Name: Dataclass Mutability Randint
# - Difficulty: easy
# - Lines: 3
# - Minutes: 3
# %% 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
# %% English
# 1. Create class `Hero`, with attributes:
# - `name: str` (required)
# - `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:
# - `name: str` (wymagane)
# - `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:...)`
# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python has an is invalid version; expected: `3.9` or newer.'
>>> from inspect import isclass
>>> from random import seed; seed(0)
>>> assert isclass(Hero), \
'Object `Hero` has an invalid type; expected: `class`.'
>>> assert hasattr(Hero, '__annotations__'), \
'Object `Hero` has an invalid attribute; expected: to have an attribute `__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
"""
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`
# %% Imports
from dataclasses import dataclass, field
from random import randint
# %% Types
Hero: type
name: str
health: int
# %% Data
# %% Result
@dataclass
class Hero:
...