Jonathan Moo
Jonathan Moo

Reputation: 3277

Python lambda using for loop to dynamically add parameters

I have the following lambda expression:

constraints = lambda values: (
                              values['volume'] < 10, 
                              values['mass'] < 100,
                              values['nItems'] <= 10, 
                              values['nItems'] >= 5
                              )

I have a list that contains brands (dynamically populated), such as

brands = ['nestle', 'gatorate'] 

I want to be able to put in additional expressions dynamically such that:

constraints = lambda values: (
                              values['volume'] < 10, 
                              values['mass'] < 100,
                              values['nItems'] <= 10, 
                              values['nItems'] >= 5,

                              #dynamically adds these parameters
                              values['nestle'] < 5,
                              values['gatorate'] < 5
                              )

How can I do that using a for loop that iterates over brands and dynamically populates additional parameters into constraints from the brands list?

The answer can be in any form provided that I can get the desired constraints function.

Upvotes: 4

Views: 2898

Answers (2)

pacholik
pacholik

Reputation: 8982

First, don't use lambda where you don't need it

PEP8

Always use a def statement instead of an assignment statement that binds a lambda expression directly to an identifier.

Yes:

def f(x): return 2*x

No:

f = lambda x: 2*x

The first form means that the name of the resulting function object is specifically 'f' instead of the generic ''. This is more useful for tracebacks and string representations in general. The use of the assignment statement eliminates the sole benefit a lambda expression can offer over an explicit def statement (i.e. that it can be embedded inside a larger expression)

Make a list of functions

Now to the solution. Make a list of functions that will check your values.

Updating the list will mean updating the constraints.

def _constrain_main(values):
    """Static constraints"""
    return (values['volume'] < 10 and values['mass'] < 100 and
            values['nItems'] <= 10 and values['nItems'] >= 5)


def constrain(values):
    """Check for all constraints"""
    return all(c(values) for c in constraints)


# list of constraints
constraints = [_constrain_main]

# example
values = {
    'volume': 8,
    'mass': 80,
    'nItems': 8,
    'nestle': 6,
    'gatorate': 6,
}

print(constrain(values))    # True
# now append other lambdas (lambda is desired here)
constraints.append(lambda values: values['nestle'] < 5)
constraints.append(lambda values: values['gatorate'] < 5)
print(constrain(values))    # False

Upvotes: 4

Imanol Luengo
Imanol Luengo

Reputation: 15909

You can replace the lambda by a proper function and get some speedups. There is nothing that prevents your lambda from being a function.

Additionally, you cannot add dynamic expressions to a function. The closest approach would be to pass the keys and thresholds as parameters to your function:

def comparevalues(values, keys, thresholds, cmpfuncs):
    return [f(values[k], t) for f, k, t in zip(cmpfuncs, keys, thresholds)]

Here values is a dictionary, keys is a list of keys used in each "rule", threshold is the threshold applied to each "rule" and cmpfuncs are comparison functions used in each rule (e.g. <, <=, >...), you can create your own operators if they are complex, for simple thresholds you can use python's operators:

>>> import operator as op
>>> (5 > 6) == op.gt(5, 6) # Same operation, explicit vs function call

All 3 lists keys, thresholds and cmpfuncs should have the same length, as each of the elements forms a rule triplet.

As an example:

>>> import operator as op
>>> dictionary = {'a': 5, 'b': 10}

>>> keys = ['a', 'b', 'a']
>>> thresholds = [1, 4, 5]
>>> cmpfuncs = [op.gt, op.gt, op.lt]

>>> comparevalues(dictionary, keys, thresholds, cmpfuncs)
[True, True, False]

The above is creating 3 rules: (1) 'a' > 1, (2) 'b' > 4 and (3) 'a' < 5. Note that op.gt = > and op.lt = <.

If you want to dynamically add more operators, you just have to maintain a global lists of keys, threshodlsandcmpfuncs`, update them with new rules, and pass them with every function call.


Now that the approach is explained, find bellow a way of making the same problem more portable:

def comparerules(values, rules):
    return [f(values[k], t) for f, k, t in rules]

The approach is the same as above, but in this case rules is a list of triplets, where each triplet is a tuple of the form (cmpfunc, key, threshold).

To use this function with equivalent rules to the above example:

>>> values = {'a': 5, 'b': 10}
>>> rules = [(op.gt, 'a', 1), (op.gt, 'b', 4), (op.lt, 'a', 5)]

>>> comparerules(values, rules)
[True, True, False]

This way, you only have to maintain a global rules array, edit it to append/remove new rules, and send it in every function call:

>>> rules += [(op.gt, 'b', 10)] # values['b'] > 10
>>> comparerules(values, rules)
[True, True, False, False]

And last, to check whether values satisfies all the conditions, just use python's all:

>>> all(comparerules(values, rules))
False

Upvotes: 2

Related Questions