]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-91603: Speed up isinstance/issubclass on union types (GH-91631)
authorYurii Karabas <1998uriyyo@gmail.com>
Thu, 28 Apr 2022 15:24:19 +0000 (18:24 +0300)
committerGitHub <noreply@github.com>
Thu, 28 Apr 2022 15:24:19 +0000 (23:24 +0800)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
Doc/library/functions.rst
Include/internal/pycore_unionobject.h
Lib/test/test_isinstance.py
Lib/test/test_types.py
Misc/NEWS.d/next/Core and Builtins/2022-04-17-11-03-45.gh-issue-91603.hYw1Lv.rst [new file with mode: 0644]
Objects/abstract.c
Objects/unionobject.c

index f3b8e40babbd88a934f6ac63a13fc75f57d8ed9e..394281462ded59569dc6f2994465ed9939ea689d 100644 (file)
@@ -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`.
index 9962f57610387d67e851b90f20f35cf93965954b..a9ed5651a410e87c6ded94de37af2d1af3d559f0 100644 (file)
@@ -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
 }
index 9d37cff990338559b1fc2c19e938d323b77eb9ab..a0974640bc11466394e5170fd6f3a8b60ba332f1 100644 (file)
@@ -225,7 +225,7 @@ class TestIsInstanceIsSubclass(unittest.TestCase):
         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)
 
 
 
index 42fd4f56235fab015272780e9360fa9871156314..cde9dadc5e97fa982bccb8b9b93e92c635096dce 100644 (file)
@@ -951,9 +951,9 @@ class UnionTests(unittest.TestCase):
         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 (file)
index 0000000..957bd5e
--- /dev/null
@@ -0,0 +1,2 @@
+Speed up :func:`isinstance` and :func:`issubclass` checks for :class:`types.UnionType`.
+Patch by Yurii Karabas.
index 79f5a5f760f8e252cb5d130d9599625f1f8eca8f..cfb0edcab0e5d34e2482f4a5dfe1fc80bb38c6d2 100644 (file)
@@ -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__")) {
index 36b032c0c5c1286f015edb26d8f6f9e85abf254f..5eee27c08fa7e20609468fbefa6f9a05369fde0c 100644 (file)
@@ -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,