Create/Initialize a series of functions from data

Question:

I’m trying to initialize a collection of function calls based on input data, but not sure how to do this. The ideas is like below. I expect the local_v will stay unchanged once being defined. Can anyone shed some light how to deal with this or where to start with?

ls = [1,2]
for v in ls:
    def fn():
        local_v = v
        print(local_v)
    locals()[f'print{v}'] = fn

print1() # output: 2 expect to be 1
print2() # output: 2

Asked By: xappppp

||

Answers:

If you want v to be tied to the local context, you’ll have to pass it inside the function. You will also need to return a callable to print rather than using the print function directly, or else you will find that print1 and print2 will be None. And at this point, there is no need to redefine fnat each iteration of the loop. Something like that will work:

ls = [1, 2]

def fn(v):
        return lambda: print(v)

for v in ls:
    locals()[f'print{v}'] = fn(v)

print1() # output: 1
print2() # output: 2

Just to try to be clearer: invoking print1() does not call fn(1), it calls the lambda that returned by fn(1).

Answered By: ye olde noobe

Better to store the functions in a dictionary rather than changing locals, which is likely to get confusing. You could use a nested function like this:

def function_maker(v):
    def fn():
        print(v)
    return fn

print_functions = {v: function_maker(v) for v in [1, 2]}
print_functions[1]()
print_functions[2]()

Alternatively use functools.partial

from functools import partial

def fn(v):
    print(v)
    
print_functions = {v: partial(fn, v) for v in [1, 2]}
Answered By: Stuart

What you’re trying to create is called a "closure", which is a special type of function that retains the values of its free variables. A closure is created automatically when free variables go out of scope. In your example, since you never leave the scope in which v is defined, your fn objects are ordinary functions, not closures (you can verify that by inspecting their __closure__ property).

To actually create a closure you need to construct a new scope, inject your loop variable into that scope and then leave that scope. In python, a new scope is created for defs, lambdas, comprehensions and classes. You can use any of these, for example, given

ls = [1, 2, 3, 4, 5]
fns = []

you can use a helper function:

def make_fn(arg):
    def f():
        print(arg)
    return f    

for item in ls:
    fns.append(make_fn(item))

for fn in fns:
    fn()

or a lambda:

for item in ls:
    fn = (lambda x: lambda: print(x))(item)
    fns.append(fn)

or a (pointless) list comprehension:

for item in ls:
    fn = [
        lambda: print(x)
        for x in [item]
    ][0]
    fns.append(fn)

As a side note, modifying locals is not a good idea.

Answered By: gog
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.