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
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 fn
at 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)
.
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]}
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 def
s, lambda
s, 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.
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
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 fn
at 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)
.
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]}
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 def
s, lambda
s, 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.