4.1. Dataclass About¶
Used for easier class definition
Since Python 3.7: PEP 557 -- Data Classes
This are not static fields!
Dataclasses require Type Annotations
SetUp:
>>> from dataclasses import dataclass
Class:
>>> class User:
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
Dataclass:
>>> @dataclass
... class User:
... firstname: str
... lastname: str
4.1.1. Problem¶
>>> class User:
... firstname: str
... lastname: str
...
... def __init__(self, firstname: str, lastname: str) -> None:
... self.firstname = firstname
... self.lastname = lastname
...
... def __str__(self) -> str:
... return f'{self.firstname} {self.lastname}'
...
... def __repr__(self):
... clsname = self.__class__.__name__
... firstname = self.firstname
... lastname = self.lastname
... return f'{clsname}({firstname=}, {lastname=})'
...
... def __eq__(self, other):
... return self.__class__ is other.__class__ \
... and self.firstname == other.firstname \
... and self.lastname == other.lastname
To add field middlename: str we need to make change in 7 different places:
>>> class User:
... firstname: str
... middlename: str # 1
... lastname: str
...
... def __init__(self, firstname: str, middlename: str, lastname: str) -> None: # 2
... self.firstname = firstname
... self.middlename = middlename # 3
... self.lastname = lastname
...
... def __str__(self) -> str:
... return f'{self.firstname} {self.middlename} {self.lastname}' # 4
...
... def __repr__(self):
... clsname = self.__class__.__name__
... firstname = self.firstname
... middlename = self.middlename # 5
... lastname = self.lastname
... return f'{clsname}({firstname=}, {middlename=}, {lastname=})' # 6
...
... def __eq__(self, other):
... return (
... self.__class__ is other.__class__ and
... self.firstname == other.firstname and
... self.middlename == other.middlename and # 7
... self.lastname == other.lastname
... )
4.1.2. Solution¶
>>> @dataclass
... class User:
... firstname: str
... lastname: str
To add field middlename: str we need to make change in 1 place:
>>> @dataclass
... class User:
... firstname: str
... middlename: str # 1
... lastname: str
4.1.3. Use Case - 0x01¶
>>> from dataclasses import dataclass
>>> from datetime import date
>>> from typing import Literal, Self
>>>
>>>
>>> @dataclass
... class Group:
... gid: int
... name: str
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
... email: str
... username: str
... password: str
... birthdate: date | None = None
... height: int | float | None = None
... weight: int | float | None = None
... role: Literal['admin', 'user', 'guest'] = 'user'
... friends: list[Self] | None = None
... groups: list[Group] | None = None
4.1.4. Use Case - 0x02¶
>>> from dataclasses import dataclass, field, asdict
>>> from datetime import date, time, datetime, timezone, timedelta
>>> from pprint import pprint
>>> from typing import ClassVar
>>> import pickle
>>>
>>>
>>> @dataclass
... class Group:
... gid: int
... name: str
>>>
>>>
>>> @dataclass(frozen=True)
... class User:
... firstname: str
... lastname: str
... email: str | None = None
... birthdate: date | None = None
... height: int | float | None = field(default=None, metadata={'unit': 'cm', 'min': 156, 'max': 210})
... weight: int | float | None = field(default=None, metadata={'unit': 'kg', 'min': 50, 'max': 90})
... groups: list[Group] = field(default_factory=list)
... account_type: str = field(default='user', metadata={'choices': ['guest', 'user', 'admin']})
... account_created: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
... account_modified: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
... account_lastlogin: datetime | None = None
... account_expiration: timedelta | None = None
... AGE_MIN: ClassVar[int] = 30
... AGE_MAX: ClassVar[int] = 50
>>> mark = User(
... firstname='Mark',
... lastname='Watney',
... email='mwatney@nasa.gov',
... birthdate=date(1969, 4, 12),
... height=178.0,
... weight=75.5,
... groups=[Group(gid=1, name='users'), Group(gid=2, name='staff')],
... account_type='user',
... account_created=datetime(1969, 7, 21, 2, 56, 15, 0, tzinfo=timezone.utc),
... account_modified=datetime(1969, 7, 21, 2, 56, 15, 0, tzinfo=timezone.utc),
... account_lastlogin=None,
... account_expiration=None,
... )
4.1.5. Use Case - 0x03¶
>>> from dataclasses import dataclass
>>> from itertools import starmap
>>>
>>>
>>> DATA = [
... ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
... (5.8, 2.7, 5.1, 1.9, 'virginica'),
... (5.1, 3.5, 1.4, 0.2, 'setosa'),
... (5.7, 2.8, 4.1, 1.3, 'versicolor'),
... (6.3, 2.9, 5.6, 1.8, 'virginica'),
... (6.4, 3.2, 4.5, 1.5, 'versicolor'),
... (4.7, 3.2, 1.3, 0.2, 'setosa'),
... ]
>>>
>>> @dataclass
... class Iris:
... sepal_length: float
... sepal_width: float
... petal_length: float
... petal_width: float
... species: str
>>>
>>> result = starmap(Iris, DATA[1:])
>>> list(result)
[Iris(sepal_length=5.8, sepal_width=2.7, petal_length=5.1, petal_width=1.9, species='virginica'),
Iris(sepal_length=5.1, sepal_width=3.5, petal_length=1.4, petal_width=0.2, species='setosa'),
Iris(sepal_length=5.7, sepal_width=2.8, petal_length=4.1, petal_width=1.3, species='versicolor'),
Iris(sepal_length=6.3, sepal_width=2.9, petal_length=5.6, petal_width=1.8, species='virginica'),
Iris(sepal_length=6.4, sepal_width=3.2, petal_length=4.5, petal_width=1.5, species='versicolor'),
Iris(sepal_length=4.7, sepal_width=3.2, petal_length=1.3, petal_width=0.2, species='setosa')]