【问题标题】:Is it possible to add a where clause with list comprehension?是否可以添加带有列表理解的 where 子句?
【发布时间】:2012-07-21 10:27:02
【问题描述】:

考虑以下列表理解

[ (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 ]

有没有其他方法可以让它更具 Python 风格和可读性?


更新

即将在 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]

【问题讨论】:

  • 确定编译的时候会计算两次吗?
  • 不确定如何编译。但是在 python 提示符下,它被执行了两次。我通过添加打印语句进行检查。
  • 如果不想计算f(x)两次,请尝试在f()中添加缓存。
  • @Vixen:是的,python 将在第一条语句中为每个x in iterable 调用两次f(x)
  • 这与其说是“[... where ...] 子句”,不如说是想优化[... if ...] 子句和/或引入let 样式的匿名绑定。

标签: python python-3.x list-comprehension python-assignment-expression python-3.8


【解决方案1】:

没有什么说你必须使用推导式。事实上,我见过的大多数风格指南都要求您将它们限制为简单的结构。

您可以改为使用生成器表达式。

def fun(iterable):
    for x in iterable:
        y = f(x)
        if y:
            yield x, y


print list(fun(iterable))

【讨论】:

    【解决方案2】:

    您希望在 python 列表推导中拥有 let-statement 语义,其范围可用于推导的 ___ for..in(map) 和 if ___(filter) 部分,其范围取决于..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 装饰器添加到fexample(没有最大缓存大小)。只要 x 是可散列的(例如数字、元组、frozenset 等),这将起作用。


    虚拟值:

    主要思想:在闭包中捕获 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,则您必须修改它。

    【讨论】:

    • 加上xfx() 可以通过将f 函数作为参数传递给它来概括。
    【解决方案3】:

    地图和邮编?

    fnRes = map(f, iterable)
    [(x,fx) for x,fx in zip(iterable, fnRes) if fx)]
    

    【讨论】:

    • y==1 是极差的形式; y 已经是一个布尔值,你可以说if y(而不是比较 True==1 / False==1;这有点像说return myBoolean==bool(1),比return myBoolean==True 差,而不是通常的return myBoolean)。 y 也可以命名为具有语义意义的名称,例如 fx。除此之外,这是一个合理的答案。 [编辑:+1 =)]
    • 如果 iterable 是生成器,则不会工作。必须使用itertools.tee 来获得两个迭代器。
    • @balki 好点,在某些情况下这也可能效率低下。
    【解决方案4】:

    没有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子句。

    【讨论】:

      猜你喜欢
      • 2012-12-28
      • 2015-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-16
      • 1970-01-01
      • 2011-01-05
      相关资源
      最近更新 更多