我正在阅读Python烹饪书,目前正在研究生成器。我觉得很难理解。
由于我有Java背景,那么Java中是否有对等的语言?这本书讲的是“生产者/消费者”,但当我听到这个词时,我想到了线程。
什么是发电机,你为什么要用它?显然,没有引用任何书籍(除非你能直接从一本书中找到一个体面、简单的答案)。如果你慷慨的话,还可以举个例子!
我正在阅读Python烹饪书,目前正在研究生成器。我觉得很难理解。
由于我有Java背景,那么Java中是否有对等的语言?这本书讲的是“生产者/消费者”,但当我听到这个词时,我想到了线程。
什么是发电机,你为什么要用它?显然,没有引用任何书籍(除非你能直接从一本书中找到一个体面、简单的答案)。如果你慷慨的话,还可以举个例子!
当前回答
对于Stephan202的回答,我唯一能补充的是建议您看一看David Beazley的PyCon '08演示文稿“生成器技巧给系统程序员”,这是我所见过的关于如何以及为什么使用生成器的最好的解释。这就是让我从“Python看起来很有趣”变成“这就是我一直在寻找的东西”的原因。网址是http://www.dabeaz.com/generators/。
其他回答
生成器可以看作是创建迭代器的简写。它们的行为类似于Java迭代器。例子:
>>> g = (x for x in range(10))
>>> g
<generator object <genexpr> at 0x7fac1c1e6aa0>
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> list(g) # force iterating the rest
[3, 4, 5, 6, 7, 8, 9]
>>> g.next() # iterator is at the end; calling next again will throw
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
希望这有助于/是你正在寻找的。
更新:
正如许多其他答案所示,有不同的方法来创建生成器。你可以像上面的例子一样使用圆括号语法,也可以使用yield。另一个有趣的特性是生成器可以是“无限的”——迭代器不会停止:
>>> def infinite_gen():
... n = 0
... while True:
... yield n
... n = n + 1
...
>>> g = infinite_gen()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
...
对于Stephan202的回答,我唯一能补充的是建议您看一看David Beazley的PyCon '08演示文稿“生成器技巧给系统程序员”,这是我所见过的关于如何以及为什么使用生成器的最好的解释。这就是让我从“Python看起来很有趣”变成“这就是我一直在寻找的东西”的原因。网址是http://www.dabeaz.com/generators/。
注意:本文假设Python 3。x语法。__
生成器只是一个函数,它返回一个对象,接下来可以对其调用,这样对于每次调用它都会返回一些值,直到引发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]
Observe that a generator object is generated once, but its code is not run all at once. Only calls to next actually execute (part of) the code. Execution of the code in a generator stops once a yield statement has been reached, upon which it returns a value. The next call to next then causes execution to continue in the state in which the generator was left after the last yield. This is a fundamental difference with regular functions: those always start execution at the "top" and discard their state upon returning a value.
关于这个问题还有很多要说的。例如,可以将数据发送回生成器(参考)。但是,我建议您在了解生成器的基本概念之前不要研究这个问题。
现在你可能会问:为什么要使用生成器?有几个很好的理由:
Certain concepts can be described much more succinctly using generators. Instead of creating a function which returns a list of values, one can write a generator which generates the values on the fly. This means that no list needs to be constructed, meaning that the resulting code is more memory efficient. In this way one can even describe data streams which would simply be too large to fit in memory. Generators allow for a natural way to describe infinite streams. Consider for example the Fibonacci numbers: >>> 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] This code uses itertools.islice to take a finite number of elements from an infinite stream. You are advised to have a good look at the functions in the itertools module, as they are essential tools for writing advanced generators with great ease.
†关于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
我给出了这段代码,解释了关于生成器的3个关键概念:
def numbers():
for i in range(10):
yield i
gen = numbers() #this line only returns a generator object, it does not run the code defined inside numbers
for i in gen: #we iterate over the generator and the values are printed
print(i)
#the generator is now empty
for i in gen: #so this for block does not print anything
print(i)
使用列表推导式的经验表明,它们在Python中具有广泛的实用性。然而,许多用例不需要在内存中创建一个完整的列表。相反,它们每次只需要迭代一个元素。
例如,下面的求和代码将在内存中构建一个完整的方块列表,遍历这些值,当引用不再需要时,删除列表:
Sum ([x*x for x in range(10)])
通过使用生成器表达式来节省内存:
求和(x*x for x in range(10))
容器对象的构造函数也有类似的好处:
s = Set(word for line in page for word in line.split())
d = dict( (k, func(k)) for k in keylist)
生成器表达式对于sum(), min()和max()这样的函数特别有用,它们将可迭代输入减少为单个值:
max(len(line) for line in file if line.strip())
more