PEP 695 type aliases not suitable as replacement for typing.TypeAlias?

Also to answer this question. I would use explicit aliases and PEP695 aliases in exactly the same way: To give a more succinct, descriptive name to complex type expressions that repeatedly appear in type annotations, it’s also handy for generic unions, to avoid having to repeat the same type parameter over and over again inside a type expression.

If I want a shorter name for a class I will use a regular assignment, because it’s not just a type alias, it’s an actual type.

Typeshed handles this exactly the same in its pyi stub files. If there’s a different name for the same type, they will either use a regular assignment or give it an annotation of type[Foo]. They only use TypeAlias when it’s an actual type alias, which is only intended to be used inside annotations or as an argument to a generic type.

This quote doesn’t say anything about runtime types.

You haven’t provided anything to indicate that it’s intended to support your use case.

There are a lot of run type checking tools. Pydantic, cattrs, typeguard, beartype, and more rely on runtime type introspection. Some of these tools can also be considered type checkers (pyanalyze). A library/application intended for type annotations (including special forms) does not at all imply it is based on static analysis.

With old alias style using a type alias generally just worked. The new style either requires any runtime type tool to add new special logic for pep 695 aliases or just fail on them. As a heavy user of those tools I consider distinction between type valid in type checker vs runtime type rather flawed. Even special forms that today only work as type annotations like Literal I’d prefer runtime type utilities like isinstance grow to support them where possible vs having a lot of subtle edge cases like today. At least for isinstance/issubclass, most special forms make sense with them (which is what allows typeguard to make sense a library). Subclassing type alias less so clear for some special forms but I also have the same examples of code that uses generic aliases and then subclasses them later. Especially to work around runtime vs stub inconsistency including standard library where a class might be generic at type checking time but not runtime.

I think promoting pep 695 type aliases to not support functionality that pep 613 type aliases will end up either pushing a bunch of tools to add new special logic or just avoid them entirely. Especially when situations where they don’t work feel rather unobvious to explain to normal user.

Edit: Also Pep 613 are not just documentation purposes. Type checkers have heuristics for whether an assignment is intended to be type alias usable as an annotation later. Those heuristics can fail and is why that pep was created. If you have long complex type that you want simple name for and type checker may get wrong (which is currently under specified behavior) you need explicit TypeAlias or pep 695 type for it to work properly. If you want to be safe and avoid guessing will heuristic work for you, assignments intended as future names for a type should generally use some explicit flavor of type alias.

I think this is mainly an issue of support for runtime introspection of type annotations in the standard library. Current utilities like typing.get_type_hints are insufficient, since they don’t work in every context you need it to work (e.g. within a generic class after it’s bound to a type). I don’t think it is unreasonable having to use some special set of functions to inspect type annotations at runtime, the unreasonable part is how difficult they are to use right now. PEP649 improves get_type_hints quite a bit, but we could still use an utility function that does the same thing for a single annotation, provided a context.

But than in itself is not really a compelling argument to make special typing constructs so transparent that you can use them in places where you otherwise wouldn’t expect to be able to use them, there’s a reason why runtime_checkable on Protocol is opt-in and not the default, it’s very expensive to do some of those things at runtime and it’s easy to pass these constructs on accident, rather than on purpose.

I think an alias for a class is distinctly different from a TypeAlias and there’s no ambiguity about what it means from a type checker’s perspective. If you want a different name for a class you usually want it to be available at runtime, i.e. both instantiable and subclassable.

For isinstance/issubclass things get more muddy, because it’s easier to extend special typing constructs to support them, but it’s also important to recognize that these checks can get very costly, so they should be added with care.

It’s been a long time since I read pep 695, but I was, like OP, under the impression that

test: TypeAlias = str

and

type test = str

were equivalent and I also find it kinda weird that they aren’t. Like I get in the general case were the type is some special form that weirdness can happen, but when it’s a regular type the resulting type alias should behave as a transparent alias IMO.

One of the problems PEP 695 was solving is that old style TypeAliases lose a lot of information and you can’t explicitly specify generic arguments. To solve this, the PEP instructed TypeAliasType, which is also backported AFAIK. Truly making test a see through for str in the above example is not possible without losing the information that the alias is named test. (extreme example. inst.__class__ is test can only be true if test is str, which would defeat the point of the new alias).

So making the alias fully transparent is not an option within the goals of PEP 695. The question just is how transparent should it be? What is the cutoff for what features are useful?

They are equivalent within the intended usecases of TypeAlias. People using it for stuff it wasn’t designed to do but happened to support “by accident” is not something the designers have to take into account.

Well, I do remember that the introduction of the new type alias syntax was fairly contentious, and to me it completely misses the mark in term of runtime type checking. The usefulness threshold for me is that StrAlias should have the same runtime behaviour as str in this code

type StrAlias = str

But from the examples in the OP it seems they don’t. If that was one of the design goals of PEP 695, then I think the design is poor.

