How to Type Hint a Method With the Type of the Enclosing Class

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

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.

Type hint for class method return error - name is not defined?

The name X doesn't exist until the class is fully defined. You can fix this by importing a __future__ feature called annotations. Just put this at the top of your file.

from __future__ import annotations

This wraps all annotations in quotation marks, to suppress errors like this. It's the same as doing this

class X:
@classmethod
def create(cls) -> 'X': # <-- Note the quotes
pass

but automatically. This will be the default behavior in some future Python version (originally, it was going to be 3.10, but it's been pushed back due to compatibility issues), but for now the import will make it behave the way you want.

The future import was added in Python 3.7. If you're on an older version of Python, you'll have to manually wrap the types in strings, as I did in the example above.

type hinting within a class

"self" references in type checking are typically done using strings:

class Node:
def append_child(self, node: 'Node'):
if node != None:
self.first_child = node
self.child_nodes += [node]

This is described in the "Forward references" section of PEP-0484.

Please note that this doesn't do any type-checking or casting. This is a type hint which python (normally) disregards completely1. However, third party tools (e.g. mypy), use type hints to do static analysis on your code and can generate errors before runtime.

Also, starting with python3.7, you can implicitly convert all of your type-hints to strings within a module by using the from __future__ import annotations (and in python4.0, this will be the default).

1The hints are introspectable -- So you could use them to build some kind of runtime checker using decorators or the like if you really wanted to, but python doesn't do this by default.

Python: type-hinting a classmethod that returns an instance of the class, for a class that is inherited

You will have to use a TypeVar, thankfully, in Python 3.11 the typing.Self type is coming out. This PEP describes it in detail. It also specifies how to use the TypeVar until then.

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 to create type-hints for method chaining?

You can use your class in type hints, according to this excellent post, using your class as string.

In this case, you will need to modify your function signature as the following:

def chain(self) -> "A":


Related Topics



Leave a reply



Submit