Skip to content

gh-104555: Runtime-checkable protocols: Don't let previous calls to isinstance() influence whether issubclass() raises an exception#104559

Merged
AlexWaygood merged 12 commits into
python:mainfrom
AlexWaygood:fix-runtime-protocols
May 17, 2023
Merged

gh-104555: Runtime-checkable protocols: Don't let previous calls to isinstance() influence whether issubclass() raises an exception#104559
AlexWaygood merged 12 commits into
python:mainfrom
AlexWaygood:fix-runtime-protocols

Conversation

@AlexWaygood
Copy link
Copy Markdown
Member

@AlexWaygood AlexWaygood commented May 16, 2023

ABCMeta.__instancecheck__ caches isinstance() calls against classes that have ABCMeta as their metaclass. It uses these cache entries not only to inform how future isinstance() calls behave, but also how issubclass() calls behave. That means that on main we now have some rather unfortunate behaviour when it comes to runtime-checkable protocols, due to the fact that typing._ProtocolMeta is a subclass of ABCMeta, and typing._ProtocolMeta.__instancecheck__ calls super().__instancecheck__() too soon (following 47753ec):

Python 3.12.0a7+ (heads/main:1163782868, May 16 2023, 18:09:28) [MSC v.1932 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from typing import *
>>> @runtime_checkable
... class Foo(Protocol):
...     x: int
...
>>> class Bar:
...     x = 42
...
>>> issubclass(Bar, Foo)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\alexw\coding\cpython\Lib\abc.py", line 123, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\alexw\coding\cpython\Lib\typing.py", line 1875, in _proto_hook
    raise TypeError("Protocols with non-method members"
TypeError: Protocols with non-method members don't support issubclass()
>>> isinstance(Bar(), Foo)
True
>>> issubclass(Bar, Foo)
False

This PR fixes the incorrect behaviour. It means that these isinstance() checks will be about twice as slow as they are on main:

@runtime_checkable
class Foo:
    def meth(self): ...

@Foo.register
class Bar: ...

isinstance(Bar(), Foo)

But they'll still be much faster than they are on 3.11. Use of ABCMeta.register with protocols is pretty rare anyway, as far as I know, since ABCMeta.register isn't supported by type checkers. Other kinds of isinstance() checks do not suffer a significant performance regression.

Fixes #104555.

(Skipping news, as this bug doesn't exist on any released version of Python.)

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3.12 only security fixes topic-typing type-bug An unexpected behavior, bug, or error

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Typing: runtime-checkable protocols are broken on main

5 participants