summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2008-08-26 22:42:08 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2008-08-26 22:42:08 (GMT)
commit0668c62677e76b2acf7e4d11d5e9c1f4420a54f1 (patch)
tree0d6001ed47c5e6f715996eb2fc7e512d8c7a2de7
parent14cb6bcf2ba953735ec1ef622f8ff8e23db1f326 (diff)
downloadcpython-0668c62677e76b2acf7e4d11d5e9c1f4420a54f1.zip
cpython-0668c62677e76b2acf7e4d11d5e9c1f4420a54f1.tar.gz
cpython-0668c62677e76b2acf7e4d11d5e9c1f4420a54f1.tar.bz2
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.
-rw-r--r--Include/abstract.h5
-rw-r--r--Lib/test/test_abc.py23
-rw-r--r--Lib/test/test_exceptions.py32
-rw-r--r--Lib/test/test_typechecks.py13
-rw-r--r--Misc/NEWS7
-rw-r--r--Objects/abstract.c171
-rw-r--r--Objects/typeobject.c45
-rw-r--r--Python/errors.c1
8 files changed, 210 insertions, 87 deletions
diff --git a/Include/abstract.h b/Include/abstract.h
index 04c007a..f3bc8bc 100644
--- a/Include/abstract.h
+++ b/Include/abstract.h
@@ -1377,6 +1377,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
diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py
index 8fed220..3e0955f 100644
--- a/Lib/test/test_abc.py
+++ b/Lib/test/test_abc.py
@@ -88,15 +88,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:
@@ -105,20 +111,26 @@ 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
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(basestring)
self.assertEqual(isinstance("", A), True)
+ self.assertEqual(isinstance("", (A,)), True)
self.assertEqual(issubclass(str, A), True)
+ self.assertEqual(issubclass(str, (A,)), True)
def test_registration_edge_cases(self):
class A:
@@ -141,29 +153,40 @@ class TestABC(unittest.TestCase):
class A:
__metaclass__ = abc.ABCMeta
self.failUnless(issubclass(A, A))
+ self.failUnless(issubclass(A, (A,)))
class B:
__metaclass__ = abc.ABCMeta
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
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:
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index c3a9308..0576b62 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -333,7 +333,19 @@ class ExceptionTests(unittest.TestCase):
return g()
except ValueError:
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):
# Make sure both instances and classes have a str and unicode
@@ -363,12 +375,20 @@ 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 "
- "<type 'exceptions.KeyError'> ignored\n")
+ self.fail("Should have raised KeyError")
+
+ with captured_output("stderr") as stderr:
+ 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():
run_unittest(ExceptionTests)
diff --git a/Lib/test/test_typechecks.py b/Lib/test/test_typechecks.py
index 21be62d..32aa660 100644
--- a/Lib/test/test_typechecks.py
+++ b/Lib/test/test_typechecks.py
@@ -41,26 +41,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 testInfiniteRecursionCaughtProperly(self):
e = Evil()
diff --git a/Misc/NEWS b/Misc/NEWS
index f1aec99..822d1c9 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,13 @@ What's New in Python 2.6 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.
+
- Fix crashes on memory allocation failure found with failmalloc.
- Fix memory leaks found with valgrind and update suppressions file.
diff --git a/Objects/abstract.c b/Objects/abstract.c
index 68e1549..956c4f4 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -2778,39 +2778,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
@@ -2828,7 +2827,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;
@@ -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 {
if (!check_class(cls,
"isinstance() arg 2 must be a class, type,"
@@ -2910,6 +2890,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 = PyString_InternFromString("__instancecheck__");
if (name == NULL)
@@ -2934,47 +2932,28 @@ 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 (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 (!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;
- }
-
+ if (!check_class(cls,
+ "issubclass() arg 2 must be a class"
+ " or tuple of classes"))
+ return -1;
retval = abstract_issubclass(derived, cls);
}
else {
@@ -2992,20 +2971,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 = PyString_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);
@@ -3015,7 +3014,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);
}
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 02b5012..405b773 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -571,6 +571,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},
@@ -579,6 +622,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}
};
diff --git a/Python/errors.c b/Python/errors.c
index 5d9cab5..c88a190 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -113,7 +113,6 @@ PyErr_GivenExceptionMatches(PyObject *err, PyObject *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);