classification
Title: PEP 585 breaks inspect.isclass
Type: Stage:
Components: Versions: Python 3.9
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Jelle Zijlstra, gvanrossum, joperez, kj, levkivskyi
Priority: normal Keywords:

Created on 2021-06-02 22:26 by joperez, last changed 2021-06-05 22:35 by levkivskyi.

Messages (8)
msg394950 - (view) Author: Joseph Perez (joperez) * Date: 2021-06-02 22:26
PEP 585 has the side-effect of making `list[int]` an instance of `type`. This is not the case for other generic aliases.

It also implies that `inspect.isclass(list[int]) is True`, while `list[int]` is not a class; as a proof of this statement `issubclass(list[int], collections.abc.Collection)` raises `TypeError: issubclass() arg 1 must be a class`.

By the way, there is the awkward thing of having `isinstance(list[int], type) is True` while `issubclass(type(list[int]), type) is False`.
msg394963 - (view) Author: Jelle Zijlstra (Jelle Zijlstra) * (Python triager) Date: 2021-06-03 00:23
The reason for this is that types.GenericAlias.__getattribute__ delegates to the alias's origin (in the `ga_getattro` function). As a result, `list[int].__class__` calls `list.__class__` and returns `type`. And the implementation of `isinstance(obj, type)` ultimately calls `issubclass(obj.__class__, type)`. (That's in `object_isinstance()` in abstract.c. It's news to me; I didn't know you could customize isinstance() behavior on the object side.)

To fix this, we could make `ga_getattro` not delegate for `__class__`, so that `list[int].__class__` would return `GenericAlias` instead of `type`. The current implementation of GenericAlias has been around for a few releases by now, though, so that change might break some use cases.

> This is not the case for other generic aliases.

This is not true; it is the same for e.g. `set[int]`. Unless you meant something else here.
msg394966 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-06-03 01:21
Since these are new forms (list[int] previously was an error), does it actually matter? Especially since these are primarily used in annotations.

@Joseph Perez, is there a specific library or pattern that is broken by this?

FWIW I did think rather carefully about which attributes to delegate or not, and delegating __class__ was intentional.
msg394988 - (view) Author: Joseph Perez (joperez) * Date: 2021-06-03 07:46
@Jelle Zijlstra Thank you for the explanation.

> The current implementation of GenericAlias has been around for a few releases by now, though, so that change might break some use cases.

I agree that a "fix" could have unexpected side-effect, my issue comes quite late indeed. By the way, Python typing is so much unstable (every version breaks the previous one), it's very complicated to write code that support multiple versions, so whatever the typing internal implementation, we must adapt.

> This is not true; it is the same for e.g. `set[int]`. Unless you meant something else here.

I have chosen `list[int]` as an example of `types.GenericAlias` introduced by PEP 585 (i could have chosen `set[int]` or `collections.abc.Collection[int]`). But other generic aliases, e.g. `typing.List[int]` or `MyClass[int]` (where `MyClass` inherits `Generic[T]`), are not instances of `type`.

> @Joseph Perez, is there a specific library or pattern that is broken by this?

Because `issubclass` requires a "class" as arg 1, I use the pattern `if isinstance(tp, type) and issubclass(tp, SomeClass)` (`isinstance` check being equivalent to `inspect.isclass`). With PEP 585, it breaks for `list[int]` and other builtin generic aliases.

> FWIW I did think rather carefully about which attributes to delegate or not, and delegating __class__ was intentional.

I don't have the context of the decision, so I can quite understand that delegating `__class__` was the right thing to do, especially when `__mro__` and other `type` attributes are also delegated. 
I mainly wanted to highlight this side effect, especially on the pattern mentioned above. (My issue title is a little bit excessive in this regard)

But as I've written, I've already so many wrappers to maintain compatibility between Python versions of typing that I can write a new one to handle this particularity of PEP 585. So this issue is not critical to me.
msg395001 - (view) Author: Ken Jin (kj) * (Python triager) Date: 2021-06-03 13:51
@Jelle thanks for nosy-ing me too and the thorough investigation.

@Joseph

Thanks for taking the time to raise this inconvenience on the bug tracker.

> By the way, Python typing is so much unstable (every version breaks the previous one), it's very complicated to write code that support multiple versions, so whatever the typing internal implementation, we must adapt.

Compared to some of the more mature modules in Python, I have to agree that typing.py is mildly unstable. However, you're not supposed to be using/importing from the internal constructs - those have no guarantee of stability. If you feel some common use cases aren't met by the current introspection helpers, please please please create a new issue for that and we'll consider it. How we use typing may differ from how you use it and so there's a lot we don't see. User bug reports and feedback have helped to surface such issues and improved typing for everyone :). 

> I have chosen `list[int]` as an example of `types.GenericAlias` introduced by PEP 585 (i could have chosen `set[int]` or `collections.abc.Collection[int]`). But other generic aliases, e.g. `typing.List[int]` or `MyClass[int]` (where `MyClass` inherits `Generic[T]`), are not instances of `type`.

This is an implementation detail. Most typing PEPs don't usually specify the runtime behavior in detail because most of them focus on static analysis. The implementation is usually up to the contributor's judgement. FWIW, to accommodate the new 3.9 GenericAlias, typing.py just added additional checks for `isinstance(tp, types.GenericAlias)` instead of checking only for `isinstance(tp, type)` and friends.
msg395006 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2021-06-03 15:34
Instead of introspecting types, use this library:
https://github.com/ilevkivskyi/typing_inspect
msg395008 - (view) Author: Ivan Levkivskyi (levkivskyi) * (Python committer) Date: 2021-06-03 15:51
Btw this reminds me I should make a PyPI release of typing_inspect (last release was May 2020), hopefully will make a release on this weekend.
msg395185 - (view) Author: Ivan Levkivskyi (levkivskyi) * (Python committer) Date: 2021-06-05 22:35
Uploaded typing_inspect 0.7.0 to PyPI (it should work with Python 3.9 hopefully)
History
Date User Action Args
2021-06-05 22:35:28levkivskyisetmessages: + msg395185
2021-06-03 15:51:57levkivskyisetmessages: + msg395008
2021-06-03 15:34:31gvanrossumsetmessages: + msg395006
2021-06-03 13:51:25kjsetmessages: + msg395001
2021-06-03 07:46:14joperezsetmessages: + msg394988
2021-06-03 01:21:22gvanrossumsetmessages: + msg394966
2021-06-03 00:23:58Jelle Zijlstrasetnosy: + Jelle Zijlstra, kj, gvanrossum, levkivskyi
messages: + msg394963
2021-06-02 22:26:01joperezcreate