生成器与列表推导式

5

我有一个东西,当作为列表推导式运行时正常运行。

看起来像是,

[myClass().Function(things) for things in biggerThing]

Function 是一种方法,它构建一个列表。该方法本身不返回任何东西,但列表在其中得到操作。

现在当我将其更改为 生成器

(myClass().Function(things) for things in biggerThing)

它没有像我预期的那样操作数据。实际上,它似乎根本没有对其进行任何操作。

列表推导式生成器之间的功能区别是什么?


1
https://dev59.com/AHVD5IYBdhLWcg3wOo9h - karthikr
8
不要使用列表推导式进行副作用操作。现在你正在构建一个由None值组成的列表,然后将其丢弃,浪费CPU和内存。 - Martijn Pieters
4
如果您不想构建列表,为什么要使用列表推导式?通常人们在学习列表推导式之前会先学习for循环,但也许您走的是相反的路线? - DSM
5个回答

5

生成器在被消耗时即时评估。因此,如果您从未迭代生成器,则其元素永远不会被评估。

所以,如果您执行以下操作:

for _ in (myClass().Function(things) for things in biggerThing):
    pass

Function将会运行。


现在,你的意图并不是很清楚。

相反,考虑使用map

map(myClass().Function, biggerThing)  

请注意,这将始终使用MyClass的相同实例。如果这是一个问题,那么请执行以下操作:
for things in BiggerThing:
    myClass().Function(things)

2
我认为map不如生成器或列表推导式更好。如果您不关心myClass().Function(things)的返回值,那么没有必要浪费内存来存储结果。 - Blender
常规的for循环是我之前的结构。我只是想知道是否使用生成器可以加快程序的执行速度。 - myacobucci
@Blender 我的本意是指出使用map会更清晰明了。不过,如果性能是一个问题,for循环可能是最合适的选择。 - Thomas Orozco

3

生成器是惰性求值的。你需要处理生成器以便评估你的函数。可以使用collections.deque来消耗生成器:

import collections
generator = (myClass().Function(thing) for thing in biggerThing) 
collections.deque(generator , maxlen=0)

考虑使用@staticmethod@classmethod,或者更改为
myfunc = myClass().Function
generator = (myfunc(thing) for thing in biggerThing) 
collections.deque(generator , maxlen=0)

为了减少每个“thing”处理时创建新的myClass实例。

更新,性能

  1. collectionsiteration
def l():
    for x in range(100):
       y = x**2
      yield y
def consume(it): for i in it: pass
>>> timeit.timeit('from __main__ import l, consume; consume(l())', number=10000) 0.4535369873046875 >>> timeit.timeit('from __main__ import l, collections; collections.deque(l(), 0)', number=10000) 0.24533605575561523
  1. 实例 vs 类 vs 静态方法
class Test(object):
    @staticmethod
    def stat_pow(x):
        return x**2
    @classmethod
    def class_pow(cls, x):
        return x**2
    def inst_pow(self, x):
        return x**2
def static_gen(): for x in range(100): yield Test.stat_pow(x)
def class_gen(): for x in range(100): yield Test.class_pow(x)
def inst_gen(): for x in range(100): yield Test().inst_pow(x)
>>> timeit.timeit('from __main__ import static_gen as f, collections; collections.deque(f(), 0)', number=10000) 0.5983021259307861 >>> timeit.timeit('from __main__ import class_gen as f, collections; collections.deque(f(), 0)', number=10000) 0.6772890090942383 >>> timeit.timeit('from __main__ import inst_gen as f, collections; collections.deque(f(), 0)', number=10000) 0.8273470401763916
这段代码定义了一个Test类和三个生成器函数,分别返回静态方法、类方法和实例方法的平方值。接下来是使用timeit模块测试这些生成器函数的性能。

使用 collections.deque 的性能优势是什么? - myacobucci
collections.deque可能是消耗生成器最快的方法,但对于这种情况,在实例化myClass()对象以调用一个方法然后将其丢弃的开销几乎肯定会淹没任何微小优化。 - Duncan
@Duncan 如果您查看我的机器上的测试结果(此答案底部),在简单情况下,deque方法的优势胜过myClass实例化。对于更复杂的类和函数,情况可能会有所不同,您可以进行自己的测试。 - alko
我无法重现你的收集与迭代时间。运行你的确切代码,我得到了0.42的“consume”和0.43的“dequeue”。我运行了几次,“consume”总是稍微快一些。此外,你并没有进行类似的比较。在原始代码上应该进行比较的是collections.deque(generator, maxlen=0)与仅执行for thing in biggerThing: myClass().Function(things)。你的比较强制多了一个for循环来消耗生成器,而实际上根本不需要生成器。 - Duncan
@Duncan,你使用的是cPython还是PyPy或其他?机器架构(64位或32位)和操作系统是什么? - alko
1
那是在Windows 7上的32位Python 3.3.0。刚刚重新运行了一下,consume()得到了0.427,而dequeue()则是0.431。在Python 2.7.2上运行相同的代码,我得到了0.139和0.143。只有在Pypy 1.7上运行时,deque才会获胜:第一次运行时为0.148,而第二次重复每个timeit调用时为0.023对0.030。你难道不喜欢一个好的JIT吗? - Duncan

2
当你创建一个生成器时,每个元素只能使用一次。就像我正在制作一批饼干,并在制作过程中食用它们。它们有自己的用途(让我快乐),但是一旦使用它们,它们就消失了。
列表推导式创建列表,它们将允许您永远访问该数据结构(理论上)。您还可以在它们上使用所有列表方法(非常有用)。但是,这种方法会创建一个实际的数据结构(一个为您保存数据的东西)。
请查看此帖子:生成器与列表推导式

1
生成器不会执行函数,直到您在生成器上调用next()
 >>>def f():
 ...    print 'Hello'
 >>>l = [f() for _ in range(3)]
 Hello
 Hello
 Hello
 >>>g = (f() for _ in range(3)) # nothing happens 
 >>>
 >>>next(g)
 Hello

0

列表推导式:

  • 列表可以被索引。例如:[0, 1, 2, 3, 4][0]

  • 创建的列表可以使用任意次数。

  • 一个空列表占用72个字节,每个项目增加8个字节。

生成器:

  • 生成器不能被索引。

  • 生成器只能使用一次。

  • 生成器占用的内存要少得多(80个字节)。

请注意,在生成器中,一旦使用后,其中的内容就会被清空。

>>> sys.getsizeof([])
72
>>> list1 = [x for x in range(0, 5)]
>>> sys.getsizeof(list1)
136
>>>
>>> generator1 = (x for x in range(0,100))
>>> sys.getsizeof(generator1)
80
>>> generator1 = (x for x in range(0,5))
>>> sys.getsizeof(generator1)
80
>>> list(generator1)
[0, 1, 2, 3, 4]
>>> list(generator1)
[]
>>> 

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