Skip to content

Commit 0668c62

Browse files
committed
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.
1 parent 14cb6bc commit 0668c62

File tree

8 files changed

+210
-87
lines changed

8 files changed

+210
-87
lines changed

Include/abstract.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,11 @@ PyAPI_FUNC(int) PyObject_IsSubclass(PyObject *object, PyObject *typeorclass);
13771377
/* issubclass(object, typeorclass) */
13781378

13791379

1380+
PyAPI_FUNC(int) _PyObject_RealIsInstance(PyObject *inst, PyObject *cls);
1381+
1382+
PyAPI_FUNC(int) _PyObject_RealIsSubclass(PyObject *derived, PyObject *cls);
1383+
1384+
13801385
#ifdef __cplusplus
13811386
}
13821387
#endif

Lib/test/test_abc.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,21 @@ class B(object):
8888
pass
8989
b = B()
9090
self.assertEqual(issubclass(B, A), False)
91+
self.assertEqual(issubclass(B, (A,)), False)
9192
self.assertEqual(isinstance(b, A), False)
93+
self.assertEqual(isinstance(b, (A,)), False)
9294
A.register(B)
9395
self.assertEqual(issubclass(B, A), True)
96+
self.assertEqual(issubclass(B, (A,)), True)
9497
self.assertEqual(isinstance(b, A), True)
98+
self.assertEqual(isinstance(b, (A,)), True)
9599
class C(B):
96100
pass
97101
c = C()
98102
self.assertEqual(issubclass(C, A), True)
103+
self.assertEqual(issubclass(C, (A,)), True)
99104
self.assertEqual(isinstance(c, A), True)
105+
self.assertEqual(isinstance(c, (A,)), True)
100106

101107
def test_isinstance_invalidation(self):
102108
class A:
@@ -105,20 +111,26 @@ class B(object):
105111
pass
106112
b = B()
107113
self.assertEqual(isinstance(b, A), False)
114+
self.assertEqual(isinstance(b, (A,)), False)
108115
A.register(B)
109116
self.assertEqual(isinstance(b, A), True)
117+
self.assertEqual(isinstance(b, (A,)), True)
110118

111119
def test_registration_builtins(self):
112120
class A:
113121
__metaclass__ = abc.ABCMeta
114122
A.register(int)
115123
self.assertEqual(isinstance(42, A), True)
124+
self.assertEqual(isinstance(42, (A,)), True)
116125
self.assertEqual(issubclass(int, A), True)
126+
self.assertEqual(issubclass(int, (A,)), True)
117127
class B(A):
118128
pass
119129
B.register(basestring)
120130
self.assertEqual(isinstance("", A), True)
131+
self.assertEqual(isinstance("", (A,)), True)
121132
self.assertEqual(issubclass(str, A), True)
133+
self.assertEqual(issubclass(str, (A,)), True)
122134

123135
def test_registration_edge_cases(self):
124136
class A:
@@ -141,29 +153,40 @@ def test_registration_transitiveness(self):
141153
class A:
142154
__metaclass__ = abc.ABCMeta
143155
self.failUnless(issubclass(A, A))
156+
self.failUnless(issubclass(A, (A,)))
144157
class B:
145158
__metaclass__ = abc.ABCMeta
146159
self.failIf(issubclass(A, B))
160+
self.failIf(issubclass(A, (B,)))
147161
self.failIf(issubclass(B, A))
162+
self.failIf(issubclass(B, (A,)))
148163
class C:
149164
__metaclass__ = abc.ABCMeta
150165
A.register(B)
151166
class B1(B):
152167
pass
153168
self.failUnless(issubclass(B1, A))
169+
self.failUnless(issubclass(B1, (A,)))
154170
class C1(C):
155171
pass
156172
B1.register(C1)
157173
self.failIf(issubclass(C, B))
174+
self.failIf(issubclass(C, (B,)))
158175
self.failIf(issubclass(C, B1))
176+
self.failIf(issubclass(C, (B1,)))
159177
self.failUnless(issubclass(C1, A))
178+
self.failUnless(issubclass(C1, (A,)))
160179
self.failUnless(issubclass(C1, B))
180+
self.failUnless(issubclass(C1, (B,)))
161181
self.failUnless(issubclass(C1, B1))
182+
self.failUnless(issubclass(C1, (B1,)))
162183
C1.register(int)
163184
class MyInt(int):
164185
pass
165186
self.failUnless(issubclass(MyInt, A))
187+
self.failUnless(issubclass(MyInt, (A,)))
166188
self.failUnless(isinstance(42, A))
189+
self.failUnless(isinstance(42, (A,)))
167190

168191
def test_all_new_methods_are_called(self):
169192
class A:

