【问题标题】:Can you dynamically combine multiple conditional functions into one in Python?你能在 Python 中将多个条件函数动态组合成一个吗?
【发布时间】:2010-06-10 08:02:00
【问题描述】:

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

基本用例是当您有大量条件参数(例如“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 建议这是相当骇人听闻的。对这有多糟糕有任何想法吗?在编写这样的解决方案可以被认为是安全的函数时,是否可以足够仔细地检查参数?毕竟,无论需要什么严格的检查,都只需要执行一次,而更快的组合条件的好处可以通过大量的调用来累积。人们是在部署场景中使用这样的东西还是这主要是一种可以玩弄的技术?

【问题讨论】:

  • 你所有的条件参数都是最大/最小对吗?这绝对是可能的,但如何去做取决于你将要进行什么样的测试。
  • 删除对all()的调用中的列表,即只是all (f(x) for f in f_array)。如果任何f 产生错误值,这将提前停止。
  • @doublep:这只适用于最近的python。有些人仍然必须使用过时的版本 ;-)
  • @liori:好的,你说得有道理。然后放弃 lambda 并返回执行 for f in f_array: ... 的本地函数定义会更有效。当前版本永远不会提前停止并创建一个不需要的列表对象。
  • 规格不清楚。似乎首先您要检查min_a &lt; x.a &lt; max_a,但对于下一对检查其他属性,大概是x.b?而且由于 args 不带有它们的名字,因此无法知道下一个要检查的参数是什么!

标签: python functional-programming


【解决方案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 变成了一个函数,所以上面的代码是不可移植的。

【讨论】:

    【解决方案2】:

    根据您的示例,如果您的可能参数列表只是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
    

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

    【讨论】:

    • 糟糕,我错过了问题中的.a...我会相应地进行编辑。
    • 这里有一个问题,规范不清楚。似乎您需要检查min_a &lt; x.a &lt; max_a,但对于下一对检查其他属性,大概是'x.b'。而且由于 args 不带有它们的名字,因此无法知道下一个要检查的参数是什么!
    • @EnTerr:当然,如果我以这种方式解释问题,我会写其他东西。你说得对,不清楚。
    【解决方案3】:

    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
    

    【讨论】:

    • combining_function(None, None, None, type('', (), {'__repr__': lambda x:'os.system("sudo rm -rf /")'})()) ;-)
    • 这个界面只是为了演示,对于这类问题,你有什么建议的理想界面?
    • @liori:我不明白你的意思。你可以很容易地写type('',(),{'__lt__': lambda x,y: os.system("whatever")})()
    • @erich:如果有很多最小/最大对,那么界面的增量改进可能是get_inrange(a=(None,1), b=(1, 2)) -> get_inrange = lambda **kwargs: lambda x: all(op(getattr(x, attr), value) for attr, minmax in kwargs.items() for op, value in zip([operator.gt, operator.lt], minmax) if value is not None)
    • @J.F. Sebastian:我的观点是这段代码需要检查它的参数或者明确声明它对于某些输入是不安全的;否则“eval”可以做任何事情。
    猜你喜欢
    • 1970-01-01
    • 2014-11-12
    • 1970-01-01
    • 2019-05-15
    • 2018-09-27
    • 1970-01-01
    • 2019-12-19
    • 1970-01-01
    • 2018-10-31
    相关资源
    最近更新 更多