在列表理解中的if语句中捕获方法

use*_*455 3 python if-statement list-comprehension list

我有以下用例:

[(x, f(x)) for x in list_x if f(x) == cond(x)]
Run Code Online (Sandbox Code Playgroud)

上面列表理解,我认为使af(x)调用两次?如何避免这种情况并捕获f(x)的值,以便只调用f(x)一次.

我想,简单的解决方法是将上面的列表理解转换为for循环,但我很好奇是否可以使用列表理解有效地完成.

Mar*_*ers 6

您可以使用嵌套的生成器表达式仅调用一次函数:

[(x, fx) for (x, fx) in ((x, f(x)) for x in list_x) if fx == cond(x)]
Run Code Online (Sandbox Code Playgroud)

生成器表达式以锁步方式迭代,以生成(x, fx)列表推导的元组.

如果您在阅读器上发现这一点更容易,您可以先将生成器表达式拆分为单独的名称:

mapped_x = ((x, f(x)) for x in list_x)
filtered_x = [(x, fx) for (x, fx) in mapped_x if fx == cond(x)]
Run Code Online (Sandbox Code Playgroud)

重新迭代这一点:生成器表达式是懒惰地执行的; 对于for循环中的每个步骤,表达式中的for ... in mapped_x循环都是逐步进行的.

演示:

>>> list_x = range(5)
>>> f = lambda x: print('f({!r})'.format(x)) or (x ** 2 - 1)
>>> cond = lambda x: print('cond({!r})'.format(x)) or x % 2 == 0
>>> mapped_x = ((x, f(x)) for x in list_x)
>>> [(x, fx) for (x, fx) in mapped_x if fx == cond(x)]
f(0)
cond(0)
f(1)
cond(1)
f(2)
cond(2)
f(3)
cond(3)
f(4)
cond(4)
[(1, 0)]
Run Code Online (Sandbox Code Playgroud)

注意如何f(x)只调用一次,并立即检查条件.

这有多高效取决于f(x)通话的成本; 生成器表达式作为单独的函数帧执行,解释器将在两个帧之间切换(列表推导的循环也是帧对象).

如果f(x)是Python函数,那么你已经赢了,因为你现在减少了创建的函数框架对象的数量(每个f(x)调用也创建一个框架对象,并且创建它们相对昂贵).对于C函数,您应该使用该timeit模块创建一些试运行,以查看预期列表大小的更快速度.