Commit 5dce1f41 authored by Antoine Pitrou's avatar Antoine Pitrou

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.
parent ab2f6d17
...@@ -1273,6 +1273,11 @@ PyAPI_FUNC(int) PyObject_IsSubclass(PyObject *object, PyObject *typeorclass); ...@@ -1273,6 +1273,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
......
...@@ -75,15 +75,21 @@ class TestABC(unittest.TestCase): ...@@ -75,15 +75,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(metaclass=abc.ABCMeta): class A(metaclass=abc.ABCMeta):
...@@ -92,22 +98,29 @@ class TestABC(unittest.TestCase): ...@@ -92,22 +98,29 @@ 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(metaclass=abc.ABCMeta): class A(metaclass=abc.ABCMeta):
pass pass
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(str) B.register(str)
class C(str): pass class C(str): pass
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)
self.assertEqual(issubclass(C, A), True) self.assertEqual(issubclass(C, A), True)
self.assertEqual(issubclass(C, (A,)), True)
def test_registration_edge_cases(self): def test_registration_edge_cases(self):
class A(metaclass=abc.ABCMeta): class A(metaclass=abc.ABCMeta):
...@@ -130,29 +143,40 @@ class TestABC(unittest.TestCase): ...@@ -130,29 +143,40 @@ class TestABC(unittest.TestCase):
class A(metaclass=abc.ABCMeta): class A(metaclass=abc.ABCMeta):
pass pass
self.failUnless(issubclass(A, A)) self.failUnless(issubclass(A, A))
self.failUnless(issubclass(A, (A,)))
class B(metaclass=abc.ABCMeta): class B(metaclass=abc.ABCMeta):
pass pass
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(metaclass=abc.ABCMeta): class C(metaclass=abc.ABCMeta):
pass pass
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(metaclass=abc.ABCMeta): class A(metaclass=abc.ABCMeta):
......
...@@ -582,12 +582,18 @@ class ExceptionTests(unittest.TestCase): ...@@ -582,12 +582,18 @@ 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() " def g():
"in <class 'KeyError'> ignored\n") 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): def test_MemoryError(self):
......
...@@ -33,26 +33,39 @@ class TypeChecksTest(unittest.TestCase): ...@@ -33,26 +33,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 test_main(): def test_main():
......
...@@ -12,6 +12,13 @@ What's New in Python 3.0 release candidate 1 ...@@ -12,6 +12,13 @@ What's New in Python 3.0 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.
- Issue #3663: Py_None was decref'd when printing SyntaxErrors. - Issue #3663: Py_None was decref'd when printing SyntaxErrors.
- Issue #3657: Fix uninitialized memory read when pickling longs. - Issue #3657: Fix uninitialized memory read when pickling longs.
......
...@@ -2474,39 +2474,38 @@ abstract_get_bases(PyObject *cls) ...@@ -2474,39 +2474,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;
}
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++) { for (i = 0; i < n; i++) {
if (derived == PyTuple_GET_ITEM(cls, i)) r = abstract_issubclass(PyTuple_GET_ITEM(bases, i), cls);
return 1; 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
...@@ -2524,7 +2523,7 @@ check_class(PyObject *cls, const char *error) ...@@ -2524,7 +2523,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;
...@@ -2553,25 +2552,6 @@ recursive_isinstance(PyObject *inst, PyObject *cls, int recursion_depth) ...@@ -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 { 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,"
...@@ -2601,6 +2581,24 @@ PyObject_IsInstance(PyObject *inst, PyObject *cls) ...@@ -2601,6 +2581,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 = PyUnicode_InternFromString("__instancecheck__"); name = PyUnicode_InternFromString("__instancecheck__");
if (name == NULL) if (name == NULL)
...@@ -2625,51 +2623,25 @@ PyObject_IsInstance(PyObject *inst, PyObject *cls) ...@@ -2625,51 +2623,25 @@ 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; 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 (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 (!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 int
...@@ -2678,20 +2650,40 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls) ...@@ -2678,20 +2650,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 = PyUnicode_InternFromString("__subclasscheck__"); name = PyUnicode_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);
...@@ -2701,7 +2693,19 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls) ...@@ -2701,7 +2693,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);
} }
......
...@@ -584,6 +584,49 @@ type_get_doc(PyTypeObject *type, void *context) ...@@ -584,6 +584,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},
...@@ -592,6 +635,8 @@ static PyGetSetDef type_getsets[] = { ...@@ -592,6 +635,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}
}; };
......
...@@ -160,11 +160,12 @@ PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc) ...@@ -160,11 +160,12 @@ PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc)
int res = 0; int res = 0;
PyObject *exception, *value, *tb; PyObject *exception, *value, *tb;
PyErr_Fetch(&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 */ /* 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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment