I guess you got this exception:

NameError: name 'Position' is not defined

This is because in the original implementation of annotations, Position must be defined before you can use it in an annotation.

Python 3.14+: It'll just work

Python 3.14 has a new, lazily evaluated annotation implementation specified by PEP 749 and 649. Annotations will be compiled to special __annotate__ functions, executed when an object's __annotations__ dict is first accessed instead of at the point where the annotation itself occurs.

Thus, annotating your function as def __add__(self, other: Position) -> Position: no longer requires Position to already exist:

class Position:
    def __add__(self, other: Position) -> Position:
        ...

Python 3.7+, deprecated: from __future__ import annotations

from __future__ import annotations turns on an older solution to this problem, PEP 563, where all annotations are saved as strings instead of as __annotate__ functions or evaluated values. This was originally planned to become the default behavior, and almost became the default in 3.10 before being reverted.

With the acceptance of PEP 749, this will be deprecated in Python 3.14, and it will be removed in a future Python version. Still, it works for now:

from __future__ import annotations

class Position:
    def __add__(self, other: Position) -> Position:
        ...

Python 3+: Use a string

This is the original workaround, specified in PEP 484. Write your annotations as string literals containing the text of whatever expression you originally wanted to use as an annotation:

class Position:
    def __add__(self, other: 'Position') -> 'Position':
        ...

from __future__ import annotations effectively automates doing this for all annotations in a file.

typing.Self might sometimes be appropriate

Introduced in Python 3.11, typing.Self refers to the type of the current instance, even if that type is a subclass of the class the annotation appears in. So if you have the following code:

from typing import Self

class Parent:
    def me(self) -> Self:
        return self

class Child(Parent): pass

x: Child = Child().me()

then Child().me() is treated as returning Child, instead of Parent.

This isn't always what you want. But when it is, it's pretty convenient.

For Python versions < 3.11, if you have typing_extensions installed, you can use:

from typing_extensions import Self

Sources

The relevant parts of PEP 484, PEP 563, and PEP 649, to spare you the trip:

Forward references

When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later.

A situation where this occurs commonly is the definition of a container class, where the class being defined occurs in the signature of some of the methods. For example, the following code (the start of a simple binary tree implementation) does not work:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

To address this, we write:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

The string literal should contain a valid Python expression (i.e., compile(lit, '', 'eval') should be a valid code object) and it should evaluate without errors once the module has been fully loaded. The local and global namespace in which it is evaluated should be the same namespaces in which default arguments to the same function would be evaluated.

and PEP 563, deprecated:

Implementation

In Python 3.10, function and variable annotations will no longer be evaluated at definition time. Instead, a string form will be preserved in the respective __annotations__ dictionary. Static type checkers will see no difference in behavior, whereas tools using annotations at runtime will have to perform postponed evaluation.

...

Enabling the future behavior in Python 3.7

The functionality described above can be enabled starting from Python 3.7 using the following special import:

from __future__ import annotations

and PEP 649:

Overview

This PEP adds a new dunder attribute to the objects that support annotations–functions, classes, and modules. The new attribute is called __annotate__, and is a reference to a function which computes and returns that object’s annotations dict.

At compile time, if the definition of an object includes annotations, the Python compiler will write the expressions computing the annotations into its own function. When run, the function will return the annotations dict. The Python compiler then stores a reference to this function in __annotate__ on the object.

Furthermore, __annotations__ is redefined to be a “data descriptor” which calls this annotation function once and caches the result.

Things that you may be tempted to do instead

A. Define a dummy Position

Before the class definition, place a dummy definition:

class Position(object):
    pass

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)

This will get rid of the NameError and may even look OK:

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}

But is it?

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: False
other is Position: False

And mypy will report a pile of errors:

main.py:4: error: Name "Position" already defined on line 1  [no-redef]
main.py:11: error: Too many arguments for "Position"  [call-arg]
main.py:11: error: "Position" has no attribute "x"  [attr-defined]
main.py:11: error: "Position" has no attribute "y"  [attr-defined]
Found 4 errors in 1 file (checked 1 source file)

