Skip to content

Commit 8198905

Browse files
bpo-44606: Fix __instancecheck__ and __subclasscheck__ for the union type. (pythonGH-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.
1 parent 0093876 commit 8198905

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]
@@ -754,9 +787,9 @@ def __eq__(self, other):
754787
for type_ in union_ga:
755788
with self.subTest(f"check isinstance/issubclass is invalid for {type_}"):
756789
with self.assertRaises(TypeError):
757-
isinstance(list, type_)
790+
isinstance(1, type_)
758791
with self.assertRaises(TypeError):
759-
issubclass(list, type_)
792+
issubclass(int, type_)
760793

761794
def test_or_type_operator_with_bad_module(self):
762795
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
@@ -70,8 +70,14 @@ union_instancecheck(PyObject *self, PyObject *instance)
7070
if (arg == Py_None) {
7171
arg = (PyObject *)&_PyNone_Type;
7272
}
73-
if (PyType_Check(arg) && PyObject_IsInstance(instance, arg) != 0) {
74-
Py_RETURN_TRUE;
73+
if (PyType_Check(arg)) {
74+
int res = PyObject_IsInstance(instance, arg);
75+
if (res < 0) {
76+
return NULL;
77+
}
78+
if (res) {
79+
Py_RETURN_TRUE;
80+
}
7581
}
7682
}
7783
Py_RETURN_FALSE;
@@ -93,8 +99,17 @@ union_subclasscheck(PyObject *self, PyObject *instance)
9399
Py_ssize_t nargs = PyTuple_GET_SIZE(alias->args);
94100
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
95101
PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
96-
if (PyType_Check(arg) && (PyType_IsSubtype((PyTypeObject *)instance, (PyTypeObject *)arg) != 0)) {
97-
Py_RETURN_TRUE;
102+
if (arg == Py_None) {
103+
arg = (PyObject *)&_PyNone_Type;
104+
}
105+
if (PyType_Check(arg)) {
106+
int res = PyObject_IsSubclass(instance, arg);
107+
if (res < 0) {
108+
return NULL;
109+
}
110+
if (res) {
111+
Py_RETURN_TRUE;
112+
}
98113
}
99114
}
100115
Py_RETURN_FALSE;

0 commit comments

Comments
 (0)