3

I have written a function for generating a dictionnary of lambda constant functions (it's part of a more complex function but I have simplified it to the code below):

def function(a):
    _interpolators = {}
    for key in a.keys():
        _interpolators[key] = lambda t: a[key]

    def _interpolate(t):
        return {key:f(t) for (key,f) in _interpolators.items()}
    return _interpolate

if __name__ == '__main__':    
    d = {"foo": 2, "bar":4}
    f = function(d)
    print(f(0)["bar"])

Using Python 3.5.1 on OSX El Capitan, the output of program is random. It can be 2or 4 while I would expect to have only 4. I don't quite understand this behaviour.

Note that the "bug" does not seem to occur with python 2.7.10 and I always get 4 when I run the program several times.

Is it some kind of bug in python 3.5.1 or did I miss something obvious ?

Mike Müller
  • 71,943
  • 15
  • 139
  • 140
Nicolas Rougier
  • 608
  • 6
  • 14

2 Answers2

1

key in the lambda is taken from the scope of the function function. So it has the value of the last element in a.keys(). The order of keys in a dictionary is arbitrary and changes in Python 3 for each run, due to security reasons. Make key a local variable of the lambda:

def function(a):
    _interpolators = {key: (lambda t, k=key: a[k]) for key in a}

    def _interpolate(t):
        return {key:f(t) for (key,f) in _interpolators.items()}
    return _interpolate
PM 2Ring
  • 50,023
  • 5
  • 64
  • 150
Daniel
  • 39,063
  • 4
  • 50
  • 76
  • @NicolasRougier: FWIW, you can read more about this hash randomization at [hash function in Python 3.3 returns different results between sessions](http://stackoverflow.com/q/27522626/4014959) – PM 2Ring Jan 01 '16 at 09:23
  • @Kasramvd: Daniel has explained the main problem with the OP's code in his first two sentences. However, the results of the OP's buggy code vary from run to run due to the random `PYTHONHASHSEED` issue. – PM 2Ring Jan 01 '16 at 09:27
  • @PM2Ring Actually if I was to say better, this doesn't explained the main problem and the items order is the last and least problem. it's all about functions namespaces. – kasravnd Jan 01 '16 at 09:31
1

The point is that in your lambda function which you have defined it as following :

lambda t: a[key]

You didn't used variable t and tried to get the a's value by passing the variable key as the key to your dictionary. And at the next time which you attempt to use this function in you enclosing function within dictionary comprehension:

{key:f(t) for (key,f) in _interpolators.items()}

Each time that you try to get the f(t), since the existence of t is redundant here python tries to get the a's value by passing the variable key to it, and it has nothing to do with t.

And the most important point is, that you expect to python override the key variable because you are using key in your dictionary comprehension.

And since lamda object is a function in python and has its own local namespace, each time that it tries to return the a[key] it cant access the iteration variable key inside the comprehension so due to LEGB manner after it (lambda) looks for key in Local namespace and then Enclosing and find nothing, it looks for it in Global scope.

And since the global key is the last iteration variable in following part :

for key in a.keys():
        _interpolators[key] = lambda t: a[key]

The lambda function will use it as the key. And since in python 3.X the order of dict.keys() is randomly, the result would be randomly as well. While it's not like so in python 2.

And for getting ride of this problem you can simply change your lambda as following:

lambda t: a[t]

And in your code :

def function(a):
    _interpolators = {}
    for key in a.keys():
        _interpolators[key] = lambda t: a[t]

    def _interpolate():
        return {k:f(k) for k,f in _interpolators.items()}
    return _interpolate

if __name__ == '__main__': 
    d = {"foo": 2, "bar":4}
    f = function(d)
    print(f()["bar"])
kasravnd
  • 94,640
  • 16
  • 137
  • 166
  • @NicolasRougier Welcome, So you can tell this to community by accepting the answer. – kasravnd Jan 01 '16 at 12:53
  • Your function does something else, than the OP's intention was. Your `_interpolators` dictionary doesn't make any sense. Look at my solution, to see, how to do it correctly. – Daniel Jan 01 '16 at 13:13
  • @Daniel This is not OP's intention, it's just an example which OP tried to explain the main problem, and what I said in my answer was a complete describe of the problem which OP is faced with, and the last suggestion was across this aim. – kasravnd Jan 01 '16 at 13:30
  • @Daniel Actually,I didn't care about this imaginary points which I gain with up vote and accepts, the thing is that the most complete answer should be accept by OP. And that's because the future readers care more about accepted answer. – kasravnd Jan 01 '16 at 13:36
  • @Kasramvd: the OP has some functions `f[key](t)`. This is clearly something else than `f(k)`. An explanation may be as nice as possible, if it does not address the problem, its worthless. The `t` in OP's lambdas may be unused in his simple example, but not in his real problem. – Daniel Jan 01 '16 at 14:09