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:
TypeAliasType
is meant for type checker use and its design should not be restricted by run-time behaviour.TypeAliasType
is a replacement for the oldtyping.TypeAlias
and 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
TypeAliasType
and continue with the deprecation oftyping.TypeAlias
with 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 betweenTypeAliasType
or doing normal assignment without a way to explicitly mark an identifier as a type alias.
-
Option B is to not restrict
TypeAliasType
and 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
TypeAliasType
to be used as “transparent references” of “concrete types” and continue with the deprecation oftyping.TypeAlias
with 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
TypeAliasType
and “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.TypeAlias
it should be scheduled for removal at some point. ↩︎However, unions cannot support instantiation or subclassing since it’s ambiguous which choice should be used. ↩︎