有没有Python习惯用法实现短路求值的函数/表达式列表?

8 投票
1 回答
2435 浏览
提问于 2025-04-16 02:18

我写了一个简单的脚本来解决一个“逻辑谜题”,这种谜题就像我们在学校时遇到的那种,你会得到一些规则,然后需要找出解决方案,比如“有五个音乐家,分别叫A、B、C、D和E,他们在一个音乐会上依次演奏……如果A在B之前,D不是最后一个……那他们演奏的顺序是什么?”等等。

为了评估可能的解决方案,我把每个“规则”写成一个单独的函数,这些函数会检查一个可能的解决方案(简单来说就是一个字符串列表)是否有效,比如:

#Fifth slot must be B or D
def rule1(solution):
    return solution[4] == 'B' or solution[4] == 'D'

#There must be at least two spots between A and B
def rule2(solution):
    returns abs(solution.index('A') - solution.index('B')) >= 2

#etc...

我想找到一种更符合Python风格的方法来测试一个可能的解决方案是否符合所有这些规则,并且在第一个规则失败后能够停止评估。

一开始,我写了最简单的代码:

def is_valid(solution):
    return rule1(solution) and rule2(solution) and rule3(solution) and ...

但这样看起来有点丑。我想也许可以用列表推导式让代码看起来更优雅一些……

def is_valid(solution)
    rules = [rule1, rule2, rule3, rule4, ... ]
    return all([r(solution) for f in rules])

……但后来我意识到,由于列表推导式是在all()函数评估之前生成的,这样会导致所有规则都会被评估,即使第一个返回了False

所以我的问题是:有没有一种更符合Python风格或函数式的方法,可以评估一系列True/False的表达式,并且能够短路处理,而不需要写出一长串return f1(s) and f2(s) and f3(s) ...

1 个回答

13

使用一个生成器表达式

rules = [ rule1, rule2, rule3, rule4, ... ]
rules_generator = ( r( solution ) for r in rules )
return all( rules_generator )

语法糖:你可以省略多余的括号:

rules = [ rule1, rule2, rule3, rule4, ... ]
return all( r( solution ) for r in rules )

生成器(generator)基本上是一个有.next()方法的对象,这个方法会返回某个可迭代对象中的下一个项目。这意味着它们可以做一些很有用的事情,比如分块读取文件,而不是一次性把整个文件都加载到内存中,或者可以遍历非常大的整数。你可以用for循环来遍历它们,Python会在后台处理这些事情。例如,range在Python 3中就是一个生成器。

你可以通过在函数定义中使用yield语句来创建自己的自定义生成器表达式,而不是使用return

def integers():
    i = 0
    while True:
        yield i

Python会处理保存函数的状态等等。这些真是太棒了!

撰写回答