Lib/test/test_exceptions.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,19 @@ def g():
333333
return g()
334334
except ValueError:
335335
return -1
336-
self.assertRaises(RuntimeError, g)
336+
337+
# The test prints an unraisable recursion error when
338+
# doing "except ValueError", this is because subclass
339+
# checking has recursion checking too.
340+
with captured_output("stderr"):
341+
try:
342+
g()
343+
except RuntimeError:
344+
pass
345+
except:
346+
self.fail("Should have raised KeyError")
347+
else:
348+
self.fail("Should have raised KeyError")
337349

338350
def testUnicodeStrUsage(self):
339351
# Make sure both instances and classes have a str and unicode
@@ -363,12 +375,20 @@ class MyException(Exception):
363375
except KeyError:
364376
pass
365377
except:
366-
self.fail("Should have raised TypeError")
378+
self.fail("Should have raised KeyError")
367379
else:
368-
self.fail("Should have raised TypeError")
369-
self.assertEqual(stderr.getvalue(),
370-
"Exception ValueError: ValueError() in "
371-
"<type 'exceptions.KeyError'> ignored\n")
380+
self.fail("Should have raised KeyError")
381+
382+
with captured_output("stderr") as stderr:
383+
def g():
384+
try:
385+
return g()
386+
except RuntimeError:
387+
return sys.exc_info()
388+
e, v, tb = g()
389+
self.assert_(e is RuntimeError, e)
390+
self.assert_("maximum recursion depth exceeded" in str(v), v)
391+
372392

373393
def test_main():
374394
run_unittest(ExceptionTests)

Lib/test/test_typechecks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,39 @@ def testIsSubclassInternal(self):
4141

4242
def testIsSubclassBuiltin(self):
4343
self.assertEqual(issubclass(int, Integer), True)
44+
self.assertEqual(issubclass(int, (Integer,)), True)
4445
self.assertEqual(issubclass(float, Integer), False)
46+
self.assertEqual(issubclass(float, (Integer,)), False)
4547

4648
def testIsInstanceBuiltin(self):
4749
self.assertEqual(isinstance(42, Integer), True)
50+
self.assertEqual(isinstance(42, (Integer,)), True)
4851
self.assertEqual(isinstance(3.14, Integer), False)
52+
self.assertEqual(isinstance(3.14, (Integer,)), False)
4953

5054
def testIsInstanceActual(self):
5155
self.assertEqual(isinstance(Integer(), Integer), True)
56+
self.assertEqual(isinstance(Integer(), (Integer,)), True)
5257

5358
def testIsSubclassActual(self):
5459
self.assertEqual(issubclass(Integer, Integer), True)
60+
self.assertEqual(issubclass(Integer, (Integer,)), True)
5561

5662
def testSubclassBehavior(self):
5763
self.assertEqual(issubclass(SubInt, Integer), True)
64+
self.assertEqual(issubclass(SubInt, (Integer,)), True)
5865
self.assertEqual(issubclass(SubInt, SubInt), True)
66+
self.assertEqual(issubclass(SubInt, (SubInt,)), True)
5967
self.assertEqual(issubclass(Integer, SubInt), False)
68+
self.assertEqual(issubclass(Integer, (SubInt,)), False)
6069
self.assertEqual(issubclass(int, SubInt), False)
70+
self.assertEqual(issubclass(int, (SubInt,)), False)
6171
self.assertEqual(isinstance(SubInt(), Integer), True)
72+
self.assertEqual(isinstance(SubInt(), (Integer,)), True)
6273
self.assertEqual(isinstance(SubInt(), SubInt), True)
74+
self.assertEqual(isinstance(SubInt(), (SubInt,)), True)
6375
self.assertEqual(isinstance(42, SubInt), False)
76+
self.assertEqual(isinstance(42, (SubInt,)), False)
6477

6578
def testInfiniteRecursionCaughtProperly(self):
6679
e = Evil()

Misc/NEWS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ What's New in Python 2.6 release candidate 1?
1212
Core and Builtins
1313
-----------------
1414

15+
- Issue #2534: speed up isinstance() and issubclass() by 50-70%, so as to
16+
match Python 2.5 speed despite the __instancecheck__ / __subclasscheck__
17+
mechanism. In the process, fix a bug where isinstance() and issubclass(),
18+
when given a tuple of classes as second argument, were looking up
19+
__instancecheck__ / __subclasscheck__ on the tuple rather than on each
20+
type object.
21+
1522
- Fix crashes on memory allocation failure found with failmalloc.
1623

1724
- Fix memory leaks found with valgrind and update suppressions file.

0 commit comments

Comments
 (0)