Python 3.11 introduced an undocumented behaviour change for protocols decorated with both @final and @runtime_checkable. On 3.10:
>>> from typing import *
>>> @final
... @runtime_checkable
... class Foo(Protocol):
... def bar(self): ...
...
>>> class Spam:
... def bar(self): ...
...
>>> issubclass(Spam, Foo)
True
>>> isinstance(Spam(), Foo)
True
On 3.11:
>>> from typing import *
>>> @final
... @runtime_checkable
... class Foo(Protocol):
... def bar(self): ...
...
>>> class Spam:
... def bar(self): ...
...
>>> issubclass(Spam, Foo)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<frozen abc>", line 123, in __subclasscheck__
File "C:\Users\alexw\coding\cpython\Lib\typing.py", line 1547, in _proto_hook
raise TypeError("Protocols with non-method members"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Protocols with non-method members don't support issubclass()
>>> isinstance(Spam(), Foo)
False
This is because, following 0bbf30e (by @JelleZijlstra), the @final decorator sets a __final__ attribute wherever it can, so that it is introspectable by runtime tools. But the runtime-checkable-protocol isinstance() machinery doesn't know anything about the __final__ attribute, so it assumes that the __final__ attribute is just a regular protocol member.
This should be pretty easy to fix: we just need to add __final__ to the set of "special attributes" ignored by runtime-checkable-protocol isinstance()/issubclass() checks here:
|
_TYPING_INTERNALS = frozenset({ |
|
'__parameters__', '__orig_bases__', '__orig_class__', |
|
'_is_protocol', '_is_runtime_protocol' |
|
}) |
@JelleZijlstra do you agree with that course of action?
Linked PRs
Python 3.11 introduced an undocumented behaviour change for protocols decorated with both
@finaland@runtime_checkable. On 3.10:On 3.11:
This is because, following 0bbf30e (by @JelleZijlstra), the
@finaldecorator sets a__final__attribute wherever it can, so that it is introspectable by runtime tools. But the runtime-checkable-protocolisinstance()machinery doesn't know anything about the__final__attribute, so it assumes that the__final__attribute is just a regular protocol member.This should be pretty easy to fix: we just need to add
__final__to the set of "special attributes" ignored by runtime-checkable-protocolisinstance()/issubclass()checks here:cpython/Lib/typing.py
Lines 1906 to 1909 in 848bdbe
@JelleZijlstra do you agree with that course of action?
Linked PRs
@final#103173@final#105445@final#105473@final(GH-105473) #105474