Skip to content

Commit 521a6a5

Browse files
committed
Deprecate inherited runtime checkability of protocols
1 parent 548526b commit 521a6a5

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

Doc/library/typing.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2527,6 +2527,12 @@ types.
25272527

25282528
.. versionadded:: 3.8
25292529

2530+
.. deprecated-removed:: 3.15 3.20
2531+
It is deprecated to call :func:`isinstance` and :func:`issubclass` checks on
2532+
protocol classes that were not explicitly decorated with :func:`!runtime_checkable`
2533+
after subclassing runtime-checkable protocol classes. This will throw
2534+
a :exc:`TypeError` in Python 3.20.
2535+
25302536
.. decorator:: runtime_checkable
25312537

25322538
Mark a protocol class as a runtime protocol.
@@ -2588,6 +2594,11 @@ types.
25882594
protocol. See :ref:`What's new in Python 3.12 <whatsnew-typing-py312>`
25892595
for more details.
25902596

2597+
.. deprecated-removed:: 3.15 3.20
2598+
It is deprecated to call :func:`isinstance` and :func:`issubclass` checks on
2599+
protocol classes that were not explicitly decorated with :func:`!runtime_checkable`
2600+
after subclassing runtime-checkable protocol classes. This will throw
2601+
a :exc:`TypeError` in Python 3.20.
25912602

25922603
.. function:: TypedDict
25932604

Lib/typing.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1825,8 +1825,8 @@ class _TypingEllipsis:
18251825

18261826
_TYPING_INTERNALS = frozenset({
18271827
'__parameters__', '__orig_bases__', '__orig_class__',
1828-
'_is_protocol', '_is_runtime_protocol', '__protocol_attrs__',
1829-
'__non_callable_proto_members__', '__type_params__',
1828+
'_is_protocol', '_is_runtime_protocol', '_is_deprecated_inherited_runtime_protocol',
1829+
'__protocol_attrs__', '__non_callable_proto_members__', '__type_params__',
18301830
})
18311831

18321832
_SPECIAL_NAMES = frozenset({
@@ -2015,6 +2015,16 @@ def __subclasscheck__(cls, other):
20152015
"Instance and class checks can only be used with "
20162016
"@runtime_checkable protocols"
20172017
)
2018+
if getattr(cls, '_is_deprecated_inherited_runtime_protocol', False):
2019+
# See GH-132604.
2020+
import warnings
2021+
depr_message = (
2022+
f"{cls!r} isn't explicitly decorated with @runtime_checkable but "
2023+
"it is used in issubclass() or isinstance(). Instance and class "
2024+
"checks can only be used with @runtime_checkable protocols. "
2025+
"This may stop working in Python 3.20."
2026+
)
2027+
warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2)
20182028
if (
20192029
# this attribute is set by @runtime_checkable:
20202030
cls.__non_callable_proto_members__
@@ -2044,6 +2054,18 @@ def __instancecheck__(cls, instance):
20442054
raise TypeError("Instance and class checks can only be used with"
20452055
" @runtime_checkable protocols")
20462056

2057+
if getattr(cls, '_is_deprecated_inherited_runtime_protocol', False):
2058+
# See GH-132604.
2059+
import warnings
2060+
2061+
depr_message = (
2062+
f"{cls!r} isn't explicitly decorated with @runtime_checkable but "
2063+
"it is used in issubclass() or isinstance(). Instance and class "
2064+
"checks can only be used with @runtime_checkable protocols. "
2065+
"This may stop working in Python 3.20."
2066+
)
2067+
warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2)
2068+
20472069
if _abc_instancecheck(cls, instance):
20482070
return True
20492071

@@ -2136,6 +2158,10 @@ def __init_subclass__(cls, *args, **kwargs):
21362158
if not cls.__dict__.get('_is_protocol', False):
21372159
cls._is_protocol = any(b is Protocol for b in cls.__bases__)
21382160

2161+
# Mark inherited runtime checkability (deprecated). See GH-132604.
2162+
if cls._is_protocol and getattr(cls, '_is_runtime_protocol', False):
2163+
cls._is_deprecated_inherited_runtime_protocol = True
2164+
21392165
# Set (or override) the protocol subclass hook.
21402166
if '__subclasshook__' not in cls.__dict__:
21412167
cls.__subclasshook__ = _proto_hook
@@ -2282,6 +2308,9 @@ def close(self): ...
22822308
raise TypeError('@runtime_checkable can be only applied to protocol classes,'
22832309
' got %r' % cls)
22842310
cls._is_runtime_protocol = True
2311+
# See GH-132604.
2312+
if hasattr(cls, '_is_deprecated_inherited_runtime_protocol'):
2313+
cls._is_deprecated_inherited_runtime_protocol = False
22852314
# PEP 544 prohibits using issubclass()
22862315
# with protocols that have non-method members.
22872316
# See gh-113320 for why we compute this attribute here,

0 commit comments

Comments
 (0)