Type forms¶
TypeForm¶
(Originally specified in PEP 747.)
When a type expression is evaluated at runtime, the resulting value is a type form object. This value encodes the information supplied in the type expression, and it represents the type described by that type expression.
TypeForm is a special form that, when used in a type expression,
describes a set of type form objects. It accepts a single type argument, which
must be a valid type expression.
TypeForm[T] describes the set of all type form objects that represent
the type T or types that are assignable to T. For example,
TypeForm[str | None] describes the set of all type form objects that
represent a type assignable to str | None:
from typing import Any, Literal, Optional
from typing_extensions import TypeForm
ok1: TypeForm[str | None] = str | None # OK
ok2: TypeForm[str | None] = str # OK
ok3: TypeForm[str | None] = None # OK
ok4: TypeForm[str | None] = Literal[None] # OK
ok5: TypeForm[str | None] = Optional[str] # OK
ok6: TypeForm[str | None] = "str | None" # OK
ok7: TypeForm[str | None] = Any # OK
err1: TypeForm[str | None] = str | int # Error
err2: TypeForm[str | None] = list[str | None] # Error
By this same definition, TypeForm[object] describes a type form object
that represents the type object or any type that is assignable to
object. Since all types in the Python type system are assignable to
object, TypeForm[object] describes the set of all type form objects
evaluated from all valid type expressions.
TypeForm[Any] describes a TypeForm type whose type argument is not
statically known but is a valid type form object. It is assignable both
to and from any other TypeForm type (because Any is assignable both
to and from any type).
The type expression TypeForm, with no type argument provided, is
equivalent to TypeForm[Any].
Implicit TypeForm evaluation¶
When a static type checker encounters a valid type expression, the evaluated
type of this expression should be assignable to TypeForm[T] if the type it
describes is assignable to T.
For example, if a static type checker encounters the expression
str | None, it may normally evaluate its type as UnionType because it
produces a runtime value that is an instance of types.UnionType. However,
because this expression is a valid type expression, it is also assignable to
the type TypeForm[str | None]:
from types import GenericAlias, UnionType
from typing_extensions import TypeForm
v1_actual: UnionType = str | None # OK
v1_type_form: TypeForm[str | None] = str | None # OK
v2_actual: GenericAlias = list[int] # OK
v2_type_form: TypeForm = list[int] # OK
The Annotated special form is allowed in type expressions, so it can
also appear in an expression that is assignable to TypeForm. Consistent
with the general rules for Annotated, a static type checker may
choose to ignore any Annotated metadata that it does not understand:
from typing import Annotated
from typing_extensions import TypeForm
v3: TypeForm[int | str] = Annotated[int | str, "metadata"] # OK
A string literal expression containing a valid type expression should likewise
be assignable to TypeForm:
from typing_extensions import TypeForm
v4: TypeForm[set[str]] = "set[str]" # OK
Valid type expressions¶
This specification defines syntactic rules for type expressions in the form of a
formal grammar. Semantic rules are specified as
comments along with the grammar definition. Contextual requirements are
detailed throughout the text in sections that discuss concepts that
appear within type expressions. For example, the special form Self can be
used in a type expression only within a class, and a type variable can be used
within a type expression only when it is associated with a valid scope.
A valid type expression is an expression that follows all of the syntactic, semantic, and contextual rules for a type expression.
Expressions that are not valid type expressions should not evaluate to a
TypeForm type:
from typing import ClassVar, Final, Literal, Optional, Self, TypeVarTuple, Unpack
from typing_extensions import TypeForm
Ts = TypeVarTuple("Ts")
var = 1
bad1: TypeForm = tuple() # Error: call expression not allowed in type expression
bad2: TypeForm = (1, 2) # Error: tuple expression not allowed in type expression
bad3: TypeForm = 1 # Error: non-class object not allowed in type expression
bad4: TypeForm = Self # Error: Self not allowed outside of a class
bad5: TypeForm = Literal[var] # Error: variable not allowed in type expression
bad6: TypeForm = Literal[f""] # Error: f-strings not allowed in type expression
bad7: TypeForm = ClassVar[int] # Error: ClassVar not allowed in type expression
bad8: TypeForm = Final[int] # Error: Final not allowed in type expression
bad9: TypeForm = Unpack[Ts] # Error: Unpack not allowed in this context
bad10: TypeForm = Optional # Error: invalid use of Optional special form
bad11: TypeForm = "int + str" # Error: invalid quoted type expression
Explicit TypeForm evaluation¶
TypeForm also acts as a function that can be called with a single
argument. Type checkers should validate that this argument is a valid type
expression:
from typing import assert_type
from typing_extensions import TypeForm
x1 = TypeForm(str | None)
assert_type(x1, TypeForm[str | None])
x2 = TypeForm("list[int]")
assert_type(x2, TypeForm[list[int]])
x3 = TypeForm("type(1)") # Error: invalid type expression
The static type of a TypeForm(T) expression is TypeForm[T].
At runtime the TypeForm(...) callable simply returns the value passed to
it.
This explicit syntax serves two purposes. First, it documents the developer’s intent to use the value as a type form object. Second, static type checkers validate that all rules for type expressions are followed:
x4 = type(1) # No error, evaluates to "type[int]"
x5 = TypeForm(type(1)) # Error: call not allowed in type expression
Assignability¶
TypeForm has a single type parameter, which is covariant. That means
TypeForm[B] is assignable to TypeForm[A] if B is assignable to
A:
from typing_extensions import TypeForm
def get_type_form() -> TypeForm[int]: ...
t1: TypeForm[int | str] = get_type_form() # OK
t2: TypeForm[str] = get_type_form() # Error
type[T] is a subtype of TypeForm[T], which means that type[B] is
assignable to TypeForm[A] if B is assignable to A:
from typing_extensions import TypeForm
def get_type() -> type[int]: ...
t3: TypeForm[int | str] = get_type() # OK
t4: TypeForm[str] = get_type() # Error
TypeForm is a subtype of object and is assumed to have all of the
attributes and methods of object.