Содержание:
- Ключевые магические атрибуты Python
- Паттерны рассорки и связанные с ними dunder-методы
- Распространённые ловушки и их решения
- Инструменты и утилиты для отладки
- Практические кейсы
При разработке крупных приложений на Python часто возникает необходимость разделить (расcортировать) объекты по категориям, слоям или микросервисам. Именно магические атрибуты (dunder-атрибуты) позволяют «подсветить» объекты для последующего автоматического анализа без необходимости вручную маркировать каждый класс.
Основные преимущества использования магических атрибутов
- Низкая связность кода: не нужно наследоваться от специального базового класса.
- Гибкость: можно добавлять или убирать категории без изменения кода классов.
- Интеграция с инструментами: линтеры, тест-раннеры и ORM-фреймворки «видят» эти метки во время импорта.
Ключевые магические атрибуты Python
__dict__ и __slots__
__dict__ позволяет динамически добавлять атрибуты, что удобно для меток рассорки, но неэкономно по памяти. __slots__ ограничивает перечень атрибутов, но экономит память и ускоряет доступ.
Пример гибридного подхода:
class ServiceLayerMeta(type):
def __new__(mcls, name, bases, namespace, *kw):
# Добавим служебный атрибут, только если он не задан
namespace.setdefault("_layer", kw.pop("layer", None))
# Создаём класс без __dict__, но с фиксированным слотом
namespace["__slots__"] = ("_layer", namespace.get("__slots__", ))
return super.__new__(mcls, name, bases, namespace, **kw)
class UserService(metaclass=ServiceLayerMeta, layer="services"):
pass
__qualname__ и __module__
Используются для определения модуля/пакета, к которому относится класс. Это критично, если рассорка происходи по пакетам.
Паттерны рассорки и связанные с ними dunder-методы
Паттерн «Аннотация» через __annotations__
PEP-526 позволяет добавлять аннотации типов, которые можно использовать как метки категории.
from typing import TYPE_CHECKING, Literal
Layer = Literal["services", "repositories", "controllers"]
class ProductController:
__annotations__: dict[str, Layer] = {"_layer": "controllers"}
Паттерн «Метакласс-маркер» через __init_subclass__
Позволяет автоматически регистрировать все подклассы определённого типа без явного импорта.
class RegistryMixin:
_registry: dict[str, type] = {}
def __init_subclass__(cls, *, category: str = "", kw):
super.__init_subclass__(kw)
if category:
RegistryMixin._registry[category] = cls
class OrderService(RegistryMixin, category="services"):
pass
Паттерн «Декоратор» через __wrapped__
Декораторы могут сохранять исходную функцию в __wrapped__, что позволяет позже собрать все декорированные функции определённой категории.
import functools
def endpoint(route: str):
def decorator(func):
@functools.wraps(func)
def wrapper(args, kw):
return func(args, kw)
wrapper.__route__ = route
return wrapper
return decorator
Распространённые ловушки и их решения
Затирание __dict__ при использовании __slots__
Если задать __slots__, то у экземпляра не будет __dict__ по умолчанию. Это мешает добавлять динамические метки. Решение — добавить "__dict__" в кортеж слотов:
class Repository:
__slots__ = ("__dict__", "_repo_tag")
Потеря контекста при сериализации
Метки, записанные в __dict__, могут не сериализоваться модулем pickle. Используйте __getstate__/__setstate__ для явного контроля.
Проблемы с производительностью при интроспекции
Поиск всех классов модуля через inspect.getmembers может быть медленным на больших проектах. Решение — кэшировать результаты в __module_cache__ и обновлять по importlib.reload.
Инструменты и утилиты для отладки
- inspect — стандартная библиотека для runtime-анализа.
- ast, разбор исходников без импорта; Полезно для статического анализа.
- importlib.metadata, получение метаданных пакетов (версия, точка входа).
- pydantic-resolve — библиотека для автоматического построения графов зависимостей через аннотации.
Практические кейсы
Кейс 1: Разделение ORM-моделей и Pydantic-схем по слоям
from sqlalchemy.orm import DeclarativeBase
from typing import Protocol, runtime_checkable
@runtime_checkable
class DomainModel(Protocol):
__tablename__: str
__layer__: str = "persistence"
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
__layer__ = "persistence" # метка для рассорки
...
Кейс 2: Автоматическое построение CLI-команд из функций-маркеров
import click
from typing import get_type_hints
def command(name: str):
def decorator(func):
func.__click_command__ = name
return func
return decorator
@command("sync-db")
def sync_database:
...
- Используйте
__slots__с"__dict__", если нужна совместимость с динамическими метками. - Следите за тем, чтобы магические атрибуты не переопределяли важные методы (
__hash__,__eq__). - Кэшируйте результаты интроспекции.
- Документируйте используемые dunder-атрибуты в docstring класса (
__doc__). - Покрывайте рассорку unit-тестами с использованием
importlib.reload.
Соблюдая эти тонкости, вы сможете безболезненно масштабировать проект и автоматизировать рассорку компонентов даже в больших командах.