考虑以下列表理解
[ (x,f(x)) for x in iterable if f(x) ]
这会根据条件f
过滤迭代,并返回x,f(x)
对。这种方法的问题是f(x)
计算两次。
如果我们能像
[ (x,fx) for x in iterable if fx where fx = f(x) ]
or
[ (x,fx) for x in iterable if fx with f(x) as fx ]
但是在python中我们必须使用嵌套的理解来编写,以避免重复调用f(x)并且它使得理解看起来不那么清晰
[ (x,fx) for x,fx in ( (y,f(y) for y in iterable ) if fx ]
还有其他方法可以让它更具有pythonic和可读性吗?
更新
即将推出的python 3.8! PEP
# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]
答案 0 :(得分:10)
您希望在python列表推导中使用let
- 语句语义,其范围可用于理解的___ for..in
(映射)和if ___
(过滤器)部分,其范围取决于..for ___ in...
。
您的解决方案,已修改:
您的(因为您承认不可读)[ (x,fx) for x,fx in ( (y,f(y) for y in iterable ) if fx ]
的解决方案是编写优化的最直接的方法。
主要思想:将x提升为元组(x,f(x))。
有些人认为最“pythonic”的做法是最初的[(x,f(x)) for x in iterable if f(x)]
,并接受效率低下的问题。
如果您计划执行此操作,则可以将((y,fy) for y in iterable)
分解为函数。这很糟糕,因为如果您希望访问的变量多于x,fx
(例如x,fx,ffx
),那么您需要重写所有列表推导。因此,除非您确定只需要x,fx
并计划重复使用此模式,否则这不是一个很好的解决方案。
生成器表达式:
主要思想:使用更复杂的生成器表达式替代方法:python允许你写多行。
你可以使用一个生成器表达式,python可以很好地用于:
def xfx(iterable):
for x in iterable:
fx = f(x)
if fx:
yield (x,fx)
xfx(exampleIterable)
我个人这样做。
<强>记忆化/高速缓存:强>
主要想法:您还可以使用(滥用?)副作用,并使f
具有全局记忆缓存,因此您不会重复操作。
这可能会产生一些开销,并且需要一个策略,指出缓存应该有多大以及什么时候应该进行垃圾收集。因此,只有当你有其他用于记忆f的用途,或者如果f非常昂贵时,才应该使用它。但它会让你写...
[ (x,f(x)) for x in iterable if f(x) ]
...就像你最初想要的那样,在f
两次进行昂贵的操作时没有性能,即使你在技术上称之为两次。您可以将@memoized
装饰器添加到f
:example(没有最大缓存大小)。只要x是可清除的(例如数字,元组,冻结集等),这将起作用。
虚拟值:
主要思想:在闭包中捕获fx = f(x)并修改列表推导的行为。
filterTrue(
(lambda fx=f(x): (x,fx) if fx else None)() for x in iterable
)
其中filterTrue(iterable)是filter(None,iterable)。如果您的列表类型(2元组)实际上能够None
,则必须修改此项。
答案 1 :(得分:10)
没有where
声明,但您可以使用for
“模仿”它:
a=[0]
def f(x):
a[0] += 1
return 2*x
print [ (x, y) for x in range(5) for y in [f(x)] if y != 2 ]
print "The function was executed %s times" % a[0]
执行:
$ python 2.py
[(0, 0), (2, 4), (3, 6), (4, 8)]
The function was executed 5 times
如您所见,这些功能执行了5次,而不是10次或9次。
这个for
构造:
for y in [f(x)]
模仿where子句。
答案 2 :(得分:5)
没有什么说你必须使用理解力。事实上,我见过的大多数样式指南要求你将它们限制为简单的结构。
您可以使用生成器表达式。
def fun(iterable):
for x in iterable:
y = f(x)
if y:
yield x, y
print list(fun(iterable))
答案 3 :(得分:3)
地图和邮编?
fnRes = map(f, iterable)
[(x,fx) for x,fx in zip(iterable, fnRes) if fx)]