Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 747 – Annotating Type Forms

PEP 747 – Annotating Type Forms

Author:
David Foster <david at dafoster.net>, Eric Traut <erictr at microsoft.com>
Sponsor:
Jelle Zijlstra <jelle.zijlstra at gmail.com>
Discussions-To:
Discourse thread
Status:
Draft
Type:
Standards Track
Topic:
Typing
Created:
27-May-2024
Python-Version:
3.15
Post-History:
19-Apr-2024,04-May-2024,17-Jun-2024

Table of Contents

Abstract

Type expressions provide a standardized wayto specify types in the Python type system. When a type expression isevaluated at runtime, the resultingtype form object encodes the informationsupplied in the type expression. This enables a variety of use cases includingruntime type checking, introspection, and metaprogramming.

Such use cases have proliferated, but there is currently no way to accuratelyannotate functions that accept type form objects. Developers are forced to usean overly-wide type likeobject, which makes some use cases impossible andgenerally reduces type safety. This PEP addresses this limitation byintroducing a new special formtyping.TypeForm.

This PEP makes no changes to the Python grammar. Correct usage ofTypeForm isintended to be enforced only by type checkers, not by the Python runtime.

Motivation

A function that operates on type form objects must understand how typeexpression details are encoded in these objects. For example,int|str,"int|str",list[int], andMyTypeAlias are all valid typeexpressions, and they evaluate to instances oftypes.UnionType,builtins.str,types.GenericAlias, andtyping.TypeAliasType,respectively.

There is currently no way to indicate to a type checker that a function acceptstype form objects and knows how to work with them.TypeForm addresses thislimitation. For example, here is a function that checks whether a value isassignable to a specified type and returns None if it is not:

deftrycast[T](typx:TypeForm[T],value:object)->T|None:...

The use ofTypeForm and the type variableT describes a relationshipbetween the type form passed to parametertypx and the function’sreturn type.

TypeForm can also be used withTypeIs to define custom typenarrowing behaviors:

defisassignable[T](value:object,typx:TypeForm[T])->TypeIs[T]:...request_json:object=...ifisassignable(request_json,MyTypedDict):assert_type(request_json,MyTypedDict)# Type of variable is narrowed

Theisassignable function implements something like an enhancedisinstance check. This is useful for validating whether a value decodedfrom JSON conforms to a particular structure of nestedTypedDicts,lists, unions,Literals, or any other type form that can be describedwith a type expression. This kind of check was alluded to inPEP 589 but could not be implemented withoutTypeForm.

Why nottype[C]?

One might think thattype[C] would suffice for these use cases. However,only class objects (instances of thebuiltins.type class) are assignabletotype[C]. Many type form objects do not meet this requirement:

deftrycast[T](typx:type[T],value:object)->T|None:...trycast(str,'hi')# OKtrycast(Literal['hi'],'hi')# Type violationtrycast(str|None,'hi')# Type violationtrycast(MyProtocolClass,obj)# Type violation

TypeForm use cases

A survey of Python libraries reveals several categories of functions thatwould benefit fromTypeForm:

  • Assignability checkers:
    • Determines whether a value is assignable to a specified type
    • Pattern 1:defis_assignable[T](value:object,typx:TypeForm[T])->TypeIs[T]
    • Pattern 2:defis_match[T](value:object,typx:TypeForm[T])->TypeGuard[T]
    • Examples: beartype.is_bearable, trycast.isassignable,typeguard.check_type, xdsl.isa
  • Converters:
    • If a value is assignable to (or coercible to) a specified type,aconverter returns the value narrowed to (or coerced to) that type.Otherwise, an exception is raised.
    • Pattern 1:
      defconvert[T](value:object,typx:TypeForm[T])->T
    • Pattern 2:
      classConverter[T]:def__init__(self,typx:TypeForm[T])->None:...defconvert(self,value:object)->T:...

The survey also identified some introspection functions that accept runtimetype forms as input. Today, these functions are annotated withobject:

  • General introspection operations:

These functions accept values evaluated from arbitrary annotation expressions,not just type expressions, so they cannot be altered to useTypeForm.

Specification

When a type expression is evaluated at runtime, the resulting value is atype form object. This value encodes the information supplied in the typeexpression, and it represents the type described by that type expression.

TypeForm is a special form that, when used in a type expression, describesa set of type form objects. It accepts a single type argument, which must be avalid type expression.TypeForm[T] describes the set of all type formobjects that represent the typeT or types that areassignable toT. For example,TypeForm[str|None] describes the set of all type form objectsthat represent a type assignable tostr|None:

