Skip to content
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

[3.12] gh-105280: Ensure isinstance([], collections.abc.Mapping) always evaluates to False (GH-105281) #105318

Merged
merged 1 commit into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import collections.abc
from collections import defaultdict
from functools import lru_cache, wraps
import gc
import inspect
import itertools
import pickle
Expand Down Expand Up @@ -2758,6 +2759,19 @@ def x(self): ...
with self.assertRaisesRegex(TypeError, only_classes_allowed):
issubclass(1, BadPG)

def test_isinstance_checks_not_at_whim_of_gc(self):
self.addCleanup(gc.enable)
gc.disable()

with self.assertRaisesRegex(
TypeError,
"Protocols can only inherit from other protocols"
):
class Foo(collections.abc.Mapping, Protocol):
pass

self.assertNotIsInstance([], collections.abc.Mapping)

def test_issubclass_and_isinstance_on_Protocol_itself(self):
class C:
def x(self): pass
Expand Down
28 changes: 20 additions & 8 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1777,6 +1777,25 @@ def _pickle_pskwargs(pskwargs):
class _ProtocolMeta(ABCMeta):
# This metaclass is somewhat unfortunate,
# but is necessary for several reasons...
def __new__(mcls, name, bases, namespace, /, **kwargs):
if name == "Protocol" and bases == (Generic,):
pass
elif Protocol in bases:
for base in bases:
if not (
base in {object, Generic}
or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
or (
issubclass(base, Generic)
and getattr(base, "_is_protocol", False)
)
):
raise TypeError(
f"Protocols can only inherit from other protocols, "
f"got {base!r}"
)
return super().__new__(mcls, name, bases, namespace, **kwargs)

def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
if getattr(cls, "_is_protocol", False):
Expand Down Expand Up @@ -1912,14 +1931,7 @@ def _proto_hook(other):
if not cls._is_protocol:
return

# ... otherwise check consistency of bases, and prohibit instantiation.
for base in cls.__bases__:
if not (base in (object, Generic) or
base.__module__ in _PROTO_ALLOWLIST and
base.__name__ in _PROTO_ALLOWLIST[base.__module__] or
issubclass(base, Generic) and getattr(base, '_is_protocol', False)):
raise TypeError('Protocols can only inherit from other'
' protocols, got %r' % base)
# ... otherwise prohibit instantiation.
if cls.__init__ is Protocol.__init__:
cls.__init__ = _no_init_or_replace_init

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix bug where ``isinstance([], collections.abc.Mapping)`` could evaluate to
``True`` if garbage collection happened at the wrong time. The bug was
caused by changes to the implementation of :class:`typing.Protocol` in
Python 3.12.