8000 gh-74690: typing: Call `_get_protocol_attrs` and `_callable_members_only` at protocol class creation time, not during `isinstance()` checks by AlexWaygood · Pull Request #103160 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-74690: typing: Call _get_protocol_attrs and _callable_members_only at protocol class creation time, not during isinstance() checks #103160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 5, 2023
Next Next commit
gh-74690: typing: Cache results of _get_protocol_attrs and `_callab…
…le_member_only`
  • Loading branch information
AlexWaygood committed Mar 31, 2023
commit f15c64abf5fb66d16cf4723abac07f474f752f05
23 changes: 12 additions & 11 deletions 8000 Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1935,9 +1935,9 @@ def _get_protocol_attrs(cls):
return attrs


def _is_callable_members_only(cls, protocol_attrs):
def _is_callable_members_only(cls):
# PEP 544 prohibits using issubclass() with protocols that have non-method members.
return all(callable(getattr(cls, attr, None)) for attr in protocol_attrs)
return all(callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__)


def _no_init_or_replace_init(self, *args, **kwargs):
Expand Down Expand Up @@ -2016,20 +2016,19 @@ def __instancecheck__(cls, instance):
if not is_protocol_cls and issubclass(instance.__class__, cls):
return True

protocol_attrs = _get_protocol_attrs(cls)
if not hasattr(cls, "__protocol_attrs__"):
cls.__protocol_attrs__ = _get_protocol_attrs(cls)
cls.__callable_proto_members_only__ = _is_callable_members_only(cls)

if (
_is_callable_members_only(cls, protocol_attrs)
and issubclass(instance.__class__, cls)
):
if cls.__callable_proto_members_only__ 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 protocol_attrs):
for attr in cls.__protocol_attrs__):
return True
return super().__instancecheck__(instance)

Expand Down Expand Up @@ -2087,9 +2086,11 @@ def _proto_hook(other):
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")

protocol_attrs = _get_protocol_attrs(cls)
if not hasattr(cls, "__protocol_attrs__"):
cls.__protocol_attrs__ = _get_protocol_attrs(cls)
cls.__callable_proto_members_only__ = _is_callable_members_only(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"
Expand All @@ -2099,7 +2100,7 @@ def _proto_hook(other):
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__:
Expand Down
0