2024-06-16 18:42:44 -04:00
|
|
|
|
PEP: 747
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Title: Annotating Type Forms
|
|
|
|
|
Author: David Foster <david at dafoster.net>, Eric Traut <erictr at microsoft.com>
|
2024-06-16 18:42:44 -04:00
|
|
|
|
Sponsor: Jelle Zijlstra <jelle.zijlstra at gmail.com>
|
2024-06-17 10:47:12 -04:00
|
|
|
|
Discussions-To: https://discuss.python.org/t/pep-747-typeexpr-type-hint-for-a-type-expression/55984
|
2024-06-16 18:42:44 -04:00
|
|
|
|
Status: Draft
|
|
|
|
|
Type: Standards Track
|
|
|
|
|
Topic: Typing
|
|
|
|
|
Created: 27-May-2024
|
|
|
|
|
Python-Version: 3.14
|
2024-06-17 10:47:12 -04:00
|
|
|
|
Post-History: `19-Apr-2024 <https://discuss.python.org/t/typeform-spelling-for-a-type-annotation-object-at-runtime/51435>`__, `04-May-2024 <https://discuss.python.org/t/typeform-spelling-for-a-type-annotation-object-at-runtime/51435/7/>`__, `17-Jun-2024 <https://discuss.python.org/t/pep-747-typeexpr-type-hint-for-a-type-expression/55984>`__
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
========
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
:ref:`Type expressions <typing:type-expression>` provide a standardized way
|
|
|
|
|
to specify types in the Python type system. When a type expression is
|
|
|
|
|
evaluated at runtime, the resulting *type form object* encodes the information
|
|
|
|
|
supplied in the type expression. This enables a variety of use cases including
|
|
|
|
|
runtime type checking, introspection, and metaprogramming.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Such use cases have proliferated, but there is currently no way to accurately
|
|
|
|
|
annotate functions that accept type form objects. Developers are forced to use
|
|
|
|
|
an overly-wide type like ``object``, which makes some use cases impossible and
|
|
|
|
|
generally reduces type safety. This PEP addresses this limitation by
|
|
|
|
|
introducing a new special form ``typing.TypeForm``.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
This PEP makes no changes to the Python grammar. ``TypeForm`` is
|
|
|
|
|
intended to be enforced only by type checkers, not by the Python runtime.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Motivation
|
|
|
|
|
==========
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
A function that operates on type form objects must understand how type
|
|
|
|
|
expression details are encoded in these objects. For example, ``int | str``,
|
|
|
|
|
``"int | str"``, ``list[int]``, and ``MyTypeAlias`` are all valid type
|
|
|
|
|
expressions, and they evaluate to instances of ``types.UnionType``,
|
|
|
|
|
``builtins.str``, ``types.GenericAlias``, and ``typing.TypeAliasType``,
|
|
|
|
|
respectively.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
There is currently no way to indicate to a type checker that a function accepts
|
|
|
|
|
type form objects and knows how to work with them. ``TypeForm`` addresses this
|
|
|
|
|
limitation. For example, here is a function that checks whether a value is
|
|
|
|
|
assignable to a specified type and returns None if it is not::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def trycast[T](typx: TypeForm[T], value: object) -> T | None: ...
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
The use of ``TypeForm`` and the type variable ``T`` describes a relationship
|
|
|
|
|
between the type form passed to parameter ``typx`` and the function's
|
|
|
|
|
return type.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``TypeForm`` can also be used with :ref:`typing:typeis` to define custom type
|
|
|
|
|
narrowing behaviors::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def isassignable[T](value: object, typx: TypeForm[T]) -> TypeIs[T]: ...
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
request_json: object = ...
|
|
|
|
|
if isassignable(request_json, MyTypedDict):
|
|
|
|
|
assert_type(request_json, MyTypedDict) # Type of variable is narrowed
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
The ``isassignable`` function implements something like an enhanced
|
|
|
|
|
``isinstance`` check. This is useful for validating whether a value decoded
|
|
|
|
|
from JSON conforms to a particular structure of nested ``TypedDict``\ s,
|
|
|
|
|
lists, unions, ``Literal``\ s, or any other type form that can be described
|
|
|
|
|
with a type expression. This kind of check was alluded to in
|
|
|
|
|
:pep:`PEP 589 <589#using-typeddict-types>` but could not be implemented without
|
|
|
|
|
``TypeForm``.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Why not ``type[C]``?
|
|
|
|
|
--------------------
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
One might think that ``type[C]`` would suffice for these use cases. However,
|
|
|
|
|
only class objects (instances of the ``builtins.type`` class) are assignable
|
|
|
|
|
to ``type[C]``. Many type form objects do not meet this requirement::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def trycast[T](typx: type[T], value: object) -> T | None: ...
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
trycast(str, 'hi') # OK
|
|
|
|
|
trycast(Literal['hi'], 'hi') # Type violation
|
|
|
|
|
trycast(str | None, 'hi') # Type violation
|
|
|
|
|
trycast(MyProtocolClass, obj) # Type violation
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
TypeForm use cases
|
|
|
|
|
------------------
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
`A survey of Python libraries`_ reveals several categories of functions that
|
|
|
|
|
would benefit from ``TypeForm``:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
.. _A survey of Python libraries: https://github.com/python/mypy/issues/9773#issuecomment-2017998886
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
- Assignability checkers:
|
2024-06-21 21:26:30 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
- Determines whether a value is assignable to a specified type
|
|
|
|
|
- Pattern 1:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``def is_assignable[T](value: object, typx: TypeForm[T]) -> TypeIs[T]``
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
- Pattern 2:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``def is_match[T](value: object, typx: TypeForm[T]) -> TypeGuard[T]``
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
- Examples: beartype.\ `is_bearable`_, trycast.\ `isassignable`_,
|
|
|
|
|
typeguard.\ `check_type`_, xdsl.\ `isa`_
|
|
|
|
|
|
|
|
|
|
.. _is_bearable: https://github.com/beartype/beartype/issues/255
|
|
|
|
|
.. _isassignable: https://github.com/davidfstr/trycast?tab=readme-ov-file#isassignable-api
|
|
|
|
|
.. _check_type: https://typeguard.readthedocs.io/en/latest/api.html#typeguard.check_type
|
|
|
|
|
.. _isa: https://github.com/xdslproject/xdsl/blob/ac12c9ab0d64618475efb98d1d197bdd79f593c3/xdsl/utils/hints.py#L23
|
|
|
|
|
|
|
|
|
|
- Converters:
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
- If a value is assignable to (or coercible to) a specified type,
|
|
|
|
|
a *converter* returns the value narrowed to (or coerced to) that type.
|
|
|
|
|
Otherwise, an exception is raised.
|
|
|
|
|
|
2024-06-16 18:42:44 -04:00
|
|
|
|
- Pattern 1:
|
2024-09-15 18:10:35 -04:00
|
|
|
|
|
|
|
|
|
``def convert[T](value: object, typx: TypeForm[T]) -> T``
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
- Examples: cattrs.BaseConverter.\ `structure`_, trycast.\ `checkcast`_,
|
|
|
|
|
typedload.\ `load`_
|
|
|
|
|
|
|
|
|
|
- Pattern 2:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
class Converter[T]:
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def __init__(self, typx: TypeForm[T]) -> None: ...
|
2024-06-16 18:42:44 -04:00
|
|
|
|
def convert(self, value: object) -> T: ...
|
|
|
|
|
|
|
|
|
|
- Examples: pydantic.\ `TypeAdapter(T).validate_python`_,
|
|
|
|
|
mashumaro.\ `JSONDecoder(T).decode`_
|
|
|
|
|
|
|
|
|
|
.. _structure: https://github.com/python-attrs/cattrs/blob/5f5c11627a7f67a23d6212bc7df9f96243c62dc5/src/cattrs/converters.py#L332-L334
|
|
|
|
|
.. _checkcast: https://github.com/davidfstr/trycast#checkcast-api
|
|
|
|
|
.. _load: https://ltworf.github.io/typedload/
|
|
|
|
|
.. _TypeAdapter(T).validate_python: https://stackoverflow.com/a/61021183/604063
|
|
|
|
|
.. _JSONDecoder(T).decode: https://github.com/Fatal1ty/mashumaro?tab=readme-ov-file#usage-example
|
|
|
|
|
|
|
|
|
|
- Typed field definitions:
|
|
|
|
|
|
|
|
|
|
- Pattern:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
class Field[T]:
|
2024-09-15 18:10:35 -04:00
|
|
|
|
value_type: TypeForm[T]
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
- Examples: attrs.\ `make_class`_,
|
|
|
|
|
dataclasses.\ `make_dataclass`_ [#DataclassInitVar]_, `openapify`_
|
|
|
|
|
|
|
|
|
|
.. _make_class: https://www.attrs.org/en/stable/api.html#attrs.make_class
|
|
|
|
|
.. _make_dataclass: https://github.com/python/typeshed/issues/11653
|
|
|
|
|
.. _openapify: https://github.com/Fatal1ty/openapify/blob/c8d968c7c9c8fd7d4888bd2ddbe18ffd1469f3ca/openapify/core/models.py#L16
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
The survey also identified some introspection functions that accept runtime
|
|
|
|
|
type forms as input. Today, these functions are annotated with ``object``:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
- General introspection operations:
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
- Pattern: ``def get_annotation_info(typx: object) -> object``
|
|
|
|
|
|
2024-06-16 18:42:44 -04:00
|
|
|
|
- Examples: typing.{`get_origin`_, `get_args`_},
|
|
|
|
|
`typing_inspect`_.{is_*_type, get_origin, get_parameters}
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
These functions accept values evaluated from arbitrary annotation expressions,
|
|
|
|
|
not just type expressions, so they cannot be altered to use ``TypeForm``.
|
|
|
|
|
|
2024-06-16 18:42:44 -04:00
|
|
|
|
.. _get_origin: https://docs.python.org/3/library/typing.html#typing.get_origin
|
|
|
|
|
.. _get_args: https://docs.python.org/3/library/typing.html#typing.get_args
|
|
|
|
|
.. _typing_inspect: https://github.com/ilevkivskyi/typing_inspect?tab=readme-ov-file#readme
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specification
|
|
|
|
|
=============
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
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.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``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
|
|
|
|
|
:term:`assignable to <typing:assignable>` ``T``. For example,
|
|
|
|
|
``TypeForm[str | None]`` describes the set of all type form objects
|
|
|
|
|
that represent a type assignable to ``str | None``::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
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
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
err1: TypeForm[str | None] = str | int # Error
|
|
|
|
|
err2: TypeForm[str | None] = list[str | None] # Error
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
By this same definition, ``TypeForm[Any]`` describes a type form object
|
|
|
|
|
that represents the type ``Any`` or any type that is assignable to ``Any``.
|
|
|
|
|
Since all types in the Python type system are assignable to ``Any``,
|
|
|
|
|
``TypeForm[Any]`` describes the set of all type form objects
|
|
|
|
|
evaluated from all valid type expressions.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
The type expression ``TypeForm``, with no type argument provided, is
|
|
|
|
|
equivalent to ``TypeForm[Any]``.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Implicit ``TypeForm`` Evaluation
|
|
|
|
|
--------------------------------
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
When a static type checker encounters an expression that follows all of the
|
|
|
|
|
syntactic, semantic and contextual rules for a type expression as detailed
|
|
|
|
|
in the typing spec, the evaluated type of this expression should be assignable
|
|
|
|
|
to ``TypeForm[T]`` if the type it describes is assignable to ``T``.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
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]``:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
v1_actual: UnionType = str | None # OK
|
|
|
|
|
v1_type_form: TypeForm[str | None] = str | None # OK
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
v2_actual: type = list[int] # OK
|
|
|
|
|
v2_type_form: TypeForm = list[int] # OK
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
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 typing spec's rules for ``Annotated``, a static type checker may choose
|
|
|
|
|
to ignore any ``Annotated`` metadata that it does not understand::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
v3: TypeForm[int | str] = Annotated[int | str, "metadata"] # OK
|
|
|
|
|
v4: TypeForm[Annotated[int | str, "metadata"]] = int | str # OK
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
A string literal expression containing a valid type expression should likewise
|
|
|
|
|
be assignable to ``TypeForm``::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
v5: TypeForm[set[str]] = "set[str]" # OK
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Expressions that violate one or more of the syntactic, semantic, or contextual
|
|
|
|
|
rules for type expressions should not evaluate to a ``TypeForm`` type. The rules
|
|
|
|
|
for type expression validity are explained in detail within the typing spec, so
|
|
|
|
|
they are not repeated here::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
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 # 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 = Required[int] # Error: Required not allowed in type expression
|
|
|
|
|
bad9: TypeForm = Final[int] # Error: Final not allowed in type expression
|
|
|
|
|
bad10: TypeForm = Unpack[Ts] # Error: Unpack not allowed in this context
|
|
|
|
|
bad11: TypeForm = Optional # Error: Invalid use of Optional special form
|
|
|
|
|
bad12: TypeForm = T # Error if T is an out-of-scope TypeVar
|
|
|
|
|
bad13: TypeForm = "int + str" # Error: invalid quoted type expression
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Explicit ``TypeForm`` Evaluation
|
|
|
|
|
--------------------------------
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``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::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
x1 = TypeForm(str | None)
|
|
|
|
|
reveal_type(v1) # Revealed type is "TypeForm[str | None]"
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
x2 = TypeForm("list[int]")
|
|
|
|
|
revealed_type(v2) # Revealed type is "TypeForm[list[int]]"
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
x3 = TypeForm('type(1)') # Error: invalid type expression
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
At runtime the ``TypeForm(...)`` callable simply returns the value passed to it.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
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::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
x4 = type(int) # No error, evaluates to "type[int]"
|
|
|
|
|
|
|
|
|
|
x5 = TypeForm(type(int)) # Error: call not allowed in type expression
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Assignability
|
|
|
|
|
-------------
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``TypeForm`` has a single type parameter, which is covariant. That means
|
|
|
|
|
``TypeForm[B]`` is assignable to ``TypeForm[A]`` if ``B`` is assignable to
|
|
|
|
|
``A``::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def get_type_form() -> TypeForm[int]: ...
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
t1: TypeForm[int | str] = get_type_form() # OK
|
|
|
|
|
t2: TypeForm[str] = get_type_form() # Error
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``type[T]`` is a subtype of ``TypeForm[T]``, which means that ``type[B]`` is
|
|
|
|
|
assignable to ``TypeForm[A]`` if ``B`` is assignable to ``A``::
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def get_type() -> type[int]: ...
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
t3: TypeForm[int | str] = get_type() # OK
|
|
|
|
|
t4: TypeForm[str] = get_type() # Error
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``TypeForm`` is a subtype of ``object`` and is assumed to have all of the
|
|
|
|
|
attributes and methods of ``object``.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Backward Compatibility
|
|
|
|
|
======================
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
This PEP clarifies static type checker behaviors when evaluating type
|
|
|
|
|
expressions in "value expression" contexts (that is, contexts where type
|
|
|
|
|
expressions are not mandated by the typing spec). In the absence of a
|
|
|
|
|
``TypeForm`` type annotation, existing type evaluation behaviors persist,
|
|
|
|
|
so no backward compatibility issues are anticipated. For example, if a static
|
|
|
|
|
type checker previously evaluated the type of expression ``str | None`` as
|
|
|
|
|
``UnionType``, it will continue to do so unless this expression is assigned
|
|
|
|
|
to a variable or parameter whose type is annotated as ``TypeForm``.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
How to Teach This
|
|
|
|
|
=================
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Type expressions are used in annotations to describe which values are accepted
|
|
|
|
|
by a function parameter, returned by a function, or stored in a variable:
|
2024-06-21 21:26:30 -04:00
|
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
|
|
|
|
parameter type return type
|
|
|
|
|
| |
|
|
|
|
|
v v
|
|
|
|
|
def plus(n1: int, n2: int) -> int:
|
|
|
|
|
sum: int = n1 + n2
|
|
|
|
|
^
|
|
|
|
|
|
|
|
|
|
|
variable type
|
|
|
|
|
|
|
|
|
|
return sum
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Type expressions evaluate to valid *type form* objects at runtime and can be
|
2024-06-21 21:26:30 -04:00
|
|
|
|
assigned to variables and manipulated like any other data in a program:
|
|
|
|
|
|
|
|
|
|
.. code-block:: text
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
a variable a type expression
|
|
|
|
|
| |
|
|
|
|
|
v v
|
|
|
|
|
int_type_form: TypeForm = int | None
|
2024-06-21 21:26:30 -04:00
|
|
|
|
^
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
the type of a type form object
|
2024-06-21 21:26:30 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``TypeForm[]`` is how you spell the type of a *type form* object, which is
|
|
|
|
|
a runtime representation of a type.
|
2024-06-21 21:26:30 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``TypeForm`` is similar to ``type``, but ``type`` is compatible only with
|
|
|
|
|
**class objects** like ``int``, ``str``, ``list``, or ``MyClass``.
|
|
|
|
|
``TypeForm`` accommodates any type form that can be expressed using
|
|
|
|
|
a valid type expression, including those with brackets (``list[int]``), union
|
|
|
|
|
operators (``int | None``), and special forms (``Any``, ``LiteralString``,
|
|
|
|
|
``Never``, etc.).
|
2024-07-08 22:17:30 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Most programmers will not define their *own* functions that accept a ``TypeForm``
|
|
|
|
|
parameter or return a ``TypeForm`` value. It is more common to pass a type
|
|
|
|
|
form object to a library function that knows how to decode and use such objects.
|
2024-07-08 22:17:30 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
For example, the ``isassignable`` function in the ``trycast`` library
|
2024-06-21 21:26:30 -04:00
|
|
|
|
can be used like Python's built-in ``isinstance`` function to check whether
|
2024-09-15 18:10:35 -04:00
|
|
|
|
a value matches the shape of a particular type. ``isassignable`` accepts *any*
|
|
|
|
|
type form object as input.
|
2024-06-21 21:26:30 -04:00
|
|
|
|
|
|
|
|
|
- Yes:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
from trycast import isassignable
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
if isassignable(some_object, MyTypedDict): # OK: MyTypedDict is a TypeForm[]
|
2024-06-21 21:26:30 -04:00
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
- No:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
if isinstance(some_object, MyTypedDict): # ERROR: MyTypedDict is not a type[]
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Advanced Examples
|
|
|
|
|
=================
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
If you want to write your own runtime type checker or a function that
|
|
|
|
|
manipulates type form objects as values at runtime, this section provides
|
|
|
|
|
examples of how such a function can use ``TypeForm``.
|
2024-06-21 21:26:30 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Introspecting type form objects
|
|
|
|
|
-------------------------------
|
2024-06-21 21:26:30 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Functions like ``typing.get_origin`` and ``typing.get_args`` can be used to
|
|
|
|
|
extract components of some type form objects.
|
2024-06-21 21:26:30 -04:00
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
import typing
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def strip_annotated_metadata(typx: TypeForm[T]) -> TypeForm[T]:
|
2024-06-21 21:26:30 -04:00
|
|
|
|
if typing.get_origin(typx) is typing.Annotated:
|
2024-09-15 18:10:35 -04:00
|
|
|
|
typx = cast(TypeForm[T], typing.get_args(typx)[0])
|
2024-06-21 21:26:30 -04:00
|
|
|
|
return typx
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``isinstance`` and ``is`` can also be used to distinguish between different
|
|
|
|
|
kinds of type form objects:
|
2024-06-21 21:26:30 -04:00
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
import types
|
|
|
|
|
import typing
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def split_union(typx: TypeForm) -> tuple[TypeForm, ...]:
|
|
|
|
|
if isinstance(typ, types.UnionType): # X | Y
|
|
|
|
|
return cast(tuple[TypeForm, ...], typing.get_args(typ))
|
|
|
|
|
if typing.get_origin(typ) is typing.Union: # Union[X, Y]
|
|
|
|
|
return cast(tuple[TypeForm, ...], typing.get_args(typ))
|
|
|
|
|
if typ in (typing.Never, typing.NoReturn,):
|
2024-06-21 21:26:30 -04:00
|
|
|
|
return ()
|
2024-09-15 18:10:35 -04:00
|
|
|
|
return (typ,)
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Combining with a type variable
|
|
|
|
|
------------------------------
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``TypeForm`` can be parameterized by a type variable that is used elsewhere
|
|
|
|
|
within the same function definition:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def as_instance[T](typx: TypeForm[T]) -> T | None:
|
|
|
|
|
return typ() if isinstance(typ, type) else None
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Combining with ``type``
|
|
|
|
|
-----------------------
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Both ``TypeForm`` and ``type`` can be parameterized by the same type
|
2024-06-16 18:42:44 -04:00
|
|
|
|
variable within the same function definition:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def as_type[T](typx: TypeForm[T]) -> type[T] | None:
|
|
|
|
|
return typ if isinstance(typ, type) else None
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Combining with ``TypeIs`` and ``TypeGuard``
|
|
|
|
|
-------------------------------------------
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
A type variable can also be used by a ``TypeIs`` or ``TypeGuard`` return type:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def isassignable[T](value: object, typx: TypeForm[T]) -> TypeIs[T]: ...
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
count: int | str = ...
|
|
|
|
|
if isassignable(count, int):
|
|
|
|
|
assert_type(count, int)
|
|
|
|
|
else:
|
|
|
|
|
assert_type(count, str)
|
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Challenges When Accepting All TypeForms
|
2024-06-16 18:42:44 -04:00
|
|
|
|
---------------------------------------
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
A function that takes an *arbitrary* ``TypeForm`` as input must support a
|
|
|
|
|
variety of possible type form objects. Such functions are not easy to write.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
- New special forms are introduced with each new Python version, and
|
|
|
|
|
special handling may be required for each one.
|
|
|
|
|
- Quoted annotations [#quoted_less_common]_ (like ``'list[str]'``)
|
|
|
|
|
must be *parsed* (to something like ``list[str]``).
|
|
|
|
|
- Resolving quoted forward references inside type expressions is typically
|
|
|
|
|
done with ``eval()``, which is difficult to use in a safe way.
|
|
|
|
|
- Recursive types like ``IntTree = list[int | 'IntTree']`` are difficult
|
|
|
|
|
to resolve.
|
|
|
|
|
- User-defined generic types (like Django’s ``QuerySet[User]``) can introduce
|
|
|
|
|
non-standard behaviors that require runtime support.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Reference Implementation
|
|
|
|
|
========================
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Pyright (version 1.1.379) provides a reference implementation for ``TypeForm``.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Mypy contributors also `plan to implement <https://github.com/python/mypy/issues/9773>`__
|
|
|
|
|
support for ``TypeForm``.
|
2024-07-08 22:17:30 -04:00
|
|
|
|
|
|
|
|
|
A reference implementation of the runtime component is provided in the
|
|
|
|
|
``typing_extensions`` module.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rejected Ideas
|
|
|
|
|
==============
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Alternative names
|
|
|
|
|
-----------------
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Alternate names were considered for ``TypeForm``. ``TypeObject``
|
|
|
|
|
and ``TypeType`` were deemed too generic. ``TypeExpression`` and ``TypeExpr``
|
|
|
|
|
were also considered, but these were considered confusing because these objects
|
|
|
|
|
are not themselves "expressions" but rather the result of evaluating a type
|
|
|
|
|
expression.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Widen ``type[C]`` to support all type expressions
|
|
|
|
|
-------------------------------------------------
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``type`` was `designed`_ to describe class objects, subclasses of the
|
|
|
|
|
``type`` class. A value with the type ``type`` is assumed to be instantiable
|
|
|
|
|
through a constructor call. Widening the meaning of ``type`` to represent
|
|
|
|
|
arbitrary type form objects would present backward compatibility problems
|
|
|
|
|
and would eliminate a way to describe the set of values limited to subclasses
|
|
|
|
|
of ``type``.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
.. _designed: https://mail.python.org/archives/list/typing-sig@python.org/message/D5FHORQVPHX3BHUDGF3A3TBZURBXLPHD/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Accept arbitrary annotation expressions
|
|
|
|
|
---------------------------------------
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Certain special forms act as type qualifiers and can be used in
|
|
|
|
|
*some* but not *all* annotation contexts:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
For example. the type qualifier ``Final`` can be used as a variable type but
|
|
|
|
|
not as a parameter type or a return type:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
some_const: Final[str] = ... # OK
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def foo(not_reassignable: Final[object]): ... # Error: Final not allowed here
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def nonsense() -> Final[object]: ... # Error: Final not alowed here
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
With the exception of ``Annotated``, type qualifiers are not allowed in type
|
|
|
|
|
expressions. ``TypeForm`` is limited to type expressions because its
|
|
|
|
|
assignability rules are based on the assignability rules for types. It is
|
|
|
|
|
nonsensical to ask whether ``Final[int]`` is assignable to ``int`` because the
|
|
|
|
|
former is not a valid type expression.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Functions that wish to operate on objects that are evaluated from annotation
|
|
|
|
|
expressions can continue to accept such inputs as ``object`` parameters.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Pattern matching on type forms
|
|
|
|
|
------------------------------
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
It was asserted that some functions may wish to pattern match on the
|
|
|
|
|
interior of type expressions in their signatures.
|
|
|
|
|
|
|
|
|
|
One use case is to allow a function to explicitly enumerate all the
|
|
|
|
|
*specific* kinds of type expressions it supports as input.
|
|
|
|
|
Consider the following possible pattern matching syntax:
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
@overload
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def checkcast(typx: TypeForm[AT=Annotated[T, *A]], value: str) -> T: ...
|
2024-06-16 18:42:44 -04:00
|
|
|
|
@overload
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def checkcast(typx: TypeForm[UT=Union[*Ts]], value: str) -> Union[*Ts]: ...
|
2024-06-16 18:42:44 -04:00
|
|
|
|
@overload
|
|
|
|
|
def checkcast(typx: type[C], value: str) -> C: ...
|
|
|
|
|
# ... (more)
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
All functions observed in the wild that conceptually accept type form
|
|
|
|
|
objects generally try to support *all* kinds of type expressions, so it
|
|
|
|
|
doesn’t seem valuable to enumerate a particular subset.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
Additionally, the above syntax isn’t precise enough to fully describe the
|
|
|
|
|
input constraints for a typical function in the wild. For example, many
|
|
|
|
|
functions do not support type expressions with quoted subexpressions
|
|
|
|
|
like ``list['Movie']``.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
A second use case for pattern matching is to explicitly match an ``Annotated``
|
|
|
|
|
form to extract the interior type argument and strip away any metadata:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
def checkcast(
|
2024-09-15 18:10:35 -04:00
|
|
|
|
typx: TypeForm[T] | TypeForm[AT=Annotated[T, *A]],
|
2024-06-16 18:42:44 -04:00
|
|
|
|
value: object
|
|
|
|
|
) -> T:
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
However, ``Annotated[T, metadata]`` is already treated equivalent to ``T``
|
|
|
|
|
by static type checkers. There’s no additional value in being explicit about
|
|
|
|
|
this behavior. The example above could more simply be written as the equivalent:
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
def checkcast(typx: TypeForm[T], value: object) -> T:
|
2024-08-13 15:17:36 -04:00
|
|
|
|
|
|
|
|
|
|
2024-06-16 18:42:44 -04:00
|
|
|
|
Footnotes
|
|
|
|
|
=========
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
.. [#type_t]
|
|
|
|
|
:ref:`Type[T] <typing:type-brackets>` spells a class object
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
.. [#TypeIs]
|
|
|
|
|
:ref:`TypeIs[T] <typing:typeis>` is similar to bool
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
.. [#DataclassInitVar]
|
2024-09-15 18:10:35 -04:00
|
|
|
|
``dataclass.make_dataclass`` allows the type qualifier ``InitVar[...]``,
|
|
|
|
|
so ``TypeForm`` cannot be used in this case.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
.. [#forward_ref_normalization]
|
|
|
|
|
Special forms normalize string arguments to ``ForwardRef`` instances
|
|
|
|
|
at runtime using internal helper functions in the ``typing`` module.
|
|
|
|
|
Runtime type checkers may wish to implement similar functions when
|
|
|
|
|
working with string-based forward references.
|
|
|
|
|
|
2024-09-15 18:10:35 -04:00
|
|
|
|
.. [#quoted_less_common]
|
|
|
|
|
Quoted annotations are expected to become less common starting in Python
|
|
|
|
|
3.14 when :pep:`deferred annotations <649>` is implemented. However,
|
|
|
|
|
code written for earlier Python versions relies on quoted annotations and
|
|
|
|
|
will need to be supported for several years.
|
2024-06-16 18:42:44 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
This document is placed in the public domain or under the
|
|
|
|
|
CC0-1.0-Universal license, whichever is more permissive.
|