Skip to content

Commit 18e9fd8

Browse files
authored
[3.11] gh-103171: Revert undocumented behaviour change for runtime-checkable protocols decorated with @final (#105445)
1 parent 2ffeb0e commit 18e9fd8

File tree

3 files changed

+70
-1
lines changed

3 files changed

+70
-1
lines changed

Lib/test/test_typing.py

+65
Original file line numberDiff line numberDiff line change
@@ -3349,6 +3349,71 @@ def __init__(self):
33493349

33503350
Foo() # Previously triggered RecursionError
33513351

3352+
def test_empty_protocol_decorated_with_final(self):
3353+
@final
3354+
@runtime_checkable
3355+
class EmptyProtocol(Protocol): ...
3356+
3357+
self.assertIsSubclass(object, EmptyProtocol)
3358+
self.assertIsInstance(object(), EmptyProtocol)
3359+
3360+
def test_protocol_decorated_with_final_callable_members(self):
3361+
@final
3362+
@runtime_checkable
3363+
class ProtocolWithMethod(Protocol):
3364+
def startswith(self, string: str) -> bool: ...
3365+
3366+
self.assertIsSubclass(str, ProtocolWithMethod)
3367+
self.assertNotIsSubclass(int, ProtocolWithMethod)
3368+
self.assertIsInstance('foo', ProtocolWithMethod)
3369+
self.assertNotIsInstance(42, ProtocolWithMethod)
3370+
3371+
def test_protocol_decorated_with_final_noncallable_members(self):
3372+
@final
3373+
@runtime_checkable
3374+
class ProtocolWithNonCallableMember(Protocol):
3375+
x: int
3376+
3377+
class Foo:
3378+
x = 42
3379+
3380+
only_callable_members_please = (
3381+
r"Protocols with non-method members don't support issubclass()"
3382+
)
3383+
3384+
with self.assertRaisesRegex(TypeError, only_callable_members_please):
3385+
issubclass(Foo, ProtocolWithNonCallableMember)
3386+
3387+
with self.assertRaisesRegex(TypeError, only_callable_members_please):
3388+
issubclass(int, ProtocolWithNonCallableMember)
3389+
3390+
self.assertIsInstance(Foo(), ProtocolWithNonCallableMember)
3391+
self.assertNotIsInstance(42, ProtocolWithNonCallableMember)
3392+
3393+
def test_protocol_decorated_with_final_mixed_members(self):
3394+
@final
3395+
@runtime_checkable
3396+
class ProtocolWithMixedMembers(Protocol):
3397+
x: int
3398+
def method(self) -> None: ...
3399+
3400+
class Foo:
3401+
x = 42
3402+
def method(self) -> None: ...
3403+
3404+
only_callable_members_please = (
3405+
r"Protocols with non-method members don't support issubclass()"
3406+
)
3407+
3408+
with self.assertRaisesRegex(TypeError, only_callable_members_please):
3409+
issubclass(Foo, ProtocolWithMixedMembers)
3410+
3411+
with self.assertRaisesRegex(TypeError, only_callable_members_please):
3412+
issubclass(int, ProtocolWithMixedMembers)
3413+
3414+
self.assertIsInstance(Foo(), ProtocolWithMixedMembers)
3415+
self.assertNotIsInstance(42, ProtocolWithMixedMembers)
3416+
33523417

33533418
class GenericTests(BaseTestCase):
33543419

Lib/typing.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1883,7 +1883,7 @@ class _TypingEllipsis:
18831883

18841884

18851885
_TYPING_INTERNALS = ['__parameters__', '__orig_bases__', '__orig_class__',
1886-
'_is_protocol', '_is_runtime_protocol']
1886+
'_is_protocol', '_is_runtime_protocol', '__final__']
18871887

18881888
_SPECIAL_NAMES = ['__abstractmethods__', '__annotations__', '__dict__', '__doc__',
18891889
'__init__', '__module__', '__new__', '__slots__',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Revert undocumented behaviour change with runtime-checkable protocols
2+
decorated with :func:`typing.final` in Python 3.11. The behaviour change had
3+
meant that objects would not be considered instances of these protocols at
4+
runtime unless they had a ``__final__`` attribute. Patch by Alex Waygood.

0 commit comments

Comments
 (0)