【问题标题】:one liner to go through an iterable (generator)一个班轮通过一个可迭代(生成器)
【发布时间】:2020-10-06 00:49:26
【问题描述】:

我遇到了一些类似的代码:

[func(val) for val in iterable]

有一个可迭代对象(在我的例子中是一个生成器),用户希望为其副作用调用每个值的函数(例如,func 可能只是 print)但返回值无关紧要.

我不喜欢这种方法的是,它会创建一个临时的list,如果生成器产生大量值,它可能会消耗相当多的内存。

如果func 的返回值始终计算为False,则以下工作:

any(func(val) for val in iterable)

如果 func 的返回值总是True,那么下面的工作:

all(func(val) for val in iterable)

如果 func 的返回值可以评估为True 或False,我该怎么办

还有什么比将值强制为 False 更好的方法吗?

我想出的最好的是:

any(func(val) and False for val in iterable)

或

all(func(val) or True for val in iterable)

【问题讨论】:

  • 您能否澄清或举例说明预期的输出/结果?如果是你害怕的内存,只需使用(func(val) for val in iterable),它是一个构造函数,仅在被询问时才产生结果,因此在启动时所有答案都不会保存在内存中。
  • 事实上,这个人不想写两行代码:for val in iterable(): 后跟func(val) 他想写一个优雅且快速执行的方式来为可迭代的每个成员调用一个函数,并且使用列表表达式。所以她在寻找一个单行(理想情况下)一个表达式,它为可迭代的每个成员调用一个函数,但不会白白地列出一个列表
  • 不确定这是否属实(懒得反编译和比较),但我的印象是,for 语句的性能略低于使用any 或all,但是我对此不是 100% 确定的。我遇到了上面的代码,并对改进它的最佳方法感到好奇(内存和性能方面)

标签: python one-liner


【解决方案1】:

可能只是

for val in iterable:
   func(val)

最清晰。

for val in iterable: func(val)

如果确实需要单线,则可用。

【讨论】:

  • 同意,但这是一个两班轮。我想向编写带有列表的一个衬里的人推荐另一种衬里。
  • 是的,刚刚评论我记得,for 可以是一个班轮
  • 您可以将 for 循环隐藏在某个函数中。即def exhaustIterable(iterable): ...
  • 因为一个班轮是可以的。我更想知道是否已经有一些内置的东西,就像你建议的exhaustIterable
  • 可能会接受这个答案。唯一的小缺点是,它不能用作lambda,因为它不是一个表达式,而是一个声明,但这不是我的问题的一部分。
【解决方案2】:

将set 与bool 函数一起使用怎么样?

{bool(func(val)) for val in iterable}

已编辑:
看了@gelonida的分析,相信下面的还是快了一点。

{None for val in iterable(N) if func(val)}

【讨论】:

  • 是的,第二种解决方案更好。它避免了确定func 的返回值的哈希值。你的第二个解决方案给了我一个替代的想法:[None for val in iterable(N) if func(val) and False] 但并不比你的解决方案更高效,也没有更多的可读性
【解决方案3】:

只是对给定答案/潜在解决方案的综合和时序分析

@LeopardShark 的答案似乎是最短、最易读的答案。 并且是最快的。 (时间不准确,我没看字节码)

速度明智 - @LeopardShark 的回答 - @ywbaek 的第二个建议 - 如果 N 不太大(~10000),我发现的初始代码 - 我在问题中发布的建议

初始代码有分配释放内存的缺点。

我在我的问题中建议的代码的缺点是理解起来不太直观,如果弄乱了 (all, any) 和 (and False, or True) 的组合,可能无法执行所有操作正如预期的那样,性能也稍差

@ywbaek 的解决方案比我的建议更安全,也更直观,但执行速度更快。

最简单的解决方案有一个小缺点,即不能用作 lambda。

我的计时码:

N=10000
M=500

called = 0
def func(v):
    global called
    called += 1
    v * v * v * v * v *v / (v+0.1)

def iterable(N):
    for v in range(N):
        v * 2
        yield v

def testrun():
    global called
    called=0
    print(timeit(test, number=M), end=" ")
    print(called)

print("consume some CPU")
timeit(lambda: 3**.5 **.2) # it seems, that timeit is a little more predictable if I let the process warm up a little

print("Start measures")

def test():
    for val in iterable(N): func(val)
testrun()

def test():
    {None for val in iterable(N) if func(val)}
testrun()

def test():
    [func(val) for val in iterable(N)]
testrun()

def test():
    all(func(val) or True for val in iterable(N))
testrun()

def test():
    any(func(val) and False for val in iterable(N))
testrun()

我的旧电脑上的结果:

consume some CPU
Start measures
3.864932143012993 5000000
3.916696268017404 5000000
4.0817033689818345 5000000
4.293206526956055 5000000
4.319622751965653 5000000

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-15
    • 2010-11-05
    • 2015-06-17
    相关资源
    最近更新 更多