[Python-checkins] gh-91603: Speed up isinstance/issubclass on union types (GH-91631)

Fidget-Spinner webhook-mailer at python.org
Thu Apr 28 11:24:45 EDT 2022


https://github.com/python/cpython/commit/0ef8d921f5c6945aa8f386e472c4110b81ac773d
commit: 0ef8d921f5c6945aa8f386e472c4110b81ac773d
branch: main
author: Yurii Karabas <1998uriyyo at gmail.com>
committer: Fidget-Spinner <kenjin4096 at gmail.com>
date: 2022-04-28T23:24:19+08:00
summary:

gh-91603: Speed up isinstance/issubclass on union types (GH-91631)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra at gmail.com>
Co-authored-by: Shantanu <12621235+hauntsaninja at users.noreply.github.com>

files:
A Misc/NEWS.d/next/Core and Builtins/2022-04-17-11-03-45.gh-issue-91603.hYw1Lv.rst
M Doc/library/functions.rst
M Include/internal/pycore_unionobject.h
M Lib/test/test_isinstance.py
M Lib/test/test_types.py
M Objects/abstract.c
M Objects/unionobject.c

diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst
index f3b8e40babbd8..394281462ded5 100644
--- a/Doc/library/functions.rst
+++ b/Doc/library/functions.rst
@@ -905,7 +905,8 @@ are always available.  They are listed here in alphabetical order.
    tuples) or a :ref:`types-union` of multiple types, return ``True`` if
    *object* is an instance of any of the types.
    If *classinfo* is not a type or tuple of types and such tuples,
-   a :exc:`TypeError` exception is raised.
+   a :exc:`TypeError` exception is raised. :exc:`TypeError` may not be
+   raised for an invalid type if an earlier check succeeds.
 
    .. versionchanged:: 3.10
       *classinfo* can be a :ref:`types-union`.
diff --git a/Include/internal/pycore_unionobject.h b/Include/internal/pycore_unionobject.h
index 9962f57610387..a9ed5651a410e 100644
--- a/Include/internal/pycore_unionobject.h
+++ b/Include/internal/pycore_unionobject.h
@@ -15,6 +15,7 @@ extern PyObject *_Py_union_type_or(PyObject *, PyObject *);
 #define _PyGenericAlias_Check(op) PyObject_TypeCheck(op, &Py_GenericAliasType)
 extern PyObject *_Py_subs_parameters(PyObject *, PyObject *, PyObject *, PyObject *);
 extern PyObject *_Py_make_parameters(PyObject *);
+extern PyObject *_Py_union_args(PyObject *self);
 
 #ifdef __cplusplus
 }
diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py
index 9d37cff990338..a0974640bc114 100644
--- a/Lib/test/test_isinstance.py
+++ b/Lib/test/test_isinstance.py
@@ -225,7 +225,7 @@ def test_isinstance_with_or_union(self):
         with self.assertRaises(TypeError):
             isinstance(2, list[int] | int)
         with self.assertRaises(TypeError):
-            isinstance(2, int | str | list[int] | float)
+            isinstance(2, float | str | list[int] | int)
 
 
 
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index 42fd4f56235fa..cde9dadc5e97f 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -951,9 +951,9 @@ def __eq__(self, other):
         with self.assertRaises(ZeroDivisionError):
             list[int] | list[bt]
 
-        union_ga = (int | list[str], int | collections.abc.Callable[..., str],
-                    int | d)
-        # Raise error when isinstance(type, type | genericalias)
+        union_ga = (list[str] | int, collections.abc.Callable[..., str] | int,
+                    d | int)
+        # Raise error when isinstance(type, genericalias | type)
         for type_ in union_ga:
             with self.subTest(f"check isinstance/issubclass is invalid for {type_}"):
                 with self.assertRaises(TypeError):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-04-17-11-03-45.gh-issue-91603.hYw1Lv.rst b/Misc/NEWS.d/next/Core and Builtins/2022-04-17-11-03-45.gh-issue-91603.hYw1Lv.rst
new file mode 100644
index 0000000000000..957bd5ea09b58
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-04-17-11-03-45.gh-issue-91603.hYw1Lv.rst	
@@ -0,0 +1,2 @@
+Speed up :func:`isinstance` and :func:`issubclass` checks for :class:`types.UnionType`.
+Patch by Yurii Karabas.
diff --git a/Objects/abstract.c b/Objects/abstract.c
index 79f5a5f760f8e..cfb0edcab0e5d 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -2625,6 +2625,10 @@ object_recursive_isinstance(PyThreadState *tstate, PyObject *inst, PyObject *cls
         return object_isinstance(inst, cls);
     }
 
