0

Question

In the code below I have defined a function func which takes in one input and simply prints it. Then I create a list, data, containing two empty dict objects, before populating them with a random number and a lambda pointing to func with the random number as input. Next, I print the list using json.dumps to show the content. Then I iterate over the list and call the lambda function contained in each dict - The output shows that func is called with the correct inputs. Lastly, I get the dicts in the list by indexing it, and then calling the lambda function as before - Now we see that func is no longer called with the correct inputs.

I'm wondering what is happening here?

>>> from random import random
>>> import json
>>> def func(x):
...     print('x:', x)
    
>>> data = [dict(), dict()]
>>> for d in data:
...     d['x'] = random()
...     d['f'] = lambda: func(d['x'])

>>> print(json.dumps(data, indent=4, default=str))
[
    {
        "x": 0.6519067978068578,
        "f": "<function <lambda> at 0x00000238E2672F78>"
    },
    {
        "x": 0.48881125686952465,
        "f": "<function <lambda> at 0x00000238E2678048>"
    }
]
>>> for d in data:
...     d['f']()
    
x: 0.6519067978068578
x: 0.48881125686952465
>>> data[0]['f']()
x: 0.48881125686952465
>>> data[1]['f']()
x: 0.48881125686952465

Solution 1

from random import random


def func(x):
    print('x:', x)


def make_lambda(x):
    return lambda: func(x)


data = [dict(), dict()]
for d in data:
    d['x'] = random()
    d['f'] = make_lambda(d['x'])

Solution 2

from random import random


def func(x):
    print('x:', x)


data = [dict(), dict()]
for d in data:
    d['x'] = random()
    d['f'] = lambda x=d['x']: func(x)
wjandrea
  • 28,235
  • 9
  • 60
  • 81
rindis
  • 869
  • 11
  • 25
  • The function you create has a free variable, `lambda: func(d['x'])`, namely `d` (and `func`). But `d` refers to the variable in the scope where the function was defined, i.e. python uses lexical scoping. In this case, the global `d`, which you keep changing. – juanpa.arrivillaga Apr 02 '21 at 17:12
  • Another duplicate: [Python lambda doesn't remember argument in for loop](https://stackoverflow.com/q/11723217/4518341) – wjandrea Apr 02 '21 at 17:15
  • Please don't put solutions in your question. That's what answers are for. However, this question has already been asked and answered, so no answers can be submitted ¯\\_(ツ)\_/¯ So you might as well leave them for future reference. – wjandrea Apr 02 '21 at 17:18
  • Thanks for all the feedback! This problem has been solved by the duplicates, but I've updated this question with two possible solutions. – rindis Apr 02 '21 at 17:18
  • 1
    @wjandrea: I figured it would be nice to have the solution available for anyone who might land here, without having to navigate through to duplicates. – rindis Apr 02 '21 at 17:20
  • I feel like this is an issue that is potentially difficult to find, because as long as you are iterating over the list, the lambdas are executed correctly, you only get the wrong behavior when retrieving the lambda through indexing. It seems to me that this is not extensively discussed in the other questions, which could be an argument against closing the question IMO. – rindis Apr 02 '21 at 17:23
  • @Martin It's the exact same issue at its core (late binding), the only difference is that you're reusing the name `d` as the loop variable. And yes, by all means, leave the solutions in. – wjandrea Apr 02 '21 at 19:00

0 Answers0