Supply condition in function call

Peter Otten __peter__ at web.de
Thu Mar 26 05:03:27 EDT 2015


Cameron Simpson wrote:

> On 26Mar2015 07:27, Manuel Graune <manuel.graune at koeln.de> wrote:
>>Gary Herron <gherron at digipen.edu> writes:
>>> On 03/25/2015 10:29 AM, Manuel Graune wrote:
>>>> def test1(a, b, condition="True"):
>>>>      for i,j in zip(a,b):
>>>>          c=i+j
>>>>          if eval(condition):
>>>>             print("Foo")
>>>>
>>>> test1([0,1,2,3],[1,2,3,4],"i+j >4")
>>>> print("Bar")
>>>> test1([0,1,2,3],[1,2,3,4],"c >4")
>>>> print("Bar")
>>>> test1([0,1,2,3],[1,2,3,4],"a[i] >2")
>>>
>>> This is nicely done with lambda expressions:
>>>
>>> To pass in a condition as a function:
>>>    test1([0,1,2,3],[1,2,3,4], lambda i,j: i+j<4)
>>>
>>> To check the condition in the function:
>>>     if condition(i,j):
>>
>>This seems to be the right direction and a good solution for simple
>>cases. Unfortunately this:
>>
>>> To get the full range of conditions, you will need to include all the
>>> variables needed by any condition you can imagine.  So the above
>>> suggestions may need to be expanded to:
>>>  ... lambda i,j,a,b: ... or whatever
>>>
>>> and
>>>   ... condition(i,j,a,b) ... or whatever
>>>
>>
>>is not as concise as I had hoped for. Is there a possibility to do
>>(maybe with a helper function inside the main function's body) solve
>>this more elegantly? I'm thinking of some combination of e. g. **kwargs,
>>dir() and introspection.
> 
> Yes.
> 
> Consider locals():
> 
>   https://docs.python.org/3/library/functions.html#locals
> 
> which is a built in function returning a copy of the current local
> variables in a dict. Example:
> 
>   condition_test = lambda vars: vars['i'] + vars[j'] > 4
> 
>   def test1(a, b, condition):
>     for i, j in zip(a,b):
>       c = i + j
>       if condition(locals()):
>         print("Foo")
> 
>   test1([0,1,2,3], [1,2,3,4], condition_test)
> 
> This passes the local variables inside test1() to "condition" as a single
> parameter. Now, I grant that vars['i'] is a miracle of tediousness. So
> consider this elaboration:
> 
>   from collections import namedtuple
> 
>   condition_test = lambda vars: vars.i + vars.j > 4
> 
>   def test1(a, b, condition):
>     for i, j in zip(a,b):
>       c = i + j
>       vars = locals()
>       varnames = list(vars.keys())

That leaves varnames in undefined order. Consider

varnames = sorted(vars)

instead or pass the list of arguments explicitly, optionally with some 
inspect fallback:

$ cat pass_condition_inspect.py
import inspect

def test3(a, b, condition, args=None):
    if args is None:
        args = inspect.getargspec(condition).args

    for i, j in zip(a,b):
        c = i + j
        _locals = locals()
        if condition(*[_locals[name] for name in args]):
            print("Foo", i, j)

def condition(c, i):
    return i * i > c

test3([1, 2, 3], [2, 3, 4], condition)
print("---")
# note reverse order of argument names
test3([1, 2, 3], [2, 3, 4], condition, ["i", "c"]) 
$ python3 pass_condition_inspect.py
Foo 3 4
---
Foo 1 2
Foo 2 3
Foo 3 4

A simpler alternative is changing the signature of condition() and passing 
keyword arguments:

$ cat pass_condition.py
def test2(a, b, condition):
    for i, j in zip(a,b):
        c = i + j
        if condition(**locals()):
            print("Foo", i, j)

def condition(c, i, **unused):
    return i * i > c

test2([1, 2, 3], [2, 3, 4], condition)
$ python3 pass_condition.py 
Foo 3 4

Creating a locals() dict on every iteration is still costly, and personally 
I would prefer the tighter interface where you pass a limited set of 
arguments explicitly.

>       varstupletype = namedtuple("locals", varnames)
>       varstuple = varstupletype(*[ vars[k] for k in varnames ])
>       if condition(varstuple):
>         print("Foo")
> 
> Here, the condition_test function/lambda uses "vars.i" and "vars.j", which
> i think you'll agree is easier to read and write. The price is the
> construction of a "namedtuple" to hold the variable name values. See:
> 
>   
https://docs.python.org/3/library/collections.html#collections.namedtuple
> 
> So the (untested) code above:
> 
>   - get the locals() as before
>   - get the names of the variables; it is important to have this in a
>   array because we need to access the values in the same order when we
>   make the tuple - make a new namedtuple class "varstupletype", which is
>   used to make the named tuple - make the named tuple itself with the
>   values of the variables in order
> 
> If you're writing a lot of test functions like test1 you can push the
> namedtuple stuff off into a helper function:
> 
>   def vartuple(vars):
>     varnames = list(vars.keys())
>     varstupletype = namedtuple("locals", varnames)
>     varstuple = varstupletype(*[ vars[k] for k in varnames ])
>     return varstuple
> 
> and then "test1()" can look like this:
> 
>   def test1(a, b, condition):
>     for i, j in zip(a,b):
>       c = i + j
>       if condition(vartuple(locals())):
>         print("Foo")
> 
> which makes it much easier to write test2 and so on later.





More information about the Python-list mailing list