+    if (_PyUnion_Check(cls)) {
+        cls = _Py_union_args(cls);
+    }
+
     if (PyTuple_Check(cls)) {
         /* Not a general sequence -- that opens up the road to
            recursion and stack overflow. */
@@ -2714,6 +2718,10 @@ object_issubclass(PyThreadState *tstate, PyObject *derived, PyObject *cls)
         return recursive_issubclass(derived, cls);
     }
 
+    if (_PyUnion_Check(cls)) {
+        cls = _Py_union_args(cls);
+    }
+
     if (PyTuple_Check(cls)) {
 
         if (_Py_EnterRecursiveCall(tstate, " in __subclasscheck__")) {
diff --git a/Objects/unionobject.c b/Objects/unionobject.c
index 36b032c0c5c12..5eee27c08fa7e 100644
--- a/Objects/unionobject.c
+++ b/Objects/unionobject.c
@@ -48,73 +48,6 @@ union_hash(PyObject *self)
     return hash;
 }
 
-static int
-is_generic_alias_in_args(PyObject *args)
-{
-    Py_ssize_t nargs = PyTuple_GET_SIZE(args);
-    for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
-        PyObject *arg = PyTuple_GET_ITEM(args, iarg);
-        if (_PyGenericAlias_Check(arg)) {
-            return 0;
-        }
-    }
-    return 1;
-}
-
-static PyObject *
-union_instancecheck(PyObject *self, PyObject *instance)
-{
-    unionobject *alias = (unionobject *) self;
-    Py_ssize_t nargs = PyTuple_GET_SIZE(alias->args);
-    if (!is_generic_alias_in_args(alias->args)) {
-        PyErr_SetString(PyExc_TypeError,
-            "isinstance() argument 2 cannot contain a parameterized generic");
-        return NULL;
-    }
-    for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
-        PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
-        if (PyType_Check(arg)) {
-            int res = PyObject_IsInstance(instance, arg);
-            if (res < 0) {
-                return NULL;
-            }
-            if (res) {
-                Py_RETURN_TRUE;
-            }
-        }
-    }
-    Py_RETURN_FALSE;
-}
-
-static PyObject *
-union_subclasscheck(PyObject *self, PyObject *instance)
-{
-    if (!PyType_Check(instance)) {
-        PyErr_SetString(PyExc_TypeError, "issubclass() arg 1 must be a class");
-        return NULL;
-    }
-    unionobject *alias = (unionobject *)self;
-    if (!is_generic_alias_in_args(alias->args)) {
-        PyErr_SetString(PyExc_TypeError,
-            "issubclass() argument 2 cannot contain a parameterized generic");
-        return NULL;
-    }
-    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)) {
-            int res = PyObject_IsSubclass(instance, arg);
-            if (res < 0) {
-                return NULL;
-            }
-            if (res) {
-                Py_RETURN_TRUE;
-            }
-        }
-    }
-    Py_RETURN_FALSE;
-}
-
 static PyObject *
 union_richcompare(PyObject *a, PyObject *b, int op)
 {
@@ -342,12 +275,6 @@ static PyMemberDef union_members[] = {
         {0}
 };
 
-static PyMethodDef union_methods[] = {
-        {"__instancecheck__", union_instancecheck, METH_O},
-        {"__subclasscheck__", union_subclasscheck, METH_O},
-        {0}};
-
-
 static PyObject *
 union_getitem(PyObject *self, PyObject *item)
 {
@@ -434,6 +361,13 @@ union_getattro(PyObject *self, PyObject *name)
     return PyObject_GenericGetAttr(self, name);
 }
 
+PyObject *
+_Py_union_args(PyObject *self)
+{
+    assert(_PyUnion_Check(self));
+    return ((unionobject *) self)->args;
+}
+
 PyTypeObject _PyUnion_Type = {
     PyVarObject_HEAD_INIT(&PyType_Type, 0)
     .tp_name = "types.UnionType",
@@ -449,7 +383,6 @@ PyTypeObject _PyUnion_Type = {
     .tp_hash = union_hash,
     .tp_getattro = union_getattro,
     .tp_members = union_members,
-    .tp_methods = union_methods,
     .tp_richcompare = union_richcompare,
     .tp_as_mapping = &union_as_mapping,
     .tp_as_number = &union_as_number,



More information about the Python-checkins mailing list