ok1:TypeForm[str|None]=str|None# OKok2:TypeForm[str|None]=str# OKok3:TypeForm[str|None]=None# OKok4:TypeForm[str|None]=Literal[None]# OKok5:TypeForm[str|None]=Optional[str]# OKok6:TypeForm[str|None]="str | None"# OKok7:TypeForm[str|None]=Any# OKerr1:TypeForm[str|None]=str|int# Errorerr2:TypeForm[str|None]=list[str|None]# Error

By this same definition,TypeForm[object] describes a type form objectthat represents the typeobject or any type that is assignable toobject.Since all types in the Python type system are assignable toobject,TypeForm[object] describes the set of all type form objectsevaluated from all valid type expressions.

TypeForm[Any] describes aTypeForm type whose type argument is notstatically known but is a valid type form object. It is thus assignable bothto and from any otherTypeForm type (becauseAny is assignable bothto and from any type).

The type expressionTypeForm, with no type argument provided, isequivalent toTypeForm[Any].

ImplicitTypeForm Evaluation

When a static type checker encounters a valid type expression, the evaluated type of this expression should be assignabletoTypeForm[T] if the type it describes is assignable toT.

For example, if a static type checker encounters the expressionstr|None,it may normally evaluate its type asUnionType because it produces aruntime value that is an instance oftypes.UnionType. However, becausethis expression is a valid type expression, it is also assignable to thetypeTypeForm[str|None]:

v1_actual:UnionType=str|None# OKv1_type_form:TypeForm[str|None]=str|None# OKv2_actual:type=list[int]# OKv2_type_form:TypeForm=list[int]# OK

TheAnnotated special form is allowed in type expressions, so it canalso appear in an expression that is assignable toTypeForm. Consistentwith the typing spec’s rules forAnnotated, a static type checker may chooseto ignore anyAnnotated metadata that it does not understand:

v3:TypeForm[int|str]=Annotated[int|str,"metadata"]# OKv4:TypeForm[Annotated[int|str,"metadata"]]=int|str# OK

A string literal expression containing a valid type expression should likewisebe assignable toTypeForm:

v5:TypeForm[set[str]]="set[str]"# OK

Valid Type Expressions

The typing spec defines syntactic rules for type expressions in the form of aformal grammar. Semantic rules are specifiedas comments along with the grammar definition. Contextual requirements are detailedthroughout the typing spec in sections that discuss concepts that appear withintype expressions. For example, the special formSelf can be used in atype expression only within a class, and a type variable can be used withina 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 aTypeForm type:

bad1:TypeForm=tuple()# Error: Call expression not allowed in type expressionbad2:TypeForm=(1,2)# Error: Tuple expression not allowed in type expressionbad3:TypeForm=1# Non-class object not allowed in type expressionbad4:TypeForm=Self# Error: Self not allowed outside of a classbad5:TypeForm=Literal[var]# Error: Variable not allowed in type expressionbad6:TypeForm=Literal[f""]# Error: f-strings not allowed in type expressionbad7:TypeForm=ClassVar[int]# Error: ClassVar not allowed in type expressionbad8:TypeForm=Required[int]# Error: Required not allowed in type expressionbad9:TypeForm=Final[int]# Error: Final not allowed in type expressionbad10:TypeForm=Unpack[Ts]# Error: Unpack not allowed in this contextbad11:TypeForm=Optional# Error: Invalid use of Optional special formbad12:TypeForm=T# Error if T is an out-of-scope TypeVarbad13:TypeForm="int + str"# Error: invalid quoted type expression

ExplicitTypeForm 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:

x1=TypeForm(str|None)reveal_type(v1)# Revealed type is "TypeForm[str | None]"x2=TypeForm('list[int]')revealed_type(v2)# Revealed type is "TypeForm[list[int]]"x3=TypeForm('type(1)')# Error: invalid type expression

The static type of aTypeForm(T) isTypeForm[T].

At runtime theTypeForm(...) callable simply returns the value passed to it.

This explicit syntax serves two purposes. First, it documents the developer’sintent to use the value as a type form object. Second, static type checkersvalidate 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 meansTypeForm[B] is assignable toTypeForm[A] ifB is assignable toA:

defget_type_form()->TypeForm[int]:...t1:TypeForm[int|str]=get_type_form()# OKt2:TypeForm[str]=get_type_form()# Error

type[T] is a subtype ofTypeForm[T], which means thattype[B] isassignable toTypeForm[A] ifB is assignable toA:

defget_type()->type[int]:...t3:TypeForm[int|str]=get_type()# OKt4:TypeForm[str]=get_type()# Error

TypeForm is a subtype ofobject and is assumed to have all of theattributes and methods ofobject.

Backward Compatibility

