cpython: d6e86a96f9b3 (original) (raw)

Mercurial > cpython

changeset 48875:d6e86a96f9b3

Issue #2534: speed up isinstance() and issubclass() by 50-70%, so as to match Python 2.5 speed despite the __instancecheck__ / __subclasscheck__ mechanism. In the process, fix a bug where isinstance() and issubclass(), when given a tuple of classes as second argument, were looking up __instancecheck__ / __subclasscheck__ on the tuple rather than on each type object. Reviewed by Benjamin Peterson and Raymond Hettinger. [#2534]

Antoine Pitrou solipsis@pitrou.net
date Tue, 26 Aug 2008 22:40:48 +0000
parents 4db1f5098175
children 12d2762076ba
files Include/abstract.h Lib/test/test_abc.py Lib/test/test_exceptions.py Lib/test/test_typechecks.py Misc/NEWS Objects/abstract.c Objects/typeobject.c Python/errors.c
diffstat 8 files changed, 202 insertions(+), 97 deletions(-)[+] [-] Include/abstract.h 5 Lib/test/test_abc.py 24 Lib/test/test_exceptions.py 16 Lib/test/test_typechecks.py 13 Misc/NEWS 7 Objects/abstract.c 184 Objects/typeobject.c 45 Python/errors.c 5

line wrap: on

line diff

