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)
self.assertEqual(issubclass(B, (A,)), False)[](#l2.7) self.assertEqual(isinstance(b, A), False)[](#l2.8)
self.assertEqual(isinstance(b, (A,)), False)[](#l2.9) A.register(B)[](#l2.10) self.assertEqual(issubclass(B, A), True)[](#l2.11)
self.assertEqual(issubclass(B, (A,)), True)[](#l2.12) self.assertEqual(isinstance(b, A), True)[](#l2.13)
self.assertEqual(isinstance(b, (A,)), True)[](#l2.14) class C(B):[](#l2.15) pass[](#l2.16) c = C()[](#l2.17) self.assertEqual(issubclass(C, A), True)[](#l2.18)
self.assertEqual(issubclass(C, (A,)), True)[](#l2.19) self.assertEqual(isinstance(c, A), True)[](#l2.20)
self.assertEqual(isinstance(c, (A,)), True)[](#l2.21)
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)
self.assertEqual(isinstance(b, (A,)), False)[](#l2.29) A.register(B)[](#l2.30) self.assertEqual(isinstance(b, A), True)[](#l2.31)
self.assertEqual(isinstance(b, (A,)), True)[](#l2.32)
def test_registration_builtins(self): class A(metaclass=abc.ABCMeta): pass A.register(int) self.assertEqual(isinstance(42, A), True)
self.assertEqual(isinstance(42, (A,)), True)[](#l2.39) self.assertEqual(issubclass(int, A), True)[](#l2.40)
self.assertEqual(issubclass(int, (A,)), True)[](#l2.41) class B(A):[](#l2.42) pass[](#l2.43) B.register(str)[](#l2.44) class C(str): pass[](#l2.45) self.assertEqual(isinstance("", A), True)[](#l2.46)
self.assertEqual(isinstance("", (A,)), True)[](#l2.47) self.assertEqual(issubclass(str, A), True)[](#l2.48)
self.assertEqual(issubclass(str, (A,)), True)[](#l2.49) self.assertEqual(issubclass(C, A), True)[](#l2.50)
self.assertEqual(issubclass(C, (A,)), True)[](#l2.51)
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))
self.failUnless(issubclass(A, (A,)))[](#l2.59) class B(metaclass=abc.ABCMeta):[](#l2.60) pass[](#l2.61) self.failIf(issubclass(A, B))[](#l2.62)
self.failIf(issubclass(A, (B,)))[](#l2.63) self.failIf(issubclass(B, A))[](#l2.64)
self.failIf(issubclass(B, (A,)))[](#l2.65) class C(metaclass=abc.ABCMeta):[](#l2.66) pass[](#l2.67) A.register(B)[](#l2.68) class B1(B):[](#l2.69) pass[](#l2.70) self.failUnless(issubclass(B1, A))[](#l2.71)
self.failUnless(issubclass(B1, (A,)))[](#l2.72) class C1(C):[](#l2.73) pass[](#l2.74) B1.register(C1)[](#l2.75) self.failIf(issubclass(C, B))[](#l2.76)
self.failIf(issubclass(C, (B,)))[](#l2.77) self.failIf(issubclass(C, B1))[](#l2.78)
self.failIf(issubclass(C, (B1,)))[](#l2.79) self.failUnless(issubclass(C1, A))[](#l2.80)
self.failUnless(issubclass(C1, (A,)))[](#l2.81) self.failUnless(issubclass(C1, B))[](#l2.82)
self.failUnless(issubclass(C1, (B,)))[](#l2.83) self.failUnless(issubclass(C1, B1))[](#l2.84)
self.failUnless(issubclass(C1, (B1,)))[](#l2.85) C1.register(int)[](#l2.86) class MyInt(int):[](#l2.87) pass[](#l2.88) self.failUnless(issubclass(MyInt, A))[](#l2.89)
self.failUnless(issubclass(MyInt, (A,)))[](#l2.90) self.failUnless(isinstance(42, A))[](#l2.91)
self.failUnless(isinstance(42, (A,)))[](#l2.92)
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:
self.fail("Should have raised TypeError")[](#l3.7)
self.fail("Should have raised KeyError")[](#l3.8) else:[](#l3.9)
self.fail("Should have raised TypeError")[](#l3.10)
self.assertEqual(stderr.getvalue(),[](#l3.11)
"Exception ValueError: ValueError() "[](#l3.12)
"in <class 'KeyError'> ignored\n")[](#l3.13)
self.fail("Should have raised KeyError")[](#l3.14)
def g():[](#l3.16)
try:[](#l3.17)
return g()[](#l3.18)
except RuntimeError:[](#l3.19)
return sys.exc_info()[](#l3.20)
e, v, tb = g()[](#l3.21)
self.assert_(isinstance(v, RuntimeError), type(v))[](#l3.22)
self.assert_("maximum recursion depth exceeded" in str(v), str(v))[](#l3.23)
--- 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)
self.assertEqual(issubclass(int, (Integer,)), True)[](#l4.7) self.assertEqual(issubclass(float, Integer), False)[](#l4.8)
self.assertEqual(issubclass(float, (Integer,)), False)[](#l4.9)
def testIsInstanceBuiltin(self): self.assertEqual(isinstance(42, Integer), True)
self.assertEqual(isinstance(42, (Integer,)), True)[](#l4.13) self.assertEqual(isinstance(3.14, Integer), False)[](#l4.14)
self.assertEqual(isinstance(3.14, (Integer,)), False)[](#l4.15)
def testIsInstanceActual(self): self.assertEqual(isinstance(Integer(), Integer), True)
self.assertEqual(isinstance(Integer(), (Integer,)), True)[](#l4.19)
def testIsSubclassActual(self): self.assertEqual(issubclass(Integer, Integer), True)
self.assertEqual(issubclass(Integer, (Integer,)), True)[](#l4.23)
def testSubclassBehavior(self): self.assertEqual(issubclass(SubInt, Integer), True)
self.assertEqual(issubclass(SubInt, (Integer,)), True)[](#l4.27) self.assertEqual(issubclass(SubInt, SubInt), True)[](#l4.28)
self.assertEqual(issubclass(SubInt, (SubInt,)), True)[](#l4.29) self.assertEqual(issubclass(Integer, SubInt), False)[](#l4.30)
self.assertEqual(issubclass(Integer, (SubInt,)), False)[](#l4.31) self.assertEqual(issubclass(int, SubInt), False)[](#l4.32)
self.assertEqual(issubclass(int, (SubInt,)), False)[](#l4.33) self.assertEqual(isinstance(SubInt(), Integer), True)[](#l4.34)
self.assertEqual(isinstance(SubInt(), (Integer,)), True)[](#l4.35) self.assertEqual(isinstance(SubInt(), SubInt), True)[](#l4.36)
self.assertEqual(isinstance(SubInt(), (SubInt,)), True)[](#l4.37) self.assertEqual(isinstance(42, SubInt), False)[](#l4.38)
self.assertEqual(isinstance(42, (SubInt,)), False)[](#l4.39)
--- 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
- 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. +
- Issue #3663: Py_None was decref'd when printing SyntaxErrors.
- Issue #3657: Fix uninitialized memory read when pickling longs.
--- 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);