Also allow TypeAliasType in isinstance and issubclass

The old way to annotate aliases, TypeAlias (TypeT: TypeAlias = int | str) is currently supported in isinstance and issubclass checks.

Since 3.12 we have new, better type aliases, TypeAliasType, which are created by putting type before the name of the alias.

x = StringNode(...)

TextNodeT = StringNode | IdentifierNode

if issubclass(type, TextNodeT):
    ...
else:
    ...

This works at type checking and runtime currently, but this does not:

x = StringNode(...)

type TextNodeT = StringNode | IdentifierNode

if issubclass(type, TextNodeT):
    ...
else:
    ...

Is there a reason we don’t support both? Does it have to do how 3.14 introduced later evaluation of type expressions and if so, can we work it out?

Here is the issue where support for TypeAlias type was added for isinstance and issubclass:

Yeah, it’s this. We don’t support isinstance or issubclass on string-literal forward references either:

ForwardRefType = "int | str"
isinstance(0, ForwardRefType) # TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
issubclass(int, ForwardRefType) # TypeError: issubclass() arg 2 must be a class, a tuple of classes, or a union

We could probably use TypeAliasType.__value__. I’d imagine this would have the following effects:

  • A small runtime cost on non-alias, exception-raising isinstance and issubclass calls (since we’d have to check for TypeAliasType if the initial check fails).
  • The potential to make isinstance and issubclass expensive if the second argument is indeed a TypeAliasType.

Neither’s immediately fatal, I’d imagine, but they’re worth considering.

Only sometimes.

from typing import TypeAlias

Foo: TypeAlias = int | str
isinstance(1, Foo) # Works

Bar: TypeAlias = int | list[str]
isinstance(["a"], Bar) # TypeError: isinstance() argument 2 cannot be a parameterized generic

I don’t think TypeAliasType should support this as long as it can go from “working” to “not working” because someone added another member to the union.

I don’t see an issue with this. “type X = y” is not really supposed to have any runtime meaning. You can always do “X=y” in replacement.

However what surprised me recently is the use of generic types is not supported, e.g list[int] will throw an error if used in issubclass, but not list

This is a case where I wish the original beahvior hadn’t happened. isinstance shouldn’t have supported typing unions to begin with, exactly because isinstance isn’t for typing, but for runtime, and there’s edges where it won’t work. It became an additional way to do something you already could, while implying that it’s desirable to use it that way in general (it isn’t)

2 Likes