4.8. Dataclass Field¶
default
- Default value for the fielddefault_factory
- Field factoryinit
- Use this field in__init__()
repr
- Use this field in__repr__()
hash
- Use this field in__hash__()
compare
- Use this field in comparison functions (le, lt, gt, ge, eq, ne)metadata
- For storing extra information about fieldkw_only
- field will become a keyword-only parameter to__init__()
def field(*,
default: Any,
default_factory: Any,
init: bool = True,
repr: bool = True,
hash: bool = None,
compare: bool = True,
metadata: dict[str,Any] = None,
kw_only: bool) -> None
4.8.1. Default¶
default
- Default value for the field
>>> from dataclasses import dataclass, field
>>>
>>>
>>> @dataclass
... class Astronaut:
... firstname: str
... lastname: str
... mission: str = 'Ares3'
>>> from dataclasses import dataclass, field
>>>
>>>
>>> @dataclass
... class Astronaut:
... firstname: str
... lastname: str
... mission: str = field(default='Ares3')
4.8.2. Default Factory¶
default_factory
- Field factory
The following code will not work:
>>> from dataclasses import dataclass, field
>>>
>>>
>>> @dataclass
... class Astronaut:
... firstname: str
... lastname: str
... missions: list[str] = ['Ares3', 'Apollo18']
Traceback (most recent call last):
ValueError: mutable default <class 'list'> for field missions is not allowed: use default_factory
If you want to create a list with default values, you have to create a field
with default_factory=lambda: ['Ares3', 'Apollo18']
. Lambda expression
will be evaluated on field initialization.
>>> from dataclasses import dataclass, field
>>>
>>>
>>> @dataclass
... class Astronaut:
... firstname: str
... lastname: str
... missions: list[str] = field(default_factory=lambda: ['Ares3', 'Apollo18'])
>>>
>>>
>>> Astronaut('Mark', 'Watney')
Astronaut(firstname='Mark', lastname='Watney', missions=['Ares3', 'Apollo18'])
4.8.3. Init¶
>>> from dataclasses import dataclass, field
>>> from typing import ClassVar
>>>
>>>
>>> @dataclass
... class Astronaut:
... firstname: str
... lastname: str
... age: int
... AGE_MIN: ClassVar[int] = field(default=27, init=False)
... AGE_MAX: ClassVar[int] = field(default=50, init=False)
>>>
>>>
>>> Astronaut('Mark', 'Watney', age=44)
Astronaut(firstname='Mark', lastname='Watney', age=44)
4.8.4. Repr¶
>>> from dataclasses import dataclass, field
>>> from typing import ClassVar
>>>
>>>
>>> @dataclass
... class Astronaut:
... firstname: str
... lastname: str
... age: int
... AGE_MIN: ClassVar[int] = field(default=27, init=False, repr=False)
... AGE_MAX: ClassVar[int] = field(default=50, init=False, repr=False)
>>>
>>>
>>> Astronaut('Mark', 'Watney', age=44)
Astronaut(firstname='Mark', lastname='Watney', age=44)
4.8.5. kw_only¶
Since Python 3.10
If true, this field will be marked as keyword-only. This is used when the generated __init__() method's parameters are computed.
>>> from dataclasses import dataclass, field
>>>
>>>
>>> @dataclass
... class Astronaut:
... firstname: str
... lastname: str
... age: int = field(kw_only=True)
4.8.6. Use Case - 0x01¶
Validation
>>> from typing import ClassVar
>>> from dataclasses import dataclass, field
>>> from datetime import time, datetime, timezone
>>>
>>>
>>> @dataclass
... class Mission:
... year: int
... name: str
>>>
>>>
>>> @dataclass(frozen=True)
... class Astronaut:
... firstname: str
... lastname: str
... groups: list[str] = field(default_factory=lambda: ['astronauts', 'managers'])
... friends: dict[str,str] = field(default_factory=dict, kw_only=True)
... assignments: list[str] = field(default_factory=list, kw_only=True)
... missions: list[Mission] = field(default_factory=list, kw_only=True)
... account_created: datetime = field(default_factory=lambda: datetime.now(tz=timezone.utc), kw_only=True)
... AGE_MIN: ClassVar[int] = field(default=30, init=False, repr=False)
... AGE_MAX: ClassVar[int] = field(default=50, init=False, repr=False)
4.8.7. Assignments¶
"""
* Assignment: Dataclass Field Addressbook
* Complexity: easy
* Lines of code: 12 lines
* Time: 8 min
English:
1. Model `DATA` using `dataclasses`
2. Create class definition, fields and their types:
a. Do not use Python 3.10 syntax for Optionals, ie: `str | None`
b. Use old style `Optional[str]` instead
3. Do not write code converting `DATA` to your classes
4. Run doctests - all must succeed
Polish:
1. Zamodeluj `DATA` wykorzystując `dataclass`
2. Stwórz definicję klas, pól i ich typów
a. Nie używaj składni Optionali z Python 3.10, np.: `str | None`
b. Użyj starego sposobu, tj. `Optional[str]`
3. Nie pisz kodu konwertującego `DATA` do Twoich klas
4. Uruchom doctesty - wszystkie muszą się powieść
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> from dataclasses import is_dataclass
>>> assert isclass(Astronaut)
>>> assert isclass(Address)
>>> assert is_dataclass(Astronaut)
>>> assert is_dataclass(Address)
>>> astronaut = Astronaut.__dataclass_fields__
>>> address = Address.__dataclass_fields__
>>> assert 'firstname' in astronaut, \
'Class Astronaut is missing field: firstname'
>>> assert 'lastname' in astronaut, \
'Class Astronaut is missing field: lastname'
>>> assert 'addresses' in astronaut, \
'Class Astronaut is missing field: addresses'
>>> assert 'street' in address, \
'Class Address is missing field: street'
>>> assert 'city' in address, \
'Class Address is missing field: city'
>>> assert 'post_code' in address, \
'Class Address is missing field: post_code'
>>> assert 'region' in address, \
'Class Address is missing field: region'
>>> assert 'country' in address, \
'Class Address is missing field: country'
>>> assert astronaut['firstname'].type is str, \
'Astronaut.firstname has invalid type annotation, expected: str'
>>> assert astronaut['lastname'].type is str, \
'Astronaut.lastname has invalid type annotation, expected: str'
>>> assert astronaut['addresses'].type.__name__ == 'list', \
'Astronaut.addresses has invalid type annotation, expected: list[Address]'
>>> assert address['street'].type is Optional[str], \
'Address.street has invalid type annotation, expected: Optional[str]'
>>> assert address['city'].type is str, \
'Address.city has invalid type annotation, expected: str'
>>> assert address['post_code'].type is Optional[int], \
'Address.post_code has invalid type annotation, expected: Optional[int]'
>>> assert address['region'].type is str, \
'Address.region has invalid type annotation, expected: str'
>>> assert address['country'].type is str, \
'Address.country has invalid type annotation, expected: str'
TODO: Add support for Python 3.10 Optional and Union syntax
"""
from dataclasses import dataclass, field
from typing import Optional
DATA = [
{"firstname": "Pan", "lastname": "Twardowski", "addresses": [
{"street": "Kamienica Pod św. Janem Kapistranem", "city": "Kraków",
"post_code": 31008, "region": "Małopolskie", "country": "Poland"}]},
{"firstname": "Mark", "lastname": "Watney", "addresses": [
{"street": "2101 E NASA Pkwy", "city": "Houston", "post_code": 77058,
"region": "Texas", "country": "USA"},
{"street": None, "city": "Kennedy Space Center", "post_code": 32899,
"region": "Florida", "country": "USA"}]},
{"firstname": "Melissa", "lastname": "Lewis", "addresses": [
{"street": "4800 Oak Grove Dr", "city": "Pasadena", "post_code": 91109,
"region": "California", "country": "USA"},
{"street": "2825 E Ave P", "city": "Palmdale", "post_code": 93550,
"region": "California", "country": "USA"}]},
{"firstname": "Rick", "lastname": "Martinez"},
{"firstname": "Alex", "lastname": "Vogel", "addresses": [
{"street": "Linder Hoehe", "city": "Köln", "post_code": 51147,
"region": "North Rhine-Westphalia", "country": "Germany"}]}
]
# Model `DATA` using `dataclasses`, do not use: `str | None` syntax
# type: Type
class Address:
...
# Model `DATA` using `dataclasses`, do not use: `str | None` syntax
# type: Type
class Astronaut:
...