4.2. Dataclass Define Basic

4.2.1. SetUp

>>> from dataclasses import dataclass, field
>>> from typing import Literal, Final

4.2.2. Required Fields

>>> @dataclass
... class User:
...     firstname: str
...     lastname: str

4.2.3. Default Fields

>>> @dataclass
... class User:
...     firstname: str
...     lastname: str
...     role: str = 'admin'

4.2.4. Lists

>>> @dataclass
... class User:
...     firstname: str
...     lastname: str
...     groups: list[str]

4.2.5. Union Fields

>>> @dataclass
... class User:
...     firstname: str
...     lastname: str
...     age: int | float

4.2.6. Optional Fields

>>> @dataclass
... class User:
...     firstname: str
...     lastname: str
...     age: int | None = None

4.2.7. Literal Field

Import:

>>> from typing import Literal

Define class:

>>> @dataclass
... class User:
...     firstname: str
...     lastname: str
...     role: Literal['users', 'staff', 'admins']

4.2.8. ClassVar Fields

  • from typing import ClassVar

  • Defines static field

One of two places where dataclass() actually inspects the type of a field is to determine if a field is a class variable as defined in PEP 526. It does this by checking if the type of the field is typing.ClassVar. If a field is a ClassVar, it is excluded from consideration as a field and is ignored by the dataclass mechanisms. Such ClassVar pseudo-fields are not returned by the module-level fields() function.

Import:

>>> from typing import ClassVar

Define Class:

>>> @dataclass
... class User:
...     firstname: str
...     lastname: str
...     age: int
...     AGE_MIN: ClassVar[int] = 30
...     AGE_MAX: ClassVar[int] = 50

Note, that those fields will not be displayed in repr or while printing.

>>> User('Mark', 'Watney', age=42)
User(firstname='Mark', lastname='Watney', age=42)

4.2.9. Keyword Arguments Only

  • Since Python 3.10

  • from dataclasses import KW_ONLY

Any fields after a pseudo-field with the type of KW_ONLY are marked as keyword-only fields. Note that a pseudo-field of type KW_ONLY is otherwise completely ignored. This includes the name of such a field. By convention, a name of _ is used for a KW_ONLY field.

Import:

>>> from dataclasses import KW_ONLY

Define class:

>>> @dataclass
... class User:
...     firstname: str
...     lastname: str
...     _: KW_ONLY
...     age: int
...     height: float
...     weight: float
>>> User('Mark', 'Watney', age=42, height=178.0, weight=75.5)
User(firstname='Mark', lastname='Watney', age=42, height=178.0, weight=75.5)
>>> mark = User('Mark', 'Watney', 42, height=178.0, weight=75.5)
Traceback (most recent call last):
TypeError: User.__init__() takes 3 positional arguments but 4 positional arguments (and 2 keyword-only arguments) were given
>>> mark = User('Mark', 'Watney', 42, 178.0, weight=75.5)
Traceback (most recent call last):
TypeError: User.__init__() takes 3 positional arguments but 5 positional arguments (and 1 keyword-only argument) were given
>>> mark = User('Mark', 'Watney', 42, 178.0, 75.5)
Traceback (most recent call last):
TypeError: User.__init__() takes 3 positional arguments but 6 were given

4.2.10. Assignments

Code 4.35. Solution
"""
* Assignment: Dataclass Definition Attributes
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min

English:
    1. Use Dataclass to define class `Point` with attributes:
        a. `x: int` with default value `0`
        b. `y: int` with default value `0`
    2. Run doctests - all must succeed

Polish:
    1. Użyj Dataclass do zdefiniowania klasy `Point` z atrybutami:
        a. `x: int` z domyślną wartością `0`
        b. `y: int` z domyślną wartością `0`
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass
    >>> from dataclasses import is_dataclass

    >>> assert isclass(Point), 'Point is not a class'
    >>> assert is_dataclass(Point), 'Point is not a dataclass, add decorator'
    >>> assert hasattr(Point, 'x')
    >>> assert hasattr(Point, 'y')

    >>> Point()
    Point(x=0, y=0)

    >>> Point(x=0, y=0)
    Point(x=0, y=0)

    >>> Point(x=1, y=2)
    Point(x=1, y=2)
"""

from dataclasses import dataclass


# Use Dataclass to define class `Point` with attributes `x` and `y`
# type: type
class Point:
    ...


Code 4.36. Solution
"""
* Assignment: Dataclass Definition AccessModifiers
* Complexity: easy
* Lines of code: 6 lines
* Time: 3 min

English:
    1. Modify dataclass `User` to add attributes:
        a. Public: `firstname`, `lastname`
        b. Protected: `email`, `phone`
        c. Private: `username`, `password`
    2. Run doctests - all must succeed

Polish:
    1. Zmodyfikuj dataclass `User` aby dodać atrybuty:
        a. Publiczne: `firstname`, `lastname`
        b. Chronione: `email`, `phone`
        c. Prywatne: `username`, `password`
    2. Uruchom doctesty - wszystkie muszą się powieść

Hint:
    * Public attribute name starts with lowercase letter
    * Protected attribute name starts with underscore `_`
    * Private attribute name starts with double underscore `__`

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 '_phone' in User.__dataclass_fields__
    >>> assert '_email' in User.__dataclass_fields__
    >>> assert '_User__username' in User.__dataclass_fields__
    >>> assert '_User__password' in User.__dataclass_fields__
"""
from dataclasses import dataclass


# Public attributes: `firstname`, `lastname`
# Protected attributes: `email`, `phone`
# Private attributes: `username`, `password`
# type: type[User]
@dataclass
class User:
    ...


