1. Продвинутый синтаксис и модель данных Python
Продвинутый синтаксис и модель данных Python
Эта статья открывает курс Python: углубленный курс и задаёт фундамент: как читать и писать современный Python-код и как интерпретатор на самом деле работает с вашими объектами. Дальше в курсе мы будем опираться на эти знания при проектировании API, написании библиотечного кода, оптимизации, тестировании и работе с асинхронностью.
Ключевая мысль: в Python почти всё — это объект, а поведение объектов описывается моделью данных через специальные методы (их ещё называют dunder-методы от double underscore, например __len__).
Продвинутый синтаксис, который встречается в промышленном коде
Распаковка и расширенная распаковка
Распаковка позволяет «разложить» составные структуры (кортежи, списки, итераторы) по переменным.
Частые применения:
Сопоставление с образцом (structural pattern matching)
Начиная с Python 3.10 появился оператор match, который делает ветвление по форме данных.
Важно:
match не заменяет полностью if/elif, но делает код чище, когда проверяется структура данныхИсточник: Документация Python: match statement
Выражение присваивания (оператор :=)
Оператор морж := позволяет присвоить значение прямо внутри выражения. Это полезно, когда значение нужно и для проверки, и для использования.
Используйте умеренно: если выражение становится тяжело читаемым, лучше сделать обычное присваивание отдельной строкой.
Источник: PEP 572 — Assignment Expressions
F-строки и отладочное представление
F-строки — стандарт де-факто для форматирования строк в современном Python.
Полезные приёмы:
!r — использовать repr() (удобно для отладки):.2f для чисел с плавающей точкойИсточник: Документация Python: f-strings
Модель данных Python: как язык «вызывает» ваш код
Что такое модель данных
Модель данных Python — это набор правил, описывающих, как объекты ведут себя в базовых операциях:
+, *)==, <)obj.x)in, индексация)for)str(), repr())Главный практический вывод: большинство возможностей языка — это синтаксический сахар над вызовами специальных методов.
Источник: Документация Python: Data model
!Схема показывает, какие специальные методы стоят за привычным синтаксисом
Специальные методы: читаем код через «перевод» в dunder
Некоторые соответствия:
| Синтаксис | Что фактически происходит |
|---|---|
| len(x) | x.__len__() |
| x[i] | x.__getitem__(i) |
| x[i] = v | x.__setitem__(i, v) |
| v in x | x.__contains__(v) (или перебор через итератор) |
| for v in x | iter(x) → x.__iter__() → __next__() |
| x + y | x.__add__(y) (или обратный y.__radd__(x)) |
| x == y | x.__eq__(y) |
| with cm: | cm.__enter__() / cm.__exit__(...) |
Это важно для:
Атрибуты, методы и протокол доступа: где Python ищет obj.x
Когда вы пишете obj.x, Python не просто берёт поле из объекта. Он следует правилам поиска атрибутов.
Упрощённо:
property). Такие объекты называют дескрипторами.obj.__dict__).__getattr__ (если определён).__getattribute__ и __getattr__
__getattribute__(self, name) вызывается всегда при доступе к атрибуту. Ошибка в нём легко приводит к бесконечной рекурсии.__getattr__(self, name) вызывается только если обычный поиск атрибута не нашёл значение.Дескрипторы и property: управляем доступом к полям
Дескриптор — это объект, который реализует один или несколько методов __get__, __set__, __delete__. Если такой объект лежит в атрибутах класса, Python использует его для управления доступом.
Самый распространённый дескриптор — property, с которым вы создаёте вычисляемые атрибуты и валидацию.
Источник: Документация Python: Descriptors
Итераторы и генераторы: протокол итерации
Итераторный протокол
Чтобы объект был итерируемым, обычно достаточно:
__iter__, который возвращает итератор__next__, который возвращает следующий элемент или выбрасывает StopIterationПример собственного итератора:
Генераторы
Генератор — это функция с yield, которая автоматически реализует протокол итератора.
Практический критерий:
Источник: Документация Python: Generators
Контекстные менеджеры: протокол with
Контекстный менеджер гарантирует корректную настройку и освобождение ресурса.
__enter__ выполняется при входе в with__exit__ выполняется при выходе (даже при исключении)На практике чаще используют стандартную библиотеку contextlib.
Источник: Документация Python: contextlib
dataclass: меньше шаблонного кода, больше смысла
dataclasses (Python 3.7+) позволяют объявлять классы-«контейнеры данных» с автогенерацией __init__, __repr__, сравнений.
Что здесь происходит:
frozen=True делает объект неизменяемым (попытка присвоить p.x = ... приведёт к ошибке)slots=True убирает обычный __dict__ у экземпляра и экономит память, а также ускоряет доступ к атрибутам в некоторых сценарияхИсточник: Документация Python: dataclasses
__repr__ и __str__: интерфейс для людей и отладки
__str__ — «человекочитаемая» строка (используется print())__repr__ — отладочное представление (используется в REPL и repr()), по хорошей практике максимально однозначноеПрактика для библиотечного кода: делайте хороший __repr__, потому что он экономит часы отладки.
Сравнение, хеширование и неизменяемость
Сравнения и хеширование важны для корректной работы:
set и ключей dictБазовые правила:
__eq__, подумайте, должен ли объект быть хешируемым (тогда нужен согласованный __hash__)В dataclass это частично решается опциями frozen=True и настройками генерации сравнений.
Частые ошибки при работе с моделью данных
__getattribute__ без крайней необходимостиTrue из __exit__ «на всякий случай» и тем самым скрывать ошибки__hash____repr__, которые могут выполняться очень часто (например, при логировании)Итоги
В этой статье вы:
match, :=, f-строки)propertydataclass, а также с идеями неизменяемости и slotsДальше по курсу мы будем использовать эту базу для проектирования классов и API, написания надёжного кода и осмысленной оптимизации.