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.
This commit is contained in:
Antoine Pitrou 2008-08-26 22:42:08 +00:00
parent 14cb6bcf2b
commit 0668c62677
8 changed files with 211 additions and 88 deletions

View file

@ -1377,6 +1377,11 @@ PyAPI_FUNC(int) PyObject_IsSubclass(PyObject *object, PyObject *typeorclass);
/* issubclass(object, typeorclass) */ /* issubclass(object, typeorclass) */
PyAPI_FUNC(int) _PyObject_RealIsInstance(PyObject *inst, PyObject *cls);
PyAPI_FUNC(int) _PyObject_RealIsSubclass(PyObject *derived, PyObject *cls);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -88,15 +88,21 @@ class TestABC(unittest.TestCase):
pass pass
b = B() b = B()
self.assertEqual(issubclass(B, A), False) self.assertEqual(issubclass(B, A), False)
self.assertEqual(issubclass(B, (A,)), False)
self.assertEqual(isinstance(b, A), False) self.assertEqual(isinstance(b, A), False)
self.assertEqual(isinstance(b, (A,)), False)
A.register(B) A.register(B)
self.assertEqual(issubclass(B, A), True) self.assertEqual(issubclass(B, A), True)
self.assertEqual(issubclass(B, (A,)), True)
self.assertEqual(isinstance(b, A), True) self.assertEqual(isinstance(b, A), True)
self.assertEqual(isinstance(b, (A,)), True)
class C(B): class C(B):
pass pass
c = C() c = C()
self.assertEqual(issubclass(C, A), True) self.assertEqual(issubclass(C, A), True)
self.assertEqual(issubclass(C, (A,)), True)
self.assertEqual(isinstance(c, A), True) self.assertEqual(isinstance(c, A), True)
self.assertEqual(isinstance(c, (A,)), True)
def test_isinstance_invalidation(self): def test_isinstance_invalidation(self):
class A: class A:
@ -105,20 +111,26 @@ class TestABC(unittest.TestCase):
pass pass
b = B() b = B()
self.assertEqual(isinstance(b, A), False) self.assertEqual(isinstance(b, A), False)
self.assertEqual(isinstance(b, (A,)), False)
A.register(B) A.register(B)
self.assertEqual(isinstance(b, A), True) self.assertEqual(isinstance(b, A), True)
self.assertEqual(isinstance(b, (A,)), True)
def test_registration_builtins(self): def test_registration_builtins(self):
class A: class A:
__metaclass__ = abc.ABCMeta __metaclass__ = abc.ABCMeta
A.register(int) A.register(int)
self.assertEqual(isinstance(42, A), True) self.assertEqual(isinstance(42, A), True)
self.assertEqual(isinstance(42, (A,)), True)
self.assertEqual(issubclass(int, A), True) self.assertEqual(issubclass(int, A), True)
self.assertEqual(issubclass(int, (A,)), True)
class B(A): class B(A):
pass pass
B.register(basestring) B.register(basestring)
self.assertEqual(isinstance("", A), True) self.assertEqual(isinstance("", A), True)
self.assertEqual(isinstance("", (A,)), True)
self.assertEqual(issubclass(str, A), True) self.assertEqual(issubclass(str, A), True)
self.assertEqual(issubclass(str, (A,)), True)
def test_registration_edge_cases(self): def test_registration_edge_cases(self):
class A: class A:
@ -141,29 +153,40 @@ class TestABC(unittest.TestCase):
class A: class A:
__metaclass__ = abc.ABCMeta __metaclass__ = abc.ABCMeta
self.failUnless(issubclass(A, A)) self.failUnless(issubclass(A, A))
self.failUnless(issubclass(A, (A,)))
class B: class B:
__metaclass__ = abc.ABCMeta __metaclass__ = abc.ABCMeta
self.failIf(issubclass(A, B)) self.failIf(issubclass(A, B))
self.failIf(issubclass(A, (B,)))
self.failIf(issubclass(B, A)) self.failIf(issubclass(B, A))
self.failIf(issubclass(B, (A,)))
class C: class C:
__metaclass__ = abc.ABCMeta __metaclass__ = abc.ABCMeta
A.register(B) A.register(B)
class B1(B): class B1(B):
pass pass
self.failUnless(issubclass(B1, A)) self.failUnless(issubclass(B1, A))
self.failUnless(issubclass(B1, (A,)))
class C1(C): class C1(C):
pass pass
B1.register(C1) B1.register(C1)
self.failIf(issubclass(C, B)) self.failIf(issubclass(C, B))
self.failIf(issubclass(C, (B,)))
self.failIf(issubclass(C, B1)) self.failIf(issubclass(C, B1))
self.failIf(issubclass(C, (B1,)))
self.failUnless(issubclass(C1, A)) self.failUnless(issubclass(C1, A))
self.failUnless(issubclass(C1, (A,)))
self.failUnless(issubclass(C1, B)) self.failUnless(issubclass(C1, B))
self.failUnless(issubclass(C1, (B,)))
self.failUnless(issubclass(C1, B1)) self.failUnless(issubclass(C1, B1))
self.failUnless(issubclass(C1, (B1,)))
C1.register(int) C1.register(int)
class MyInt(int): class MyInt(int):
pass pass
self.failUnless(issubclass(MyInt, A)) self.failUnless(issubclass(MyInt, A))
self.failUnless(issubclass(MyInt, (A,)))
self.failUnless(isinstance(42, A)) self.failUnless(isinstance(42, A))
self.failUnless(isinstance(42, (A,)))
def test_all_new_methods_are_called(self): def test_all_new_methods_are_called(self):
class A: class A:

View file

@ -333,7 +333,19 @@ class ExceptionTests(unittest.TestCase):
return g() return g()
except ValueError: except ValueError:
return -1 return -1
self.assertRaises(RuntimeError, g)
# The test prints an unraisable recursion error when
# doing "except ValueError", this is because subclass
# checking has recursion checking too.
with captured_output("stderr"):
try:
g()
except RuntimeError:
pass
except:
self.fail("Should have raised KeyError")
else:
self.fail("Should have raised KeyError")
def testUnicodeStrUsage(self): def testUnicodeStrUsage(self):
# Make sure both instances and classes have a str and unicode # Make sure both instances and classes have a str and unicode
@ -363,12 +375,20 @@ class ExceptionTests(unittest.TestCase):
except KeyError: except KeyError:
pass pass
except: except:
self.fail("Should have raised TypeError") self.fail("Should have raised KeyError")
else: else:
self.fail("Should have raised TypeError") self.fail("Should have raised KeyError")
self.assertEqual(stderr.getvalue(),
"Exception ValueError: ValueError() in " with captured_output("stderr") as stderr:
"<type 'exceptions.KeyError'> ignored\n") def g():
try:
return g()
except RuntimeError:
return sys.exc_info()
e, v, tb = g()
self.assert_(e is RuntimeError, e)
self.assert_("maximum recursion depth exceeded" in str(v), v)
def test_main(): def test_main():
run_unittest(ExceptionTests) run_unittest(ExceptionTests)

View file