Code 4.37. Solution
"""
* Assignment: Dataclass Definition Flat
* Complexity: easy
* Lines of code: 6 lines
* Time: 3 min

English:
    1. You received input data in JSON format from the API
    2. Using `dataclass` model data to create class `Pet`
    3. Run doctests - all must succeed

Polish:
    1. Otrzymałeś z API dane wejściowe w formacie JSON
    2. Wykorzystując `dataclass` zamodeluj dane aby stwórzyć klasę `Pet`
    3. Uruchom doctesty - wszystkie muszą się powieść

References:
    [1]: https://petstore.swagger.io/#/pet/getPetById

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass
    >>> from dataclasses import is_dataclass
    >>> import json

    >>> assert isclass(Pet)
    >>> assert is_dataclass(Pet)

    >>> fields = {'id', 'category', 'name', 'photoUrls', 'tags', 'status'}
    >>> assert set(Pet.__dataclass_fields__.keys()) == fields, \
    f'Invalid fields, your fields should be: {fields}'

    >>> data = json.loads(DATA)
    >>> result = Pet(**data)

    >>> result  # doctest: +NORMALIZE_WHITESPACE
    Pet(id=0, category='dogs', name='doggie', photoUrls='img/dogs/0.png',
        tags=['dog', 'hot-dog'], status='available')
"""

from dataclasses import dataclass


DATA = """
{
  "id": 0,
  "category": "dogs",
  "name": "doggie",
  "photoUrls": "img/dogs/0.png",
  "tags": ["dog", "hot-dog"],
  "status": "available"
}
"""


# Using `dataclass` model data to create class `Pet`
# type: type
@dataclass
class Pet:
    ...


Code 4.38. Solution
"""
* Assignment: Dataclass Definition Nested
* Complexity: easy
* Lines of code: 6 lines
* Time: 3 min

English:
    1. You received input data in JSON format from the API
    2. Using `dataclass` model `DATA` to create class `Pet`
       a. Leave `category` as `dict`
       b. Leave `tags` as `list[dicts]`
    3. Run doctests - all must succeed

Polish:
    1. Otrzymałeś z API dane wejściowe w formacie JSON
    2. Wykorzystując `dataclass` zamodeluj `DATA` aby stwórzyć klasę `Pet`
       a. Pozostaw `category` jako `dict`
       b. Pozostaw `tags` jako `list[dicts]`
    3. Uruchom doctesty - wszystkie muszą się powieść

References:
    [1]: https://petstore.swagger.io/#/pet/getPetById

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass
    >>> from dataclasses import is_dataclass
    >>> import json

    >>> assert isclass(Pet)
    >>> assert is_dataclass(Pet)

    >>> fields = {'id', 'category', 'name', 'photoUrls', 'tags', 'status'}
    >>> assert set(Pet.__dataclass_fields__.keys()) == fields, \
    f'Invalid fields, your fields should be: {fields}'

    >>> data = json.loads(DATA)
    >>> result = Pet(**data)

    >>> result  # doctest: +NORMALIZE_WHITESPACE
    Pet(id=0, category={'id': 0, 'name': 'dogs'}, name='doggie',
        photoUrls=['img/dogs/0.png'], tags=[{'id': 0, 'name': 'dog'},
                                            {'id': 1, 'name': 'hot-dog'}],
        status='available')
"""

from dataclasses import dataclass


DATA = """
{
  "id": 0,
  "category": {
    "id": 0,
    "name": "dogs"
  },
  "name": "doggie",
  "photoUrls": [
    "img/dogs/0.png"
  ],
  "tags": [
    {
      "id": 0,
      "name": "dog"
    },
    {
      "id": 1,
      "name": "hot-dog"
    }
  ],
  "status": "available"
}
"""


# Using `dataclass` model data to create class `Pet`
# type: type
@dataclass
class Pet:
    ...


Code 4.39. Solution
"""
* Assignment: Dataclass Definition ClassVar
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min

English:
    1. Define class User with:
        a. instance variable `age: int` (no default value)
        b. class variable `AGE_MIN: int` with default value `30`
        c. class variable `AGE_MAX: int` with default value `50`
    2. Use `dataclass`
    3. Use `typing`
    4. Run doctests - all must succeed

Polish:
    1. Zdefiniuj klasę User z polami klasowymi:
        a. zmienną instancji `age: int` (bez domyślnej wartości)
        b. zmienną klasy `AGE_MIN: int` z domyślną wartością `30`
        c. zmienną klasy `AGE_MAX: int` z domyślną wartością `50`
    2. Użyj `dataclass`
    3. Użyj `typing`
    4. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass
    >>> from dataclasses import _FIELD_CLASSVAR

    >>> assert isclass(User)
    >>> assert hasattr(User, '__annotations__')
    >>> assert hasattr(User, '__dataclass_fields__')

    >>> fields = User.__dataclass_fields__
    >>> assert 'age' in fields
    >>> assert 'AGE_MIN' in fields
    >>> assert 'AGE_MAX' in fields

    >>> assert fields['AGE_MIN']._field_type is _FIELD_CLASSVAR
    >>> assert fields['AGE_MAX']._field_type is _FIELD_CLASSVAR
"""
from dataclasses import dataclass
from typing import ClassVar


# Define class User with class variables:
# - instance variable `age: int` (no default value)
# - class variable `AGE_MIN: int` with default value `30`
# - class variable `AGE_MAX: int` with default value `50`
# Use `dataclass` and `typing`
# type: type[User]
@dataclass
class User:
    ...