diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2021-07-14 04:35:39 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-14 04:35:39 (GMT) |
commit | 81989058de381108dfd0a4255b93d4fb34417002 (patch) | |
tree | ed13e2a5964c4efb0fe450b6af8db5ad2e2233ff | |
parent | 0093876328afa330224c9d887c18dee0b3117852 (diff) | |
download | cpython-81989058de381108dfd0a4255b93d4fb34417002.zip cpython-81989058de381108dfd0a4255b93d4fb34417002.tar.gz cpython-81989058de381108dfd0a4255b93d4fb34417002.tar.bz2 |
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.
-rw-r--r-- | Lib/test/test_types.py | 37 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2021-07-13-20-22-12.bpo-44606.S3Bv2w.rst | 1 | ||||
-rw-r--r-- | Objects/unionobject.c | 23 |
3 files changed, 55 insertions, 6 deletions
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 7f7ce86..f2c6464 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -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] @@ -754,9 +787,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: diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-13-20-22-12.bpo-44606.S3Bv2w.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-13-20-22-12.bpo-44606.S3Bv2w.rst new file mode 100644 index 0000000..3c7ef3b --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-13-20-22-12.bpo-44606.S3Bv2w.rst @@ -0,0 +1 @@ +Fix ``__instancecheck__`` and ``__subclasscheck__`` for the union type. diff --git a/Objects/unionobject.c b/Objects/unionobject.c index d2a10df..cf04de1 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -70,8 +70,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; @@ -93,8 +99,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; |