B. Monkey-patch in order to add the annotations:

You may want to try some Python metaprogramming magic and write a decorator to monkey-patch the class definition in order to add annotations:

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

The decorator should be responsible for the equivalent of this:

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position

It'll work right at runtime:

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: True
other is Position: True

But static analyzers like mypy won't understand it, and static analysis is the biggest use case of type annotations.

Answer from Paulo Scardine on Stack Overflow
Top answer
1 of 9
1795

I guess you got this exception:

NameError: name 'Position' is not defined

This is because in the original implementation of annotations, Position must be defined before you can use it in an annotation.

Python 3.14+: It'll just work

Python 3.14 has a new, lazily evaluated annotation implementation specified by PEP 749 and 649. Annotations will be compiled to special __annotate__ functions, executed when an object's __annotations__ dict is first accessed instead of at the point where the annotation itself occurs.

Thus, annotating your function as def __add__(self, other: Position) -> Position: no longer requires Position to already exist:

class Position:
    def __add__(self, other: Position) -> Position:
        ...

Python 3.7+, deprecated: from __future__ import annotations

from __future__ import annotations turns on an older solution to this problem, PEP 563, where all annotations are saved as strings instead of as __annotate__ functions or evaluated values. This was originally planned to become the default behavior, and almost became the default in 3.10 before being reverted.

With the acceptance of PEP 749, this will be deprecated in Python 3.14, and it will be removed in a future Python version. Still, it works for now:

from __future__ import annotations

class Position:
    def __add__(self, other: Position) -> Position:
        ...

Python 3+: Use a string

This is the original workaround, specified in PEP 484. Write your annotations as string literals containing the text of whatever expression you originally wanted to use as an annotation:

class Position:
    def __add__(self, other: 'Position') -> 'Position':
        ...

from __future__ import annotations effectively automates doing this for all annotations in a file.

typing.Self might sometimes be appropriate

Introduced in Python 3.11, typing.Self refers to the type of the current instance, even if that type is a subclass of the class the annotation appears in. So if you have the following code:

from typing import Self

class Parent:
    def me(self) -> Self:
        return self

class Child(Parent): pass

x: Child = Child().me()

then Child().me() is treated as returning Child, instead of Parent.

This isn't always what you want. But when it is, it's pretty convenient.

For Python versions < 3.11, if you have typing_extensions installed, you can use:

from typing_extensions import Self

Sources

The relevant parts of PEP 484, PEP 563, and PEP 649, to spare you the trip:

Forward references

When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later.

A situation where this occurs commonly is the definition of a container class, where the class being defined occurs in the signature of some of the methods. For example, the following code (the start of a simple binary tree implementation) does not work:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

To address this, we write:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

The string literal should contain a valid Python expression (i.e., compile(lit, '', 'eval') should be a valid code object) and it should evaluate without errors once the module has been fully loaded. The local and global namespace in which it is evaluated should be the same namespaces in which default arguments to the same function would be evaluated.

and PEP 563, deprecated:

Implementation

In Python 3.10, function and variable annotations will no longer be evaluated at definition time. Instead, a string form will be preserved in the respective __annotations__ dictionary. Static type checkers will see no difference in behavior, whereas tools using annotations at runtime will have to perform postponed evaluation.

...

Enabling the future behavior in Python 3.7

The functionality described above can be enabled starting from Python 3.7 using the following special import:

from __future__ import annotations

and PEP 649:

Overview

This PEP adds a new dunder attribute to the objects that support annotations–functions, classes, and modules. The new attribute is called __annotate__, and is a reference to a function which computes and returns that object’s annotations dict.

At compile time, if the definition of an object includes annotations, the Python compiler will write the expressions computing the annotations into its own function. When run, the function will return the annotations dict. The Python compiler then stores a reference to this function in __annotate__ on the object.

Furthermore, __annotations__ is redefined to be a “data descriptor” which calls this annotation function once and caches the result.

Things that you may be tempted to do instead

A. Define a dummy Position

Before the class definition, place a dummy definition:

class Position(object):
    pass

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)

