4

I have this program:

funcs = { idx: lambda: print(idx) for idx in range(5) }

In essence, we have a dictionary of indices and functions:

{0: <function __main__.<dictcomp>.<lambda>()>,
 1: <function __main__.<dictcomp>.<lambda>()>,
 2: <function __main__.<dictcomp>.<lambda>()>,
 3: <function __main__.<dictcomp>.<lambda>()>,
 4: <function __main__.<dictcomp>.<lambda>()>}

However, the following function calls have the same result:

funcs[4]()
4
funcs[0]()
4

Why do I get 4 as output both times, even though I am using different inputs?

khelwood
  • 55,782
  • 14
  • 81
  • 108
MikiBelavista
  • 2,470
  • 10
  • 48
  • 70
  • 2
    You have 5 functions that all say "print the current value of `idx`". The current value of `idx` after the loop is 4. – khelwood Aug 22 '18 at 09:33
  • So it's a story about the local variable `idx`. When you call the functions (whichever) it is going to print the variable `idx` which is not the key, but the last value taken by the range. Now I don't know the correct syntax to print the key. – Mathieu Aug 22 '18 at 09:33
  • @khelwood I'm not sure that's the best dupe but I'm struggling to find a canonical. – roganjosh Aug 22 '18 at 09:34
  • You basically want: `{idx: lambda idx=idx: print(idx) for idx in range(5)}` because `idx` the print refers to in your code is the last bound value... (4 in this case)... the `idx=idx` binds the variable present at the time instead... Not sure what the best dupe is for this... – Jon Clements Aug 22 '18 at 09:38
  • Jon,now dict is ordered? – MikiBelavista Aug 22 '18 at 09:39
  • @MikiBelavista what difference does that make? – roganjosh Aug 22 '18 at 09:39
  • I thought indexing problem would be solved. – MikiBelavista Aug 22 '18 at 09:40
  • @MikiBelavista it's not anything to do with indexing, it's to do with late binding and parameter names. The lambdas all refer to the same value – roganjosh Aug 22 '18 at 09:41
  • Ok,I understand now. – MikiBelavista Aug 22 '18 at 09:42

1 Answers1

2

You are accessing the same object each time, since dict are not ordered, and accessing them by index does not do what you expect

funcs = {
        idx: lambda: print(idx, end=' ') for idx in range(5)
        }

for f in funcs:
    print(f, id(f))

print('#'*20)

for idx in range(5):
    print(id(funcs[idx]()))

0 1477866096
1 1477866128
2 1477866160
3 1477866192
4 1477866224
####################
4 1477430720
4 1477430720
4 1477430720
4 1477430720
4 1477430720

The previous explanation is wrong. What actually happens is this:

The lambdas were constructed using idx in range(5). When a lambda is called, no matter the caller idx parameter (i.e. it doesn't matter what idxis insidefuncs[idx]()) the lambda is using the idx from range(5). Since the range(5) now (at its end) points to 4, that is the idx each lambda uses, resulting in all of them printing 4.

The lambda are different objects, but they still all use the current inner idx.

Here is an example that helped me:

ls=[]
for i in range(2):
    ls.append(lambda : print(i))

for f in ls:
    print(id(f), end=' ')
    f()

for i in range(3,5):
    ls.append(lambda : print(i))


for f in ls:
    print(id(f), end=' ')
    f()

44467888608 1 # 1st lambda - uses the last idx (for range was 2)
44467888336 1 # 2nd lambda - uses the last idx (for range was 2)
44467888608 4 # 1st lambda - uses the current last idx (2nd for range was 5)
44467888336 4 # 2nd lambda - uses the current last idx (2nd for range was 5)
44467921240 4 # 3rd + 4th lambdas
44467921512 4
CIsForCookies
  • 12,097
  • 11
  • 59
  • 124