mirror of
https://github.com/python/cpython.git
synced 2025-08-04 17:08:35 +00:00
gh-74690: typing: Don't unnecessarily call _get_protocol_attrs
twice in _ProtocolMeta.__instancecheck__
(#103141)
Speed up `isinstance()` calls against runtime-checkable protocols
This commit is contained in:
parent
80163e17d3
commit
9048d73f7a
1 changed files with 21 additions and 10 deletions
|
@ -1931,9 +1931,9 @@ def _get_protocol_attrs(cls):
|
|||
return attrs
|
||||
|
||||
|
||||
def _is_callable_members_only(cls):
|
||||
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 _get_protocol_attrs(cls))
|
||||
return all(callable(getattr(cls, attr, None)) for attr in protocol_attrs)
|
||||
|
||||
|
||||
def _no_init_or_replace_init(self, *args, **kwargs):
|
||||
|
@ -2000,24 +2000,32 @@ class _ProtocolMeta(ABCMeta):
|
|||
def __instancecheck__(cls, instance):
|
||||
# We need this method for situations where attributes are
|
||||
# assigned in __init__.
|
||||
is_protocol_cls = getattr(cls, "_is_protocol", False)
|
||||
if (
|
||||
getattr(cls, '_is_protocol', False) and
|
||||
is_protocol_cls and
|
||||
not getattr(cls, '_is_runtime_protocol', False) and
|
||||
not _allow_reckless_class_checks(depth=2)
|
||||
):
|
||||
raise TypeError("Instance and class checks can only be used with"
|
||||
" @runtime_checkable protocols")
|
||||
|
||||
if ((not getattr(cls, '_is_protocol', False) or
|
||||
_is_callable_members_only(cls)) and
|
||||
issubclass(instance.__class__, cls)):
|
||||
if not is_protocol_cls and issubclass(instance.__class__, cls):
|
||||
return True
|
||||
if cls._is_protocol:
|
||||
|
||||
protocol_attrs = _get_protocol_attrs(cls)
|
||||
|
||||
if (
|
||||
_is_callable_members_only(cls, protocol_attrs)
|
||||
and issubclass(instance.__class__, cls)
|
||||
):
|
||||
return True
|
||||
|
||||
if is_protocol_cls:
|
||||
if all(hasattr(instance, attr) and
|
||||
# All *methods* can be blocked by setting them to None.
|
||||
(not callable(getattr(cls, attr, None)) or
|
||||
getattr(instance, attr) is not None)
|
||||
for attr in _get_protocol_attrs(cls)):
|
||||
for attr in protocol_attrs):
|
||||
return True
|
||||
return super().__instancecheck__(instance)
|
||||
|
||||
|
@ -2074,7 +2082,10 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
|
|||
return NotImplemented
|
||||
raise TypeError("Instance and class checks can only be used with"
|
||||
" @runtime_checkable protocols")
|
||||
if not _is_callable_members_only(cls):
|
||||
|
||||
protocol_attrs = _get_protocol_attrs(cls)
|
||||
|
||||
if not _is_callable_members_only(cls, protocol_attrs):
|
||||
if _allow_reckless_class_checks():
|
||||
return NotImplemented
|
||||
raise TypeError("Protocols with non-method members"
|
||||
|
@ -2084,7 +2095,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 _get_protocol_attrs(cls):
|
||||
for attr in protocol_attrs:
|
||||
for base in other.__mro__:
|
||||
# Check if the members appears in the class dictionary...
|
||||
if attr in base.__dict__:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue