10000 gh-104555: Runtime-checkable protocols: Don't let previous calls to `isinstance()` influence whether `issubclass()` raises an exception by AlexWaygood · Pull Request #104559 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-104555: Runtime-checkable protocols: Don't let previous calls to isinstance() influence whether issubclass() raises an exception #104559

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 12 commits into from
May 17, 2023
Merged
Prev Previous commit
Next Next commit
Better fix
  • Loading branch information
AlexWaygood committed May 17, 2023
commit 5f72b820e68c63c44c51f6c7f7f068e6cfab57c2
31 changes: 15 additions & 16 deletions Lib/typing.py
B0CC
Original file line number Diff line number Diff line change
Expand Up @@ -1775,8 +1775,8 @@ def _pickle_pskwargs(pskwargs):


class _ProtocolMeta(ABCMeta):
# This metaclass is really unfortunate and exists only because of
# the lack of __instancehook__.
# This metaclass is somewhat unfortunate,
# but is necessary for several reasons...
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
cls.__protocol_attrs__ = _get_protocol_attrs(cls)
Expand All @@ -1786,6 +1786,17 @@ def __init__(cls, *args, **kwargs):
callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
)

def __subclasscheck__(cls, other):
if (
getattr(cls, '_is_protocol', False)
and not cls.__callable_proto_members_only__
and not _allow_reckless_class_checks(depth=2)
):
raise TypeError(
"Protocols with non-method members don't support issubclass()"
)
return super().__subclasscheck__(other)

def __instancecheck__(cls, instance):
# We need this method for situations where attributes are
# assigned in __init__.
Expand All @@ -1798,17 +1809,10 @@ def __instancecheck__(cls, instance):
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")

# gh-104555: Don't call super().__instancecheck__ here,
# ABCMeta.__instancecheck__ would erroneously use it to populate the cache,
# which would cause incorrect results for *issubclass()* calls
if type.__instancecheck__(cls, instance):
if super().__instancecheck__(instance):
return True

if is_protocol_cls:
# Fast path for protocols with only callable members
if cls.__callable_proto_members_only__ and issubclass(type(instance), cls):
return True

getattr_static = _lazy_load_getattr_static()
for attr in cls.__protocol_attrs__:
try:
Expand All @@ -1820,7 +1824,7 @@ def __instancecheck__(cls, instance):
else:
return True

return super().__instancecheck__(instance)
return False


class Protocol(Generic, metaclass=_ProtocolMeta):
Expand Down Expand Up @@ -1876,11 +1880,6 @@ def _proto_hook(other):
raise TypeError("Instance and class checks can only be used with"
" @runtime_checkable protocols")

if not cls.__callable_proto_members_only__ :
if _allow_reckless_class_checks():
return NotImplemented
raise TypeError("Protocols with non-method members"
" don't support issubclass()")
if not isinstance(other, type):
# Same error message as for issubclass(1, int).
raise TypeError('issubclass() arg 1 must be a class')
Expand Down
0