5

Possible Duplicate:
What is the Python equivalent of static variables inside a function?

I try to write a recursive function. It iterates over a vector and gives a value which depends upon current value and previous value. In Matlab I can declare a variable as persistent inside the function and the value is stored in memory after the function is called, so next call of the function start from the previous values.

This is what i started for simple moving average:

def AvgFilter(x):
    if not firstRun:  # checks if runs for first time, i.e. firstRun is empty
        k = 1        # setup initial variables if run for first time
        prevAvg = 0  # prevAvg - the average calculated during last call
        firstRun = 1 # only for initialisation
    alpha = (k-1)/k  
    avg = alpha * prevAvg + (1 - alpha)*x
    prevAvg = avg
    k = k + 1    
    return avg

I need the variables k prevAvg firstRun to be remembered between function calls. I read it can be done via decorator, I did try to implement it seting @counter before function but not sure how I should implement it. Is decorator the only way (did not find anything else)? and how to write counter function to store my variables? I bit worry that later on with more complicated recursions I will be completely lost with the idea of decorator.

Community
  • 1
  • 1
tomasz74
  • 16,031
  • 10
  • 37
  • 51
  • 1
    Have a look at: http://stackoverflow.com/questions/279561/what-is-the-python-equivalent-of-static-variables-inside-a-function – Xyand Oct 06 '12 at 22:11

3 Answers3

5

Sounds like a job for generators! Generators let you pretend you're just calculating the values one at a time in a loop, but it will actually pause execution and return a value when you call yield.

def AvgFilter():
    k = 1
    avg = 0
    while True:
        alpha = (k-1)/float(k)
        x = yield avg # yield/return the old average, and get the new input value
        avg = alpha * avg + (1 - alpha)*x
        k = k + 1

f = AvgFilter()
print f.next()
print f.send(1)
print f.send(2)
print f.send(20)
print f.send(20)

# 0
# 1.0
# 1.5
# 7.66666666667
# 10.75
Community
  • 1
  • 1
jtbandes
  • 115,675
  • 35
  • 233
  • 266
2

You could do the following:

  • Using an object and storing those variables in the object, reusing the same object between function calls.
  • Using global variables (strongly discouraged!)
  • Using a generator (see jtbandes' answer)

Here's an example:

class AvgFilter(object):
    def __init__(self):
        self.k = 1
        self.avg = 0

    def run(x):
        alpha = float(self.k-1) / self.k  
        self.avg = alpha * self.avg + (1 - alpha)*x
        self.k +=1
        return self.avg
Thomas Orozco
  • 53,284
  • 11
  • 113
  • 116
  • I could not get it working, as have error `AttributeError: 'AvgFilter' object has no attribute 'avg'`. And after correction had a problem with output as not as expected (int division in python), but the idea is what I needed – tomasz74 Oct 09 '12 at 18:04
  • @tomasz74 Yes you're indeed right I'll modify the bits that need it! – Thomas Orozco Oct 09 '12 at 19:54
2

Thanks to answers of @Thomas Orozco and @jtbandes After a few long days at last I get it working how I wanted. The code together with some testing is:

x = [10,2,30,4,50]
class avgFil2(object):
    def __init__(self):
        self.avg = 0
        self.k = 1.0
    def __call__(self, x):
        alpha = (self.k-1.0)/self.k  
        self.avg = alpha * self.avg + (1 - alpha)*x
        self.k +=1
       return self.avg

def simpleTest(x = x):     
    average = []
    avg = avgFil2()               # new variable and creation of instance (all initialisation)
    for i in range(len(x)):
        print 'input %f' % x[i] 
        print 'before change avg.avg=%f, k=%f' % (avg.avg, avg.k)   
        average.append(avg(x[i])) # class is called here, so all changes going on
        print 'after change avg.avg=%f, k=%f' % (avg.avg, avg.k)  
        print 'The output average is %f' % average[i]

simpleTest()

The code is really different from Matlab, but is nice. Important traps are to initialise k=1.0 to float not k=1 to int because alpha would be zero as division would evaluate to integer. Another thing, I had to initialise to 'self.k' not 'k' as it was giving the error otherwise. Generators probably work as well, but I like class solution.

tomasz74
  • 16,031
  • 10
  • 37
  • 51