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:40:48 +00:00
parent e2dffc0aeb
commit ec569b7947
8 changed files with 202 additions and 97 deletions

View File

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

View File

@ -75,15 +75,21 @@ class TestABC(unittest.TestCase):
pass
b = B()
self.assertEqual(issubclass(B, A), False)
self.assertEqual(issubclass(B, (A,)), False)
self.assertEqual(isinstance(b, A), False)
self.assertEqual(isinstance(b, (A,)), False)
A.register(B)
self.assertEqual(issubclass(B, A), True)
self.assertEqual(issubclass(B, (A,)), True)
self.assertEqual(isinstance(b, A), True)
self.assertEqual(isinstance(b, (A,)), True)
class C(B):
pass
c = C()
self.assertEqual(issubclass(C, A), True)
self.assertEqual(issubclass(C, (A,)), True)
self.assertEqual(isinstance(c, A), True)
self.assertEqual(isinstance(c, (A,)), True)
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)
A.register(B)
self.assertEqual(isinstance(b, A), True)
self.assertEqual(isinstance(b, (A,)), True)
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)
self.assertEqual(issubclass(int, A), True)
self.assertEqual(issubclass(int, (A,)), True)
class B(A):
pass
B.register(str)
class C(str): pass
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(C, A), True)
self.assertEqual(issubclass(C, (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))
self.failUnless(issubclass(A, (A,)))
class B(metaclass=abc.ABCMeta):
pass
self.failIf(issubclass(A, B))
self.failIf(issubclass(A, (B,)))
self.failIf(issubclass(B, A))
self.failIf(issubclass(B, (A,)))
class C(metaclass=abc.ABCMeta):
pass
A.register(B)
class B1(B):
pass
self.failUnless(issubclass(B1, A))
self.failUnless(issubclass(B1, (A,)))
class C1(C):
pass
B1.register(C1)
self.failIf(issubclass(C, B))
self.failIf(issubclass(C, (B,)))
self.failIf(issubclass(C, B1))
self.failIf(issubclass(C, (B1,)))
self.failUnless(issubclass(C1, A))
self.failUnless(issubclass(C1, (A,)))
self.failUnless(issubclass(C1, B))
self.failUnless(issubclass(C1, (B,)))
self.failUnless(issubclass(C1, B1))
self.failUnless(issubclass(C1, (B1,)))
C1.register(int)
class MyInt(int):
pass
self.failUnless(issubclass(MyInt, A))
self.failUnless(issubclass(MyInt, (A,)))
self.failUnless(isinstance(42, A))
self.failUnless(isinstance(42, (A,)))
def test_all_new_methods_are_called(self):
class A(metaclass=abc.ABCMeta):

View File

@ -582,12 +582,18 @@ class ExceptionTests(unittest.TestCase):
except KeyError:
pass
except:
self.fail("Should have raised TypeError")
self.fail("Should have raised KeyError")
else:
self.fail("Should have raised TypeError")
self.assertEqual(stderr.getvalue(),
"Exception ValueError: ValueError() "
"in <class 'KeyError'> ignored\n")
self.fail("Should have raised KeyError")
def g():
try:
return g()
except RuntimeError:
return sys.exc_info()
e, v, tb = g()
self.assert_(isinstance(v, RuntimeError), type(v))
self.assert_("maximum recursion depth exceeded" in str(v), str(v))
def test_MemoryError(self):

View File

@ -33,26 +33,39 @@ class TypeChecksTest(unittest.TestCase):
def testIsSubclassBuiltin(self):
self.assertEqual(issubclass(int, Integer), True)
self.assertEqual(issubclass(int, (Integer,)), True)
self.assertEqual(issubclass(float, Integer), False)
self.assertEqual(issubclass(float, (Integer,)), False)
def testIsInstanceBuiltin(self):
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)
def testIsInstanceActual(self):
self.assertEqual(isinstance(Integer(), Integer), True)
self.assertEqual(isinstance(Integer(), (Integer,)), True)
def testIsSubclassActual(self):
self.assertEqual(issubclass(Integer, Integer), True)
self.assertEqual(issubclass(Integer, (Integer,)), True)
def testSubclassBehavior(self):
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(Integer, SubInt), False)
self.assertEqual(issubclass(Integer, (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(), SubInt), True)
self.assertEqual(isinstance(SubInt(), (SubInt,)), True)
self.assertEqual(isinstance(42, SubInt), False)
self.assertEqual(isinstance(42, (SubInt,)), False)
def test_main():

View File

@ -12,6 +12,13 @@ What's New in Python 3.0 release candidate 1
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.

View File

@ -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);
for (i = 0; i < n; i++) {
if (derived == PyTuple_GET_ITEM(cls, i))
return 1;
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++) {
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 *error)
}
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, 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 {
if (!check_class(cls,
"isinstance() arg 2 must be a class, type,"
@ -2601,6 +2581,24 @@ PyObject_IsInstance(PyObject *inst, PyObject *cls)
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, PyObject *cls)
}
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 (!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);
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;
return retval;
return abstract_issubclass(derived, cls);
}
int
@ -2678,20 +2650,40 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls)
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, PyObject *cls)
}
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

@ -584,6 +584,49 @@ type_get_doc(PyTypeObject *type, void *context)
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}
};

View File

@ -160,11 +160,12 @@ PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc)
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);