Нашли ошибку?
Скачать чистый
урок 2 из 4≈ 20 минупражнения: 0 из 4

Настройки dataclass

__post_init__ проверяет поля после __init__, frozen=True делает экземпляры неизменяемыми и хешируемыми, order=True — сравнимыми, а replace создаёт изменённую копию.

Python запустится при первом выполнении

@dataclass сам пишет __init__ — а где тогда проверять значения, как сеттер в модуле 3? И как сделать объект, который нельзя изменить, чтобы он не «пропадал» из set? У декоратора есть настройки на эти случаи.

__post_init__: проверка после создания

Если в dataclass объявить метод __post_init__, сгенерированный __init__ вызовет его последним действием — когда все поля уже заполнены. Это место для проверок и для приведения значений к нужному виду:

[ ]Проверка объёма и название с большой буквы
from dataclasses import dataclass


@dataclass
class Cup:
    name: str
    volume: int

    def __post_init__(self):  # вызывается в конце __init__
        self.name = self.name.title()
        if self.volume < 50:
            raise ValueError(f"объём {self.volume} мл — меньше 50 мл не бывает")


print(Cup("капучино", 300))
try:
    Cup("эспрессо", 30)
except ValueError as error:
    print("ValueError:", error)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
Cup(name='Капучино', volume=300)
ValueError: объём 30 мл — меньше 50 мл не бывает

Учтите: __post_init__ проверяет значения только при создании. Присваивание позже, cup.volume = 10, пройдёт без проверки — сеттер из модуля 3 такое ловил. Если поле нельзя испортить и потом, сделайте экземпляр неизменяемым (frozen=True, ниже) или верните свойство с сеттером.

[ ]Упражнение 1Товар с проверками решено

Допишите dataclass Product (поля name и price): метод __post_init__ убирает пробелы по краям названия, а если название после этого пустое или цена не больше нуля — выбрасывает ValueError.

from dataclasses import dataclass


@dataclass
class Product:
    name: str
    price: int

    def __post_init__(self):
        ...

frozen=True: неизменяемый экземпляр

@dataclass(frozen=True) запрещает менять поля после создания: присваивание выбрасывает FrozenInstanceError (это подкласс AttributeError). Зато такой экземпляр хешируемый. Обычный dataclass — нет: он пишет __eq__ и, как в уроке о равенстве, отключает хеш. А с frozen=True dataclass сам пишет __hash__ из полей, и объект можно класть в set и делать ключом dict — не боясь, что он «потеряется» после изменения, как в уроке о равенстве. Запрет действует везде, даже в __post_init__: self.name = self.name.strip() в неизменяемом классе выбросит FrozenInstanceError — такой класс только проверяет поля, а не исправляет их.

[ ]Неизменяемый купон
@dataclass(frozen=True)
class Coupon:
    code: str
    percent: int


autumn = Coupon("ОСЕНЬ", 10)
try:
    autumn.percent = 50
except AttributeError as error:
    print(type(error).__name__, error)
used = {autumn, Coupon("ОСЕНЬ", 10), Coupon("ЗИМА", 15)}
print(len(used), Coupon("ОСЕНЬ", 10) in used)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
FrozenInstanceError cannot assign to field 'percent'
2 True
[ ]Упражнение 2Деньги — неизменяемые решено

Объявите dataclass Money с одним полем rubles (целое), неизменяемый. Проверьте себя: экземпляр не даёт менять rubles и годится как ключ словаря.

from dataclasses import dataclass


class Money:
    ...

replace: изменённая копия

Как «поменять» цену неизменяемого товара? Создать новый экземпляр с другой ценой. Функция replace из модуля dataclasses делает это коротко: копирует все поля, кроме названных:

[ ]Тот же купон, другой процент
from dataclasses import replace

winter = replace(autumn, code="ЗИМА", percent=15)
print(autumn)
print(winter)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
Coupon(code='ОСЕНЬ', percent=10)
Coupon(code='ЗИМА', percent=15)

order=True: сравнение и сортировка

@dataclass(order=True) пишет __lt__, __le__, __gt__ и __ge__: экземпляры сравниваются как кортежи своих полей — по первому полю, при равенстве — по второму и так далее. Поэтому порядок полей становится важен: сначала поле, по которому сортируют.

[ ]Упражнение 3Брони по времени решено

Объявите dataclass Booking с полями time (строка «ЧЧ:ММ») и name (имя гостя) так, чтобы брони сортировались по времени, а при одинаковом времени — по имени. Затем сохраните в ordered список имён из броней bookings, отсортированных по порядку.

from dataclasses import dataclass


class Booking:
    ...


bookings = [Booking("18:30", "Вера"), Booking("09:30", "Борис"), Booking("18:30", "Анна")]
ordered = ...

[ ]Упражнение 4Подорожание без изменения решено

Товары menu — неизменяемые (Price из заготовки). Сохраните в new_menu новый список, где у каждого товара цена выше на 10 %, округлённая до целых. Исходный список и его товары не меняются.

from dataclasses import dataclass, replace


@dataclass(frozen=True)
class Price:
    name: str
    price: int


menu = [Price("Латте", 220), Price("Чай", 120), Price("Какао", 190), Price("Маффин", 133)]
new_menu = ...

Вопрос

Что верно для @dataclass(frozen=True)?

Нашли ошибку?