开发者

Can you dynamically combine multiple conditional functions into one in Python?

I'm curious if it's possible to take several conditional functions and create one function that checks them all (e.g. the way a generator takes a procedure for iterating through a series and creates an iterator).

The basic usage case would be when you have a large number of conditional parameters (e.g. "max_a", "min_a", "max_b", "min_b", etc.), many of which could be blank. They would all be passed to this "function creating" func开发者_开发知识库tion, which would then return one function that checked them all. Below is an example of a naive way of doing what I'm asking:

def combining_function(max_a, min_a, max_b, min_b, ...):
    f_array = []
    if max_a is not None:
        f_array.append( lambda x: x.a < max_a )
    if min_a is not None:
        f_array.append( lambda x: x.a > min_a )
    ...

    return lambda x: all( [ f(x) for f in f_array ] )

What I'm wondering is what is the most efficient to achieve what's being done above? It seems like executing a function call for every function in f_array would create a decent amount of overhead, but perhaps I'm engaging in premature/unnecessary optimization. Regardless, I'd be interested to see if anyone else has come across usage cases like this and how they proceeded.

Also, if this isn't possible in Python, is it possible in other (perhaps more functional) languages?

EDIT: It looks like the consensus solution is to compose a string containing the full collection of conditions and then use exec or eval to generate a single function. @doublep suggests this is pretty hackish. Any thoughts on how bad this is? Is it plausible to check the arguments closely enough when composing the function that a solution like this could be considered safe? After all, whatever rigorous checking is required only needs to be performed once whereas the benefit from a faster combined conditional can be accrued over a large number of calls. Are people using stuff like this in deployment scenarios or is this mainly a technique to play around with?


Replacing

return lambda x: all( [ f(x) for f in f_array ] )

with

return lambda x: all( f(x) for f in f_array )

will give a more efficient lambda as it will stop early if any f returns a false value and doesn't need to create unnecessary list. This is only possible on Python 2.4 or 2.5 and up, though. If you need to support ancient values, do the following:

def check (x):
    for f in f_array:
        if not f (x):
            return False
    return True

return check

Finally, if you really need to make this very efficient and are not afraid of bounding-on-hackish solutions, you could try compilation at runtime:

def combining_function (max_a, min_a):
    constants = { }
    checks    = []

    if max_a is not None:
        constants['max_a'] = max_a
        checks.append ('x.a < max_a')

    if min_a is not None:
        constants['min_a'] = min_a
        checks.append ('x.a > min_a')

    if not checks:
        return lambda x: True
    else:
        func = 'def check (x): return (%s)' % ') and ('.join (checks)
        exec func in constants, constants
        return constants['check']

class X:
    def __init__(self, a):
        self.a = a

check = combining_function (3, 1)
print check (X (0)), check (X (2)), check (X (4))

Note that in Python 3.x exec becomes a function, so the above code is not portable.


Based on your example, if your list of possible parameters is just a sequence of max,min,max,min,max,min,... then here's an easy way to do it:

def combining_function(*args):
    maxs, mins = zip(*zip(*[iter(args)]*2))
    minv = max(m for m in mins if m is not None)
    maxv = min(m for m in maxs if m is not None)
    return lambda x: minv < x.a < maxv

But this kind of "cheats" a bit: it precomputes the smallest maximum value and the largest minimum value. If your tests can be something more complicated than just max/min testing, the code will need to be modified.


The combining_function() interface is horrible, but if you can't change it then you could use:

def combining_function(min_a, max_a, min_b, max_b):
    conditions = []
    for name, value in locals().items():
        if value is None:
            continue
        kind, sep, attr = name.partition("_")
        op = {"min": ">", "max": "<"}.get(kind, None)
        if op is None:
            continue
        conditions.append("x.%(attr)s %(op)s %(value)r" % dict(
            attr=attr, op=op, value=value))

    if conditions:
        return eval("lambda x: " + " and ".join(conditions), {})
    else:
        return lambda x: True
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