2

I want to create functions and add them to a list, reusing the same name every time.

def fconstruct():
    flist  = []
    for x in xrange(0,5):
        def kol():
            return x
        flist.append(kol)
        del kol #this doesn't fix the problem.
    return flist

k = fconstruct()

However, this fails, even if i delete the function every loop, and no matter which of the functions in k i call, the result is the the same: 4, because the newest definition of kol has changed all the previous ones. For a simple function such as this,

kol = lambda: x 

would work. However, i need to do this for a much more complex function

As solutions, i could store the function as a string in the list and use exec to call it.

I could generate disposable and random function names:

fname = '_'+str(random.random())
exec fname + ' = kol'
exec 'flist.append('+fname+')'

I could play around with this implementation of multiline lambdas: https://github.com/whaatt/Mu

None of these seem elegant, so what is the preferred way of doing this?

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
shimao
  • 312
  • 2
  • 6
  • 15
  • 4
    What are you trying to accomplish? Seems like an XY problem. – Russia Must Remove Putin Mar 20 '14 at 01:57
  • I want to use scipy.optimize.leastsq which asks for a function that returns multiple values (presumably one from each equation). I'm putting the functions into a list so i can call them and return the values separately. Now that you mention it, I guess I could just store the values rather than all the separate functions. Thanks! – shimao Mar 20 '14 at 02:06
  • possible duplicate of [Local variables in Python nested functions](http://stackoverflow.com/questions/12423614/local-variables-in-python-nested-functions) – Martijn Pieters Mar 20 '14 at 02:11
  • This has nothing to do with function names and everything with closures.. – Martijn Pieters Mar 20 '14 at 02:11

2 Answers2

3

You have to use another function that generates the function you want with the x parameter set. Here I use the kol_factory (see also the answer to Closures in Python):

def fconstruct():
    flist  = []
    # create a function g with the closure x
    def kol_factory(y):
        # y is local here
        def kol():
            # kol uses the y
            return y
        return kol
    for x in xrange(0,5):
        # we create a new g with x "burned-in"
        flist.append(kol_factory(x))
    return flist

for k in  fconstruct():
   print k()

You can define the factory function factory outside the fconstruct function:

def kol_factory(y):
    # y is local here
    def kol():
        # kol uses the y
        return y
    return kol

def fconstruct():
    flist  = []
    for x in xrange(0,5):
        # we create a new kol_factory with x "burned-in"
        flist.append(kol_factory(x))
    return flist

for k in  fconstruct():
   print k()
Community
  • 1
  • 1
Michael_Scharf
  • 33,154
  • 22
  • 74
  • 95
2

When you're defining kol, you're establishing a closure around x. In fact, each time through the loop you're getting a closure around the same variable.

So while you have 5 different functions all named kol, they all return the value of the same variable. When the loop is finished, that variable's value is 4, so each function returns 4.

Consider this:

def fconstruct():
    flist = []
    for x in range(5):
        def get_kol(y):
            def kol():
                return y
            return kol
        flist.append(get_kol(x))

    return flist

In this case, the function get_kol() returns a function who's return value is get_kol()'s argument.

The closure in kol() is now around y, which is local to the get_kol() function, not the loop. Each time get_kol() is called, a new local y is created, so each kol() gets a different variable closure.


An alternative way is to create a partial function with functools.partial. This accomplishes the same thing (creates a function which, when called executes another function with arguments), and is a lot more powerful

def f(a): # whatever arguments you like
    return a

# create a bunch of functions that return f(x)
flist = [functools.partial(f, x) for x in range(5)]

def g(a, b): 
    return a + b

# create a bunch of functions that take a single argument (b), but have a set to
# 0..4: 
flist = [functools.partial(g, x) for x in range(5)] 

some_g = flist[0]
some_g(1) # returns 0 + 1
Seth
  • 45,033
  • 10
  • 85
  • 120