Sunday, May 21, 2006

Function Attributes assigned by decorator

It appears to be straight forward to initialize function attributes by a decorator (the setup function).
The new function factory looks a lot like the class based version of my previous post, and performs similarly.


def make_standard_deviator2():
'''Generate functions that return running standard deviations
Uses function attributes applied by decorator
'''

def setup(func):
func.N, func.X, func.X2 = [0.0]*3
return func

@setup
def sd(x):
'''returns standard deviation.
Uses function attributes holding running sums
'''
from math import sqrt

sd.N += 1 # Num values
sd.X += x # sum values
sd.X2 += x**2 # sum squares

if sd.N<2: return 0.0
return sqrt((sd.N*sd.X2 - sd.X**2)
/(sd.N*(sd.N-1)))

return sd

Python function attributes

Prompted by a presentation were someone said Python doesn't have full closures (noodle.odp), I looked at their example given and thought first that I could do that with funtion attributes, and then it hit me: I know of Pythons function attributes but had never used them.

I wrote a function generator that when called, returns a function that when it is called with successive numbers calculates and returns their standard deviation so far. The returned function stores accumulated data between calls as attributes.

The more usual Python way of doing this is to use a Clss instance. I created a Class based version too for comparison.

In speed terms, the Class based solution is only, (but consistently), just less than two percent faster than the function generator based solution. In terms of maintainability though both are readable, I expect most people to be trained in the Class based solution and so be more comfortable with it.
The function generator solution has the initializer section after the inner function definition which might be a minus, I wonder if a decorator could put the initializer 'up-front'.


#=== file: fn_attributes.py ===

def make_standard_deviator():
'''Generate functions that return running standard deviations
Uses function attributes
'''
def sd(x):
'''returns standard deviation.
Uses function attributes holding running sums
'''
from math import sqrt

sd.N += 1 # Num values
sd.X += x # sum values
sd.X2 += x**2 # sum squares

if sd.N<2: return 0.0
return sqrt((sd.N*sd.X2 - sd.X**2)
/(sd.N*(sd.N-1)))

# Initialize attributes
sd.N, sd.X, sd.X2 = [0.0]*3

return sd

class Make_standard_deviator(object):
'''Return running standard deviations
when instance called as a function
'''
def __init__(self):
self.N, self.X, self.X2 = [0.0]*3

def __call__(self, x):
'''Returns standard deviation.
Uses instance attributes holding running sums
'''
from math import sqrt

self.N += 1 # Num values
self.X += x # sum values
self.X2 += x**2 # sum squares

if self.N < color="#804040">return 0.0
return sqrt((self.N*self.X2 - self.X**2)
/(self.N*(self.N-1)))

if __name__ == '__main__':
import timeit

print "function:",(
timeit.Timer('[sd(x) for x in xrange(100000)][-1]',
"from fn_attributes import *; sd = make_standard_deviator()").timeit(number=5)
)

print " Class:",(
timeit.Timer('[sd(x) for x in xrange(100000)][-1]',
"from fn_attributes import *; sd = Make_standard_deviator()").timeit(number=5)
)