Python type annotations on classes

You can’t normally access the class itself inside the class’ function declarations (because the class hasn’t been finished declaring itself yet, because you’re still declaring its methods).

So something like this isn’t valid Python:

class MyClass:
    def __init__(self, x: str) -> None:
        self.x = x

    def copy(self) -> MyClass:
        copied_object = MyClass(x=self.x)
        return copied_object

Solutions

There’s two ways to fix this.

Strings for classnames

Turn the classname into a string: The creators of PEP 484 and Mypy knew that such cases exist where you might need to define a return type which doesn’t exist yet. So, mypy is able to check types if they’re wrapped in strings.

class MyClass:
    def __init__(self, x: str) -> None:
        self.x = x

    def copy(self) -> 'MyClass':
        copied_object = MyClass(x=self.x)
        return copied_object

Postponed evaluation of type annotations

Use from __future__ import annotations. What this does, is turn on a new feature in Python called “postponed evaluation of type annotations”. This essentially makes Python treat all type annotations as strings, storing them in the internal __annotations__ attribute. Details are described in PEP 563.

from __future__ import annotations

class MyClass:
    def __init__(self, x: str) -> None:
        self.x = x

    def copy(self) -> MyClass:
        copied_object = MyClass(x=self.x)
        return copied_object

Info

Starting with Python 3.11, the Postponed evaluation behaviour will become default, and you won’t need to have the __future__ import anymore.

Credits

Most of the contents in this note were copied verbatim from Tushar Sadhwani’s The Comprehensive Guide to Mypy.