能在Python中动态组合多个条件函数吗?
我很好奇是否可以把几个条件函数合并成一个函数,这个函数可以检查所有条件(就像生成器通过一个过程来遍历一系列数据并创建一个迭代器一样)。
基本的使用场景是,当你有很多条件参数(比如“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 个回答
根据你的例子,如果你可能的参数列表只是一个简单的 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
不过这种方法有点“作弊”:它提前计算了最小的最大值和最大的最小值。如果你的测试内容比单纯的最大值和最小值测试要复杂,代码就需要进行修改。
这个 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
用
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
变成了一个函数,所以上面的代码不能在不同的版本间通用。