Azmy Rajab
Azmy Rajab

Reputation: 444

How to create an array of functions which partly depend on outside parameters? (Python)

I am interested in creating a list / array of functions "G" consisting of many small functions "g". This essentially should correspond to a series of functions 'evolving' in time.

Each "g" takes-in two variables and returns the product of these variables with an outside global variable indexed at the same time-step.

Assume obs_mat (T x 1) is a pre-defined global array, and t corresponds to the time-steps

G = []

for t in range(T):
    # tried declaring obs here too.               
    def g(current_state, observation_noise):
        obs = obs_mat[t] 
        return current_state * observation_noise * obs 

G.append(g) 

Unfortunately when I test the resultant functions, they do not seem to pick up on the difference in the obs time-varying constant i.e. (Got G[0](100,100) same as G[5](100,100)). I tried playing around with the scope of obs but without much luck. Would anyone be able to help guide me in the right direction?

Upvotes: 4

Views: 410

Answers (3)

Sean Vieira
Sean Vieira

Reputation: 159885

The issue (assuming that your G.append call is mis-indented) is simply that the name t is mutated when you loop over the iterator returned by range(T). Since every function g you create stores returns the same name t, they wind up all returning the same value, T - 1. The fix is to de-reference the name (the simplest way to do this is by sending t into your function as a default value for an argument in g's argument list):

G = []

for t in range(T):
    def g(current_state, observation_noise, t_kw=t):
        obs = obs_mat[t_kw] 
        return current_state * observation_noise * obs

    G.append(g)

This works because it creates another name that points at the value that t references during that iteration of the loop (you could still use t rather than t_kw and it would still just work because tg is bound to the value that tf is bound to - the value never changes, but tf is bound to another value on the next iteration, while tg still points at the "original" value.

Upvotes: 2

Blckknght
Blckknght

Reputation: 104712

This is a common "gotcha" to referencing variables from an outer scope when in an inner function. The outer variable is looked up when the inner function is run, not when the inner function is defined (so all versions of the function see the variable's last value). For each function to see a different value, you either need to make sure they're looking in separate namespaces, or you need to bind the value to a default parameter of the inner function.

Here's an approach that uses an extra namespace:

def make_func(x):
    def func(a, b):
        return a*b*x
    return func

list_of_funcs = [make_func(i) for i in range(10)]

Each inner function func has access to the x parameter in the enclosing make_func function. Since they're all created by separate calls to make_func, they each see separate namespaces with different x values.

Here's the other approach that uses a default argument (with functions created by a lambda expression):

list_of_funcs = [lambda a, b, x=i: a*b*x for i in range(10)]

In this version, the i variable from the list comprehension is bound to the default value of the x parameter in the lambda expression. This binding means that the functions wont care about the value of i changing later on. The downside to this solution is that any code that accidentally calls one of the functions with three arguments instead of two may work without an exception (perhaps with odd results).

Upvotes: 4

erewok
erewok

Reputation: 7835

The problem you are running into is one of scoping. Function bodies aren't evaluated until the fuction is actually called, so the functions you have there will use whatever is the current value of the variable within their scope at time of evaluation (which means they'll have the same t if you call them all after the for-loop has ended)

In order to see the value that you would like, you'd need to immediately call the function and save the result.

I'm not really sure why you're using an array of functions. Perhaps what you're trying to do is map a partial function across the time series, something like the following?

from functools import partial

def g(current_state, observation_noise, t):
    obs = obs_mat[t]
    return current_state * observation_noise * obs

g_maker = partial(g, current, observation)
results = list(map(g_maker, range(T)))

What's happening here is that partial creates a partially-applied function, which is merely waiting for its final value to be evaluated. That final value is dynamic (but the first two are fixed in this example), so mapping that partially-applied function over a range of values gets you answers for each value.

Honestly, this is a guess because it's hard to see what else you are trying to do with this data and it's hard to see what you're trying to achieve with the array of functions (and there are certainly other ways to do this).

Upvotes: 2

Related Questions