This will get rid of the NameError and may even look OK:

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}

But is it?

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: False
other is Position: False

And mypy will report a pile of errors:

main.py:4: error: Name "Position" already defined on line 1  [no-redef]
main.py:11: error: Too many arguments for "Position"  [call-arg]
main.py:11: error: "Position" has no attribute "x"  [attr-defined]
main.py:11: error: "Position" has no attribute "y"  [attr-defined]
Found 4 errors in 1 file (checked 1 source file)

B. Monkey-patch in order to add the annotations:

You may want to try some Python metaprogramming magic and write a decorator to monkey-patch the class definition in order to add annotations:

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

The decorator should be responsible for the equivalent of this:

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position

It'll work right at runtime:

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: True
other is Position: True

But static analyzers like mypy won't understand it, and static analysis is the biggest use case of type annotations.

2 of 9
224

PEP 673 which is implemented in Python 3.11, adds the Self type.

from typing import Self    

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: Self) -> Self:
        return type(self)(self.x + other.x, self.y + other.y)

Returning Self is often a good idea, but you must return an object of the same type as self, which means calling type(self) rather than Position.


For older versions of Python (currently 3.7 and later), use the typing-extensions package. One of its purposes is to

Enable use of new type system features on older Python versions. For example, typing.TypeGuard is new in Python 3.10, but typing_extensions allows users on previous Python versions to use it too.

Then you just import from typing_extensions instead of typing, e.g. from typing_extensions import Self.

🌐
Adam Johnson
adamj.eu › tech › 2021 › 05 › 16 › python-type-hints-return-class-not-instance
Python type hints: specify a class rather than an instance thereof - Adam Johnson
In a type hint, if we specify a type (class), then we mark the variable as containing an instance of that type. To specify that a variable instead contains a type, we need to use type[Cls] (or the old syntax typing.Type).
🌐
Reddit
reddit.com › r/learnpython › type-hinting with user defined classes
r/learnpython on Reddit: Type-hinting with user defined classes
November 14, 2023 -

I have two custom classes, one of which requires the other as an input argument.

class Authentication(object):
    def __init__(self) -> None:
        self.auth_data = self._resolve_auth()

class Data(object):
    def __init__(self, authentication) -> None:
        self.baseurl = authentication.baseurl
        self.token = authentication.token

I have the Authentication class defined in a seperate file in my project. So my question is, if I want to apply type-hinting to the Data class i.e.

def __init__(self, authentication: Authentication) -> None:

How do I go about doing this without having to import the Authentication class into my file which contains the Data class? It seems like overkill just to get proper type-hinting.

🌐
Real Python
realpython.com › python-type-self
Python's Self Type: How to Annotate Methods That Return self – Real Python
June 10, 2023 - You annotate SavingsAccount.from_application() with the TBankAccount type variable, and you annotate the cls parameter with type[TBankAccount]. Most static type checkers should recognize this as valid type hinting for both BankAccount and SavingsAccount. The main drawback is that TypeVar is verbose, and a developer can easily forget to instantiate a TypeVar instance or properly bind the instance to a class.
🌐
Readthedocs
python-type-checking.readthedocs.io › en › latest › types.html
Type Classes — Guide to Python Type Checking 1.0 documentation
It’s what gives them the bracket ... type-hints. By using Generic as an alternative base class to object when creating your own collection classes, your classes can be used both as a collection (by instantiating it as you normally would) and as a type annotation (by using [] on the class itself)...
🌐
GitHub
github.com › python › typing › issues › 58
How to add hint to factory method? · Issue #58 · python/typing
March 5, 2015 - How to hint to method receiving class object and returns instance of the class? Case 1: class Base: registry = set() @classmethod def create(cls, *args) -> 'instanceof(cls)': a = cls(*args) cls.registry.add(a) return a Case 2: class Base...
Published   Mar 05, 2015
🌐
Python documentation
docs.python.org › 3 › library › typing.html
typing — Support for type hints
If you want to retrieve the original type wrapped by Annotated, use the __origin__ attribute: >>> from typing import Annotated, get_origin >>> Password = Annotated[str, "secret"] >>> Password.__origin__ <class 'str'> Note that using get_origin() will return Annotated itself:
🌐
Dagster
dagster.io › blog › python-type-hinting
Using Type Hinting in Python Projects
At Dagster, we really like pyright because it is faster than other alternatives such as mypy. Python itself is a dynamically-typed language, which means type checks happen at runtime and it does not enforce type hinting rules.
Find elsewhere
Top answer
1 of 2
287

