What is `typing.TYPE_CHECKING` and `from __future__ import annotation`
Kexin Tang

typing.TYPE_CHECKING

Purpose

typing.TYPE_CHECKING is a boolean flag, it’s True during static type checking(e.g. mypy), and it’s False at runtime.

Use Case

It is primarily used to guard imports or code blocks that are only necessary for type checking and should not be executed at runtime.

Circular Import

Circular import means two modules import each other.

1
2
3
4
5
# module_a.py
from module_b import function_b

def function_a():
function_b()
1
2
3
4
5
# module_b.py
from module_a import function_a

def function_b():
function_a()

When type hints introduce circular dependencies between modules, TYPE_CHECKING can be used to conditionally import modules only during type checking, preventing runtime import errors.

Notice: circular import error may occur for several reasons, e.g. function invocation or type annotation, typing.TYPE_CHECKING is only useful for type annotation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from my_module import MyType

"""
Use string literal for forward references. The interpreter just stores it as a string at runtime without resolving it
immediately.
"""
def function(data: "MyType"):
other: MyType = ... # the same as forward reference, no need to add "MyType"

"""
Error, it triggers interpreter to resolve the name immediately, which leads to circular import
"""
# def function(data: MyType):
# ...

"""
Error, function invocation requires to get access to the module, which leads to circular import
"""
# def function(data: "MyType"):
# other = MyType(data) # function invocation

Avoid unnecessary import cost

If the module is only used as a type annotation, typing.TYPE_CHECKING avoid importing it at runtime so that improve the efficiency.

1
2
3
4
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from my_module import MyType # only import module on type checking phase, runtime phase will skip it

from __future__ import annotation

Instead of immediately evaluating the type hints when a function or class is defined, it stores them as string literals.

1
2
3
4
5
6
7
8
9
10
11
12
from __future__ import annotation

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from my_module import MyType

"""
Automatically apply forward reference to store it as string.
"""
def function(data: MyType):
...