[Python-checkins] bpo-44606: Fix __instancecheck__ and __subclasscheck__ for the union type. (GH-27120)

miss-islington webhook-mailer at python.org
Wed Jul 14 00:55:50 EDT 2021


https://github.com/python/cpython/commit/b42eee78e7651693aa38c390f577e5d499dcf55d
commit: b42eee78e7651693aa38c390f577e5d499dcf55d
branch: 3.10
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: miss-islington <31488909+miss-islington at users.noreply.github.com>
date: 2021-07-13T21:55:45-07:00
summary:

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 81989058de381108dfd0a4255b93d4fb34417002)

Co-authored-by: Serhiy Storchaka <storchaka at gmail.com>

files:
A Misc/NEWS.d/next/Core and Builtins/2021-07-13-20-22-12.bpo-44606.S3Bv2w.rst
M Lib/test/test_types.py
M Objects/unionobject.c

diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index ae7b17bd590e61..0e1a242f16f437 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -661,6 +661,39 @@ def test_or_types_operator(self):
             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 @@ def __eq__(self, other):
         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 00000000000000..3c7ef3b21fe092
--- /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 8435763b5ea7ca..bcba0f679bd09c 100644
--- a/Objects/unionobject.c
+++ b/Objects/unionobject.c
@@ -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;



More information about the Python-checkins mailing list