Using the class as a type hint for arguments in its methods
Because when it encounters Translate
(while compiling the class body), Vector2
hasn't been defined yet (it is currently compiling, name binding hasn't been performed); Python naturally complains.
Since this is such a common scenario (type-hinting a class in the body of that class), you should use a forward reference to it by enclosing it in quotes:
class Vector2:
# __init__ as defined
def Translate(self, pos: 'Vector2'):
self.x += pos.x
self.y += pos.y
Python (and any checkers complying to PEP 484
) will understand your hint and register it appropriately. Python does recognize this when __annotations__
are accessed through typing.get_type_hints
:
from typing import get_type_hints
get_type_hints(Vector2(1,2).Translate)
{'pos': __main__.Vector2}
This has been changed as of Python 3.7; see abarnert's answer below.
How do I type hint a method with the type of the enclosing class?
TL;DR: As of today (2019), in Python 3.7+ you can turn this feature on using a "future" statement, from __future__ import annotations
.
(The behaviour enabled by from __future__ import annotations
might become the default in future versions of Python, and was going to be made the default in Python 3.10. However, the change in 3.10 was reverted at the last minute, and now may not happen at all.)
In Python 3.6 or below, you should use a string.
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 with PEP 563 changes enabled.
Python 3.7+: 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
class Position:
def __add__(self, other: Position) -> Position:
...
This had been scheduled to become the default in Python 3.10, but this change has now been postponed. 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 for code that involves importing the typing
module, you will see an up to 7 times increase in performance when you upgrade to 3.7.
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.
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 = rightTo address this, we write:
class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
self.left = left
self.right = rightThe 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:
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
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(object):
...
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
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
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.
Python type hinting own class in method
The name Foo
doesn't yet exist, so you need to use 'Foo'
instead. (mypy
and other type checkers should recognize this as a forward reference.)
def __eq__(self, other: 'Foo'):
return self.id == other.id
Alternately, you can use
from __future__ import annotations
which prevents evaluation of all annotations and simply stores them as strings for later reference. (This will be the default in Python 3.10.)
Finally, as also pointed out in the comments, __eq__
should not be hinted this way in the first place. The second argument should be an arbitrary object; you'll return NotImplemented
if you don't know how to compare your instance to it. (Who knows, maybe it knows how to compare itself to your instance. If Foo.__eq__(Foo(), Bar())
returns NotImplemented
, then Python will try Bar.__eq__(Bar(), Foo())
.)
from typing import Any
def __eq__(self, other: Any) -> bool:
if isinstance(other, Foo):
return self.id == other.id
return NotImplemented
or using duck-typing,
def __eq__(self, other: Any) -> bool:
# Compare to anything with an `id` attribute
try:
return self.id == other.id
except AttributeError:
return NotImplemented
In either case, the Any
hint is optional.
How to write the type hint for an argument that can be of either class A or class B?
You can use Union
to specify multiple type hintings (for both, arguments and return type). You will have to import Union
from standard library typing
.
from typing import Union
class A:
def __init__(self, name) -> None:
self.name = name
class B:
def __init__(self, name) -> None:
self.name = name
class ObjectInfo:
def __init__(self) -> None:
pass
def method(self, object:Union[A, B]=None) -> list:
return dir(object)
obj = ObjectInfo()
print(obj.method())
[External Links]
More on type hinting : https://docs.python.org/3/library/typing.html
Python: How to Type Hint a Class Argument in a Static Method Python?
A possible workaround in this case would be to monkey-patch the method after defining the class:
class Circle:
def __init__(self, r, _id):
self.r = r
self.id = _id
def area(self):
return math.pi * (self.r ** 2)
def compare_circles(circle_1: Circle, circle_2: Circle) -> str:
if circle_1.r < circle_2.r:
return circle_1.id
else:
return circle_2.id
Circle.compare_circles = staticmethod(compare_circles)
del compare_circles
The usual way would be to provide a string with the type name:
class Circle:
def __init__(self, r, _id):
self.r = r
self.id = _id
def area(self):
return math.pi * (self.r ** 2)
@staticmethod
def compare_circles(circle_1: 'Circle', circle_2: 'Circle') -> str:
if circle_1.r < circle_2.r:
return circle_1.id
else:
return circle_2.id
As an aside, you might also consider turning compare_circles
into a method:
def compare(self, other: 'Circle') -> str:
if self.r < other.r:
return self.id
else:
return other.id
How to type hint an instance-level function (i.e. not a method)?
This is currently broken in mypy as it assumes you are creating a method, here is the relevant issue https://github.com/python/mypy/issues/708.
Typing the function in the init works fine as it won't think it's a method on the class, the following code passes type checking properly and func
's type is inferred from the parameter. The attribute assignment can also be typed directly if the parameter is not viable.
from collections.abc import Callable
class Foo:
def __init__(self, func: Callable[[], int]):
self.func = func
reveal_type(Foo(lambda: 0).func)
###OUTPUT###
file.py:7: note: Revealed type is "def () -> builtins.int"
An another workaround that can be found in the issue and avoids assigning in the init is to use a callback Protocol
like so:
from typing import Protocol
class FuncCallback(Protocol):
def __call__(self, /) -> int:
...
class Foo:
func: FuncCallback
def __init__(self, func):
self.func = func
This makes func
a FuncCallback
protocol which expects no arguments when called and returns an int
like your Callable
.
Related Topics
How to Execute a Python Script in Notepad++
Iterate a List with Indexes in Python
Chain-Calling Parent Initialisers in Python
Group by Pandas Dataframe and Select Latest in Each Group
Hiding a Password in a Python Script (Insecure Obfuscation Only)
Scipy: Savefig Without Frames, Axes, Only Content
Include Intermediary (Through Model) in Responses in Django Rest Framework
Call Int() Function on Every List Element
How to Convert a Numpy Array to Pil Image Applying Matplotlib Colormap
Use .Corr to Get the Correlation Between Two Columns
In-Memory Size of a Python Structure
Best Way to Create a "Reversed" List in Python
Update Row Values Where Certain Condition Is Met in Pandas
Select Pandas Rows Based on List Index
Time.Sleep -- Sleeps Thread or Process
Sorting Python List Based on the Length of the String
Named Regular Expression Group "(P<Group_Name>Regexp)": What Does "P" Stand For