4.8. Dataclass Field

  • default - Default value for the field

  • default_factory - Field factory

  • init - 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 field

  • kw_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

Code 4.32. Solution
"""
* 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:
    ...