Skip to content

Commit b42eee7

Browse files
bpo-44606: Fix __instancecheck__ and __subclasscheck__ for the union type. (GH-27120)
* Fix issubclass() for None. E.g. issubclass(type(None), int | None) returns now True. * Fix issubclass() for virtual subclasses. E.g. issubclass(dict, int | collections.abc.Mapping) returns now True. * Fix crash in isinstance() if the check for one of items raises exception. (cherry picked from commit 8198905) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent bb260c2 commit b42eee7

File tree

3 files changed

+55
-6
lines changed

3 files changed

+55
-6
lines changed

Lib/test/test_types.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,39 @@ def test_or_types_operator(self):
661661
x.__args__ = [str, int]
662662
(int | str ) == x
663663

664+
def test_instancecheck(self):
665+
x = int | str
666+
self.assertIsInstance(1, x)
667+
self.assertIsInstance(True, x)
668+
self.assertIsInstance('a', x)
669+
self.assertNotIsInstance(None, x)
670+
self.assertTrue(issubclass(int, x))
671+
self.assertTrue(issubclass(bool, x))
672+
self.assertTrue(issubclass(str, x))
673+
self.assertFalse(issubclass(type(None), x))
674+
x = int | None
675+
self.assertIsInstance(None, x)
676+
self.assertTrue(issubclass(type(None), x))
677+
x = int | collections.abc.Mapping
678+
self.assertIsInstance({}, x)
679+
self.assertTrue(issubclass(dict, x))
680+
681+
def test_bad_instancecheck(self):
682+
class BadMeta(type):
683+
def __instancecheck__(cls, inst):
684+
1/0
685+
x = int | BadMeta('A', (), {})
686+
self.assertTrue(isinstance(1, x))
687+
self.assertRaises(ZeroDivisionError, isinstance, [], x)
688+
689+
def test_bad_subclasscheck(self):
690+
class BadMeta(type):
691+
def __subclasscheck__(cls, sub):
692+
1/0
693+
x = int | BadMeta('A', (), {})
694+
self.assertTrue(issubclass(int, x))
695+
self.assertRaises(ZeroDivisionError, issubclass, list, x)
696+
664697
def test_or_type_operator_with_TypeVar(self):
665698
TV = typing.TypeVar('T')
666699
assert TV | str == typing.Union[TV, str]
@@ -744,9 +777,9 @@ def __eq__(self, other):
744777
for type_ in union_ga:
745778
with self.subTest(f"check isinstance/issubclass is invalid for {type_}"):
746779
with self.assertRaises(TypeError):
747-
isinstance(list, type_)
780+
isinstance(1, type_)
748781
with self.assertRaises(TypeError):
749-
issubclass(list, type_)
782+
issubclass(int, type_)
750783

751784
def test_or_type_operator_with_bad_module(self):
752785
class TypeVar:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``__instancecheck__`` and ``__subclasscheck__`` for the union type.

Objects/unionobject.c

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,14 @@ union_instancecheck(PyObject *self, PyObject *instance)
6767
if (arg == Py_None) {
6868
arg = (PyObject *)&_PyNone_Type;
6969
}
70-
if (PyType_Check(arg) && PyObject_IsInstance(instance, arg) != 0) {
71-
Py_RETURN_TRUE;
70+
if (PyType_Check(arg)) {
71+
int res = PyObject_IsInstance(instance, arg);
72+
if (res < 0) {
73+
return NULL;
74+
}
75+
if (res) {
76+
Py_RETURN_TRUE;
77+
}
7278
}
7379
}
7480
Py_RETURN_FALSE;
@@ -90,8 +96,17 @@ union_subclasscheck(PyObject *self, PyObject *instance)
9096
Py_ssize_t nargs = PyTuple_GET_SIZE(alias->args);
9197
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
9298
PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
93-
if (PyType_Check(arg) && (PyType_IsSubtype((PyTypeObject *)instance, (PyTypeObject *)arg) != 0)) {
94-
Py_RETURN_TRUE;
99+
if (arg == Py_None) {
100+
arg = (PyObject *)&_PyNone_Type;
101+
}
102+
if (PyType_Check(arg)) {
103+
int res = PyObject_IsSubclass(instance, arg);
104+
if (res < 0) {
105+
return NULL;
106+
}
107+
if (res) {
108+
Py_RETURN_TRUE;
109+
}
95110
}
96111
}
97112
Py_RETURN_FALSE;

0 commit comments

Comments
 (0)