@ -41,26 +41,39 @@ class TypeChecksTest(unittest.TestCase):
def testIsSubclassBuiltin(self): def testIsSubclassBuiltin(self):
self.assertEqual(issubclass(int, Integer), True) self.assertEqual(issubclass(int, Integer), True)
self.assertEqual(issubclass(int, (Integer,)), True)
self.assertEqual(issubclass(float, Integer), False) self.assertEqual(issubclass(float, Integer), False)
self.assertEqual(issubclass(float, (Integer,)), False)
def testIsInstanceBuiltin(self): def testIsInstanceBuiltin(self):
self.assertEqual(isinstance(42, Integer), True) self.assertEqual(isinstance(42, Integer), True)
self.assertEqual(isinstance(42, (Integer,)), True)
self.assertEqual(isinstance(3.14, Integer), False) self.assertEqual(isinstance(3.14, Integer), False)
self.assertEqual(isinstance(3.14, (Integer,)), False)
def testIsInstanceActual(self): def testIsInstanceActual(self):
self.assertEqual(isinstance(Integer(), Integer), True) self.assertEqual(isinstance(Integer(), Integer), True)
self.assertEqual(isinstance(Integer(), (Integer,)), True)
def testIsSubclassActual(self): def testIsSubclassActual(self):
self.assertEqual(issubclass(Integer, Integer), True) self.assertEqual(issubclass(Integer, Integer), True)
self.assertEqual(issubclass(Integer, (Integer,)), True)
def testSubclassBehavior(self): def testSubclassBehavior(self):
self.assertEqual(issubclass(SubInt, Integer), True) self.assertEqual(issubclass(SubInt, Integer), True)
self.assertEqual(issubclass(SubInt, (Integer,)), True)
self.assertEqual(issubclass(SubInt, SubInt), True) self.assertEqual(issubclass(SubInt, SubInt), True)
self.assertEqual(issubclass(SubInt, (SubInt,)), True)
self.assertEqual(issubclass(Integer, SubInt), False) self.assertEqual(issubclass(Integer, SubInt), False)
self.assertEqual(issubclass(Integer, (SubInt,)), False)
self.assertEqual(issubclass(int, SubInt), False) self.assertEqual(issubclass(int, SubInt), False)
self.assertEqual(issubclass(int, (SubInt,)), False)
self.assertEqual(isinstance(SubInt(), Integer), True) self.assertEqual(isinstance(SubInt(), Integer), True)
self.assertEqual(isinstance(SubInt(), (Integer,)), True)
self.assertEqual(isinstance(SubInt(), SubInt), True) self.assertEqual(isinstance(SubInt(), SubInt), True)
self.assertEqual(isinstance(SubInt(), (SubInt,)), True)
self.assertEqual(isinstance(42, SubInt), False) self.assertEqual(isinstance(42, SubInt), False)
self.assertEqual(isinstance(42, (SubInt,)), False)
def testInfiniteRecursionCaughtProperly(self): def testInfiniteRecursionCaughtProperly(self):
e = Evil() e = Evil()

View file

@ -12,6 +12,13 @@ What's New in Python 2.6 release candidate 1?
Core and Builtins 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.
- Fix crashes on memory allocation failure found with failmalloc. - Fix crashes on memory allocation failure found with failmalloc.
- Fix memory leaks found with valgrind and update suppressions file. - Fix memory leaks found with valgrind and update suppressions file.

View file

