A question on python performance.

Bruno Desthuilliers bdesth.quelquechose at free.quelquepart.fr
Sun Sep 23 17:48:40 CEST 2007

Joe Goldthwaite a écrit :
> Hi everyone,
> I'm a developer who's been using python for a couple of years.  I wrote a
> fairly large application using it but I was learning the language at the
> same time so it most of the code kind of sucks.
> I've learned a lot since then and I've been going through my code trying to
> organize it better and make better use of Python's features.  I'm still  not
> an expert by any definition but I'm slowly getting better.
> I've been working on a trend class that takes twelve monthly numbers and
> returns a period to date, quarter to date, year to date and quarterly year
> to date numbers for a specific period. This worked but I ended up with a lot
> of code like this;
> def getValue(trend, param, per):
>    if param == 'Ptd':
>      return trend.Ptd(per)
>    elif param == 'Qtd':
>       return trend.Qtd(per)
>    elif param == 'Ytd':
>       return trend.Ytd(per)
>    elif param == 'YtdQ':
>       return trend.YtdQ(per)

The first obvious simplification is to replace this with:

def getValue(trend, param, per):
   meth = getattr(trend, param)
   return meth(per)

The main difference is that it will raise (instead of returning None) if 
param is not the name of a method of trend.

The second simplification is to either get rid of getValue()  (which is 
mostly useless).

> The code gets kind of wordy 


> so I started trying to figure out how to call
> them dynamically since the param type is the same as the method the
> retrieves it.  I came up with this;
> def getValue(trend, param, per):
>    return trend.__class__.__dict__[param](trend, per)

Note that this is not strictly equivalent:
class Parent(object):
     def __init__(self, name):
         self.name = name
     def __repr__(self):
         return "<%s %s>" % (self.__class__.__name__, self.name)

     def dothis(self):
         return "parent.dothis %s" % self

class Child(Parent):
     def dothis(self):
         return "Child.dothis %s" % self

class OtherChild(Parent): pass

def dothat(obj):
     return "dothat %s" % obj

p = Parent('p')
c1 = Child('c1')
c2 = Child('c2')
c2.dothis = dothat.__get__(c2, type(c2))
o1 = OtherChild('o1');
o2 = OtherChild('o2');
o2.dothis = dothat.__get__(o2, type(o2))

for obj in p, c1, c2, o1, o2:
     print "obj : %s" % obj
     print "direct call :"
     print obj.dothis()
     print "via obj.__class__.__dict__ :"
         print obj.__class__.__dict__["dothis"](obj)
     except KeyError, e:
         print "oops - key error: %s" % e

obj : <Parent p>
direct call :
parent.dothis <Parent p>
via obj.__class__.__dict__ :
parent.dothis <Parent p>

obj : <Child c1>
direct call :
Child.dothis <Child c1>
via obj.__class__.__dict__ :
Child.dothis <Child c1>

obj : <Child c2>
direct call :
dothat <Child c2>
via obj.__class__.__dict__ :
Child.dothis <Child c2>

obj : <OtherChild o1>
direct call :
parent.dothis <OtherChild o1>
via obj.__class__.__dict__ :
oops - key error: 'dothis'

obj : <OtherChild o2>
direct call :
dothat <OtherChild o2>
via obj.__class__.__dict__ :
oops - key error: 'dothis'

IOW, direct access to obj.__class__.__dict__ bypasses both inheritence 
and per-instance overriding.

> That worked but it seems like the above line would have to do lots more
> object look ups at runtime so I didn't think it would be very efficient.  I
> thought maybe I could add a caller method to the trend class and I came up
> with this;
> class trend:
>    ...
>    ...
>    ...
>    def caller(self, param, *args):
>       return self.__class__.__dict__[param](self, *args)
> This simplified the getValue function to this;
> def getValue(trend, param, per):
> 	return trend.caller(param, per)

Err... It actually means *more* lookup and function calls - and still 
fails to behave correctly wrt/ polymorphic dispatch.

More information about the Python-list mailing list