你能在Python中动态地将多个条件函数组合成一个吗?

时间:2010-06-09 22:14:29

标签: python functional-programming

我很好奇是否可以采用几个条件函数并创建一个函数来检查所有这些函数(例如,生成器采用迭代一系列过程并创建迭代器的方法)。

基本用例是当您有大量条件参数(例如“max_a”,“min_a”,“max_b”,“min_b”等)时,其中许多可能是空白的。它们都将被传递给这个“函数创建”函数,然后它将返回一个检查它们的函数。以下是我正在问的一种天真的方式的例子:

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 ] )

我想知道的是,实现上述目标的最有效方法是什么?似乎为f_array中的每个函数执行一个函数调用会产生大量的开销,但也许我正在进行过早/不必要的优化。无论如何,我有兴趣看看是否有其他人遇到过这样的使用案例以及他们是如何进行的。

另外,如果在Python中无法做到这一点,是否可以使用其他(可能更实用的)语言?

编辑:看起来共识的解决方案是组成一个包含完整条件集合的字符串,然后使用exec或eval生成单个函数。 @doublep表示这是非常hackish。对这有多糟糕的想法?在编写像这样的解决方案被认为是安全的函数时,是否有必要仔细检查参数?毕竟,无论需要进行何种严格检查,都只需执行一次,而快速组合条件的好处可以在大量呼叫中产生。人们是否在部署场景中使用这样的东西,或者这主要是一种可以玩的技术?

3 个答案:

答案 0 :(得分:1)

更换

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

return lambda x: all( f(x) for f in f_array )
如果任何lambda返回false值并且不需要创建不必要的列表,

将提供更高效的f,因为它会提前停止。但这仅适用于Python 2.4或2.5及更高版本。如果您需要支持古代价值观,请执行以下操作:

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

return check

最后,如果你真的需要使这个非常有效并且不怕绑定hackish解决方案,你可以在运行时尝试编译:

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))

请注意,在Python 3.x中exec成为一个函数,因此上面的代码不可移植。

答案 1 :(得分:1)

combining_function()界面很糟糕,但是如果你不能改变它,那么你可以使用:

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

答案 2 :(得分:0)

根据您的示例,如果您的可能参数列表只是max,min,max,min,max,min,...的序列,那么这里有一个简单的方法:

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

但是这种“作弊”有点:它预先计算最小的最大值和最大的最小值。如果您的测试可能比最大/最小测试更复杂,则需要修改代码。