@ -2778,39 +2778,38 @@ abstract_get_bases(PyObject *cls)
static int static int
abstract_issubclass(PyObject *derived, PyObject *cls) abstract_issubclass(PyObject *derived, PyObject *cls)
{ {
PyObject *bases; PyObject *bases = NULL;
Py_ssize_t i, n; Py_ssize_t i, n;
int r = 0; int r = 0;
while (1) {
if (derived == cls) if (derived == cls)
return 1; return 1;
bases = abstract_get_bases(derived);
if (PyTuple_Check(cls)) { if (bases == NULL) {
/* Not a general sequence -- that opens up the road to if (PyErr_Occurred())
recursion and stack overflow. */ return -1;
n = PyTuple_GET_SIZE(cls); return 0;
for (i = 0; i < n; i++) {
if (derived == PyTuple_GET_ITEM(cls, i))
return 1;
} }
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++) {
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 static int
@ -2828,7 +2827,7 @@ check_class(PyObject *cls, const char *error)
} }
static int static int
recursive_isinstance(PyObject *inst, PyObject *cls, int recursion_depth) recursive_isinstance(PyObject *inst, PyObject *cls)
{ {
PyObject *icls; PyObject *icls;
static PyObject *__class__ = NULL; static PyObject *__class__ = NULL;
@ -2862,25 +2861,6 @@ recursive_isinstance(PyObject *inst, PyObject *cls, int recursion_depth)
} }
} }
} }
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 { else {
if (!check_class(cls, if (!check_class(cls,
"isinstance() arg 2 must be a class, type," "isinstance() arg 2 must be a class, type,"
@ -2910,6 +2890,24 @@ PyObject_IsInstance(PyObject *inst, PyObject *cls)
if (Py_TYPE(inst) == (PyTypeObject *)cls) if (Py_TYPE(inst) == (PyTypeObject *)cls)
return 1; 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) { if (name == NULL) {
name = PyString_InternFromString("__instancecheck__"); name = PyString_InternFromString("__instancecheck__");
if (name == NULL) if (name == NULL)
@ -2934,47 +2932,28 @@ PyObject_IsInstance(PyObject *inst, PyObject *cls)
} }
return ok; return ok;
} }
return recursive_isinstance(inst, cls, Py_GetRecursionLimit()); return recursive_isinstance(inst, cls);
} }
static int static int
recursive_issubclass(PyObject *derived, PyObject *cls, int recursion_depth) recursive_issubclass(PyObject *derived, PyObject *cls)
{ {
int retval; int retval;
if (PyType_Check(cls) && PyType_Check(derived)) {
/* Fast path (non-recursive) */
return PyType_IsSubtype(
(PyTypeObject *)derived, (PyTypeObject *)cls);
}
if (!PyClass_Check(derived) || !PyClass_Check(cls)) { if (!PyClass_Check(derived) || !PyClass_Check(cls)) {
if (!check_class(derived, if (!check_class(derived,
"issubclass() arg 1 must be a class")) "issubclass() arg 1 must be a class"))
return -1; return -1;
if (PyTuple_Check(cls)) { if (!check_class(cls,
Py_ssize_t i; "issubclass() arg 2 must be a class"
Py_ssize_t n = PyTuple_GET_SIZE(cls); " 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); retval = abstract_issubclass(derived, cls);
} }
else { else {
@ -2992,20 +2971,40 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls)
static PyObject *name = NULL; static PyObject *name = NULL;
PyObject *t, *v, *tb; PyObject *t, *v, *tb;
PyObject *checker; 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) { if (name == NULL) {
name = PyString_InternFromString("__subclasscheck__"); name = PyString_InternFromString("__subclasscheck__");
if (name == NULL) if (name == NULL)
return -1; return -1;
} }
PyErr_Fetch(&t, &v, &tb);
checker = PyObject_GetAttr(cls, name); checker = PyObject_GetAttr(cls, name);
PyErr_Restore(t, v, tb); PyErr_Restore(t, v, tb);
if (checker != NULL) { if (checker != NULL) {
PyObject *res; PyObject *res;
int ok = -1; int ok = -1;
if (Py_EnterRecursiveCall(" in __subclasscheck__")) if (Py_EnterRecursiveCall(" in __subclasscheck__")) {
Py_DECREF(checker);
return ok; return ok;
}
res = PyObject_CallFunctionObjArgs(checker, derived, NULL); res = PyObject_CallFunctionObjArgs(checker, derived, NULL);
Py_LeaveRecursiveCall(); Py_LeaveRecursiveCall();
Py_DECREF(checker); Py_DECREF(checker);
@ -3015,7 +3014,19 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls)
} }
return ok; 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);
} }

View file

@ -571,6 +571,49 @@ type_get_doc(PyTypeObject *type, void *context)
return result; 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[] = { static PyGetSetDef type_getsets[] = {
{"__name__", (getter)type_name, (setter)type_set_name, NULL}, {"__name__", (getter)type_name, (setter)type_set_name, NULL},
{"__bases__", (getter)type_get_bases, (setter)type_set_bases, NULL}, {"__bases__", (getter)type_get_bases, (setter)type_set_bases, NULL},
@ -579,6 +622,8 @@ static PyGetSetDef type_getsets[] = {
(setter)type_set_abstractmethods, NULL}, (setter)type_set_abstractmethods, NULL},
{"__dict__", (getter)type_dict, NULL, NULL}, {"__dict__", (getter)type_dict, NULL, NULL},
{"__doc__", (getter)type_get_doc, NULL, NULL}, {"__doc__", (getter)type_get_doc, NULL, NULL},
{"__instancecheck__", (getter)type_get_instancecheck, NULL, NULL},
{"__subclasscheck__", (getter)type_get_subclasscheck, NULL, NULL},
{0} {0}
}; };

View file

@ -113,7 +113,6 @@ PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc)
/* This function must not fail, so print the error here */ /* This function must not fail, so print the error here */
if (res == -1) { if (res == -1) {
PyErr_WriteUnraisable(err); PyErr_WriteUnraisable(err);
/* issubclass did not succeed */
res = 0; res = 0;
} }
PyErr_Restore(exception, value, tb); PyErr_Restore(exception, value, tb);