If it was exactly the same, why even bother adding a special syntax for it? The fact is that TypeAliasType carries additional information, such as type_params and also the evaluation of the type expression is deferred until you first access it, so it removes the need for string forward references. Especially the latter would be impossible if it was just a regular assignment.

In order for TypeAliasType to work transparently you would need to construct a type that behaves differently based on what the type expression contained within happens to be, while still supporting runtime type introspection for the type parameters. _typing.GenericAlias already has to have quite a lot of hacky special sauce just to support one of the many different cases TypeAliasType would need to support in order to be fully transparent.

(post deleted by author)

I try very hard not comment on people’s tone but this is not okay. I am reporting this post. You only had to tell me that probably misunderstood the design goals. But I will be the bigger man and go back and read it. Oh, my, look at this, one of the explicit design goals was to replace typing.TypeAlias.

We propose to deprecate the existing typing.TypeAlias introduced in PEP 613 The new syntax eliminates its need entirely.

It currently fails one of the stated design goals because the need for typing.TypeAlias is not eleminated entirely. Maybe for static typecheckers, but not at runtime. And overall, the PEP doesn’t say much about TypeAliasType.

But next time you feel like being a jerk, step away from the keyboard and cool off.

I totally get that it might be difficult to implement, but until this is fixed the proposed deprecation of typing.TypeAlias cannot go through, because it isn’t a drop-in replacement. Maybe the design changed between the PEPs acceptance and it’s implementation because of difficulties and that’s understandable.

I will admit that I was didn’t choose my words carefully. What I meant is that it should support (at least) the three operations that outlined in the second code block of the op: isinstance, issubclass, and subclassing from a TypeAliasType. I use them for that purpose and I disagree with that the need for TypeAliases as outlined in PEP 613 is gone. I would have to change my code quite significantly to make the TypeAliasType work, because I rely heavily on subclassing type aliases in some of the code at work. And more generally, a need doesn’t disappear just because a new PEP was introduced.

Also, I did take the time to read PEP 695, after your very rude suggestion, and pointed to where I believe it says that TypeAliasType should support the use-cases outlined in the OP. I do expect you to do the same, please back your words up with some reference that says so.

I also have to say that I did at least expect somewhat of an apology, but I think I have come to the conclusion that you’re just an insufferable jerk and I won’t engage with you anymore. Good Bye.

Could it be the case that PEP 613 failed to outline the need? I would be surprised if the author of any PEP fully understood the need for what they’re proposing.
I’m not convinced that the intention is relevant. That a usage is not intended does not imply that it’s not worth supporting. If you want to argue that the usage is not worth supporting, it’s not relevant to talk about whether it’s intended.

That’s correct, that PEP has quite rightly doesn’t - it has no business in saying how I use a type alias at runtime.

In the beginning, before Python supported annotations for static type checking, if you wanted to declare a type alias in Python, you assigned a type to a variable. You may have also been assigning a type to a variable to use it as something than an alias for that type, maybe you wanted to print it out for debugging, maybe something else. The possibilities are literally endless, but a subset of those infinite use cases are those where you have wanted to actually use it as an alias as a type.

Prior to annotations being introduced, literally the only use cases for using a type alias as an actual alias for a type were in the same places where you would use a non-aliased type name - as constructors, in class definitions, issubclass and issintance checks, and so on. It was, crucially, impossible to use type aliases as annotations, because type annotations did not yet exist. These are completely valid, Pythonic, encouraged uses of type aliases.

There’s one problem with this however - it was impossible to semantically distinguish between uses of variables as type aliases vs merely wanting to do something else with them, like print them out. Once annotations had been added to Python, we suddenly had a way to provide this semantic information, but there was no way of distinguishing type aliases from other uses of assignments of types to variables until PEP 613 was implemented. That PEP did not say anything about the runtime use of type aliases quite rightly, because that was not its aim, neither was runtime uses any of it’s business. But up until that point, the only use cases that existed for type aliases as actual aliases as types had been those runtime uses I outlined above - as ctors, in class definitions, in dynamic type checks. It was only around this time that people were starting to use them in type annotations for static analysis.

Enter PEP 695, which adds a new way to define type aliases that are only usable in type annotations. To be clear, these aren’t type aliases, they are type annotation aliases, since they can’t be used as aliases of types in any other context aside from type annotations. But you know what? That’s actually fine, and personally I think PEP 695 syntax for declaring generic classes and functions is a great step forward.

However by making type expressions only usable in type annotations and at the same time also deprecating typing.TypeAlias, it has removed the only valid way to annotate actual type aliases as such.

Type annotations as well as the term type alias, as defined by type checkers and their use predates PEP 613 by several years, so I don’t think it’s accurate to characterize things this way.

It’s true that the use of type in Python predates type checkers, but the use of TypeAlias does not, and at that point it was already well established that the type system in addition to nominal types also encompasses structural types and gradual types as well as type qualifiers and also includes the notion of forward references via string literals.

