理解Python中的生成器
我现在正在看《Python Cookbook》,最近在研究生成器的内容。这个概念让我有点难以理解。
我之前是学Java的,想知道在Java中有没有类似的东西?书里提到“生产者/消费者”,但我一想到这个就联想到线程。
生成器到底是什么?为什么要使用它?当然,不要引用书里的内容(除非你能找到简单明了的解释)。如果你愿意的话,可以给我举个例子!
13 个回答
生成器实际上就是一个函数,它在完成之前就可以返回一些数据,然后在这个点暂停,你可以在这个地方继续执行这个函数。
>>> def myGenerator():
... yield 'These'
... yield 'words'
... yield 'come'
... yield 'one'
... yield 'at'
... yield 'a'
... yield 'time'
>>> myGeneratorInstance = myGenerator()
>>> next(myGeneratorInstance)
These
>>> next(myGeneratorInstance)
words
这样一来,生成器的一个好处就是它一次处理一小块数据,这样你就可以处理大量的数据;如果用列表的话,可能会占用过多的内存,造成问题。生成器和列表一样,可以被遍历,所以可以用同样的方式使用:
>>> for word in myGeneratorInstance:
... print word
These
words
come
one
at
a
time
需要注意的是,生成器提供了另一种处理无限数据的方法,比如说:
>>> from time import gmtime, strftime
>>> def myGen():
... while True:
... yield strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
>>> myGeneratorInstance = myGen()
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:17:15 +0000
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:18:02 +0000
生成器包含了一个无限循环,但这并不是问题,因为你每次请求数据时,才会得到一个结果。
注意:这篇文章假设使用的是 Python 3.x 的语法。†
生成器就是一个函数,它会返回一个对象,你可以在这个对象上调用 next
,每次调用都会返回一个值,直到它抛出一个 StopIteration
异常,表示所有的值都已经生成完了。这样的对象叫做 迭代器。
普通的函数用 return
返回一个单一的值,就像在 Java 中一样。然而,在 Python 中,有一种替代方法,叫做 yield
。在函数中使用 yield
的话,这个函数就变成了生成器。看看这个代码:
>>> def myGen(n):
... yield n
... yield n + 1
...
>>> g = myGen(6)
>>> next(g)
6
>>> next(g)
7
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
如你所见,myGen(n)
是一个函数,它会生成 n
和 n + 1
。每次调用 next
都会返回一个值,直到所有值都被生成。for
循环在后台会调用 next
,因此:
>>> for n in myGen(6):
... print(n)
...
6
7
同样,还有 生成器表达式,它们提供了一种简洁的方式来描述某些常见类型的生成器:
>>> g = (n for n in range(3, 5))
>>> next(g)
3
>>> next(g)
4
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
注意,生成器表达式和 列表推导式 很相似:
>>> lc = [n for n in range(3, 5)]
>>> lc
[3, 4]
要注意的是,生成器对象只会被生成一次,但它的代码并不会一次性全部执行。只有调用 next
时,代码的某一部分才会被执行。当代码执行到 yield
语句时,执行会停止,并返回一个值。下一次调用 next
时,执行会从上一次 yield
停止的地方继续。这和普通函数的一个根本区别是:普通函数每次执行都是从“顶部”开始,并在返回值时丢弃它的状态。
关于这个主题还有更多的内容可以讨论。例如,可以向生成器 send
数据(参考)。但我建议在你理解生成器的基本概念之前,不要去研究这个。
现在你可能会问:为什么要使用生成器?有几个很好的理由:
- 某些概念可以用生成器更简洁地描述。
- 与其创建一个返回值列表的函数,不如写一个生成器,实时生成这些值。这意味着不需要构建一个列表,从而使得代码在内存使用上更高效。这样甚至可以描述一些数据流,这些数据流可能大到无法全部放入内存。
生成器提供了一种自然的方式来描述 无限 数据流。例如,考虑一下 斐波那契数列:
>>> def fib(): ... a, b = 0, 1 ... while True: ... yield a ... a, b = b, a + b ... >>> import itertools >>> list(itertools.islice(fib(), 10)) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
这段代码使用
itertools.islice
从一个无限流中取出有限数量的元素。建议你仔细查看itertools
模块中的函数,因为它们是编写高级生成器的基本工具。
† 关于 Python <=2.6: 在上面的例子中,next
是一个函数,它调用给定对象的 __next__
方法。在 Python <=2.6 中,使用的技术稍有不同,即用 o.next()
代替 next(o)
。在 Python 2.7 中,next()
调用 .next
,因此在 2.7 中你不需要使用以下代码:
>>> g = (n for n in range(3, 5))
>>> g.next()
3