Skip to content

Commit 0ef8d92

Browse files
uriyyoJelleZijlstrahauntsaninja
authored
gh-91603: Speed up isinstance/issubclass on union types (GH-91631)
Co-authored-by: Jelle Zijlstra <[email protected]> Co-authored-by: Shantanu <[email protected]>
1 parent 4ed3900 commit 0ef8d92

File tree

7 files changed

+24
-79
lines changed

7 files changed

+24
-79
lines changed

Doc/library/functions.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,8 @@ are always available. They are listed here in alphabetical order.
905905
tuples) or a :ref:`types-union` of multiple types, return ``True`` if
906906
*object* is an instance of any of the types.
907907
If *classinfo* is not a type or tuple of types and such tuples,
908-
a :exc:`TypeError` exception is raised.
908+
a :exc:`TypeError` exception is raised. :exc:`TypeError` may not be
909+
raised for an invalid type if an earlier check succeeds.
909910

910911
.. versionchanged:: 3.10
911912
*classinfo* can be a :ref:`types-union`.

Include/internal/pycore_unionobject.h

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extern PyObject *_Py_union_type_or(PyObject *, PyObject *);
1515
#define _PyGenericAlias_Check(op) PyObject_TypeCheck(op, &Py_GenericAliasType)
1616
extern PyObject *_Py_subs_parameters(PyObject *, PyObject *, PyObject *, PyObject *);
1717
extern PyObject *_Py_make_parameters(PyObject *);
18+
extern PyObject *_Py_union_args(PyObject *self);
1819

1920
#ifdef __cplusplus
2021
}

Lib/test/test_isinstance.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def test_isinstance_with_or_union(self):
225225
with self.assertRaises(TypeError):
226226
isinstance(2, list[int] | int)
227227
with self.assertRaises(TypeError):
228-
isinstance(2, int | str | list[int] | float)
228+
isinstance(2, float | str | list[int] | int)
229229

230230

231231

Lib/test/test_types.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -951,9 +951,9 @@ def __eq__(self, other):
951951
with self.assertRaises(ZeroDivisionError):
952952
list[int] | list[bt]
953953

954-
union_ga = (int | list[str], int | collections.abc.Callable[..., str],
955-
int | d)
956-
# Raise error when isinstance(type, type | genericalias)
954+
union_ga = (list[str] | int, collections.abc.Callable[..., str] | int,
955+
d | int)
956+
# Raise error when isinstance(type, genericalias | type)
957957
for type_ in union_ga:
958958
with self.subTest(f"check isinstance/issubclass is invalid for {type_}"):
959959
with self.assertRaises(TypeError):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up :func:`isinstance` and :func:`issubclass` checks for :class:`types.UnionType`.
2+
Patch by Yurii Karabas.

Objects/abstract.c

+8
Original file line numberDiff line numberDiff line change
@@ -2625,6 +2625,10 @@ object_recursive_isinstance(PyThreadState *tstate, PyObject *inst, PyObject *cls
26252625
return object_isinstance(inst, cls);
26262626
}
26272627

2628+
if (_PyUnion_Check(cls)) {
2629+
cls = _Py_union_args(cls);
2630+
}
2631+
26282632
if (PyTuple_Check(cls)) {
26292633
/* Not a general sequence -- that opens up the road to
26302634
recursion and stack overflow. */
@@ -2714,6 +2718,10 @@ object_issubclass(PyThreadState *tstate, PyObject *derived, PyObject *cls)
27142718
return recursive_issubclass(derived, cls);
27152719
}
27162720