The issue that TypeAlias solves is entirely in the realm of that optional type system, where ambiguity made it sometimes difficult or impossible for type checkers to know what is or isn’t a type alias. This ambiguity never involved nominal types, so using TypeAlias on an assignment to a nominal type does not add any value and in fact if you do use it, it’s a signal that the alias should only be used for type checking and not as a value.

I’d like you to show me an example where using TypeAlias in an assignment to a nominal type measurably improves readability and type checker behavior compared to just not using it.

And if you cannot live without using a PEP695 type alias as a runtime type, then you still can, you just have to access the actual value of it via the __value__ attribute. The following code still works just fine:

type Foo = int

class Bar(Foo.__value__):
    pass

There is a caveat here however, once you start nesting type aliases, you have to do things like Foo.__value__.__value__, but that does not seem like a likely use-case for an alias to a single nominal type.


  1. There’s one problem with this however - it was impossible to semantically distinguish between uses of variables as type aliases vs merely wanting to do something else with them, like print them out. Once annotations had been added to Python, we suddenly had a way to provide this semantic information, but there was no way of distinguishing type aliases from other uses of assignments of types to variables until PEP 613 was implemented. That PEP did not say anything about the runtime use of type aliases quite rightly, because that was not its aim, neither was runtime uses any of it’s business. But up until that point, ↩︎

While it’s impossible to measure readability, I like to use typing.TypeAlias because it makes it explicit that this variable should be used as a type alias. YMMV but readability is in the eye of the beholder. Type checker behaviour I’m not sure about, but it would look kinda strange if some aliases are just normal assignments and some others are “blessed” assigments:

ThingType1 = str
type ThingType2 = Literal["a", "b"]

(I think Literal is one of the special kinds that cannot be used like normal types but if it isn’t just insert whatever such type instead.)
It creates this strange dichotomy that is not very obvious.

Regarding structural subtypes (Protocols at least) they do support the runtime_checkable decorator so there is at least some previous art to suggest that adding runtime checkability to types only used in the optional type system is a good idea. I believe it is in this case, but obviously we disagree. At least, I think reverting the deprecation of typing.TypeAlias is a good idea, so we can keep using it for documentation purposes.

From the Python documentation of typing.TypeAlias:

Deprecated since version 3.12: TypeAlias is deprecated in favor of the type statement, which creates instances of TypeAliasType and which natively supports forward references. Note that while TypeAlias and TypeAliasType serve similar purposes and have similar names, they are distinct and the latter is not the type of the former. Removal of TypeAlias is not currently planned, but users are encouraged to migrate to type statements.

Especially the final sentence is relevant here. I think it’s fair to call TypeAlias soft deprecated, since for most use-cases you should just use a type statement instead. So the deprecation in this case is about nudging people towards using the superior[1] construct for the intended use-case of TypeAlias, not necessarily about preventing people from using it at all.

For now you can keep using TypeAlias for those other use-cases if you like[2], with no imminent danger of your code breaking in the future. If your preference is to use TypeAlias this way, that’s fine, but I don’t think that should restrict the design of TypeAliasType.


  1. as in it supports forward references without having to wrap them in strings and it’s runtime introspectable ↩︎

  2. even if it can hide errors with forward references ↩︎

I think that’s fair, even if I’d prefer not to have my preferred way marked as deprecated :slight_smile: Can’t have it all!

Perhaps, if you’re unable to be civil, taking a break from the keyboard will help?

Hi friends, admin here.

We’ve received quite a few reports from people in this thread about exchanges here that ended up raising the heat level too much, and in turn hurt participants.

Look, I see that this topic is clearly important to you, and you have strong opinions about how aliases should or should not be used. In fact, I have opinions on this, too!

But before we get into that, let me just say that disagreement is a normal, expected, even common thing in software design. Conflict itself isn’t abuse: we should be able to voice dissent, otherwise we hinder progress. However, if you want to be successful in this space, you should be able to recognize and avoid escalation, and you categorically have to withhold ad hominem attacks.

As an explicit reminder, this is a PSF space and the COC is in effect. I’m not saying this as a warning, I’m saying this because it provides us with clear guidelines on successful engagement.

With this in mind, please look back at this topic and edit/delete messages of your own that strayed from the path. I’d rather see that instead of forcing edits myself. Let’s avoid assigning blame and try to be better, shall we?

I hope we can get back to the actual topic, which is in fact a great find: you were able to identify an issue where polar opposite positions are both reasonable. This is one of those problems that could use direct engagement from more typing people. Let’s course-correct here and do that.

On that note, I think it might be appropriate to move this topic to the typing category, I don’t think the help category is appropriate and perhaps why it has evaded the attention of some typing people.


  1. I hope we can get back to the actual topic, which is in fact a great find: you were able to identify an issue where polar opposite positions are both reasonable. ↩︎