--- a/Include/abstract.h +++ b/Include/abstract.h @@ -1273,6 +1273,11 @@ PyAPI_FUNC(int) PyObject_IsSubclass(PyOb /* issubclass(object, typeorclass) */ +PyAPI_FUNC(int) _PyObject_RealIsInstance(PyObject *inst, PyObject *cls); + +PyAPI_FUNC(int) _PyObject_RealIsSubclass(PyObject *derived, PyObject *cls); + + #ifdef __cplusplus } #endif

--- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -75,15 +75,21 @@ class TestABC(unittest.TestCase): pass b = B() self.assertEqual(issubclass(B, A), False)

def test_isinstance_invalidation(self): class A(metaclass=abc.ABCMeta): @@ -92,22 +98,29 @@ class TestABC(unittest.TestCase): pass b = B() self.assertEqual(isinstance(b, A), False)

def test_registration_builtins(self): class A(metaclass=abc.ABCMeta): pass A.register(int) self.assertEqual(isinstance(42, A), True)

def test_registration_edge_cases(self): class A(metaclass=abc.ABCMeta): @@ -130,29 +143,40 @@ class TestABC(unittest.TestCase): class A(metaclass=abc.ABCMeta): pass self.failUnless(issubclass(A, A))

def test_all_new_methods_are_called(self): class A(metaclass=abc.ABCMeta):

--- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -582,12 +582,18 @@ class ExceptionTests(unittest.TestCase): except KeyError: pass except:

+

def test_MemoryError(self):

--- a/Lib/test/test_typechecks.py +++ b/Lib/test/test_typechecks.py @@ -33,26 +33,39 @@ class TypeChecksTest(unittest.TestCase): def testIsSubclassBuiltin(self): self.assertEqual(issubclass(int, Integer), True)

def testIsInstanceBuiltin(self): self.assertEqual(isinstance(42, Integer), True)

def testIsInstanceActual(self): self.assertEqual(isinstance(Integer(), Integer), True)

def testIsSubclassActual(self): self.assertEqual(issubclass(Integer, Integer), True)

def testSubclassBehavior(self): self.assertEqual(issubclass(SubInt, Integer), True)

def test_main():

--- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,13 @@ What's New in Python 3.0 release candida Core and Builtins ----------------- +- Issue #2534: speed up isinstance() and issubclass() by 50-70%, so as to

--- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2474,39 +2474,38 @@ abstract_get_bases(PyObject *cls) static int abstract_issubclass(PyObject *derived, PyObject *cls) { - PyObject *bases; + PyObject bases = NULL; Py_ssize_t i, n; int r = 0; - - if (derived == cls) - return 1; - - if (PyTuple_Check(cls)) { - / Not a general sequence -- that opens up the road to - recursion and stack overflow. / - n = PyTuple_GET_SIZE(cls); + while (1) { + if (derived == cls) + return 1; + bases = abstract_get_bases(derived); + if (bases == NULL) { + if (PyErr_Occurred()) + return -1; + return 0; + } + n = PyTuple_GET_SIZE(bases); + if (n == 0) { + Py_DECREF(bases); + return 0; + } + / Avoid recursivity in the single inheritance case */ + if (n == 1) { + derived = PyTuple_GET_ITEM(bases, 0); + Py_DECREF(bases); + continue; + } for (i = 0; i < n; i++) { - if (derived == PyTuple_GET_ITEM(cls, i)) - return 1; + r = abstract_issubclass(PyTuple_GET_ITEM(bases, i), cls); + if (r != 0) + break; } + Py_DECREF(bases); + return r; } - bases = abstract_get_bases(derived); - if (bases == NULL) { - if (PyErr_Occurred()) - return -1; - return 0; - } - n = PyTuple_GET_SIZE(bases); - for (i = 0; i < n; i++) { - r = abstract_issubclass(PyTuple_GET_ITEM(bases, i), cls); - if (r != 0) - break; - } - - Py_DECREF(bases); - - return r; } static int @@ -2524,7 +2523,7 @@ check_class(PyObject *cls, const char *e } static int -recursive_isinstance(PyObject *inst, PyObject *cls, int recursion_depth) +recursive_isinstance(PyObject *inst, PyObject *cls) { PyObject *icls; static PyObject *class = NULL; @@ -2553,25 +2552,6 @@ recursive_isinstance(PyObject *inst, PyO } } } - else if (PyTuple_Check(cls)) { - Py_ssize_t i, n; - - if (!recursion_depth) { - PyErr_SetString(PyExc_RuntimeError, - "nest level of tuple too deep"); - return -1; - } - - n = PyTuple_GET_SIZE(cls); - for (i = 0; i < n; i++) { - retval = recursive_isinstance( - inst, - PyTuple_GET_ITEM(cls, i), - recursion_depth-1); - if (retval != 0) - break; - } - } else { if (!check_class(cls, "isinstance() arg 2 must be a class, type," @@ -2601,6 +2581,24 @@ PyObject_IsInstance(PyObject *inst, PyOb if (Py_TYPE(inst) == (PyTypeObject *)cls) return 1; + if (PyTuple_Check(cls)) { + Py_ssize_t i; + Py_ssize_t n; + int r = 0; + + if (Py_EnterRecursiveCall(" in instancecheck")) + return -1; + n = PyTuple_GET_SIZE(cls); + for (i = 0; i < n; ++i) { + PyObject item = PyTuple_GET_ITEM(cls, i); + r = PyObject_IsInstance(inst, item); + if (r != 0) + / either found it, or got an error */ + break; + } + Py_LeaveRecursiveCall(); + return r; + } if (name == NULL) { name = PyUnicode_InternFromString("instancecheck"); if (name == NULL) @@ -2625,51 +2623,25 @@ PyObject_IsInstance(PyObject *inst, PyOb } return ok; } - return recursive_isinstance(inst, cls, Py_GetRecursionLimit()); + return recursive_isinstance(inst, cls); } static int -recursive_issubclass(PyObject *derived, PyObject *cls, int recursion_depth) +recursive_issubclass(PyObject *derived, PyObject cls) { - int retval; - - { - if (!check_class(derived, - "issubclass() arg 1 must be a class")) - return -1; - - if (PyTuple_Check(cls)) { - Py_ssize_t i; - Py_ssize_t n = PyTuple_GET_SIZE(cls); + if (PyType_Check(cls) && PyType_Check(derived)) { + / Fast path (non-recursive) */ + return PyType_IsSubtype((PyTypeObject *)derived, (PyTypeObject )cls); + } + if (!check_class(derived, + "issubclass() arg 1 must be a class")) + return -1; + if (!check_class(cls, + "issubclass() arg 2 must be a class" + " or tuple of classes")) + return -1; - if (!recursion_depth) { - PyErr_SetString(PyExc_RuntimeError, - "nest level of tuple too deep"); - return -1; - } - for (i = 0; i < n; ++i) { - retval = recursive_issubclass( - derived, - PyTuple_GET_ITEM(cls, i), - recursion_depth-1); - if (retval != 0) { - / either found it, or got an error */ - return retval; - } - } - return 0; - } - else { - if (!check_class(cls, - "issubclass() arg 2 must be a class" - " or tuple of classes")) - return -1; - } - - retval = abstract_issubclass(derived, cls); - } - - return retval; + return abstract_issubclass(derived, cls); } int @@ -2678,20 +2650,40 @@ PyObject_IsSubclass(PyObject *derived, P static PyObject *name = NULL; PyObject *t, *v, *tb; PyObject *checker; - PyErr_Fetch(&t, &v, &tb); + if (PyTuple_Check(cls)) { + Py_ssize_t i; + Py_ssize_t n; + int r = 0; + + if (Py_EnterRecursiveCall(" in subclasscheck")) + return -1; + n = PyTuple_GET_SIZE(cls); + for (i = 0; i < n; ++i) { + PyObject item = PyTuple_GET_ITEM(cls, i); + r = PyObject_IsSubclass(derived, item); + if (r != 0) + / either found it, or got an error */ + break; + } + Py_LeaveRecursiveCall(); + return r; + } if (name == NULL) { name = PyUnicode_InternFromString("subclasscheck"); if (name == NULL) return -1; } + PyErr_Fetch(&t, &v, &tb); checker = PyObject_GetAttr(cls, name); PyErr_Restore(t, v, tb); if (checker != NULL) { PyObject *res; int ok = -1; - if (Py_EnterRecursiveCall(" in subclasscheck")) + if (Py_EnterRecursiveCall(" in subclasscheck")) { + Py_DECREF(checker); return ok; + } res = PyObject_CallFunctionObjArgs(checker, derived, NULL); Py_LeaveRecursiveCall(); Py_DECREF(checker); @@ -2701,7 +2693,19 @@ PyObject_IsSubclass(PyObject *derived, P } return ok; } - return recursive_issubclass(derived, cls, Py_GetRecursionLimit()); + return recursive_issubclass(derived, cls); +} + +int +_PyObject_RealIsInstance(PyObject *inst, PyObject *cls) +{ + return recursive_isinstance(inst, cls); +} + +int +_PyObject_RealIsSubclass(PyObject *derived, PyObject *cls) +{ + return recursive_issubclass(derived, cls); }

--- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -584,6 +584,49 @@ type_get_doc(PyTypeObject *type, void *c return result; } +static PyObject * +type___instancecheck__(PyObject *type, PyObject *inst) +{ + switch (PyObject_RealIsInstance(inst, type)) { + case -1: + return NULL; + case 0: + Py_RETURN_FALSE; + default: + Py_RETURN_TRUE; + } +} + + +static PyObject * +type_get_instancecheck(PyObject *type, void *context) +{ + static PyMethodDef ml = {"instancecheck", + type___instancecheck, METH_O }; + return PyCFunction_New(&ml, type); +} + +static PyObject * +type___subclasscheck(PyObject *type, PyObject *inst) +{ + switch (PyObject_RealIsSubclass(inst, type)) { + case -1: + return NULL; + case 0: + Py_RETURN_FALSE; + default: + Py_RETURN_TRUE; + } +} + +static PyObject * +type_get_subclasscheck(PyObject *type, void *context) +{ + static PyMethodDef ml = {"subclasscheck", + type___subclasscheck, METH_O }; + return PyCFunction_New(&ml, type); +} + static PyGetSetDef type_getsets[] = { {"name", (getter)type_name, (setter)type_set_name, NULL}, {"bases", (getter)type_get_bases, (setter)type_set_bases, NULL}, @@ -592,6 +635,8 @@ static PyGetSetDef type_getsets[] = { (setter)type_set_abstractmethods, NULL}, {"dict", (getter)type_dict, NULL, NULL}, {"doc", (getter)type_get_doc, NULL, NULL}, + {"instancecheck", (getter)type_get_instancecheck, NULL, NULL}, + {"subclasscheck", (getter)type_get_subclasscheck, NULL, NULL}, {0} };

--- a/Python/errors.c +++ b/Python/errors.c @@ -160,11 +160,12 @@ PyErr_GivenExceptionMatches(PyObject *er int res = 0; PyObject *exception, *value, tb; PyErr_Fetch(&exception, &value, &tb); - res = PyObject_IsSubclass(err, exc); + / PyObject_IsSubclass() can recurse and therefore is + not safe (see test_bad_getattr in test.pickletester). */ + res = PyType_IsSubtype((PyTypeObject *)err, (PyTypeObject )exc); / This function must not fail, so print the error here / if (res == -1) { PyErr_WriteUnraisable(err); - / issubclass did not succeed */ res = 0; } PyErr_Restore(exception, value, tb);