11

I need to define a dictionary of python lambda functions through a for cycle. Each lambda function needs the value of the relative dictionary key to work, but my hope is to avoid to pass such key as an argument. Here is a dummy version of my problem.

Given

a = {'bar': 0, 'foo': 1}  # a reference dictionary 

dic1 = {'bar': lambda x: x['bar'], 'foo': lambda x: x['foo']}
dic2 = {key: lambda x: x[key] for key in a}

I expect dic1 and dic2 to do the same thing, however only dic1 behaves as I would want to. In particular, the result of

print(dic1['bar'](a), dic1['foo'](a))
print(dic2['bar'](a), dic2['foo'](a))

is

0 1
1 1

instead of

0 1
0 1
ruggero.devita
  • 125
  • 1
  • 7
  • 1
    This is an interesting problem with a beautiful solution by @jdehesa, but still I must ask what's possibly the use for that? I mean, is a big workaround for simply doing...... `a['bar']`... – Tomerikoo Jul 12 '19 at 14:36
  • 1
    @Tomerikoo I agree that this dummy version of the problem might seem useless. In my case the lambda function should act in a non-trivial way based on the their key. – ruggero.devita Jul 12 '19 at 15:23

2 Answers2

11

The reason it doesn't work has to do with late-binding closures in Python.

Python’s closures are late binding. This means that the values of variables used in closures are looked up at the time the inner function is called.

Adapting the source explanation, whenever the return function is called, the value of key is looked up in the surrounding scope at call time. By then, the loop has completed and key is left with its final value of 'foo'.

You can resolve this by creating a closure that binds immediately to its arguments by using a default arg like so:

a = {'bar': 0, 'foo': 1}  # a reference dictionary 

dic1 = {'bar': lambda x: x['bar'], 'foo': lambda x: x['foo']}
dic2 = {key: lambda x, key=key: x[key] for key in a}

print(dic1['bar'](a), dic1['foo'](a))
print(dic2['bar'](a), dic2['foo'](a))

Result:

0 1
0 1
TomNash
  • 3,147
  • 2
  • 21
  • 57
4

One simple way to do that is with operator.itemgetter:

from operator import itemgetter

a = {'bar': 0, 'foo': 1}
dic1 = {key: itemgetter(key) for key in a}
print(dic1['bar'](a), dic1['foo'](a))
# 0 1

Alternatively, you need to bind each value of key to each dictionary value, typically you do that with something like this:

a = {'bar': 0, 'foo': 1}
dic1 = {key: (lambda key: lambda x: x[key])(key) for key in a}
print(dic1['bar'](a), dic1['foo'](a))
# 0 1
jdehesa
  • 58,456
  • 7
  • 77
  • 121
  • 1
    I appreciate your answer being correct but do you know why the above doesn't work? – Akaisteph7 Jul 12 '19 at 14:36
  • See my answer for explanation – TomNash Jul 12 '19 at 14:38
  • 1
    @Akaisteph7 In your code, all the lambdas are associated to the comprehension variable `key`, not to the value of the variable on each iteration, so they all end up using the same final value that the variable gets. I added a common way in which that can be avoided, which is passing the variable as an argument to an external lambda in turn. – jdehesa Jul 12 '19 at 14:39
  • @jdehesa It's not my question but thanks for adding some explanation – Akaisteph7 Jul 12 '19 at 14:41