Python
Asyncio
__init__ method
Class Attributes
await in Constructors

How to set class attribute with await in __init__

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Python, __init__ cannot be async, so you cannot directly await inside object construction. This often surprises developers when attributes depend on async I/O such as API calls or database reads.

The standard solution is to keep __init__ synchronous and use an async factory or async initialization method. This preserves clear lifecycle semantics and keeps object creation explicit.

Core Sections

1. Use async class factory

python
1class Client:
2    def __init__(self, token: str):
3        self.token = token
4        self.profile = None
5
6    @classmethod
7    async def create(cls, token_provider):
8        token = await token_provider()
9        self = cls(token)
10        self.profile = await fetch_profile(token)
11        return self

Callers do client = await Client.create(...).

2. Two-phase init pattern

python
1class Worker:
2    def __init__(self):
3        self.config = None
4
5    async def init_async(self):
6        self.config = await load_config()
7        return self
8
9worker = await Worker().init_async()

This works but is easier to misuse than a factory.

3. Dataclass-friendly option

With dataclasses, keep async loading external and pass ready values into constructor.

python
1from dataclasses import dataclass
2
3@dataclass
4class Service:
5    cfg: dict
6
7cfg = await load_config()
8svc = Service(cfg)

Constructor remains deterministic and simple.

4. Error handling and cancellation

Async initialization can fail midway. Decide whether partially initialized instances can exist or if creation should fail atomically. Factories make this contract clearer.

5. Build a repeatable validation checklist

Once the implementation is in place, create a deterministic validation checklist for async object initialization in Python. At minimum, include one baseline scenario, one edge-case scenario, and one failure-path scenario with expected outcomes documented in plain language. This prevents knowledge from staying implicit and reduces the risk of regressions during dependency updates or refactors.

A useful checklist also captures runtime assumptions: framework versions, SDK versions, configuration flags, and environment variables required for a successful run. Many teams skip this because the setup seems obvious during initial development, but those hidden assumptions are usually what break first when code moves to CI, staging, or another developer machine.

text
1validation checklist
2- baseline case with expected output and key fields
3- edge case with constrained or unusual input
4- failure case with expected error handling behavior
5- recorded runtime and dependency assumptions

Keep this checklist versioned with code. If behavior changes, update the expected outputs in the same pull request so future debugging has an authoritative reference for what changed and why.

6. Operational hardening and maintenance

Long-term reliability for async object initialization in Python requires observability and explicit ownership. Add targeted logs and metrics around critical steps so incident responders can quickly identify whether failures come from input quality, environment drift, external service dependencies, or code regressions. Without these signals, most incident time is lost reconstructing context instead of fixing root causes.

Define maintenance routines for upgrades and compatibility checks. Libraries and platforms evolve continuously, and subtle behavior changes are common. Lightweight smoke tests should run regularly, not only during feature work, to catch drift before it reaches production.

bash
# example recurring check command
make smoke-test

Finally, document rollback criteria in advance. If a deployment changes async object initialization in Python behavior unexpectedly, teams should know when to roll back immediately versus when to hot-fix forward. This converts operational response from guesswork into a controlled process and improves overall system resilience.

Common Pitfalls

  • Trying to declare async def __init__ and expecting Python to support it.
  • Exposing objects before async fields are fully initialized.
  • Using two-phase init without enforcing await init_async().
  • Swallowing async init exceptions and leaving invalid object state.
  • Coupling network I/O directly into constructors with hidden side effects.

Summary

You cannot await in __init__, so use async factories or explicit async initialization methods. Prefer patterns that keep object validity clear and easy to enforce. A predictable initialization contract prevents subtle runtime bugs in async Python codebases.


Course illustration
Course illustration

All Rights Reserved.