Модуль 6 · Урок 21 із 58
Classes й objects: стан, methods та invariants
Клас корисний не тому, що групує кілька functions, а тому, що тримає допустимий стан і поведінку разом. Добрий object не дозволяє створити себе напіввалідним, не ділить випадково mutable state з іншими instances і має малий зрозумілий public API.
Безпечна практика модуля 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 == 1class створює новий 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 FalseInvariant перевіряється на boundary
Invariant — умова, яка має бути істинною для кожного валідного instance. Якщо priority дозволена лише від 1 до 3, object не повинен існувати з 0 або 99. Перевірка лише в UI слабка: class можуть викликати test, importer або інший service.
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 = priorityallowed_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 часто чесніша й простіша.
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.
Закріпіть матеріал уроку
Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.