[Tutor] Handling function parameters of mixed object and basic types
Duncan Gibson
duncan at thermal.esa.int
Mon Sep 4 11:46:49 CEST 2006
I've taken over someone else's code (yes, honestly!) that has a
complex class hierarchy on top of the main procedural code. This
is unfortunate because it means that isinstance() is everywhere.
Profiling recently highlighted one particular formatted output
function that has a cascade of isinstance() tests where the order
of the tests is significant, as in the example:
def oldOutput(x):
if x is None:
pass
elif isinstance(x, Z):
pass
elif isinstance(x, Y):
pass
elif isinstance(x, X):
pass
elif isinstance(x, int):
pass
elif isinstance(x, float):
pass
elif isinstance(x, str):
pass
else:
pass
# NOTE:
# In the real code, there are various enumeration classes
# derived from int, so we can't even test for the built in
# types before we test for particular classes.
I don't like this, because we are usurping Pythons class handling,
and suggested that we create methods in the classes and let Python
do the work, and replace the above with something like:
def newOutput(x):
if x is None:
pass
return
try:
x.output()
except AttributeError:
if isinstance(x, int):
pass
elif isinstance(x, float):
pass
elif isinstance(x, str):
pass
else:
pass
However, when I verified this example using timeit, the results were
completely unexpected. The time to resolve the objects remains the
same, but for the built-in types raising and catching the exception
means that resolution of built-in types takes 3 or 4 times longer.
The improved robustness of the code for objects is obviously good,
but not at the expense of killing performance for the built-in types.
Have I made a basic boo-boo in my test code? Is there a better way
of speeding up the original function? I don't really want to spend
hours (days?) implementing this in the real code if I'm barking up
the wrong tree.
I attach the full example code below.
Cheers
Duncan
#---------------------------------------------------------------------
class X(object):
def __init__(self):
self.x = 0
def output(self):
pass
class Y(X):
def __init__(self):
X.__init__(self)
self.y = 0
def output(self):
pass
class Z(Y):
def __init__(self):
Y.__init__(self)
self.z = 0
def output(self):
pass
def oldOutput(x):
if x is None:
pass
elif isinstance(x, Z):
pass
elif isinstance(x, Y):
pass
elif isinstance(x, X):
pass
elif isinstance(x, int):
pass
elif isinstance(x, float):
pass
elif isinstance(x, str):
pass
else:
pass
def newOutput(x):
if x is None:
pass
return
try:
x.output()
except AttributeError:
if isinstance(x, int):
pass
elif isinstance(x, float):
pass
elif isinstance(x, str):
pass
else:
pass
if __name__ == '__main__':
from timeit import Timer
# first test that the functions 'work' before timing them
#
for i in (None, 1, 1.0, "one", X(), Y(), Z(), []):
oldOutput(i)
newOutput(i)
# now time the functions
#
for i in ('None', '1', '1.0', '"one"', 'X()', 'Y()', 'Z()', '[]'):
s = 'oldOutput(%s)' % i
t = Timer(s,
'from __main__ import X, Y, Z, oldOutput, newOutput')
print 'old', i, t.timeit()
s = 'newOutput(%s)' % i
t = Timer(s,
'from __main__ import X, Y, Z, oldOutput, newOutput')
print 'new', i, t.timeit()
print
More information about the Tutor
mailing list