2

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.

2
  • Each iteration of the loop doesn't create a new scope. So all the functions share the same i variable. Commented Aug 13, 2022 at 4:21
  • There is a second problem, the variable is at the class level you need to initialize actions in __init__ or it will be done at the class level, which is shared for all Test, not for foo which is a single instance of Test. Commented Aug 13, 2022 at 4:22

3 Answers 3

2

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
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, this seems to be the fastest solution
2

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

1 Comment

actually i think you are bit mistaken, because the function _ isn't overridden and you can check this by inspecting the ids of the functions references in the actions list . saving reference of the function prevents the default behavior form happening thus a new function is defined (not sure why this happen)
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.