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.

Asked By: throawaway account

||

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
Answered By: Mandera

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
Answered By: Daniel

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

Answered By: med benzekri
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.