PEP 747 – TypeExpr: Type Hint for a Type Expression
- Author:
- David Foster <david at dafoster.net>
- Sponsor:
- Jelle Zijlstra <jelle.zijlstra at gmail >
- Discussions-To:
- Discourse thread
- Status:
- Draft
- Type:
- Standards Track
- Topic:
- Typing
- Created:
- 27-May-2024
- Python-Version:
- 3.14
- Post-History:
- 19-Apr-2024,04-May-2024,17-Jun-2024
Table of Contents
Abstract
PEP 484defines the notationtype[C]
whereC
is a class, to
refer to a class object that is a subtype ofC
.It explicitly does
not allowtype[C]
to refer to arbitrary
type expressionobjects such
as the runtime objectstr|None
,even ifC
is an unbounded
TypeVar
.[1]In cases where that restriction is unwanted, this
PEP proposes a new notationTypeExpr[T]
whereT
is a type, to
refer to a either a class object or some other type expression object
that is a subtype ofT
,allowing any kind of type to be referenced.
This PEP makes no Python grammar changes. Correct usage of
TypeExpr[]
is intended to be enforced only by static and runtime
type checkers and need not be enforced by Python itself at runtime.
Motivation
The introduction ofTypeExpr
allows new kinds of metaprogramming
functions that operate on type expressions to be type-annotated and
understood by type checkers.
For example, here is a function that checks whether a value is assignable to a variable of a particular type, and if so returns the original value:
deftrycast[T](typx:TypeExpr[T],value:object)->T|None:...
The use ofTypeExpr[]
and the type variableT
enables the return
type of this function to be influenced by atypx
value passed at
runtime, which is quite powerful.
Here is another function that checks whether a value is assignable to a
variable of a particular type, and if so returnsTrue
(as a special
TypeIs[]
bool[2]):
defisassignable[T](value:object,typx:TypeExpr[T])->TypeIs[T]:...
The use ofTypeExpr[]
andTypeIs[]
together enables type
checkers to narrow the return type appropriately depending on what type
expression is passed in:
request_json:object=...
ifisassignable(request_json,MyTypedDict):
assert_type(request_json,MyTypedDict)# type is narrowed!
Thatisassignable
function enables a kind of enhancedisinstance
check which is useful forchecking whether a value decoded from JSON
conforms to a particular structureof nestedTypedDict
s,
lists, unions,Literal
s, and other types. This kind
of check was alluded to inPEP 589but could
not be implemented at the time without a notation similar to
TypeExpr[]
.
Why can’ttype[]
be used?
One might think you could define the example functions above to take a
type[C]
- which is syntax that already exists - rather than a
TypeExpr[T]
.However if you were to do that then certain type
expressions likestr|None
- which are not class objects and
therefore nottype
s at runtime - would be rejected:
# NOTE: Uses a type[C] parameter rather than a TypeExpr[T]
deftrycast_type[C](typ:type[C],value:object)->T|None:...
trycast_type(str,'hi')# ok; str is a type
trycast_type(Optional[str],'hi')# ERROR; Optional[str] is not a type
trycast_type(str|int,'hi')# ERROR; (str | int) is not a type
trycast_type(MyTypedDict,dict(value='hi'))# questionable; accepted by mypy 1.9.0
To solve that problem,type[]
could be widened to include the
additional values allowed byTypeExpr
.However doing so would lose
type[]
’s current ability to spell a class object which always
supports instantiation andisinstance
checks, unlike arbitrary type
expression objects. ThereforeTypeExpr
is proposed as new notation
instead.
For a longer explanation of why we don’t just widentype[T]
to
accept all type expressions, see
Widen type[C] to support all type expressions.
Common kinds of functions that would benefit from TypeExpr
A survey of various Python librariesrevealed a few kinds of commonly
defined functions which would benefit fromTypeExpr[]
:
- Assignability checkers:
- Returns whether a value is assignable to a type expression. If so then also narrows the type of the value to match the type expression.
- Pattern 1:
defisassignable[T](value:object,typx:TypeExpr[T])->TypeIs[T]
- Pattern 2:
defismatch[T](value:object,typx:TypeExpr[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 type expression, aconverterreturns the value narrowed to (or coerced to) that type expression. Otherwise, it raises an exception.
- Pattern 1:
defconvert[T](value:object,typx:TypeExpr[T])->T
- Pattern 2:
classConverter[T]: def__init__(self,typx:TypeExpr[T])->None:... defconvert(self,value:object)->T:...
- Examples: pydantic.TypeAdapter(T).validate_ Python, mashumaro.JSONDecoder(T).decode
- Typed field definitions:
- Pattern:
classField[T]: value_type:TypeExpr[T]
- Examples: attrs.make_class, dataclasses.make_dataclass[3],openapify
- Pattern:
The survey also identified some introspection functions that take
annotation expressions as input using plainobject
s which would
notgain functionality by marking those inputs asTypeExpr[]
:
- General introspection operations:
- Pattern:
defget_annotation_info(maybe_annx:object)->object
- Examples: typing.{get_origin,get_args}, typing_inspect.{is_*_type, get_origin, get_parameters}
- Pattern:
Rationale
Before this PEP existed there were already a few definitions in use to describe different kinds of type annotations:
+----------------------------------+
| +------------------------------+ |
| | +-------------------------+ | |
| | | +---------------------+ | | |
| | | | Class object | | | | = type[C]
| | | +---------------------+ | | |
| | | Type expression object | | | = TypeExpr[T] <-- new!
| | +-------------------------+ | |
| | Annotation expression object | |
| +------------------------------+ |
| Object | = object
+----------------------------------+
- Class objects,
spelled as
type[C]
,supportisinstance
checks and are callable.- Examples:
int
,str
,MyClass
- Examples:
- Type expressions
include any type annotation which describes a type.
- Examples:
list[int]
,MyTypedDict
,int|str
,Literal['square']
,any class object
- Examples:
- Annotation expressions
include any type annotation, including those only valid in specific contexts.
- Examples:
Final[int]
,Required[str]
,ClassVar[str]
, any type expression
- Examples:
TypeExpr
aligns with an existing definition from the above list -
type expression- to avoid introducing yet another subset of type annotations
that users of Python typing need to think about.
TypeExpr
aligns withtype expressionspecifically
because a type expression is already used to parameterize type variables,
which are used in combination withTypeIs
andTypeGuard
to enable
the compelling examples mentioned inMotivation.
TypeExpr
does not align withannotation expressionfor reasons given in
Rejected Ideas » Accept arbitrary annotation expressions.
Specification
ATypeExpr
value represents atype expression
such asstr|None
,dict[str,int]
,orMyTypedDict
.
ATypeExpr
type is written as
TypeExpr[T]
whereT
is a type or a type variable. It can also be
written without brackets as justTypeExpr
,which is treated the same as
toTypeExpr[Any]
.
Using TypeExprs
ATypeExpr
is a new kind of type expression, usable in any context where a
type expression is valid, as a function parameter type, a return type,
or a variable type:
defis_union_type(typx:TypeExpr)->bool:...# parameter type
defunion_of[S,T](s:TypeExpr[S],t:TypeExpr[T])\
->TypeExpr[S|T]:...# return type
STR_TYPE:TypeExpr=str# variable type
Note however that anunannotatedvariable assigned a type expression literal
will not be inferred to be ofTypeExpr
type by type checkers because PEP
484reserves that syntax for defining type aliases:
- No:
STR_TYPE=str# OOPS; treated as a type alias!
If you want a type checker to recognize a type expression literal in a bare
assignment you’ll need to explicitly declare the assignment-target as
havingTypeExpr
type:
- Yes:
STR_TYPE:TypeExpr=str
- Yes:
STR_TYPE:TypeExpr STR_TYPE=str
- Okay, but discouraged:
STR_TYPE=str# type: TypeExpr # the type comment is significant
TypeExpr
values can be passed around and assigned just like normal
values:
defswap1[S,T](t1:TypeExpr[S],t2:TypeExpr[T])->tuple[TypeExpr[T],TypeExpr[S]]:
t1_new:TypeExpr[T]=t2# assigns a TypeExpr value to a new annotated variable
t2_new:TypeExpr[S]=t1
return(t1_new,t2_new)
defswap2[S,T](t1:TypeExpr[S],t2:TypeExpr[T])->tuple[TypeExpr[T],TypeExpr[S]]:
t1_new=t2# assigns a TypeExpr value to a new unannotated variable
t2_new=t1
assert_type(t1_new,TypeExpr[T])
assert_type(t2_new,TypeExpr[S])
return(t1_new,t2_new)
# NOTE: A more straightforward implementation would use isinstance()
defensure_int(value:object)->None:
value_type:TypeExpr=type(value)# assigns a type (a subtype of TypeExpr)
assertvalue_type==int
TypeExpr Values
A variable of typeTypeExpr[T]
whereT
is a type, can hold any
type expression object- the result of evaluating a
type expression
at runtime - which is a subtype ofT
.
Incomplete expressions like a bareOptional
orUnion
which do
not spell a type are notTypeExpr
values.
TypeExpr[...]
is itself aTypeExpr
value:
OPTIONAL_INT_TYPE:TypeExpr=TypeExpr[int|None]# OK
assertisassignable(int|None,OPTIONAL_INT_TYPE)
TypeExpr[]
values includealltype expressions including some
non-universal type expressionswhich are not valid in all annotation contexts.
In particular:
Self
(valid only in some contexts)TypeGuard[...]
(valid only in some contexts)TypeIs[...]
(valid only in some contexts)
Explicit TypeExpr Values
The syntaxTypeExpr(T)
(with parentheses) can be used to
spell aTypeExpr[T]
value explicitly:
NONE=TypeExpr(None)
INT1=TypeExpr('int')# stringified type expression
INT2=TypeExpr(int)
At runtime theTypeExpr(...)
callable returns its single argument unchanged.
Implicit TypeExpr Values
Historically static type checkers have only needed to recognize type expressionsin contexts where a type expression was expected. Nowtype expression objectsmust also be recognized in contexts where a value expression is expected.
Static type checkers already recognizeclass objects(type[C]
):
- As a value expression,
C
has typetype[C]
, for each of the following values of C:name
(wherename
must refer to a valid in-scope class, type alias, or TypeVar)name'['...']'
<type>'['...']'
The followingunparameterized type expressionscan be recognized unambiguously:
- As a value expression,
X
has typeTypeExpr[X]
, for each of the following values of X:<Any>
<Self>
<LiteralString>
<NoReturn>
<Never>
None:The type expressionNone
(NoneType
) is ambiguous with the valueNone
,
so must use the explicitTypeExpr(...)
syntax:
- As a value expression,
TypeExpr(None)
has typeTypeExpr[None]
. - As a value expression,
None
continues to have typeNone
.
The followingparameterized type expressionscan be recognized unambiguously:
- As a value expression,
X
has typeTypeExpr[X]
, for each of the following values of X:<Literal>'['...']'
<Optional>'['...']'
<Union>'['...']'
<Callable>'['...']'
<tuple>'['...']'
<TypeGuard>'['...']'
<TypeIs>'['...']'
Annotated:The type expressionAnnotated[...]
is ambiguous with
the annotation expressionAnnotated[...]
,
so must be disambiguated based on its argument type:
- As a value expression,
Annotated[x,...]
has typetype[C]
ifx
has typetype[C]
. - As a value expression,
Annotated[x,...]
has typeTypeExpr[T]
ifx
has typeTypeExpr[T]
. - As a value expression,
Annotated[x,...]
has typeobject
ifx
has a type that is nottype[C]
orTypeExpr[T]
.
Union:The type expressionT1|T2
is ambiguous with
the valueint1|int2
,set1|set2
,dict1|dict2
,and more,
so must use the explicitTypeExpr(...)
syntax:
- Yes:
ifisassignable(value,TypeExpr(int|str)):...
- No:
ifisassignable(value,int|str):...
Future PEPs may make it possible to recognize the value expressionT1|T2
directly as an
implicit TypeExpr value and avoid the need to use the explicitTypeExpr(...)
syntax,
but that work isdeferred for now.
Thestringified type expression"T"
is ambiguous with both
the stringified annotation expression"T"
and the string literal"T"
,
so must use the explicitTypeExpr(...)
syntax:
- As a value expression,
TypeExpr( "T" )
has typeTypeExpr[T]
, whereT
is a valid type expression - As a value expression,
"T"
continues to have typeLiteral[ "T" ]
.
No other kinds of type expressions currently exist.
New kinds of type expressions that are introduced should define how they will be recognized in a value expression context.
Literal[] TypeExprs
A value ofLiteral[...]
type isnotconsidered assignable to
aTypeExpr
variable even if all of its members spell valid types because
dynamic values are not allowed in type expressions:
STRS_TYPE_NAME:Literal['str','list[str]']='str'
STRS_TYPE:TypeExpr=STRS_TYPE_NAME# ERROR: Literal[] value is not a TypeExpr
HoweverLiteral[...]
itself is still aTypeExpr
:
DIRECTION_TYPE:TypeExpr[Literal['left','right']]=Literal['left','right']# OK
Static vs. Runtime Representations of TypeExprs
ATypeExpr
value appearing statically in a source file may be normalized
to a different representation at runtime. For example string-based
forward references are normalized at runtime to beForwardRef
instances
in some contexts:[4]
>>>IntTree=list[typing.Union[int,'IntTree']]
>>>IntTree
list[typing.Union[int, ForwardRef('IntTree')]]
The runtime representations ofTypeExpr
s are considered implementation
details that may change over time and therefore static type checkers are
not required to recognize them:
INT_TREE:TypeExpr=ForwardRef('IntTree')# ERROR: Runtime-only form
Runtime type checkers that wish to assign a runtime-only representation
of a type expression to aTypeExpr[]
variable must usecast()
to
avoid errors from static type checkers:
INT_TREE=cast(TypeExpr,ForwardRef('IntTree'))# OK
Subtyping
Whether aTypeExpr
value can be assigned from one variable to another is
determined by the following rules:
Relationship with type
TypeExpr[]
is covariant in its argument type, just liketype[]
:
TypeExpr[T1]
is a subtype ofTypeExpr[T2]
iffT1
is a subtype ofT2
.type[C1]
is a subtype ofTypeExpr[C2]
iffC1
is a subtype ofC2
.
An unparameterizedtype
can be assigned to an unparameterizedTypeExpr
but not the other way around:
type[Any]
is assignable toTypeExpr[Any]
.(But not the other way around.)
Relationship with object
TypeExpr[]
is a kind ofobject
,just liketype[]
:
TypeExpr[T]
for anyT
is a subtype ofobject
.
TypeExpr[T]
,whereT
is a type variable, is assumed to have all
the attributes and methods ofobject
and is not callable.
Interactions with isinstance() and issubclass()
TheTypeExpr
special form cannot be used as thetype
argument to
isinstance
:
>>>isinstance(str,TypeExpr)
TypeError: typing.TypeExpr cannot be used with isinstance()
>>>isinstance(str,TypeExpr[str])
TypeError: isinstance() argument 2 cannot be a parameterized generic
TheTypeExpr
special form cannot be used as any argument to
issubclass
:
>>>issubclass(TypeExpr,object)
TypeError: issubclass() arg 1 must be a class
>>>issubclass(object,TypeExpr)
TypeError: typing.TypeExpr cannot be used with issubclass()
Affected signatures in the standard library
Changed signatures
The following signatures related to type expressions introduce
TypeExpr
where previouslyobject
orAny
existed:
typing.cast
typing.assert_type
Unchanged signatures
The following signatures related to annotation expressions continue to
useobject
and remain unchanged:
typing.get_origin
typing.get_args
The following signatures related to class objects continue to use
type
and remain unchanged:
builtins.isinstance
builtins.issubclass
builtins.type
typing.get_type_hints(...,include_extras=False)
nearly returns only type
expressions in Python 3.12, stripping out most type qualifiers
(Required,NotRequired,ReadOnly,Annotated
) but currently preserves a
few type qualifiers which are only allowed in annotation expressions
(ClassVar,Final,InitVar,Unpack
). It may be desirable to alter the
behavior of this function in the future to also strip out those
qualifiers and actually return type expressions, although this PEP does
not propose those changes now:
typing.get_type_hints(...,include_extras=False)
- Almost returns only type expressions, but not quite
typing.get_type_hints(...,include_extras=True)
- Returns annotation expressions
Backwards Compatibility
The rules for recognizing type expression objects
in a value expression context were not previously defined, so static type checkers
varied in what types were assigned
to such objects. Existing programs manipulating type expression objects
were already limited in manipulating them as plainobject
values,
and such programs should not break with
the newly-defined rules.
How to Teach This
Normally when using type annotations in Python you’re concerned with defining the shape of values allowed to be passed to a function parameter, returned by a function, or stored in a variable:
parameter type return type
| |
v v
def plus(n1: int, n2: int) -> int:
sum: int = n1 + n2
^
|
variable type
return sum
However type annotations themselves are valid values in Python and can be assigned to variables and manipulated like any other data in a program:
a variable a type
| |
v v
MAYBE_INT_TYPE: TypeExpr = int | None
^
|
the type of a type
TypeExpr[]
is how you spell the type of a variable containing a
type annotation object describing a type.
TypeExpr[]
is similar totype[]
,buttype[]
can only
spell simpleclass objectslikeint
,str
,list
,orMyClass
.
TypeExpr[]
by contrast can additionally spell more complex types,
including those with brackets (likelist[int]
) or pipes (likeint|None
),
and including special types likeAny
,LiteralString
,orNever
.
ATypeExpr
variable (maybe_float:TypeExpr
) looks similar to
aTypeAlias
definition (MaybeFloat:TypeAlias
), butTypeExpr
can only be used where a dynamic value is expected:
- No:
maybe_float:TypeExpr=float|None defsqrt(n:float)->maybe_float:...# ERROR: Can't use TypeExpr value in a type annotation
- Okay, but discouraged in Python 3.12+:
MaybeFloat:TypeAlias=float|None defsqrt(n:float)->MaybeFloat:...
- Yes:
typeMaybeFloat=float|None defsqrt(n:float)->MaybeFloat:...
It is uncommon for a programmer to define theirownfunction which accepts
aTypeExpr
parameter or returns aTypeExpr
value. Instead it is more common
for a programmer to pass a literal type expression to anexistingfunction
accepting aTypeExpr
input which was imported from a runtime type checker
library.
For example theisassignable
function from thetrycast
library
can be used like Python’s built-inisinstance
function to check whether
a value matches the shape of a particular type.
isassignable
will acceptanykind of type as an input because its input
is aTypeExpr
.By contrastisinstance
only accepts a simple class object
(atype[]
) as input:
- Yes:
fromtrycastimportisassignable ifisassignable(some_object,MyTypedDict):# OK: MyTypedDict is a TypeExpr[] ...
- No:
ifisinstance(some_object,MyTypedDict):# ERROR: MyTypedDict is not a type[] ...
There aremany other runtime type checkers
providing useful functions that accept aTypeExpr
.
Advanced Examples
If you want to write your own runtime type checker or some other
kind of function that manipulates types as values at runtime,
this section gives examples of how you might implement such a function
usingTypeExpr
.
Introspecting TypeExpr Values
ATypeExpr
is very similar to anobject
at runtime, with no additional
attributes or methods defined.
You can use existing introspection functions liketyping.get_origin
and
typing.get_args
to extract the components of a type expression that looks
likeOrigin[Arg1,Arg2,...,ArgN]
:
importtyping
defstrip_annotated_metadata(typx:TypeExpr[T])->TypeExpr[T]:
iftyping.get_origin(typx)istyping.Annotated:
typx=cast(TypeExpr[T],typing.get_args(typx)[0])
returntypx
You can also useisinstance
andis
to distinguish one kind of
type expression from another:
importtypes
importtyping
defsplit_union(typx:TypeExpr)->tuple[TypeExpr,...]:
ifisinstance(typx,types.UnionType):# X | Y
returncast(tuple[TypeExpr,...],typing.get_args(typx))
iftyping.get_origin(typx)istyping.Union:# Union[X, Y]
returncast(tuple[TypeExpr,...],typing.get_args(typx))
iftypxin(typing.Never,typing.NoReturn,):
return()
return(typx,)
Combining with a type variable
TypeExpr[]
can be parameterized by a type variable that is used elsewhere within
the same function definition:
defas_instance[T](typx:TypeExpr[T])->T|None:
returntypx()ifisinstance(typx,type)elseNone
Combining with type[]
BothTypeExpr[]
andtype[]
can be parameterized by the same type
variable within the same function definition:
defas_type[T](typx:TypeExpr[T])->type[T]|None:
returntypxifisinstance(typx,type)elseNone
Combining with TypeIs[] and TypeGuard[]
A type variable parameterizing aTypeExpr[]
can also be used by aTypeIs[]
within the same function definition:
defisassignable[T](value:object,typx:TypeExpr[T])->TypeIs[T]:...
count:int|str=...
ifisassignable(count,int):
assert_type(count,int)
else:
assert_type(count,str)
or by aTypeGuard[]
within the same function definition:
defisdefault[T](value:object,typx:TypeExpr[T])->TypeGuard[T]:
return(value==typx())ifisinstance(typx,type)elseFalse
value:int|str=''
ifisdefault(value,int):
assert_type(value,int)
assert0==value
elifisdefault(value,str):
assert_type(value,str)
assert''==value
else:
assert_type(value,int|str)
Challenges When Accepting All TypeExprs
A function that takes anarbitraryTypeExpr
as
input must support a large variety of possible type expressions and is
not easy to write. Some challenges faced by such a function include:
- An ever-increasing number of typing special forms are introduced with each new Python version which must be recognized, with special handling required for each one.
- Stringified type annotations[5](like
'list[str]'
) must beparsed(to something liketyping.List[str]
) to be introspected.- In practice it is extremely difficult for stringified type annotations to be handled reliably at runtime, so runtime type checkers may opt to not support them at all.
- Resolving string-based forward references inside type
expressions to actual values must typically be done using
eval()
, which is difficult/impossible to use in a safe way. - Recursive types like
IntTree=list[typing.Union[int,'IntTree']]
are not possible to fully resolve. - Supporting user-defined generic types (like Django’s
QuerySet[User]
) requires user-defined functions to recognize/parse, which a runtime type checker should provide a registration API for.
Reference Implementation
The following will be true when mypy#9773is implemented:
The mypy type checker supportsTypeExpr
types.
A reference implementation of the runtime component is provided in the
typing_extensions
module.
Rejected Ideas
Widen type[C] to support all type expressions
type
wasdesignedto only be used to describe class objects. A
class object can always be used as the second argument ofisinstance()
and can usually be instantiated by calling it.
TypeExpr
on the other hand is typically introspected by the user in
some way, is not necessarily directly instantiable, and is not
necessarily directly usable in a regularisinstance()
check.
It would be possible to widentype
to include the additional values
allowed byTypeExpr
but it would reduce clarity about the user’s
intentions when working with atype
.Different concepts and usage
patterns; different spellings.
Accept arbitrary annotation expressions
Certain typing special forms can be used insomebut notall annotation contexts:
For exampleFinal[]
can be used as a variable type but not as a
parameter type or a return type:
some_const:Final[str]=...# OK
deffoo(not_reassignable:Final[object]):...# ERROR: Final[] not allowed here
defnonsense()->Final[object]:...# ERROR: Final[] not meaningful here
TypeExpr[T]
does not allow matching such annotation expressions
because it is not clear what it would mean for such an expression
to parameterized by a type variable in positionT
:
defismatch[T](value:object,typx:TypeExpr[T])->TypeGuard[T]:...
deffoo(some_arg):
ifismatch(some_arg,Final[int]):# ERROR: Final[int] is not a TypeExpr
reveal_type(some_arg)#? NOT Final[int], because invalid for a parameter
Functions that wish to operate onallkinds of annotation expressions,
including those that are notTypeExpr
s, can continue to accept such
inputs asobject
parameters, as they must do so today.
Accept only universal type expressions
Earlier drafts of this PEP only allowedTypeExpr[]
to match the subset
of type expressions which are valid inallcontexts, excluding
non-universal type expressions.
However doing that would effectively
create a new subset of annotation expressions that Python typing users
would have to understand, on top of all the existing distinctions between
“class objects”, “type expressions”, and “annotation expressions”.
To avoid introducing yet another concept that everyone has to learn,
this proposal just roundsTypeExpr[]
to exactly match the existing
definition of a “type expression”.
Support pattern matching on type expressions
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 specifickinds of type expressions it supports as input. Consider the following possible pattern matching syntax:
@overload
defcheckcast(typx:TypeExpr[AT=Annotated[T,*Anns]],value:str)->T:...
@overload
defcheckcast(typx:TypeExpr[UT=Union[*Ts]],value:str)->Union[*Ts]:...
@overload
defcheckcast(typx:type[C],value:str)->C:...
#... (more)
All functions observed in the wild that conceptually take aTypeExpr[]
generally try to supportallkinds of type expressions, so it doesn’t
seem valuable to enumerate a particular subset.
Additionally the above syntax isn’t precise enough to fully describe the
actual input constraints for a typical function in the wild. For example
many functions recognize un-stringified type expressions like
list[Movie]
but may not recognize type expressions with stringified
subcomponents likelist['Movie']
.
A second use case for pattern matching on the interior of type
expressions is to explicitly match anAnnotated[]
form to pull out the
interior type argument and strip away the metadata:
defcheckcast(
typx:TypeExpr[T]|TypeExpr[AT=Annotated[T,*Anns]],
value:object
)->T:
HoweverAnnotated[T,metadata]
is already treated equivalent toT
anyway.
There’s no additional value in being explicit about this behavior.
The example above could be more-straightforwardly written as the equivalent:
defcheckcast(typx:TypeExpr[T],value:object)->T:
Recognize (T1 | T2) as an implicit TypeExpr value
It would be nice if a value expression likeint|str
could be recognized
as an implicitTypeExpr
value and be used directly in a context where a
TypeExpr
was expected. However making that possible would require making
changes to the rules that type checkers use for the|
operator. These rules
are currently underspecified and would need to be make explicit first,
before making changes to them. The PEP author is not sufficiently motivated to
take on that specification work at the time of writing.
Footnotes
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
Source:https://github / Python /peps/blob/main/peps/pep-0747.rst
Last modified:2024-08-20 10:29:32 GMT