There is a common circular reference problem in python’s static typing. For example:
example.py
from utils import do_things
class Example:
pass
def main():
ex = Example()
do_things(ex)
utils.py
from example import Example
from foo_bar import i_look_at_annotations
@i_look_at_annotations
def do_things(ex: Example):
print(f"{ex} is an Example object")
This code won’t run due to the circular import. It’s important to note that the Example class isn’t used directly in the utils module except for in an annotation.
Static typing users avoid this using the if typing.TYPE_CHECKING idiom. This idiom is ugly and confusing, of course. But it also breaks runtime evaluation of annotations. @i_look_at_annotations will never be able to obtain a reference to Example, even if it waits until it is called to evaluate the annotation.
This is one of the last remaining static-typing “warts” of python. PEP 613 fixes forward and recursive references within type aliases, and PEP 649 fixes forward and recursive references in type annotations.
Here is my proposal: an “import type” statement reusing the soft keyword type. For example:
from example import type Example
At runtime, this would be equivalent to:
type Example = __import__('example').Example
That is, it creates a TypeAlias whose __value__ lazily evaluates to example.Example
This would avoid the circular reference problem, replace the ugly “if typing.TYPE_CHECKING” and ensure that runtime annotations can be properly checked.
OPTIONAL PROPOSALS:
- I optionally propose that this can be used in conjunction with a regular import statement, for simplicity. i.e.
from foo import Foo, type Bar, Baz, type Hoowhere Bar and Hoo are TypeAlias but Foo and Baz are not. - I optionally propose that the TypeAlias proxy be extended to be callable, similar to how GenericAlias is callable.