2

I'm trying to understand what's different about the following dictionaries, as they produce different results when passed as arguments into a third-party library's function.

x = list(range(50))
y = list(range(50))

vars = [x, y]

d = [{'func': lambda z: vars[i]} for i in range(len(vars))]
d2 = list({'func': lambda z: vars[i]} for i in range(len(vars)))
d3 = [{'func': lambda z: vars[0]}, {'func': lambda z: vars[1]}]

print(d == d2)  # False
print(d == d3)  # False
print(d2 == d3)  # False

From my understanding, all three dictionaries should be identical but inspecting the variables reveals different function types for each:

d -> function <listcomp>.<lambda>
d2 -> function <genexpr>.<lambda>
d3 -> function <lambda>

As far as my use-case, only the d3 implementation works (meaning I can't take a more dynamic approach without doing something horrifying with exec). Can someone help me understand the difference between these three types of lambdas?

Edit

After everyone's help understanding scoping better, I was able to get my code to run by including the variable as a keyword arg in the function:

d = [{'func': lambda x, z=vars[i]: z * x} for i in range(len(vars))]

As suggested by the FAQ page: https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result

rgk
  • 985
  • 8
  • 16
  • 3
    This is also covered in the official Python FAQ: https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result – Daniel Roseman Mar 14 '19 at 21:22
  • This is how lexical scoping and closures work. Read the linked duplicte. Finally, you shouldn't expect them to be equal, even if the functions were equivalent, because `==` works by identity with functions to begin with – juanpa.arrivillaga Mar 14 '19 at 21:23
  • 1
    Those aren't function signatures; they are just automatically generated names for the resulting `function` objects. – chepner Mar 14 '19 at 21:26
  • @DanielRoseman Wanted to thank you separately, definitely need to read that whole thing now :) – rgk Mar 15 '19 at 13:45

1 Answers1

4

Distinct lambdas don't compare equal to each other, not even if they do the same thing. Therefore, objects containing those lambdas will also compare unequal.

>>> f1 = lambda z: vars[i]
>>> f2 = lambda z: vars[i]
>>> f1 == f2
False

As to why the first two versions don't work, it's because when i is captured it's the variable i, not its current value. The lambdas aren't tied to a frozen value of i during the current iteration but rather the variable itself, which changes in successive loop iterations.

See also:

John Kugelman
  • 349,597
  • 67
  • 533
  • 578