This PEP clarifies static type checker behaviors when evaluating typeexpressions in “value expression” contexts (that is, contexts where typeexpressions are not mandated by the typing spec). In the absence of aTypeForm type annotation, existing type evaluation behaviors persist,so no backward compatibility issues are anticipated. For example, if a statictype checker previously evaluated the type of expressionstr|None asUnionType, it will continue to do so unless this expression is assignedto a variable or parameter whose type is annotated asTypeForm.

How to Teach This

Type expressions are used in annotations to describe which values are acceptedby a function parameter, returned by a function, or stored in a variable:

              parameter type   return type              |                |              v                vdef plus(n1: int, n2: int) -> int:    sum: int = n1 + n2          ^          |          variable type    return sum

Type expressions evaluate to validtype form objects at runtime and can beassigned to variables and manipulated like any other data in a program:

 a variable                   a type expression |                            | v                            vint_type_form: TypeForm = int | None                 ^                 |                 the type of a type form object

TypeForm[] is how you spell the type of atype form object, which isa runtime representation of a type.

TypeForm is similar totype, buttype is compatible only withclass objects likeint,str,list, orMyClass.TypeForm accommodates any type form that can be expressed usinga valid type expression, including those with brackets (list[int]), unionoperators (int|None), and special forms (Any,LiteralString,Never, etc.).

Most programmers will not define theirown functions that accept aTypeFormparameter or return aTypeForm value. It is more common to pass a typeform object to a library function that knows how to decode and use such objects.

For example, theisassignable function in thetrycast librarycan be used like Python’s built-inisinstance function to check whethera value matches the shape of a particular type.isassignable acceptsanytype form object as input.

  • Yes:
    fromtrycastimportisassignableifisassignable(some_object,MyTypedDict):# OK: MyTypedDict is a TypeForm[]...
  • No:
    ifisinstance(some_object,MyTypedDict):# ERROR: MyTypedDict is not a type[]...

Advanced Examples

If you want to write your own runtime type checker or a function thatmanipulates type form objects as values at runtime, this section providesexamples of how such a function can useTypeForm.

Introspecting type form objects

Functions liketyping.get_origin andtyping.get_args can be used toextract components of some type form objects.

importtypingfromtypingimportTypeForm,castdefstrip_annotated_metadata[T](typx:TypeForm[T])->TypeForm[T]:iftyping.get_origin(typx)istyping.Annotated:typx=cast(TypeForm[T],typing.get_args(typx)[0])returntypx

isinstance andis can also be used to distinguish between differentkinds of type form objects:

importtypesimporttypingfromtypingimportTypeForm,castdefsplit_union(typx:TypeForm)->tuple[TypeForm,...]:ifisinstance(typx,types.UnionType):# X | Yreturncast(tuple[TypeForm,...],typing.get_args(typx))iftyping.get_origin(typx)istyping.Union:# Union[X, Y]returncast(tuple[TypeForm,...],typing.get_args(typx))iftypxin(typing.Never,typing.NoReturn,):return()return(typx,)

Combining with a type variable

TypeForm can be parameterized by a type variable that is used elsewherewithin the same function definition:

defas_instance[T](typx:TypeForm[T])->T|None:returntypx()ifisinstance(typx,type)elseNone

Combining withtype

BothTypeForm andtype can be parameterized by the same typevariable within the same function definition:

defas_type[T](typx:TypeForm[T])->type[T]|None:returntypxifisinstance(typx,type)elseNone

Combining withTypeIs andTypeGuard

A type variable can also be used by aTypeIs orTypeGuard return type:

defisassignable[T](value:object,typx:TypeForm[T])->TypeIs[T]:...count:int|str=...ifisassignable(count,int):assert_type(count,int)else:assert_type(count,str)

Challenges When Accepting All TypeForms

A function that takes anarbitraryTypeForm as input must support avariety of possible type form objects. Such functions are not easy to write.

  • New special forms are introduced with each new Python version, andspecial handling may be required for each one.
  • Quoted annotations[5] (like'list[str]')must beparsed (to something likelist[str]).
  • Resolving quoted forward references inside type expressions is typicallydone witheval(), which is difficult to use in a safe way.
  • Recursive types likeIntTree=list[int|'IntTree'] are difficultto resolve.
  • User-defined generic types (like Django’sQuerySet[User]) can introducenon-standard behaviors that require runtime support.

Reference Implementation

Pyright (version 1.1.379) provides a reference implementation forTypeForm.

Mypy (commit 1b7e71; Nov 3, 2025) providesa reference implementation forTypeForm.

A reference implementation of the runtime component is provided in thetyping_extensions module.

Rejected Ideas

Alternative names

