How do I specify that the return type of a method is the same as the class itself in python?

How do I specify that the return type of a method is the same as the class itself 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 can not be resolved (in the _add__ method). How should I specify that I expect the return type to be of type Position?


Position



Edit: 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.





Oh, I see your problem. See stackoverflow.com/questions/15853469/…
– Paulo Scardine
Nov 4 '15 at 22:33




3 Answers
3



TL;DR: if you are using Python 3.7 or above import the annotations module and it will work as you expect - for Python 3.6 or below use a string.


annotations



I guess you got this exception:


NameError: name 'Position' is not defined



This is because Position must be defined before you can use it in an annotation unless you are using Python 4.


Position



Python 3.7+: from __future__ import annotations


from __future__ import annotations



Python 3.7 introduces PEP 563: postponed evaluation of annotations. A module that uses the future statement from __future__ import annotations will store annotations as strings automatically:


from __future__ import annotations


from __future__ import annotations

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



This is scheduled to become the default in Python 4.0. Since Python still is a dynamically typed language so no type checking is done at runtime, typing annotations should have no performance impact, right? Wrong! Before python 3.7 the typing module used to be one of the slowest python modules in core so if you import typing you will see up to 7 times increase in performance when you upgrade to 3.7.


import typing



Python <3.7: use a 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 use the Django framework this may be familiar as Django models also use strings for forward references (foreign key definitions where the foreign model is self or is not declared yet). This should work with Pycharm and other tools.


self



Sources



The relevant parts of PEP 484 and PEP 563, 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:



In Python 4.0, 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.


__annotations__



...



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


from __future__ import annotations



Things that you may be tempted to do instead


Position



Before the class definition, place a dummy definition:


class Position(object):
pass


class Position(object):
...



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


NameError


>>> 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 in order to add the annotations:



You may want to try some Python meta programming 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



At least it seems right:


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



Probably too much trouble.



Conclusion



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


from __future__ import annotations





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.
– Paul Everitt
Nov 5 '15 at 13:36



The name 'Position' is not avalilable 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 https://www.python.org/dev/peps/pep-0484/#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 of these annotations - they are usually meant for static-code analysis, or one could have a library/framework for type checking in run-time - but you have to explicitly set that)



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)



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Some of your past answers have not been well-received, and you're in danger of being blocked from answering.



Please pay close attention to the following guidance:



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

How do I collapse sections of code in Visual Studio Code for Windows?

Node.js puppeteer - Use values from array in a loop to cycle through pages