[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