summaryrefslogtreecommitdiffstats
path: root/Lib/typing.py
diff options
context:
space:
mode:
authorAlex Waygood <Alex.Waygood@Gmail.com>2023-04-05 11:19:03 (GMT)
committerGitHub <noreply@github.com>2023-04-05 11:19:03 (GMT)
commit3246688918a428738b61c4adb5fbc6525eae96f9 (patch)
tree8deff8a4760e9fd02d5ddc1b0e015a17735eb95d /Lib/typing.py
parentc396b6ddf3da784349bac9ebf7f28c55bde016ea (diff)
downloadcpython-3246688918a428738b61c4adb5fbc6525eae96f9.zip
cpython-3246688918a428738b61c4adb5fbc6525eae96f9.tar.gz
cpython-3246688918a428738b61c4adb5fbc6525eae96f9.tar.bz2
gh-74690: typing: Call `_get_protocol_attrs` and `_callable_members_only` at protocol class creation time, not during `isinstance()` checks (#103160)
Diffstat (limited to 'Lib/typing.py')
-rw-r--r--Lib/typing.py25
1 files changed, 14 insertions, 11 deletions
diff --git a/Lib/typing.py b/Lib/typing.py
index f50e9b0..b8420f6 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1905,7 +1905,8 @@ class _TypingEllipsis:
_TYPING_INTERNALS = frozenset({
'__parameters__', '__orig_bases__', '__orig_class__',
- '_is_protocol', '_is_runtime_protocol'
+ '_is_protocol', '_is_runtime_protocol', '__protocol_attrs__',
+ '__callable_proto_members_only__',
})
_SPECIAL_NAMES = frozenset({
@@ -1935,11 +1936,6 @@ def _get_protocol_attrs(cls):
return attrs
-def _is_callable_members_only(cls, protocol_attrs):
- # PEP 544 prohibits using issubclass() with protocols that have non-method members.
- return all(callable(getattr(cls, attr, None)) for attr in protocol_attrs)
-
-
def _no_init_or_replace_init(self, *args, **kwargs):
cls = type(self)
@@ -2012,6 +2008,15 @@ _cleanups.append(_lazy_load_getattr_static.cache_clear)
class _ProtocolMeta(ABCMeta):
# This metaclass is really unfortunate and exists only because of
# the lack of __instancehook__.
+ def __init__(cls, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ cls.__protocol_attrs__ = _get_protocol_attrs(cls)
+ # PEP 544 prohibits using issubclass()
+ # with protocols that have non-method members.
+ cls.__callable_proto_members_only__ = all(
+ callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
+ )
+
def __instancecheck__(cls, instance):
# We need this method for situations where attributes are
# assigned in __init__.
@@ -2029,7 +2034,7 @@ class _ProtocolMeta(ABCMeta):
if is_protocol_cls:
getattr_static = _lazy_load_getattr_static()
- for attr in _get_protocol_attrs(cls):
+ for attr in cls.__protocol_attrs__:
try:
val = getattr_static(instance, attr)
except AttributeError:
@@ -2095,9 +2100,7 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")
- protocol_attrs = _get_protocol_attrs(cls)
-
- if not _is_callable_members_only(cls, protocol_attrs):
+ if not cls.__callable_proto_members_only__ :
if _allow_reckless_class_checks():
return NotImplemented
raise TypeError("Protocols with non-method members"
@@ -2107,7 +2110,7 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
raise TypeError('issubclass() arg 1 must be a class')
# Second, perform the actual structural compatibility check.
- for attr in protocol_attrs:
+ for attr in cls.__protocol_attrs__:
for base in other.__mro__:
# Check if the members appears in the class dictionary...
if attr in base.__dict__: