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

Encapsulation: property, classmethod і API boundary

Encapsulation у Python — не спроба зробити дані магічно недоступними. Це домовленість про public API: які names підтримуються, де перевіряються invariants і які implementation details caller не повинен використовувати як contract.

encapsulationpropertyclassmethodAPI

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

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

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

Один underscore означає non-public convention

class WorkItem:
    def __init__(self, minutes: int) -> None:
        self._minutes = self._validate_minutes(minutes)

    @staticmethod
    def _validate_minutes(value: int) -> int:
        if value < 0:
            raise ValueError("minutes must be non-negative")
        return value

Name _minutes технічно доступний, але сигналізує: implementation detail може змінитися. Python не має абсолютних private instance variables. Double underscore запускає name mangling для уникнення випадкового collision у subclasses, а не створює security boundary.

Property додає поведінку до attribute interface

class WorkItem:
    def __init__(self, minutes: int) -> None:
        self._minutes = self._validate_minutes(minutes)

    @property
    def minutes(self) -> int:
        return self._minutes

    @minutes.setter
    def minutes(self, value: int) -> None:
        self._minutes = self._validate_minutes(value)

Caller читає item.minutes, а class зберігає validation і representation control. Property виправдана, коли attribute semantics природні. Не ховайте під property повільний network call або destructive operation: syntax виглядає як дешеве читання.

Read-only property захищає derived state від розсинхронізації

@property
def hours(self) -> float:
    return self._minutes / 60

Якщо hours обчислюється з minutes, зберігати обидва values небезпечно: один можуть оновити без другого. Property without setter робить derived value read-only через public interface. Це не криптографічний захист, але чіткий maintenance contract.

classmethod — alternative constructor для самого class

class WorkItem:
    @classmethod
    def from_record(cls, record: dict[str, object]) -> "WorkItem":
        return cls(minutes=int(record["minutes"]))

classmethod отримує cls, тому subclass може створити свій type через inherited constructor. Adapter має виконати format-specific parsing, а canonical __init__ — остаточну invariant validation. Не копіюйте правила у кілька constructors.

staticmethod не бачить ні instance, ні class

Static method — function у namespace class. Вона доречна лише коли operation концептуально близька до type, але не потребує self чи cls. Якщо helper використовується ширше або не належить domain concept, module-level function простіша для повторного використання й test.

@staticmethod
def _validate_minutes(value: int) -> int:
    if isinstance(value, bool) or not isinstance(value, int):
        raise TypeError("minutes must be int")
    return value

Public API має бути малим і стабільним

Caller повинен знати constructor, documented properties і domain methods, але не layout internal fields. Не повертайте mutable internal list напряму: caller зможе обійти validation. Поверніть tuple, iterator або copy відповідно до performance contract.

Boundary: underscore — maintenance convention, property — controlled attribute behavior, classmethod — named creation path. Жоден із них не є authorization або secret storage.

Definition of Done

  • Public і non-public names відділені та документовані.
  • Property не приховує дорогий I/O або несподівані side effects.
  • Alternative constructor делегує canonical invariant checks.
  • Derived state не дублюється у mutable fields.
  • Internal mutable collections не витікають через public API.

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

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

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

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

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

1. Що в Python зазвичай сигналізує name _cache з одним leading underscore у public module або class?
2. Навіщо використовувати @property для minutes, якщо public interface має виглядати як звичайне читання attribute?
3. Derived field hours завжди дорівнює minutes/60. Який design не дозволяє двом values розсинхронізуватися?