mirror of https://github.com/python/cpython
gh-91603: Speed up isinstance/issubclass on union types (GH-91631)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
This commit is contained in:
parent
4ed3900041
commit
0ef8d921f5
|
@ -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
|
tuples) or a :ref:`types-union` of multiple types, return ``True`` if
|
||||||
*object* is an instance of any of the types.
|
*object* is an instance of any of the types.
|
||||||
If *classinfo* is not a type or tuple of types and such tuples,
|
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
|
.. versionchanged:: 3.10
|
||||||
*classinfo* can be a :ref:`types-union`.
|
*classinfo* can be a :ref:`types-union`.
|
||||||
|
|
|
@ -15,6 +15,7 @@ extern PyObject *_Py_union_type_or(PyObject *, PyObject *);
|
||||||
#define _PyGenericAlias_Check(op) PyObject_TypeCheck(op, &Py_GenericAliasType)
|
#define _PyGenericAlias_Check(op) PyObject_TypeCheck(op, &Py_GenericAliasType)
|
||||||
extern PyObject *_Py_subs_parameters(PyObject *, PyObject *, PyObject *, PyObject *);
|
extern PyObject *_Py_subs_parameters(PyObject *, PyObject *, PyObject *, PyObject *);
|
||||||
extern PyObject *_Py_make_parameters(PyObject *);
|
extern PyObject *_Py_make_parameters(PyObject *);
|
||||||
|
extern PyObject *_Py_union_args(PyObject *self);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,7 +225,7 @@ class TestIsInstanceIsSubclass(unittest.TestCase):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
isinstance(2, list[int] | int)
|
isinstance(2, list[int] | int)
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
isinstance(2, int | str | list[int] | float)
|
isinstance(2, float | str | list[int] | int)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -951,9 +951,9 @@ class UnionTests(unittest.TestCase):
|
||||||
with self.assertRaises(ZeroDivisionError):
|
with self.assertRaises(ZeroDivisionError):
|
||||||
list[int] | list[bt]
|
list[int] | list[bt]
|
||||||
|
|
||||||
union_ga = (int | list[str], int | collections.abc.Callable[..., str],
|
union_ga = (list[str] | int, collections.abc.Callable[..., str] | int,
|
||||||
int | d)
|
d | int)
|
||||||
# Raise error when isinstance(type, type | genericalias)
|
# Raise error when isinstance(type, genericalias | type)
|
||||||
for type_ in union_ga:
|
for type_ in union_ga:
|
||||||
with self.subTest(f"check isinstance/issubclass is invalid for {type_}"):
|
with self.subTest(f"check isinstance/issubclass is invalid for {type_}"):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Speed up :func:`isinstance` and :func:`issubclass` checks for :class:`types.UnionType`.
|
||||||
|
Patch by Yurii Karabas.
|
|
@ -2625,6 +2625,10 @@ object_recursive_isinstance(PyThreadState *tstate, PyObject *inst, PyObject *cls
|
||||||
return object_isinstance(inst, cls);
|
return object_isinstance(inst, cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_PyUnion_Check(cls)) {
|
||||||
|
cls = _Py_union_args(cls);
|
||||||
|
}
|
||||||
|
|
||||||
if (PyTuple_Check(cls)) {
|
if (PyTuple_Check(cls)) {
|
||||||
/* Not a general sequence -- that opens up the road to
|
/* Not a general sequence -- that opens up the road to
|
||||||
recursion and stack overflow. */
|
recursion and stack overflow. */
|
||||||
|
@ -2714,6 +2718,10 @@ object_issubclass(PyThreadState *tstate, PyObject *derived, PyObject *cls)
|
||||||
return recursive_issubclass(derived, cls);
|
return recursive_issubclass(derived, cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_PyUnion_Check(cls)) {
|
||||||
|
cls = _Py_union_args(cls);
|
||||||
|
}
|
||||||
|
|
||||||
if (PyTuple_Check(cls)) {
|
if (PyTuple_Check(cls)) {
|
||||||
|
|
||||||
if (_Py_EnterRecursiveCall(tstate, " in __subclasscheck__")) {
|
if (_Py_EnterRecursiveCall(tstate, " in __subclasscheck__")) {
|
||||||
|
|
|
@ -48,73 +48,6 @@ union_hash(PyObject *self)
|
||||||
return hash;
|
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 *
|
static PyObject *
|
||||||
union_richcompare(PyObject *a, PyObject *b, int op)
|
union_richcompare(PyObject *a, PyObject *b, int op)
|
||||||
{
|
{
|
||||||
|
@ -342,12 +275,6 @@ static PyMemberDef union_members[] = {
|
||||||
{0}
|
{0}
|
||||||
};
|
};
|
||||||
|
|
||||||
static PyMethodDef union_methods[] = {
|
|
||||||
{"__instancecheck__", union_instancecheck, METH_O},
|
|
||||||
{"__subclasscheck__", union_subclasscheck, METH_O},
|
|
||||||
{0}};
|
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
union_getitem(PyObject *self, PyObject *item)
|
union_getitem(PyObject *self, PyObject *item)
|
||||||
{
|
{
|
||||||
|
@ -434,6 +361,13 @@ union_getattro(PyObject *self, PyObject *name)
|
||||||
return PyObject_GenericGetAttr(self, name);
|
return PyObject_GenericGetAttr(self, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
_Py_union_args(PyObject *self)
|
||||||
|
{
|
||||||
|
assert(_PyUnion_Check(self));
|
||||||
|
return ((unionobject *) self)->args;
|
||||||
|
}
|
||||||
|
|
||||||
PyTypeObject _PyUnion_Type = {
|
PyTypeObject _PyUnion_Type = {
|
||||||
PyVarObject_HEAD_INIT(&PyType_Type, 0)
|
PyVarObject_HEAD_INIT(&PyType_Type, 0)
|
||||||
.tp_name = "types.UnionType",
|
.tp_name = "types.UnionType",
|
||||||
|
@ -449,7 +383,6 @@ PyTypeObject _PyUnion_Type = {
|
||||||
.tp_hash = union_hash,
|
.tp_hash = union_hash,
|
||||||
.tp_getattro = union_getattro,
|
.tp_getattro = union_getattro,
|
||||||
.tp_members = union_members,
|
.tp_members = union_members,
|
||||||
.tp_methods = union_methods,
|
|
||||||
.tp_richcompare = union_richcompare,
|
.tp_richcompare = union_richcompare,
|
||||||
.tp_as_mapping = &union_as_mapping,
|
.tp_as_mapping = &union_as_mapping,
|
||||||
.tp_as_number = &union_as_number,
|
.tp_as_number = &union_as_number,
|
||||||
|
|
Loading…
Reference in New Issue