There was a heated discusson in another thread regarding a difference in run-time behaviour of typing.TypeAlias and the new TypeAliasType introduced in PEP 695.
In short, we disagreed on whether TypeAliasType should be a “transparent reference” to a “concrete type”.
“Concrete type” here means a type that you can subclass, instantiate/construct, and use with isinstance and issubclass checks[1].
“Transparent reference” here means being able to use a TypeAliasType as a “concrete type” if it is an alias for a “concrete type”.
typing.TypeAlias run-time behaviour
If typing.TypeAlias is used to to mark a “concrete type” as an alias, it will not change the run-time behaviour of the alias, as the typing.TypeAlias only affects static type checkers:
>>> import typing
>>> OldAlias: typing.TypeAlias = str
>>> class SomeSubclass(str):
... ...
...
>>> issubclass(OldAlias, str)
True
>>> isinstance("abc", OldAlias)
True
>>> test = OldAlias()
TypeAliasType run-time behaviour
TypeAliasType is, afaict, only meant to be used by static type checkers, so it cannot be used as a “concrete type”:
>>> type NewAlias = str
>>> class SomeSubclass(NewAlias):
... ...
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: typealias() takes exactly 2 positional arguments (3 given)
>>> issubclass(NewAlias, str)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: issubclass() arg 1 must be a class
>>> isinstance("abc", NewAlias)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
>>> test = NewAlias()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'typing.TypeAliasType' object is not callable
What’s the problem?
The previous thread had two conflicting opinions that were both concluded to be valid, which are:
TypeAliasTypeis meant for type checker use and its design should not be restricted by run-time behaviour.TypeAliasTypeis a replacement for the oldtyping.TypeAliasand should work the same at run-time.
IMO, neither PEP 695 nor the typing spec clarifies which of these are correct.
Thus, It’s probably for the best if the typing spec clarifies this.
What choices do we have?
I currently see three options, but there are probably more than I can think of at the moment.
I will label these options as A, B, and C.
- Option A is to not restrict
TypeAliasTypeand continue with the deprecation oftyping.TypeAliaswith eventual removal[2].
This option would not affect the type system nor complicate the design ofTypeAliasType, but it would force users of run-time type tools to choose betweenTypeAliasTypeor doing normal assignment without a way to explicitly mark an identifier as a type alias.
-
Option B is to not restrict
TypeAliasTypeand undeprecatetyping.TypeAlias.
This would not affect the type system but still leave users of run-time type tools with the ability to mark an identifier as a type alias. -
C would be to extend
TypeAliasTypeto be used as “transparent references” of “concrete types” and continue with the deprecation oftyping.TypeAliaswith eventual removal.
This would unify run-time and static-time type aliases but complicating the design ofTypeAliasType.
My opinion
While I am not particularly well versed in the theory of types, from my perspective as a user of type annotations both for type checking and documentation, I’d prefer Option C.
A type alias is just another name for a type and thus should be able to be used as that type.
Also, typing.TypeAlias is a nice way to document an identifier and as things currently stand this way to document aliases is discouraged due to it being soft deprecated.
There is precedence in to add similar functionality where it makes sense, like how type unions support isinstance checks despite being a special typing construct[3]:
>>> UnionType = int | str
>>> isinstance(1, UnionType)
True
>>> issubclass(int, UnionType)
True
Looking at this, I would argue that we have already recognized that there needs to be some run-time support for special typing constructs, and adding it to TypeAliasType makes a lot of sense to me.
I will admit that there are probably more cases where one might want
TypeAliasTypeand “concrete types” to behave the same but these were the ones brought up in the other thread. ↩︎I know it’s currently soft deprecated (even though the documentation currently doesn’t use that term) but ISTM that if we’re serious about replacing
typing.TypeAliasit should be scheduled for removal at some point. ↩︎However, unions cannot support instantiation or subclassing since it’s ambiguous which choice should be used. ↩︎