Alternate names were considered forTypeForm.TypeObjectandTypeType were deemed too generic.TypeExpression andTypeExprwere also considered, but these were considered confusing because these objectsare not themselves “expressions” but rather the result of evaluating a typeexpression.

Widentype[C] to support all type expressions

type wasdesigned to describe class objects, subclasses of thetype class. A value with the typetype is assumed to be instantiablethrough a constructor call. Widening the meaning oftype to representarbitrary type form objects would present backward compatibility problemsand would eliminate a way to describe the set of values limited to subclassesoftype.

Accept arbitrary annotation expressions

Certain special forms act as type qualifiers and can be used insome but notall annotation contexts:

For example. the type qualifierFinal can be used as a variable type butnot as a parameter type or a return type:

some_const:Final[str]=...# OKdeffoo(not_reassignable:Final[object]):...# Error: Final not allowed heredefnonsense()->Final[object]:...# Error: Final not allowed here

With the exception ofAnnotated, type qualifiers are not allowed in typeexpressions.TypeForm is limited to type expressions because itsassignability rules are based on the assignability rules for types. It isnonsensical to ask whetherFinal[int] is assignable toint because theformer is not a valid type expression.

Functions that wish to operate on objects that are evaluated from annotationexpressions can continue to accept such inputs asobject parameters.

Pattern matching on type forms

It was asserted that some functions may wish to pattern match on theinterior of type expressions in their signatures.

One use case is to allow a function to explicitly enumerate all thespecific kinds of type expressions it supports as input.Consider the following possible pattern matching syntax:

@overloaddefcheckcast(typx:TypeForm[AT=Annotated[T,*A]],value:str)->T:...@overloaddefcheckcast(typx:TypeForm[UT=Union[*Ts]],value:str)->Union[*Ts]:...@overloaddefcheckcast(typx:type[C],value:str)->C:...# ... (more)

All functions observed in the wild that conceptually accept type formobjects generally try to supportall kinds of type expressions, so itdoesn’t seem valuable to enumerate a particular subset.

Additionally, the above syntax isn’t precise enough to fully describe theinput constraints for a typical function in the wild. For example, manyfunctions do not support type expressions with quoted subexpressionslikelist['Movie'].

A second use case for pattern matching is to explicitly match anAnnotatedform to extract the interior type argument and strip away any metadata:

defcheckcast(typx:TypeForm[T]|TypeForm[AT=Annotated[T,*A]],value:object)->T:

However,Annotated[T,metadata] is already treated equivalent toTby static type checkers. There’s no additional value in being explicit aboutthis behavior. The example above could more simply be written as the equivalent:

defcheckcast(typx:TypeForm[T],value:object)->T:

Acknowledgements

  • David Foster drafted the initial version of this PEP, drafted themypy implementation of it, and shepherded it through the PEP process.
  • Eric Traut provided tons of feedback throughout the design process,drafted a major update to the original PEP text, and drafted thepyright implementation of it.
  • Jelle Zijlstra provided feedback especially on early drafts of the PEPand drafted thetyping_extensions implementation of theTypeExpr special form.
  • Carl Meyer and Mehdi Drissi provided valuable feedback,particularly on the question of whether to allowtype to be assignedtoTypeForm or not.
  • Cecil Curry (leycec) provided feedback from the perspective ofruntime type checkers and experimented with the in-progressTypeFormspecial form in a real-world runtime type checker (beartype).
  • Jukka Lehtosalo provided feedback on the mypy implementation of TypeForm,helping the checking algorithm run faster and use less memory.
  • Michael H (mikeshardmind) proposed syntax ideas for matching specific kindsof type forms.
  • Paul Moore advocated for several changes to the PEP to make it moreapproachable to typing novices.
  • Tin Tvrtković (Tinche) and Salvo ‘LtWorf’ Tomaselli provided positive feedbackfrom the broader community at multiple times supporting that the PEP wouldbe useful.

Footnotes

[1]
Type[T] spells a class object
[2]
TypeIs[T] is similar to bool
[3]
dataclass.make_dataclass allows the type qualifierInitVar[...],soTypeForm cannot be used in this case.
[4]
Special forms normalize string arguments toForwardRef instancesat runtime using internal helper functions in thetyping module.Runtime type checkers may wish to implement similar functions whenworking with string-based forward references.
[5]
Quoted annotations are expected to become less common starting in Python3.14 whendeferred annotations is implemented. However,code written for earlier Python versions relies on quoted annotations andwill need to be supported for several years.

Copyright

This document is placed in the public domain or under theCC0-1.0-Universal license, whichever is more permissive.


Source:https://github.com/python/peps/blob/main/peps/pep-0747.rst

Last modified:2025-12-08 19:11:10 GMT


[8]ページ先頭

©2009-2026 Movatter.jp