What does Python do if a class's `__init__` returns something other than None?
answer
- The call already has its object
- Two steps, only one makes the instance
- Initializing is not constructing
- CPython checks the initializer's result
- TypeError: should return None
basics
~20 sPython raises TypeError: init() should return None. Initialization is not what produces the object — the instance a class call hands back comes from new, so a return value from init would have nowhere to go.
solid answer
~40 sCalling `MyClass(...)` runs `type.__call__`, which first asks `MyClass.__new__` for an instance and then calls `__init__` on that instance purely for its side effects. The call already has its object, so CPython enforces that `__init__` evaluates to `None` and raises `TypeError: __init__() should return None, not 'Point'` at call time for anything else. A bare `return` or an early `return` inside `__init__` is fine — those produce `None`. If a call should hand back some other object, that decision belongs in `__new__` or in a plain factory function, never in `__init__`. The same rule is why `__init__` cannot report failure with a status value: it raises an exception instead, and the half-built instance is discarded.
code
python · 10 linesclass Point:
def __init__(self, x):
self.x = x
return self
try:
Point(1)
except TypeError as exc:
print(type(exc).__name__, "->", exc)go deeper
Recall the one-line rule and the exact error: __init__ must produce None, and anything else raises TypeError when the class is called. Be able to say that the instance itself comes from __new__.
Explain the mechanics: type.__call__ calls __new__, then __init__ on the result, and discards the initializer's value. Show where a substituted or cached object would actually be returned instead, and why async def __init__ fails for the same reason.
Demonstrate the production habit: construction reports failure by raising, arguments are validated before any state is assigned, and anything acquired mid-initializer is released on the failure path rather than leaked into an object nobody will ever reference.
Own the API-shape argument. Decide when construction should stay trivial and total, with awaitable or fallible work moved into explicit factories and start-up calls, so callers are never handed objects whose validity depends on side effects hidden in an initializer.
Calling a class in Python is not one operation, it is a small protocol. `MyClass(a, b)` is `type(MyClass).__call__(MyClass, a, b)` — normally `type.__call__` — and that method does roughly this: ```python obj = MyClass.__new__(MyClass, a, b) if isinstance(obj, MyClass): type(obj).__init__(obj, a, b) return obj ``` Two consequences fall straight out of those four lines. First, the object the caller receives is the one `__new__` produced. `__init__` gets handed an already-existing instance and can only mutate it; its own result is discarded before the `return`. Second, because that result is discarded, returning anything but `None` from `__init__` can only mean the author believes it is the thing that builds and hands back the object. CPython treats that as an error rather than letting the misunderstanding pass silently, so it raises `TypeError: __init__() should return None, not 'Point'` — at call time, not at class-definition time, since nothing is checked until the initializer actually runs. **What is still allowed.** The rule is about the *value*, not about the `return` statement. A bare `return` used to leave early is perfectly normal and evaluates to `None`, and so does falling off the end of the body. An explicit `return None` is legal but noise. Only a genuine value — `return self`, `return True`, `return 42` — trips the check. **The instinct behind the mistake.** Two habits produce it. One is the builder or fluent style common in other languages, where every setup method ends with `return self` so calls can be chained; in Python the chain would be pointless because `MyClass(...)` already evaluates to the instance, and `__init__`'s value never reaches the caller. The other is treating `__init__` as a factory that may decide to hand back a cached or substituted object. That decision is real and legitimate, but it belongs one step earlier, in `__new__`, whose return value *is* what the call produces. A module-level factory function or an alternative-constructor method is often the clearer home for it, because a plain function may return whatever it likes without touching the construction protocol at all. **How failure is reported.** Since no value can escape, `__init__` signals a bad argument or an unusable configuration by raising. `type.__call__` propagates the exception, so no reference to the half-built instance ever reaches the caller and it becomes garbage. Validate as early in the body as possible: everything assigned before the raise is state on an object that is about to be thrown away. If the initializer acquires something that must be released — a file handle, a socket — guard the rest of the body with `try`/`except`, release it there, and re-raise, or move ownership out of construction entirely so the caller can use a context manager. **The same rule blocks `async def __init__`.** Marking the initializer `async` does not make construction awaitable; it makes `__init__` return a coroutine object, so the very same check fires with `TypeError: __init__() should return None, not 'coroutine'`. Asynchronous setup is therefore done with an async factory — a method or module-level function that creates the instance, awaits what it needs, and returns it — or by keeping the awaitable work out of construction and into an explicit `await obj.start()`. **A related surprise about arguments.** `object.__init__` and `object.__new__` also police what they are given, and their rules interlock. If a class overrides neither and is called with arguments, `TypeError: MyClass() takes no arguments` is raised. If it overrides `__init__` but not `__new__`, the extra arguments reaching `object.__new__` are ignored — that is the ordinary case every class relies on. If it overrides `__new__` but not `__init__`, the mirror case holds and `object.__init__` ignores them. The trap is passing the call's arguments upward yourself: `super().__new__(cls, name)` from an overridden `__new__` raises `TypeError: object.__new__() takes exactly one argument (the type to instantiate)`, because `object.__new__` wants only the class. **`__init__` can also run more than once.** Nothing about the protocol promises one call per object. Invoking `obj.__init__(...)` by hand re-runs it, and — more importantly in real code — a `__new__` that returns a cached or shared instance still satisfies the `isinstance` check, so `type.__call__` initializes that same object again. Writing `__init__` so it is safe to run twice, or moving one-time setup into `__new__`, is what keeps that from corrupting state. The summary a candidate should be able to give in one breath: `__init__` initializes, it does not construct; construction is `__new__`'s job; and because the caller's object never comes from `__init__`, Python insists its result be `None`.
- Is a bare `return` inside `__init__` also an error?No. The check is on the value, not the statement. A bare `return` evaluates to `None`, exactly like falling off the end of the body, so it is the normal way to leave an initializer early. Only a real value — `return self`, `return True`, `return 42` — raises `TypeError`. An explicit `return None` is legal too, just redundant.
- If `__init__` cannot return a status, how should it report that construction failed?By raising. `type.__call__` propagates the exception, so no reference to the half-built instance reaches the caller and it is collected. Validate arguments at the top of the body, before assigning state you are about to throw away. If the initializer already acquired a resource, release it in an `except` block and re-raise, or move that ownership out of construction so the caller can use a context manager.
- Why can't you write `async def __init__` to await work during construction?Because an `async def` function returns a coroutine object, so `__init__` would evaluate to a coroutine and hit the same check: `TypeError: __init__() should return None, not 'coroutine'`. The usual answers are an async factory — a function or alternative-constructor method that builds the object, awaits what it needs and returns it — or keeping the awaitable work in an explicit `await obj.start()` after construction.
saying these in an interview costs you the question
- Calls __init__ the constructor that creates and returns the object
- Returns self from __init__ to allow call chaining
- Thinks the class call returns whatever __init__ returns
- Signals a construction failure with a return value instead of raising
- Believes the None rule is a style convention, not enforced
- Expects async def __init__ to make construction awaitable