How do I hard-code variables in a dynamically created function in python?
Question:
I have the following python3 code
class Test:
pos = [0,0]
actions = []
def bar(self, target):
for i in target:
def _():
print(i,end="")
self.actions.append(_)
foo = Test()
foo.bar("abcd")
for i in foo.actions:
i()
Which is meant to output:
abcd
but instead it outputs:
dddd
I’m pretty sure the function is using the value of i when executing (the last value i had) and not i’s value the function _ is declared, which is what I want.
Answers:
The general solution to this is to store the value as a default parameter value, like this:
class Test:
pos = [0,0]
actions = []
def bar(self, target):
for i in target:
def _(i=i): # This is the only changed line
print(i,end="")
self.actions.append(_)
foo = Test()
foo.bar("abcd")
for i in foo.actions:
i()
>>> abcd
Let’s output some things:
class Test:
pos = [0, 0]
actions = []
def bar(self, target):
for i in target:
print(f"i={i} id={id(i)}")
def _():
print(f"_i={i} _id={id(i)}")
print(i, end="")
self.actions.append(_)
Output:
i=a id=2590411675120
i=b id=2590411458416
i=c id=2590411377456
i=d id=2590411377200
_i=d _id=2590411377200
d_i=d _id=2590411377200
d_i=d _id=2590411377200
d_i=d _id=2590411377200
See, the i
in def _
overrides every time for loop iterates and eventually last value is what you get.
How to solve this? Pass i
as an argument:
from functools import partial
class Test:
pos = [0, 0]
actions = []
def bar(self, target):
for i in target:
print(f"i={i} id={id(i)}")
def _(i):
print(f"_i={i} _id={id(i)}")
print(i, end="")
self.actions.append(partial(_, i))
Output:
i=a id=2618064721392
i=b id=2618064504688
i=c id=2618064423728
i=d id=2618064423472
_i=a _id=2618064721392
a_i=b _id=2618064504688
b_i=c _id=2618064423728
Let’s remove print statements now:
from functools import partial
class Test:
pos = [0, 0]
actions = []
def bar(self, target):
for i in target:
def _(i):
print(i, end="")
self.actions.append(partial(_, i))
foo = Test()
foo.bar("abcd")
for i in foo.actions:
i()
# Output: abcd
the reason why you are getting the last value of i
is that the function print takes reference to i
and not literal value of it and since it’s in a loop you will get the last value i
had. a workaround to this problem would be as Mandera said to set function default parameter and this actually works because the value of i
is stored in the function’s attribute __defaults__
, which is responsible of storing default parameters values.
So the final code would be :
class Test:
pos = [0,0]
actions = []
def bar(self, target):
for i in target:
def foo(p=i):print(p,end="")
self.actions.append(foo)
if __name__=="__main__":
foo = Test()
foo.bar("abcd")
for i in foo.actions:
i()
Note
the function foo
isn’t overridden
I have the following python3 code
class Test:
pos = [0,0]
actions = []
def bar(self, target):
for i in target:
def _():
print(i,end="")
self.actions.append(_)
foo = Test()
foo.bar("abcd")
for i in foo.actions:
i()
Which is meant to output:
abcd
but instead it outputs:
dddd
I’m pretty sure the function is using the value of i when executing (the last value i had) and not i’s value the function _ is declared, which is what I want.
The general solution to this is to store the value as a default parameter value, like this:
class Test:
pos = [0,0]
actions = []
def bar(self, target):
for i in target:
def _(i=i): # This is the only changed line
print(i,end="")
self.actions.append(_)
foo = Test()
foo.bar("abcd")
for i in foo.actions:
i()
>>> abcd
Let’s output some things:
class Test:
pos = [0, 0]
actions = []
def bar(self, target):
for i in target:
print(f"i={i} id={id(i)}")
def _():
print(f"_i={i} _id={id(i)}")
print(i, end="")
self.actions.append(_)
Output:
i=a id=2590411675120
i=b id=2590411458416
i=c id=2590411377456
i=d id=2590411377200
_i=d _id=2590411377200
d_i=d _id=2590411377200
d_i=d _id=2590411377200
d_i=d _id=2590411377200
See, the i
in def _
overrides every time for loop iterates and eventually last value is what you get.
How to solve this? Pass i
as an argument:
from functools import partial
class Test:
pos = [0, 0]
actions = []
def bar(self, target):
for i in target:
print(f"i={i} id={id(i)}")
def _(i):
print(f"_i={i} _id={id(i)}")
print(i, end="")
self.actions.append(partial(_, i))
Output:
i=a id=2618064721392
i=b id=2618064504688
i=c id=2618064423728
i=d id=2618064423472
_i=a _id=2618064721392
a_i=b _id=2618064504688
b_i=c _id=2618064423728
Let’s remove print statements now:
from functools import partial
class Test:
pos = [0, 0]
actions = []
def bar(self, target):
for i in target:
def _(i):
print(i, end="")
self.actions.append(partial(_, i))
foo = Test()
foo.bar("abcd")
for i in foo.actions:
i()
# Output: abcd
the reason why you are getting the last value of i
is that the function print takes reference to i
and not literal value of it and since it’s in a loop you will get the last value i
had. a workaround to this problem would be as Mandera said to set function default parameter and this actually works because the value of i
is stored in the function’s attribute __defaults__
, which is responsible of storing default parameters values.
So the final code would be :
class Test:
pos = [0,0]
actions = []
def bar(self, target):
for i in target:
def foo(p=i):print(p,end="")
self.actions.append(foo)
if __name__=="__main__":
foo = Test()
foo.bar("abcd")
for i in foo.actions:
i()
Note
the function foo
isn’t overridden