一行代码遍历迭代器(生成器)

4
我遇到了一些类似于以下的代码:
[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的返回值可以评估为TrueFalse,我需要做什么?

有没有比强制其为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),这是一个构造器,只有在被调用时才会产生结果,因此所有答案在初始化时都不会被保存在内存中。 - Jakob Guldberg Aaes
实际上,这个人不想写一个两行的代码:for val in iterable(): 接着 func(val)他想要写一种优雅且高效的方式,用于调用可迭代对象中每个成员的函数,并使用列表表达式。因此,他正在寻找一种单行(最好是表达式)的方法,用于调用可迭代对象中每个成员的函数,但不会无谓地构建一个列表。 - gelonida
不确定这是否正确(懒得反编译和比较),但我有印象,使用anyall比使用for语句略微效率更低,但我对此并不100%确定。我遇到了上面的代码,并且很好奇如何在内存和性能方面进行最佳优化。 - gelonida
3个回答

4

可能只是

for val in iterable:
   func(val)

最清晰的。
for val in iterable: func(val)

如果真正必要,可以使用一行代码来实现。

1
同意,但那是两行代码。我想向那个写了带有列表的一行代码的人建议另一个一行代码。 - gelonida
1
是的,在评论之后我想起来了,for可以写成一行。 - gelonida
1
你可以把那个 for 循环放到某个函数中隐藏起来,例如 def exhaustIterable(iterable): ... - Daniel Walker
1
我更想知道是否已经有类似于您建议的exhaustIterable的内置功能。 - gelonida
1
可能会接受这个答案。唯一的小缺点是,它不能用作“lambda”,因为它不是表达式,而是语句。但这不是我的问题的一部分。 - gelonida

1
如何使用具有bool函数的set
{bool(func(val)) for val in iterable}

编辑:
在查看了@gelonida的分析后,我认为以下代码会稍微快一些。

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

1
是的,第二种解决方案更好。它避免了确定func返回值哈希值的步骤。你的第二个解决方案给了我一个替代性的想法:[None for val in iterable(N) if func(val) and False],但它不比你的解决方案更高效,也不更易读。 - gelonida

1

对给出的答案/潜在解决方案进行综合和时间分析

看起来@LeopardShark的答案是最短、最易读的答案,同时也是最快的。(timeit不是很准确,我没有查看字节码)

就速度而言: - @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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接