能在Python中动态组合多个条件函数吗?

1 投票
3 回答
4399 浏览
提问于 2025-04-15 23:46

我很好奇是否可以把几个条件函数合并成一个函数,这个函数可以检查所有条件(就像生成器通过一个过程来遍历一系列数据并创建一个迭代器一样)。

基本的使用场景是,当你有很多条件参数(比如“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认为这种做法有点黑科技。大家觉得这样做有多糟糕?在组合函数的时候,是否有可能仔细检查参数,使得这种方法可以被认为是安全的?毕竟,所需的严格检查只需要进行一次,而通过更快的组合条件带来的好处可以在多次调用中体现出来。人们在实际应用中会使用这样的东西吗,还是这只是一个玩玩的技巧?

3 个回答

1

根据你的例子,如果你可能的参数列表只是一个简单的 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

不过这种方法有点“作弊”:它提前计算了最小的最大值和最大的最小值。如果你的测试内容比单纯的最大值和最小值测试要复杂,代码就需要进行修改。

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
1

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

替换成

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

会让你的 lambda 更高效,因为如果有任何 f 返回假值,它会提前停止,不需要创建多余的列表。不过,这种做法只适用于 Python 2.4 或 2.5 及以上版本。如果你需要支持老版本,可以这样做:

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

return check

最后,如果你 真的 想让这个过程非常高效,并且不怕用一些比较“黑科技”的方法,可以尝试在运行时编译:

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 变成了一个函数,所以上面的代码不能在不同的版本间通用。

撰写回答