1374

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 can not be resolved (in the __add__ method). How should I specify that I expect the return type to be of type Position?

I think this is actually a PyCharm issue. It actually uses the information in its warnings, and code completion.

But correct me if I'm wrong, and need to use some other syntax.

1

9 Answers 9

1781

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.

Sign up to request clarification or add additional context in comments.

18 Comments

Right, this is less a PyCharm issue and more a Python 3.5 PEP 484 issue. I suspect you'd get the same warning if you ran it through the mypy type tool.
Important note to anyone using from __future__ import annotations - this must be imported before all other imports.
Is there a way to specify that the return type of a function is the current class, whatever that may be? e.g., @classmethod def f(cls) -> CurrentClass: where CurrentClass evaluates to whatever cls would be at runtime? So that if A and B inherit from the class that implements f, then A.f() -> A and B.f() -> B?
From PEP673: from typing import Self might make this much easier in the future (seems to be PY3.11 feature)
Python 3.11 introduced the Self annotation. docs.python.org/3.11/whatsnew/3.11.html#whatsnew311-pep673
|
222

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.

12 Comments

With Python 3.11, this solution become the least kludgy and most succinct.
Any chance they are back porting this to __future__, etc?
No. __future__ is more about making breaking syntactic features opt-in now, then making it required in a future version. (Which is not to say that a third-party library couldn't provide it now, but it won't be part of the standard library in already existing Python versions.)
I believe it's already available as part of typing_extensions, but mypy doesn't understand it yet. The Python 3.11 tracking issue is available here: github.com/python/mypy/issues/12840#issue-1244203018
Note this is different from using from __future__ import annotations and annotating with Position, where __add__ on a subclass SubPosition accepts and returns a Position. With Self, it requires and returns a SubPosition. Both approaches can be correct, it depends on the specific use case
|
62

As of Python 3.11 (released in late 2022), there is available typing.Self designed for this purpose. Check PEP 673!

For previous Python versions, one had to consider that the name 'Position' is not available at the time the class body itself is parsed. I don't know how you are using the type declarations, but Python's PEP 484 - which is what most mode should use if using these typing hints say that you can simply put the name as a string at this point:

def __add__(self, other: 'Position') -> 'Position': return Position(self.x + other.x, self.y + other.y) 

Check the PEP 484 section on forward references - tools conforming to that will know to unwrap the class name from there and make use of it. (It is always important to have in mind that the Python language itself does nothing with these annotations. They are usually meant for static-code analysis, or one could have a library/framework for type-checking at runtime - but you have to explicitly set that.)

Update: Also, as of Python 3.7, check out PEP 563. As of Python 3.8, it is possible to write from __future__ import annotations to defer the evaluation of annotations. Forward-referencing classes should work straightforward.

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.

Update 3: As of Python 3.11, PEPs 563 and 649 to resolve forward references, mentioned above are still contending and it is likely none of them will go forward as it is now.

Comments

44

Specifying the type as string is fine, but always grates me a bit that we are basically circumventing the parser. So you better not misspell any one of these literal strings:

def __add__(self, other: 'Position') -> 'Position': return Position(self.x + other.x, self.y + other.y) 

A slight variation is to use a bound typevar, at least then you have to write the string only once when declaring the typevar:

from typing import TypeVar T = TypeVar('T', bound='Position') class Position: def __init__(self, x: int, y: int): self.x = x self.y = y def __add__(self, other: T) -> T: return Position(self.x + other.x, self.y + other.y) 

9 Comments