2721+
if (_PyUnion_Check(cls)) {
2722+
cls = _Py_union_args(cls);
2723+
}
2724+
27172725
if (PyTuple_Check(cls)) {
27182726

27192727
if (_Py_EnterRecursiveCall(tstate, " in __subclasscheck__")) {

Objects/unionobject.c

+7-74
Original file line numberDiff line numberDiff line change
@@ -48,73 +48,6 @@ union_hash(PyObject *self)
4848
return hash;
4949
}
5050

51-
static int
52-
is_generic_alias_in_args(PyObject *args)
53-
{
54-
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
55-
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
56-
PyObject *arg = PyTuple_GET_ITEM(args, iarg);
57-
if (_PyGenericAlias_Check(arg)) {
58-
return 0;
59-
}
60-
}
61-
return 1;
62-
}
63-
64-
static PyObject *
65-
union_instancecheck(PyObject *self, PyObject *instance)
66-
{
67-
unionobject *alias = (unionobject *) self;
68-
Py_ssize_t nargs = PyTuple_GET_SIZE(alias->args);
69-
if (!is_generic_alias_in_args(alias->args)) {
70-
PyErr_SetString(PyExc_TypeError,
71-
"isinstance() argument 2 cannot contain a parameterized generic");
72-
return NULL;
73-
}
74-
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
75-
PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
76-
if (PyType_Check(arg)) {
77-
int res = PyObject_IsInstance(instance, arg);
78-
if (res < 0) {
79-
return NULL;
80-
}
81-
if (res) {
82-
Py_RETURN_TRUE;
83-
}
84-
}
85-
}
86-
Py_RETURN_FALSE;
87-
}
88-
89-
static PyObject *
90-
union_subclasscheck(PyObject *self, PyObject *instance)
91-
{
92-
if (!PyType_Check(instance)) {
93-
PyErr_SetString(PyExc_TypeError, "issubclass() arg 1 must be a class");
94-
return NULL;
95-
}
96-
unionobject *alias = (unionobject *)self;
97-
if (!is_generic_alias_in_args(alias->args)) {
98-
PyErr_SetString(PyExc_TypeError,
99-
"issubclass() argument 2 cannot contain a parameterized generic");
100-
return NULL;
101-
}
102-
Py_ssize_t nargs = PyTuple_GET_SIZE(alias->args);
103-
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
104-
PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
105-
if (PyType_Check(arg)) {
106-
int res = PyObject_IsSubclass(instance, arg);
107-
if (res < 0) {
108-
return NULL;
109-
}
110-
if (res) {
111-
Py_RETURN_TRUE;
112-
}
113-
}
114-
}
115-
Py_RETURN_FALSE;
116-
}
117-
11851
static PyObject *
11952
union_richcompare(PyObject *a, PyObject *b, int op)
12053
{
@@ -342,12 +275,6 @@ static PyMemberDef union_members[] = {
342275
{0}
343276
};
344277

345-
static PyMethodDef union_methods[] = {
346-
{"__instancecheck__", union_instancecheck, METH_O},
347-
{"__subclasscheck__", union_subclasscheck, METH_O},
348-
{0}};
349-
350-
351278
static PyObject *
352279
union_getitem(PyObject *self, PyObject *item)
353280
{
@@ -434,6 +361,13 @@ union_getattro(PyObject *self, PyObject *name)
434361
return PyObject_GenericGetAttr(self, name);
435362
}
436363

364+
PyObject *
365+
_Py_union_args(PyObject *self)
366+
{
367+
assert(_PyUnion_Check(self));
368+
return ((unionobject *) self)->args;
369+
}
370+
437371
PyTypeObject _PyUnion_Type = {
438372
PyVarObject_HEAD_INIT(&PyType_Type, 0)
439373
.tp_name = "types.UnionType",
@@ -449,7 +383,6 @@ PyTypeObject _PyUnion_Type = {
449383
.tp_hash = union_hash,
450384
.tp_getattro = union_getattro,
451385
.tp_members = union_members,
452-
.tp_methods = union_methods,
453386
.tp_richcompare = union_richcompare,
454387
.tp_as_mapping = &union_as_mapping,
455388
.tp_as_number = &union_as_number,

0 commit comments

Comments
 (0)