How can I indicate that the return type of a method is the same as the class itself?

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 do I indicate that I expect the return type to be of type Position ?

Change: I think this is actually a PyCharm problem. It actually uses information in its warnings and code completion.

But correct me if I am wrong and some other syntax needs to be used.

+206
python pycharm typing
Nov 04 '15 at
source share
3 answers

TL; DR : if you use Python 4.0, it just works. From today (2019) to 3. 7+, you should enable this function using the operator of the future ( from __future__ import annotations ) - for Python 3.6 or below, use the line.

I think you got this exception:

 NameError: name 'Position' is not defined 

This is because Position must be defined before you can use it in the annotation if you are not using Python 4.

Python 3. 7+: from __future__ import annotations

Python 3.7 introduces PEP 563: deferred annotation score . A module that uses the future from __future__ import annotations will automatically save annotations as strings:

 from __future__ import annotations class Position: def __add__(self, other: Position) -> Position: ... 

It is planned to become the default in Python 4.0. Since Python is still a dynamic typing language, so type checking at runtime fails, annotation input should not affect performance, right? Wrong! Prior to Python 3.7, the input module was one of the slowest Python modules in the kernel, so if you import typing you will see a performance increase of up to 7 times when upgrading to 3.7.

Python <3.7: use string

According to PEP 484 , you should use a string instead of the class itself:

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

If you are using the Django framework, this may be familiar, as Django models also use strings for direct references (defining a foreign key where the foreign model is self or not yet declared). This should work with Pycharm and other tools.

sources

Relevant parts of PEP 484 and PEP 563 to save you from traveling:

Direct links

When a type hint contains names that are not yet defined, this definition can be expressed as a string literal, which will be resolved later.

The situation in which this usually happens is the definition of the container class, where the class being defined is found in the signature of some methods. For example, the following code (beginning to implement a simple binary tree) does not work:

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

To solve this problem, we write:

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

The string literal must contain a valid Python expression (i.e., Compile (lit, '', 'eval') must be a valid code object), and it must be evaluated without errors after the module is fully loaded. The local and global namespaces in which it is evaluated must be the same namespaces in which the default arguments for the same function will be evaluated.

and PEP 563:

In Python 4.0, annotations of functions and variables will no longer be evaluated at definition time. Instead, the string form will be stored in the corresponding __annotations__ dictionary. Static type validators will not see a difference in behavior, while tools that use annotations at runtime will have to defer evaluation.

...

The functions described above can be enabled starting with Python 3.7 using the following special import:

 from __future__ import annotations 

Things you can experience instead

A. Define Fictitious Position

Before defining a class, place a dummy definition:

 class Position(object): pass class Position(object): ... 

This will NameError and may even look good:

 >>> 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 

B. Monkey patch for adding annotations:

You might want to try the magic of metaprogramming in Python and write a decorator that will correct the class definition 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 

At least that seems correct:

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

There are probably too many problems.

Conclusion

If you use 3.6 or lower, use a string literal containing the class name, in 3.7 use from __future__ import annotations and this will just work.

+305
Nov 04 '15 at 22:44
source share

The name "Position" is not available during the analysis of the class body itself. I donโ€™t know how you use type declarations, but Python PEP 484 is what most modes should use if with these typing tips they say 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 https://www.python.org/dev/peps/pep-0484/#forward-references - the tools corresponding to this will know how to deploy the class name from there and use it. (It is always important to remember that the Python language itself does not make anything out of these annotations - they are usually designed to analyze static code, or you can have a library / environment for checking types at runtime - but you must explicitly set this)

+11
Nov 04 '15 at 22:44
source share

It is good to specify a type as a string, but it is always a little happy that we mostly bypass the parser. So itโ€™s best not to write any of these letter strings:

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

A small change is to use a bound typevar, at least then you should write a line only once when declaring 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) 
+7
Feb 11 '18 at 19:45
source share



All Articles