I wish Python had a typing.Self to specify this explicitly.
I came here looking to see if something like your typing.Self existed. Returning a hard coded string fails to return the correct type when leveraging polymorphism. In my case I wanted to implement a deserialize classmethod. I settled on returning a dict (kwargs) and calling some_class(**some_class.deserialize(raw_data)).
The type annotations used here are appropriate when implementing this correctly to use subclasses. However, the implementation returns Position, and not the class, so the example above is technically incorrect. The implementation should replace Position( with something like self.__class__(.
Additionally, the annotations say that the return type depends on other, but most probably it actually depends on self. So, you would need to put the annotation on self to describe the correct behaviour (and maybe other should just be Position to show that it's not tied to the return type). This can also be used for cases when you are only working with self. e.g. def __aenter__(self: T) -> T:
typing.Self will be available in Python 3.11 (according to PEP-673).
|
35

If you only care about fixing the NameError: name 'Position' is not defined, you can either specify the class name as a string:

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

Or if you use Python 3.7 or higher, add the following line to the top of your code (just before the other imports)

from __future__ import annotations 

Technically, this will turn all annotations into strings. Starting with Python 3.14, this is no longer necessary, as Python 3.14 introduces Deferred Evaluation of Annotations 1.

However, if you also want this to work for subclasses, and return the specific subclass, you need to annotate the method as being a generic method, either Self (starting from Python 3.11) or by using a TypeVar (available since Python 3.5).

from __future__ import annotations from typing import Self class Position: def __init__(self, x: int, y: int): self.x = x self.y = y def __add__(self, other: Position) -> Self: return type(self)(self.x + other.x, self.y + other.y) def copy(self) -> Self: return type(self)(self.x, self.y) 

If you want to support earlier versions than 3.11, use a TypeVar. Basically, this typing hinting tells the type checker that the return type of __add__() and copy() are the same type as self.

from __future__ import annotations from typing import TypeVar T = TypeVar('T', bound='Position') class Position: def __init__(self, x: int, y: int): self.x = x self.y = y def __add__(self: T, other: Position) -> T: return type(self)(self.x + other.x, self.y + other.y) def copy(self: T) -> T: return type(self)(self.x, self.y) 

6 Comments

@Arjan. you are right. I'm so accustomed to from __future__ import annotations that I probably forgot. Thanks for pointing this out. I fixed it in the answer.
what is the letter ' T ' ?
Are there any clever tricks to have a generic Self that can be reused?
How does that look for a @classmethod?
Isn't T = TypeVar('T', bound=Position) referencing Position before it is defined?
|
18

When a string-based type hint is acceptable, the __qualname__ item can also be used. It holds the name of the class, and it is available in the body of the class definition.

class MyClass: @classmethod def make_new(cls) -> __qualname__: return cls() 

By doing this, renaming the class does not imply modifying the type hints. But I personally would not expect smart code editors to handle this form well.

6 Comments

This is especially useful because it does not hardcode the class name, so it keeps working in subclasses.
I'm not sure whether this will work with the postponed evaluation of annotations (PEP 563), so I've asked a question for that.
Note that this is not a valid annotation as far as mypy is concerned.
this solution fixes the hardcoding in a different manner
@user2426679 both this answer and the one you are referencing are not valid type annotations. Use the bound typevar approach here: stackoverflow.com/a/63237226/5014455
|
8

edit: @juanpa.arrivillaga brought to my attention a better way to do this; see https://stackoverflow.com/a/63237226

It's recommended to do the above answer instead of this one below.

[old answer below, kept for posterity]

I ❤️ Paulo's answer

However, there's a point to be made about type hint inheritance in relation to self, which is that if you type hint by using a literal copy paste of the class name as a string, then your type hint won't inherit in a correct or consistent way.

The solution to this is to provide return type hint by putting the type hint on the return in the function itself.

✅ For example, do this:

class DynamicParent: def func(self): # roundabout way of returning self in order to have inherited type hints of the return # https://stackoverflow.com/a/64938978 _self:self.__class__ = self return _self 

Instead of doing this:

class StaticParent: def func(self) -> 'StaticParent': return self 

Below is the reason why you want to do the type hint via the roundabout ✅ way shown above

class StaticChild(StaticParent): pass class DynamicChild(DynamicParent): pass static_child = StaticChild() dynamic_child = DynamicChild() 

dynamic_child screenshot shows that type hinting works correctly when referencing the self:

enter image description here

static_child screenshot shows that type hinting is mistakenly pointing at the parent class, i.e. the type hint does not change correctly with inheritance; it is static because it will always point at the parent even when it should point at the child

enter image description here

4 Comments

this is not a valid type annotation, and not the correct way to type annotate what you are trying to express, which should be annotated with a type variable bound to the parent class
@juanpa.arrivillaga could you post an answer to this question that is annotated with a type variable bound to the parent class? It's unclear to me how one would bind a type variable to the parent class that refers to the subsequent children instances.
Nice! It works with VSCode Intellisense. I am wondering if this assignment _self:self.__class__ = self would introduce any overhead (?)
2

For Python 3.11+ there is a 'Self' type hint in the 'typings' module.

For Cython/mypyc users who might have stumbled into this like me; it doesn't matter.. The compiler is smart enough to infer that the type specified in the function parameter correspond to the enclosing class type

Comments

1

Use:

from __future__ import annotations import sys if sys.version_info >= (3, 11): from typing import Self else: from typing_extensions import Self class Animal: def __init__(self, name: str, says: str) -> None: self.name = name self.says = says @classmethod def from_description(cls, description: str = "|") -> Self: descr = description.split("|") return cls(descr[0], descr[1]) 

2 Comments

The link is (effectively) broken. It redirects to a generic page.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.