First, let me take a step back to why we designed the type
statement the way it is today. The motivation of PEP 695, which added this construct, was to provide clear scoping semantics for generic type aliases: you can now write type SomeGeneric[T] = ...
and have the scope of T
be clear and unambiguous. While implementing the PEP, I noticed that the new syntax would also enable us to obviate the need for quoted annotations in forward references. That is why in type X = <something>
, we now do not evaluate <something>
until you explicitly ask for the value. This also made it trivial to create recursive type aliases (the initial version of PEP 695 had an odd special case to support recursive aliases).
These new type aliases thus have two advantages over the old system: they provide a clear scope for type parameters, and they are lazily evaluated so you don’t have to worry about quoting annotations. Also, if a runtime typing library wants to show the name of the alias to users, they now can.
You can do this already: use isinstance(Iso8601, typing.TypeAliasType)
to see that the object is a type
-based type alias, then access Iso8601.__value__
to see its value:
>>> type X = int
>>> import typing
>>> isinstance(X, typing.TypeAliasType)
True
>>> X.__value__
<class 'int'>
Note though that the value is lazily evaluated (to enable forward references and recursive types), so you have to account for errors that happen during evaluation:
>>> type Boom = 1/0
>>> Boom.__value__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in Boom
ZeroDivisionError: division by zero
(Obviously such a type is not meaningful, but a robust library needs to deal with this case and provide a good error message to users.)
Others in this thread brought up other libraries (pydantic, SQLAlchemy) that do runtime type introspection. They will simply need some updates to support the new type
statement.
@MegaIng above has a useful list above of other operations you can perform on classes but not on type aliases. If we wanted to, we could add special-casing to support at least some of those (e.g., we could add TypeAliasType.__mro_entries__
to support subclassing, or override __getattr__
to access class attributes), but there are two limitations:
- We’d have to implicitly evaluate the
__value__
of the type alias during this operation. Now, for example, subclassing could cause a NameError to be thrown inside the code for the type alias. This is confusing for users. - Many operations will only be valid on some kinds of type aliases. For example, it’s not meaningful to subclass a union. Again, this might be confusing for users of type aliases.