Skip to content

Commit 2031238

Browse files
[3.12] gh-105280: Ensure isinstance([], collections.abc.Mapping) always evaluates to False (GH-105281) (#105318)
gh-105280: Ensure `isinstance([], collections.abc.Mapping)` always evaluates to `False` (GH-105281) (cherry picked from commit 08756db) Co-authored-by: Alex Waygood <[email protected]>
1 parent 5175026 commit 2031238

File tree

3 files changed

+38
-8
lines changed

3 files changed

+38
-8
lines changed

Lib/test/test_typing.py

+14
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

+20-8
Original file line numberDiff line numberDiff line change
@@ -1777,6 +1777,25 @@ def _pickle_pskwargs(pskwargs):
17771777
class _ProtocolMeta(ABCMeta):
17781778
# This metaclass is somewhat unfortunate,
17791779
# but is necessary for several reasons...
1780+
def __new__(mcls, name, bases, namespace, /, **kwargs):
1781+
if name == "Protocol" and bases == (Generic,):
1782+
pass
1783+
elif Protocol in bases:
1784+
for base in bases:
1785+
if not (
1786+
base in {object, Generic}
1787+
or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
1788+
or (
1789+
issubclass(base, Generic)
1790+
and getattr(base, "_is_protocol", False)
1791+
)
1792+
):
1793+
raise TypeError(
1794+
f"Protocols can only inherit from other protocols, "
1795+
f"got {base!r}"
1796+
)
1797+
return super().__new__(mcls, name, bases, namespace, **kwargs)
1798+
17801799
def __init__(cls, *args, **kwargs):
17811800
super().__init__(*args, **kwargs)
17821801
if getattr(cls, "_is_protocol", False):
@@ -1912,14 +1931,7 @@ def _proto_hook(other):
19121931
if not cls._is_protocol:
19131932
return
19141933

1915-
# ... otherwise check consistency of bases, and prohibit instantiation.
1916-
for base in cls.__bases__:
1917-
if not (base in (object, Generic) or
1918-
base.__module__ in _PROTO_ALLOWLIST and
1919-
base.__name__ in _PROTO_ALLOWLIST[base.__module__] or
1920-
issubclass(base, Generic) and getattr(base, '_is_protocol', False)):
1921-
raise TypeError('Protocols can only inherit from other'
1922-
' protocols, got %r' % base)
1934+
# ... otherwise prohibit instantiation.
19231935
if cls.__init__ is Protocol.__init__:
19241936
cls.__init__ = _no_init_or_replace_init
19251937

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)