如何优雅实现惰性求值的all()校验?替代重复yield生成器
优雅实现惰性求值的布尔校验(告别重复yield的生成器)
你遇到的这个问题我太懂了——用all(lst)的时候,数组里的所有表达式(不管是函数调用还是属性访问)会被一次性全部执行,完全达不到“遇到第一个False就停”的短路效果;而手写生成器挨个写yield又太啰嗦,重复的关键字堆在一起既不优雅,还违反了DRY(Don't Repeat Yourself)原则。
下面给你几个更清爽的解决方案,完美满足按需计算+短路求值的需求:
方案1:生成器表达式+Lambda包装(最简洁)
把每个需要延迟计算的表达式用lambda:包装成无参函数,然后放进生成器表达式里。all()迭代的时候才会调用这些lambda,而且一旦遇到False就立刻停止,后面的表达式根本不会执行:
all( func() for func in ( lambda: 1, # 直接返回值的表达式 lambda: a(), # 函数调用 lambda: obj.prop, # 对象属性访问 # ... 其他需要校验的表达式 lambda: z() ) )
这种写法只需要给每个表达式加个lambda:前缀,没有冗余的代码,可读性也很强。
方案2:自定义惰性求值工具函数(复用性更强)
如果你经常需要做这类校验,可以封装一个小工具函数,把传入的lambda表达式逐个惰性返回:
def lazy_check(*checks): for check in checks: yield check() # 使用时直接传包装好的表达式 all(lazy_check( lambda: 1, lambda: a(), lambda: obj.prop, # ... lambda: z() ))
这个工具函数可以复用在任何需要惰性求值的场景,代码语义更清晰,一眼就能看出是在做延迟校验。
方案3:针对纯函数调用的简化版(可选)
如果你的校验项全是函数调用(没有属性访问这类表达式),可以用itertools.starmap来简化,把函数和参数分开传递:
from itertools import starmap all(starmap(func, args) for func, args in ( (lambda: 1, ()), # 无参函数传空元组 (a, ()), (b, (2, 3)), # 如果是带参函数b(2,3)就这么写 # ... (z, ()) ))
不过这个方案对属性访问的场景不太友好,适合纯函数调用的场景。
核心原理
不管用哪种方案,本质都是把需要计算的表达式转换成“可延迟执行的对象”,然后利用all()函数的短路特性——一旦迭代到第一个返回False的元素,就立刻终止迭代,后面的延迟对象根本不会被执行,完美实现了“按需计算”的需求。
内容的提问来源于stack exchange,提问作者trampampam
相关产品推荐
相关产品推荐

