Async-инициализация в Python: какие есть подходы
В Python нет нативной поддержки асинхронного __init__, поэтому приходится искать обходные пути, если объект требует асинхронной инициализации (например, получения ресурса через await get_resource()).
⬇️ Вот какие стратегии чаще всего используют — с плюсами и минусами.
1️⃣ @classmethod async def initialize()
class Klass:
def __init__(self, resource):
self.resource = resource
@classmethod
async def initialize(cls):
resource = await get_resource()
return cls(resource)
🌥 Плюсы:
— Лаконично
— Хорошо отделяет sync и async логику
— Удобно для тестов
🌥 Минусы:
— Нет устоявшейся конвенции
— Может быть неочевидно для команды
🌥 Подходит для простых случаев без необходимости в очистке ресурсов.
2️⃣ Асинхронный контекстный менеджер (__aenter__ / __aexit__)
class Klass:
async def __aenter__(self):
self.resource = await get_resource()
return self
async def __aexit__(self, exc_type, exc, tb):
pass
🌥 Плюсы:
— Устоявшийся паттерн (async with)
— Удобно добавлять логику очистки в будущем
🌥 Минусы:
— Нужно писать async with при каждом использовании
— Не всегда удобно, если объект нужен за пределами контекста
🌥 Подходит, если нужно управлять жизненным циклом ресурса.
3️⃣ Инициализация в фоне через create_task()
class Klass:
def __init__(self):
self.ready_event = asyncio.Event()
asyncio.create_task(self._load())
async def _load(self):
self.resource = await get_resource()
self.ready_event.set()
async def use(self):
await self.ready_event.wait()
await do_something_with(self.resource)
🌥 Плюсы:
— Можно запускать загрузку параллельно с другими задачами
— Подходит для высоконагруженных систем
🌥 Минусы:
— Сложнее в отладке
— Нужно явно проверять await ready_event.wait() — легко забыть
— Нет встроенного механизма очистки
🌥 Хорошо подходит для внутренних компонентов или фреймворков, где поведение контролируется централизованно.
4️⃣ Внешний async-фабричный метод / билдер
Просто выносим всю асинхронную инициализацию за пределы класса.
🌥 Плюсы:
— Чистый и легко тестируемый код
— Гибко масштабируется
🌥 Минусы:
— Логика разнесена по разным местам
— Использование может стать чуть более многословным
🌥 Идеально, если важна читаемость и разделение ответственности.
5️⃣ await instance.ready()
Гибридный подход: конструктор sync, а использование — с явной асинхронной инициализацией.
klass = Klass()
await klass.ready()
🌥 Позволяет разделить создание и инициализацию, сохраняя контроль над потоком.
6️⃣ Запрет обычного __init__, только async-конструктор
class Klass:
def __new__(cls, *args, **kwargs):
raise RuntimeError("Используйте `await Klass.create()`")
@classmethod
async def create(cls):
self = super().__new__(cls)
self.resource = await get_resource()
return self
🌥 Форсирует корректное использование через async-интерфейс.
▫️ Какой подход выбрать:
🤖 initialize() — простая async-инициализация без очистки
🤖 __aenter__/__aexit__ — нужна очистка или сложный жизненный цикл
🤖 create_task() + Event — нужно запускать инициализацию в фоне
🤖 Async factory — тестируемость, масштабируемость
🤖 .ready() — чистое разделение этапов
🤖 __new__ + async — строгий контроль над созданием
Библиотека питониста #буст