How do I type hint a method with the type of the enclosing class in Python?
I have the following code in Python 3:
class Position:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def __add__(self, other: Position) -> Position:
return Position(self.x + other.x, self.y + other.y)
But my editor (PyCharm) says that the reference Position cannot be resolved in the __add__ method. How should I specify that I expect the return type to be of type Position?
I think this might be a PyCharm issue, as it uses the information in its warnings and code completion. But correct me if I’m wrong and need to use some other syntax.
To type hint a method with the type of the enclosing class in Python, you need to use a forward reference by enclosing the class name in quotes. In your case, the correct syntax would be:
class Position:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def __add__(self, other: 'Position') -> 'Position':
return Position(self.x + other.x, self.y + other.y)
The quotes around 'Position' create a forward reference, telling Python and type checkers that this refers to the Position class, even though it’s not fully defined yet at the point where the type annotation is processed.
Contents
- Understanding the Forward Reference Problem
- The String Quote Solution
- Best Practices by Python Version
- Handling in Type Checkers and IDEs
- Alternative Approaches
Understanding the Forward Reference Problem
When Python processes class definitions, it executes the code in the class body from top to bottom. When it encounters a type annotation like -> Position, it tries to resolve the name Position immediately. However, at that point in the execution, the Position class is still being defined and isn’t fully available in the namespace yet.
This creates a forward reference problem where the type checker (and the Python runtime) can’t find the class that’s being referenced in the type annotation. As PEP 484 explains:
A common use for forward references is when e.g. Django models are needed in the signatures. Typically, each model is in a separate file, and has methods taking arguments whose type involves other models.
The same issue occurs within a single class when you need to reference the class itself in its own method signatures.
The String Quote Solution
The most widely supported solution is to enclose the class name in quotes, creating a string literal that serves as a forward reference:
class Position:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def __add__(self, other: 'Position') -> 'Position':
return Position(self.x + other.x, self.y + other.y)
def get_distance(self, other: 'Position') -> float:
return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5
According to the Python documentation:
It is allowable to use string literals as part of a type hint, for example: class Tree: … def leaves(self) -> List[‘Tree’]: …
This approach works because:
- The Python interpreter treats the quoted string as a literal and doesn’t try to resolve it immediately
- Type checkers like mypy understand this pattern and resolve the forward reference when they analyze the complete class definition
- The runtime behavior remains unchanged since type hints are typically ignored at runtime (unless explicitly evaluated)
Best Practices by Python Version
Python 3.7 and Earlier
For Python 3.7 and earlier versions, the string quote approach is the standard solution:
class Position:
def __add__(self, other: 'Position') -> 'Position':
# method implementation
pass
Python 3.7+ with Postponed Evaluation
Starting with Python 3.7, you can enable postponed evaluation of annotations using the __future__ import. This changes how type annotations are processed:
from __future__ import annotations
class Position:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def __add__(self, other: Position) -> Position:
return Position(self.x + other.x, self.y + other.y)
As explained by Adam Johnson:
With the old behaviour, Python … ‘Widget’ is not defined · Type checkers introduced a workaround: wrapping type hints in quotes, to turn them into strings.
The from __future__ import annotations feature (implemented in PEP 563) automatically treats all type annotations as strings, eliminating the need for manual quotes in most cases.
Python 3.12+ Future Possibilities
For Python 3.12 and future versions, there are ongoing discussions about PEP 649 which would allow using class names directly without quotes:
Update 2: As of Python 3.10, PEP 563 is being retought, and it may be that PEP 649 is used in instead - it would simply allow the class name to be used, plain, without any quotes: the pep proposal is that it is resolved in a lazy way.
However, as of now (and likely for the foreseeable future), the string quote approach remains the most reliable solution.
Handling in Type Checkers and IDEs
PyCharm and IntelliJ IDEA
Your PyCharm warning is actually a common occurrence, but it’s typically just a static analysis warning rather than an actual runtime error. Modern versions of PyCharm understand forward references and should resolve them correctly once you add the quotes.
If you’re still seeing warnings after adding quotes, you can:
- Invalidate caches and restart PyCharm
- Ensure you’re using a recent version of PyCharm that supports Python 3.7+ features
- Check that your Python interpreter version matches your project settings
Mypy
Mypy handles forward references automatically. According to the mypy documentation:
You may want to reference a class before it is defined. This is known as a “forward reference”.
Mypy will resolve the quoted 'Position' reference to the actual Position class when it processes the complete class definition.
Other Type Checkers
Most modern type checkers (including Pyright, Pyre, etc.) understand the forward reference pattern with quotes. If you’re using an older type checker, you might need to use explicit resolution with typing.get_type_hints().
Alternative Approaches
Using TypeVar for Self-Referential Types
For methods that return instances of the same class, you can use TypeVar:
from typing import TypeVar
T = TypeVar('T', bound='Position')
class Position:
def __add__(self, other: 'Position') -> T:
return Position(self.x + other.x, self.y + other.y)
However, this is more complex than necessary for simple cases and doesn’t provide much benefit over the string quote approach.
Using typing_extensions for Self
Python 3.11 introduced the Self type, which is designed specifically for this use case:
from typing import Self
class Position:
def __add__(self, other: 'Position') -> Self:
return Position(self.x + other.x, self.y + other.y)
Self is a special type that represents “the enclosing class” and is resolved at runtime. This is the most semantically correct approach when available.
Using Class Methods with cls
For class methods that return instances of the class, you can use cls:
class Position:
@classmethod
def create_origin(cls) -> 'Position':
return cls(0, 0)
Sources
- PEP 484 – Type Hints - Official Python Enhancement Proposal covering type hints and forward references
- Python typing documentation - Official Python documentation for the typing module
- Stack Overflow: How do I type hint a method with the type of the enclosing class? - Community discussion with multiple solutions
- Adam Johnson: Python type hints: enable postponed evaluation with future.annotations - Detailed explanation of postponed evaluation
- mypy type hints cheat sheet - Practical guide to type hints including forward references
- Python typing GitHub issue #34 - Discussion on forward reference syntax and semantics
Conclusion
To type hint a method with the type of the enclosing class in Python, use forward references by enclosing the class name in quotes:
class Position:
def __add__(self, other: 'Position') -> 'Position':
return Position(self.x + other.x, self.y + other.y)
Key takeaways:
- String quotes are the universal solution - They work across all Python versions and type checkers
- Consider
from __future__ import annotations- For Python 3.7+, this eliminates the need for manual quotes in most cases - PyCharm warnings are usually false positives - Modern IDEs understand forward references and resolve them correctly
- For Python 3.11+, consider using
Self- It’s semantically more precise for self-referential return types - Type checkers handle this automatically - No extra configuration needed for mypy, Pyright, or similar tools
The string quote approach has been the standard solution for years and continues to work reliably across all Python versions and tooling. Future versions may simplify this, but the current approach is battle-tested and well-supported.