Expanding there I have runtime typing functions that exact type annotations they work on is very hard to describe. They handle types that are registered for serialization/deserialization. This includes basic types + common generics (list/set/dict/Mapping/Sequence/etc). User defined generic classes kind of work, but fail in some ways. Polymorphic classes (classes with subclasses) work usually, but struggle when generics mixed in. String annotations usually work, but sometimes getting right namespace to evaluate them is tricky and fails. You can find bug reports to python itself on handling forward references across different modules correctly. TypedDicts mostly work, but I haven’t looked at adding any logic for ReadOnly yet. Required/NotRequired mostly works, but there’s one open bug in typing-extensions about it when mixed with forward references that causes it to misbehave.
Similarly libraries like typeguard/cattrs/etc tend to generally work but have strange exceptions/caveats where runtime analysis becomes hard and they don’t handle. So I don’t think TypeForm[int]/TypeForm[Foo] with concrete type is good idea to support especially in initial PEP.
IntOrStrType: TypeForm = Union[int, str]
This example I also find strange. Main use case I see for TypeForm is functions like,
def trycast[T](form: TypeForm[T], value: object) -> Optional[T]: ...
where TypeForm has a generic typevar with it. What does TypeForm without type variable mean? TypeForm[Any]? But TypeForm[int]/TypeForm[Foo] we’ve excluded as supported.
I would lean to only allow TypeForm[T] where T is TypeVar and fully exclude TypeForm by itself. So x: TypeForm = int
would raise a type checker error like TypeForm is unsupported outside function signature without type variable
. Similarly TypeForm[int] is not allowed. What function would you write that takes TypeForm vs object where distinction is important? TypeForm[T] is very useful because of T being usable elsewhere in signature, but TypeForm by itself lacks that. Notably most (all maybe) of your examples in Common kinds of functions that would benefit from TypeForm
are use cases where TypeForm[T] is valuable not TypeForm without type variable.
Next topic is exactly which annotations are allowed and what they would mean. Let’s focus on trycast function, what does
x: object
P = Paramspec('P')
class Foo:
...
trycast(Required[int], x)
trycast(P, x)
trycast(P.args, x)
trycast(ClassVar[int], x)
trycast(Final[int], x)
trycast('Foo', x)
mean? If you bind ClassVar[int] to T and it returns Optional[T] what does it mean to return a ClassVar? Similarly paramspec case I have no clue how to interpret even though P.args is a valid annotation in some contexts. I think these examples lead to necessary constraint that TypeForm[T] likely only works for annotations that would be valid as annotation for an argument in function signature. So Required/ReadOnly/Final/ClassVar/Paramspec/etc are all forbidden to bind to TypeForm[T].
Required/ReadOnly could make sense in very specific contexts like,
class Foo(TypedDict[T]):
x: T
Foo[Required[int]] # This could work and makes some sense.
but even there I struggle to see where TypeForm should be mentioned so I think safest choice to avoid strange behaviors/rules is only annotations that can always be placed as input/return argument annotation for a function signature are allowed.
Also I’ve focused on function signatures as that’s main place I see TypeForm[T] as valuable. I’m unsure on why/where we should allow
STR_TYPE: TypeForm[str] = str # variable type
this case especially when it also does TypeForm[str] instead of TypeForm[T]. I lean that TypeForm should not be allowed for variable types or we need examples/library use cases where allowing it for variables is valuable.
edit: I realize reading further TypeForm Values
section covers some of the Required/Final rules. My only disagreement is on variable declaration rule as I remain unclear on value/purpose of typeform next to variable. The parameter/return type rules I agree with fully.
edit 2: One comment I made on document is about handling of ForwardRefs. If you have code like,
trycast('list[int]', x)
then trycast must make ForwardRef and convert it to runtime type object. ForwardRefs have a private method _evaluate
in typing.py. I’m unaware of any good public typing api to convert forwardref object to runtime one. get_type_hints will often do it for you but depends where/how annotation is given to you. So I think for TypeForm usage either PEP acknowledges/advises libraries to re-implement their own forwardref → runtime value logic or it would be helpful to publicly expose 1/2 existing typing.py functions. ForwardRef_evaluate
is one I consider most useful although maybe similar function in typing would be preferred.