What does typing.Self mean as a return annotation on a Python method?
answer
- It is about inheritance, not run time
- Chained calls that hand the object back
- The class the call was actually made on
- A special form in typing, since 3.11
basics
~20 styping.Self is a special form meaning "whatever class the instance actually is", not the class the method is written in. Annotating a self-returning method with it keeps a subclass's own type through a chained call.
solid answer
~40 s`typing.Self`, added in Python 3.11 by PEP 673, is a special form standing for the type of the receiver — the class the call was actually made on. Use it wherever a method hands the caller back the same object, or a new object of the same class: fluent builder steps that `return self`, `@classmethod` alternative constructors that `return cls(...)`, and `__enter__` returning the object a `with` block binds. Its whole value is inheritance: inside a subclass `Self` resolves to the subclass, so a chain such as `ScoredRule().tagged("fraud").with_threshold(0.9)` still type-checks even though `tagged` is inherited. At run time `Self` does nothing — it is erased like every annotation, and nothing verifies that the method really returns the receiver. Before 3.11 the same effect needed a type variable bounded by the class.
code
python · 24 linesfrom typing import Self
class Query:
def __init__(self) -> None:
self.parts: list[str] = []
def where(self, clause: str) -> Self:
self.parts.append(clause)
return self
def limit(self, n: int) -> Self:
self.parts.append(f"LIMIT {n}")
return self
class AuditedQuery(Query):
def audited_by(self, who: str) -> Self:
self.parts.append(f"AUDIT {who}")
return self
q = AuditedQuery().where("score > 0.9").limit(10).audited_by("risk")
print(q.parts)go deeper
Be ready to recognise typing.Self in a signature and say what it stands for: the class of the object the method was called on. Knowing that a builder step returning Self is what makes .a().b().c() chains check is enough at this level.
Explain that a checker resolves Self per call site, so it becomes the subclass inside a subclass, and that it is erased at run time. Be able to place it correctly on an instance method, a @classmethod and an __enter__.
Show you can read an existing typed codebase: spot a self-returning method annotated with its own class name, explain the subclass breakage it causes, and land the fix without changing any run-time behaviour.
Own the API-surface question. Promising Self across your base classes commits every self-returning method to constructing the receiver's class rather than a fixed one, which is a rule subclass authors then depend on.
### What the annotation has to express A method that ends in `return self` — a fluent builder step, an `__enter__`, an in-place mutator that hands the object back so calls can be chained — has a return type that cannot be written down as a fixed class. The honest answer is "whatever class the caller was actually holding", and until Python 3.11 the type system had no direct way to say that. `typing.Self`, specified by PEP 673, is that way: a special form, valid only inside a class body, that a checker re-resolves at every call site to the type of the receiver. Concretely, given ```python from typing import Self class Query: def where(self, clause: str) -> Self: self.parts.append(clause) return self ``` a checker reading `Query().where("x")` types the expression as `Query`. Reading `AuditedQuery().where("x")`, where `AuditedQuery` subclasses `Query` and does not override `where`, it types the expression as `AuditedQuery`. One annotation, two answers, and nothing duplicated into the subclass. ### The three canonical places it belongs **Fluent chains.** Every step of a builder that mutates the receiver and returns it is annotated `-> Self`. This is the case the feature is named for: without it, the first inherited step in a chain widens the static type back to the base class, and every subclass-only step after it is reported as an unknown attribute. **Alternative constructors.** A `@classmethod` that builds an instance — `from_row`, `from_percent`, `parse` — is annotated `-> Self` and constructs with `cls(...)`. Inside such a method `cls` is implicitly typed `type[Self]`, so the object it builds is the receiver's class and the signature now says so. **`__enter__`.** A context manager whose `__enter__` hands back the object itself annotates `-> Self`, so `with Subclass() as handle:` binds `handle` to the subclass rather than to the base class. There is a fourth, quieter case: a method that returns a *fresh* object of the receiver's class, built as `type(self)(...)`. `Self` describes the returned type, not object identity, so this is entirely legitimate — and a checker will object if such a body hard-codes a class name instead, which is a useful extra check you get for free. ### What happens at run time Nothing at all. `Self` is an annotation, and annotations do not constrain execution. Since Python 3.14 they are not even evaluated when the function is defined: PEP 649 replaced eager evaluation with lazy evaluation, so the annotation expression runs only when something actually asks for a function's annotations. Either way, `Self` imposes no check. A method annotated `-> Self` whose body returns `42` runs happily; only the checker complains. Two useful consequences follow. Importing `Self` costs nothing measurable, and there is no forward-reference problem to solve: `Self` is an ordinary name in `typing`, always resolvable, never needing quotes — unlike writing the class's own name inside its own body, which on Python 3.13 and earlier had to be quoted because the class did not exist yet. ### Where it is legal, and where it is not `Self` only means something where there is a receiver. It is valid inside a class body — as a method's return annotation, as a parameter annotation, and as an attribute annotation — and meaningless at module level or in a plain function, where checkers reject it. It is also rejected in a `@staticmethod`, which by construction has no receiver for it to bind to; if a static helper needs the class, make it a classmethod so `cls` exists. Inside nested classes, `Self` refers to the innermost enclosing class. One modelling limit is worth naming out loud. `Self` has exactly one meaning per class, so it cannot express a relationship between two *different* types — "accepts another object of the receiver's own class and returns a pair of them", say, or "returns a related but distinct class". Signatures like that still need a named type variable you can mention in several positions. `Self` makes the overwhelmingly common case cheap; it is not a replacement for every parametrised signature. ### Why it matters more than it looks The alternative to `Self` is not merely "slightly less precise hints" — it is casts. Once a chain's static type has collapsed to the base class, the fastest way for a caller to silence the checker is `typing.cast` or a blanket ignore comment, and both of those switch off checking for everything else in that expression too. A single narrow annotation on a popular base class therefore buys suppressed diagnostics across every consumer that chains from it. `Self` costs one import and removes the pressure entirely, which is why it is now the default habit rather than a refinement.
- Does typing.Self change anything at run time?No. Like every annotation it is erased, and since Python 3.14 annotations are not even evaluated until something asks for them (PEP 649). `Self` exists for static checkers and editors; the method still just executes `return self`. Nothing enforces that a method annotated `-> Self` actually returns the receiver — the interpreter will return whatever the body returns, and only the checker objects.
- Where is typing.Self legal to write?Inside a class body: as a method's return annotation, as a parameter annotation, or as an attribute annotation. It is meaningless at module level or in a plain function, and checkers reject it in a `@staticmethod` because there is no receiver for it to bind to. Inside nested classes it refers to the innermost enclosing class; in a classmethod, `cls` is implicitly `type[Self]`.
- If a method returns a new instance rather than the receiver, is Self still correct?Yes, provided it builds the receiver's own class — `type(self)(...)`, or `cls(...)` in a classmethod. `Self` promises the returned type, not object identity. Hard-coding `return Rule()` inside a method annotated `-> Self` is an error a checker will report, because a subclass caller would receive a base instance.
Naming the class in the return type is a form letter addressed to head office; Self is a reply-to-sender envelope, so the answer goes back to whoever actually made the call.
saying these in an interview costs you the question
- Thinks Self is a run-time check that enforces the return value
- Says Self and writing the class's own name are equivalent
- Puts Self on a staticmethod, which has no receiver
- Believes Self must be quoted as a forward reference
- Thinks Self needs a backport package on Python 3.11 and later