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 ===defmake_standard_deviator():

'''Generate functions that return running standard deviations

Uses function attributes

'''

defsd(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

ifsd.N<2:return0.0

returnsqrt((sd.N*sd.X2 - sd.X**2)

/(sd.N*(sd.N-1)))

# Initialize attributes

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

returnsdclassMake_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

ifself.N < color="#804040">return0.0

returnsqrt((self.N*self.X2 - self.X**2)

/(self.N*(self.N-1)))if__name__ == '__main__':

import timeit

timeit.Timer('[sd(x) for x in xrange(100000)][-1]',

"from fn_attributes import *; sd = make_standard_deviator()").timeit(number=5)

)

timeit.Timer('[sd(x) for x in xrange(100000)][-1]',

"from fn_attributes import *; sd = Make_standard_deviator()").timeit(number=5)

)

When viewing your article, in the class' __call__ there is something weird:

ReplyDeleteif self.N < color="#804040">return 0.0

Seems to be something wrong with the syntax highlighter, just wanted to report.

In modern Pythons, you can use `nonlocal` to modify closed-over variables.

ReplyDeleteTrue, but in this case we are accessing a function attribute.

Delete