mirror of
https://github.com/python/cpython.git
synced 2025-07-24 11:44:31 +00:00
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 81989058de
)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
bb260c2a21
commit
b42eee78e7
3 changed files with 55 additions and 6 deletions
|
@ -661,6 +661,39 @@ class TypesTests(unittest.TestCase):
|
|||
x.__args__ = [str, int]
|
||||
(int | str ) == x
|
||||
|
||||
def test_instancecheck(self):
|
||||
x = int | str
|
||||
self.assertIsInstance(1, x)
|
||||
self.assertIsInstance(True, x)
|
||||
self.assertIsInstance('a', x)
|
||||
self.assertNotIsInstance(None, x)
|
||||
self.assertTrue(issubclass(int, x))
|
||||
self.assertTrue(issubclass(bool, x))
|
||||
self.assertTrue(issubclass(str, x))
|
||||
self.assertFalse(issubclass(type(None), x))
|
||||
x = int | None
|
||||
self.assertIsInstance(None, x)
|
||||
self.assertTrue(issubclass(type(None), x))
|
||||
x = int | collections.abc.Mapping
|
||||
self.assertIsInstance({}, x)
|
||||
self.assertTrue(issubclass(dict, x))
|
||||
|
||||
def test_bad_instancecheck(self):
|
||||
class BadMeta(type):
|
||||
def __instancecheck__(cls, inst):
|
||||
1/0
|
||||
x = int | BadMeta('A', (), {})
|
||||
self.assertTrue(isinstance(1, x))
|
||||
self.assertRaises(ZeroDivisionError, isinstance, [], x)
|
||||
|
||||
def test_bad_subclasscheck(self):
|
||||
class BadMeta(type):
|
||||
def __subclasscheck__(cls, sub):
|
||||
1/0
|
||||
x = int | BadMeta('A', (), {})
|
||||
self.assertTrue(issubclass(int, x))
|
||||
self.assertRaises(ZeroDivisionError, issubclass, list, x)
|
||||
|
||||
def test_or_type_operator_with_TypeVar(self):
|
||||
TV = typing.TypeVar('T')
|
||||
assert TV | str == typing.Union[TV, str]
|
||||
|
@ -744,9 +777,9 @@ class TypesTests(unittest.TestCase):
|
|||
for type_ in union_ga:
|
||||
with self.subTest(f"check isinstance/issubclass is invalid for {type_}"):
|
||||
with self.assertRaises(TypeError):
|
||||
isinstance(list, type_)
|
||||
isinstance(1, type_)
|
||||
with self.assertRaises(TypeError):
|
||||
issubclass(list, type_)
|
||||
issubclass(int, type_)
|
||||
|
||||
def test_or_type_operator_with_bad_module(self):
|
||||
class TypeVar:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fix ``__instancecheck__`` and ``__subclasscheck__`` for the union type.
|
|
@ -67,8 +67,14 @@ union_instancecheck(PyObject *self, PyObject *instance)
|
|||
if (arg == Py_None) {
|
||||
arg = (PyObject *)&_PyNone_Type;
|
||||
}
|
||||
if (PyType_Check(arg) && PyObject_IsInstance(instance, arg) != 0) {
|
||||
Py_RETURN_TRUE;
|
||||
if (PyType_Check(arg)) {
|
||||
int res = PyObject_IsInstance(instance, arg);
|
||||
if (res < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (res) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
|
@ -90,8 +96,17 @@ union_subclasscheck(PyObject *self, PyObject *instance)
|
|||
Py_ssize_t nargs = PyTuple_GET_SIZE(alias->args);
|
||||
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
|
||||
PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
|
||||
if (PyType_Check(arg) && (PyType_IsSubtype((PyTypeObject *)instance, (PyTypeObject *)arg) != 0)) {
|
||||
Py_RETURN_TRUE;
|
||||
if (arg == Py_None) {
|
||||
arg = (PyObject *)&_PyNone_Type;
|
||||
}
|
||||
if (PyType_Check(arg)) {
|
||||
int res = PyObject_IsSubclass(instance, arg);
|
||||
if (res < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (res) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
Py_RETURN_FALSE;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue