Модуль 6 · Урок 21 із 58

Classes й objects: стан, methods та invariants

Клас корисний не тому, що групує кілька functions, а тому, що тримає допустимий стан і поведінку разом. Добрий object не дозволяє створити себе напіввалідним, не ділить випадково mutable state з іншими instances і має малий зрозумілий public API.

class__init__instance stateinvariant

Безпечна практика модуля 6

Пакет містить тільки synthetic records, validated value objects, composition, Protocol, deterministic summary і локальні tests. Жодних персональних чи production-даних, secrets, мережевих викликів або зовнішніх залежностей.

Завантажити практичний пакет →

Class визначає protocol створення і поведінки

class SupportCase:
    def __init__(self, case_id: str, priority: int) -> None:
        if not case_id.strip():
            raise ValueError("case_id required")
        if priority not in {1, 2, 3}:
            raise ValueError("priority must be 1..3")
        self.case_id = case_id.strip()
        self.priority = priority

    def is_urgent(self) -> bool:
        return self.priority == 1

class створює новий type, а виклик SupportCase(...) створює instance. __init__ не «повертає object»: він ініціалізує вже створений instance. Параметр self — explicit reference на конкретний object, для якого викликано method.

Instance state має належати конкретному object

Attributes, записані через self, зберігаються окремо в кожному instance. Method call case.is_urgent() концептуально передає case як перший argument. Це binding дозволяє одному class definition працювати з багатьма незалежними objects.

first = SupportCase("case-01", 1)
second = SupportCase("case-02", 3)

assert first.is_urgent() is True
assert second.is_urgent() is False

Invariant перевіряється на boundary

Invariant — умова, яка має бути істинною для кожного валідного instance. Якщо priority дозволена лише від 1 до 3, object не повинен існувати з 0 або 99. Перевірка лише в UI слабка: class можуть викликати test, importer або інший service.

Contract: constructor приймає documented values, normalizes лише безпечні representation differences і відхиляє стан, для якого methods не можуть дати чесний result.

Class attribute і instance attribute — різні рівні

class SupportCase:
    allowed_priorities = frozenset({1, 2, 3})

    def __init__(self, case_id: str, priority: int) -> None:
        self.case_id = case_id
        self.priority = priority

allowed_priorities описує спільну незмінну policy class. case_id і priority належать instance. Lookup спочатку перевіряє instance, потім class hierarchy; assignment через self.name зазвичай створює або змінює instance attribute.

Mutable class attribute створює прихований shared state

class BadQueue:
    items = []  # один list для всіх instances

class Queue:
    def __init__(self) -> None:
        self.items: list[str] = []

У BadQueue append через один object видно в іншому. Це типова production-помилка, бо tests із одним instance її не ловлять. Mutable container створюють у __init__; справді спільну configuration краще робити immutable.

Object не повинен бути просто мішком полів

Якщо caller постійно змінює attributes у довільному порядку, invariant легко зламати. Додайте methods, що називають domain operation: escalate(), close(), assign(). Не робіть class для stateless calculation: звичайна function часто чесніша й простіша.

StateЛише дані конкретного instance.
InvariantНедопустимий object не створюється.
BehaviorMethods називають domain actions.
TestДва instances доводять isolation.

Definition of Done

  • Constructor має documented parameters і не повертає value.
  • Кожний instance одразу валідний або creation завершується exception.
  • Mutable state не лежить у class attribute випадково.
  • Public methods зберігають invariants після кожної operation.
  • Tests перевіряють valid, invalid та two-instance isolation cases.

Методичні джерела

Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть лише на офіційну документацію Python.

Практична перевірка · урок 21 з 47

Закріпіть матеріал уроку

Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.

1. У domain object priority дозволена лише від 1 до 3. Де найнадійніше зупинити створення instance з priority=99?
2. Що фактично робить __init__ під час виклику SupportCase('case-01', 1) у звичайному Python class?
3. Два objects одного class повинні мати незалежні lists. Яке розміщення list найкраще гарантує isolation?