5

I have a dictionary of properties and their limiting values. For properties containing 'max', the limiting values is the maximum allowed. Similarly for minima.

I'm trying to create a dictionary of functions. Each function will check an input value against the limiting value for a property of the list.

limits = {'variable1 max': 100,
          'variable1 min': 10}

check_acceptable = {key: (lambda x: x < lim) if 'max' in key else 
                         (lambda x: x > lim) 
                    for key, lim in limits.items()}

# Now, I'd expect this to be True, since 90 < 100:
check_acceptable['variable1 max'](90)

Out: False

What happended here is that the lambda function got assigned the variable lim, and in the for loop, lim last value was 10, then my first lambda function is wrong.

How do I fix that?

--- edit - adding a more generic example of the same behaviour:

funcs = []
for i in range(5):
    funcs.append(lambda _: i)

for j in range(5):
    print(funcs[j](j), end='')

# Expected: 01234
Out: 44444
Raf
  • 1,628
  • 3
  • 21
  • 40

2 Answers2

3

The problem is that each lambda inside the dict comprehension is capturing only the last version of lim it finds, as per this post. We can get around this limitation by doing some currying on the lim parameter:

limits = {'variable1 max': 100,
          'variable1 min': 10}

check_acceptable = {key: (lambda lim: (lambda x: x < lim))(lim) if 'max' in key else
                         (lambda lim: (lambda x: x > lim))(lim)
                    for key, lim in limits.items()}

The above will allow each lambda to capture each one of the arguments it encounters; it works as expected:

check_acceptable['variable1 max'](90)
=> True
Óscar López
  • 232,561
  • 37
  • 312
  • 386
  • @Raf To pretty it up, use named functions instead of lambdas. This has the added benefit of being able to debug the code more easily since it won't be all jammed on a single line. – Code-Apprentice Mar 31 '20 at 14:21
3

The issue - The variable lim is not "stored" in the lambda, so both lambdas compare the argument with 10.

Possible solution:

import functools
def a_less_than_b(a,b):
  return a < b

def a_greater_than_b(a,b):
  return a > b

limits = {'variable1 max': 100,
          'variable1 min': 10}

check_acceptable = {key: functools.partial(a_less_than_b, b=lim) if 'max' in key else 
                         functools.partial(a_greater_than_b, b=lim)
                    for key, lim in limits.items()}

# Now, I'd expect this to be True, since 90 < 100:
check_acceptable['variable1 max'](90)
NadavS
  • 779
  • 3
  • 12