Skip to content

Commit 08756db

Browse files
authored
gh-105280: Ensure isinstance([], collections.abc.Mapping) always evaluates to False (#105281)
1 parent 058b960 commit 08756db

File tree

3 files changed

+38
-8
lines changed

3 files changed

+38
-8
lines changed

Lib/test/test_typing.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import collections.abc
44
from collections import defaultdict
55
from functools import lru_cache, wraps
6+
import gc
67
import inspect
78
import itertools
89
import pickle
@@ -2758,6 +2759,19 @@ def x(self): ...
27582759
with self.assertRaisesRegex(TypeError, only_classes_allowed):
27592760
issubclass(1, BadPG)
27602761

2762+
def test_isinstance_checks_not_at_whim_of_gc(self):
2763+
self.addCleanup(gc.enable)
2764+
gc.disable()
2765+
2766+
with self.assertRaisesRegex(
2767+
TypeError,
2768+
"Protocols can only inherit from other protocols"
2769+
):
2770+
class Foo(collections.abc.Mapping, Protocol):
2771+
pass
2772+
2773+
self.assertNotIsInstance([], collections.abc.Mapping)
2774+
27612775
def test_issubclass_and_isinstance_on_Protocol_itself(self):
27622776
class C:
27632777
def x(self): pass

Lib/typing.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,25 @@ def _pickle_pskwargs(pskwargs):
17711771
class _ProtocolMeta(ABCMeta):
17721772
# This metaclass is somewhat unfortunate,
17731773
# but is necessary for several reasons...
1774+
def __new__(mcls, name, bases, namespace, /, **kwargs):
1775+
if name == "Protocol" and bases == (Generic,):
1776+
pass
1777+
elif Protocol in bases:
1778+
for base in bases:
1779+
if not (
1780+
base in {object, Generic}
1781+
or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
1782+
or (
1783+
issubclass(base, Generic)
1784+
and getattr(base, "_is_protocol", False)
1785+
)
1786+
):
1787+
raise TypeError(
1788+
f"Protocols can only inherit from other protocols, "
1789+
f"got {base!r}"
1790+
)
1791+
return super().__new__(mcls, name, bases, namespace, **kwargs)
1792+
17741793
def __init__(cls, *args, **kwargs):
17751794
super().__init__(*args, **kwargs)
17761795
if getattr(cls, "_is_protocol", False):
@@ -1906,14 +1925,7 @@ def _proto_hook(other):
19061925
if not cls._is_protocol:
19071926
return
19081927

1909-
# ... otherwise check consistency of bases, and prohibit instantiation.
1910-
for base in cls.__bases__:
1911-
if not (base in (object, Generic) or
1912-
base.__module__ in _PROTO_ALLOWLIST and
1913-
base.__name__ in _PROTO_ALLOWLIST[base.__module__] or
1914-
issubclass(base, Generic) and getattr(base, '_is_protocol', False)):
1915-
raise TypeError('Protocols can only inherit from other'
1916-
' protocols, got %r' % base)
1928+
# ... otherwise prohibit instantiation.
19171929
if cls.__init__ is Protocol.__init__:
19181930
cls.__init__ = _no_init_or_replace_init
19191931

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

0 commit comments

Comments
 (0)