The former is correct, if arg accepts an instance of CustomClass:

def FuncA(arg: CustomClass):
    #     ^ instance of CustomClass

In case you want the class CustomClass itself (or a subtype), then you should write:

from typing import Type  # you have to import Type

def FuncA(arg: Type[CustomClass]):
    #     ^ CustomClass (class object) itself

Like it is written in the documentation about Typing:

class typing.Type(Generic[CT_co])

A variable annotated with C may accept a value of type C. In contrast, a variable annotated with Type[C] may accept values that are classes themselves - specifically, it will accept the class object of C.

The documentation includes an example with the int class:

a = 3         # Has type 'int'
b = int       # Has type 'Type[int]'
c = type(a)   # Also has type 'Type[int]'

Update 2024: Type is now deprecated in favour of type

2 of 2
41

Willem Van Onsem's answer is of course correct, but I'd like to offer a small update. In PEP 585, type hinting generics were introduced in standard collections. For example, whereas we previously had to say e.g.

from typing import Dict

foo: Dict[str, str] = { "bar": "baz" }

we can now forgo the parallel type hierarchy in the typing module and simply say

foo: dict[str, str] = { "bar": "baz" }

This feature is available in python 3.9+, and also in 3.7+ if using from __future__ import annotations.

In terms of this specific question, it means that instead of from typing import Type, we can now simply annotate classes using the built-in type:

def FuncA(arg: type[CustomClass]):
🌐
Python
peps.python.org › pep-0673
PEP 673 – Self Type | peps.python.org
The only possible use cases would be to return a parameter itself or some element from a container passed in as a parameter. These don’t seem worth the additional complexity. class Base: @staticmethod def make() -> Self: # Rejected ... @staticmethod def return_parameter(foo: Self) -> Self: # Rejected ... Likewise, we reject Self in metaclasses. Self in this PEP consistently refers to the same type (that of self).
🌐
Python
peps.python.org › pep-0484
PEP 484 – Type Hints | peps.python.org
A generic class can be an ABC by including abstract methods or properties, and generic classes can also have ABCs as base classes without a metaclass conflict. A type variable may specify an upper bound using bound=<type> (note: <type> itself cannot be parameterized by type variables).
🌐
Mypy
mypy.readthedocs.io › en › stable › cheat_sheet_py3.html
Type hints cheat sheet - mypy 1.19.1 documentation
# Another option is to just put the type in quotes def f(foo: 'A') -> int: # Also ok ... class A: # This can also come up if you need to reference a class in a type # annotation inside the definition of that class @classmethod def create(cls) -> A: ... See Class name forward references for more details. Decorator functions can be expressed via generics. See Declaring decorators for more details. Example using Python 3.12 syntax:
🌐
InfoWorld
infoworld.com › home › software development › programming languages › python
Get started with Python type hints | InfoWorld
June 18, 2025 - If you need an explicit self hint for the class that refers to whatever class is in context, and you’re using Python 3.11 or later, you can import Self from the typing module and use that as a hint.
🌐
Medium
medium.com › @AlexanderObregon › how-pythons-type-hinting-and-annotations-work-319d952247a6
How Python’s Type Hinting and Annotations Work | Medium
July 14, 2024 - ... Here, the __annotations__ ... type. Type hints are not limited to functions; they can also be used in class definitions to specify the types of attributes and methods....
🌐
W3docs
w3docs.com › python
How do I type hint a method with the type of the enclosing class?
In Python, you can use the self keyword to refer to the instance of the enclosing class within a method. To type hint the method with the type of the enclosing class, you can use the Type[T] notation, where T is the name of the class.