我正在阅读Python烹饪书,目前正在研究生成器。我觉得很难理解。
由于我有Java背景,那么Java中是否有对等的语言?这本书讲的是“生产者/消费者”,但当我听到这个词时,我想到了线程。
什么是发电机,你为什么要用它?显然,没有引用任何书籍(除非你能直接从一本书中找到一个体面、简单的答案)。如果你慷慨的话,还可以举个例子!
我正在阅读Python烹饪书,目前正在研究生成器。我觉得很难理解。
由于我有Java背景,那么Java中是否有对等的语言?这本书讲的是“生产者/消费者”,但当我听到这个词时,我想到了线程。
什么是发电机,你为什么要用它?显然,没有引用任何书籍(除非你能直接从一本书中找到一个体面、简单的答案)。如果你慷慨的话,还可以举个例子!
当前回答
使用列表推导式的经验表明,它们在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
其他回答
首先,术语生成器最初在Python中定义不清,导致了很多混乱。你可能指的是迭代器和可迭代对象(参见这里)。然后在Python中还有生成器函数(返回生成器对象)、生成器对象(迭代器)和生成器表达式(求值为生成器对象)。
根据generator的术语表条目,现在的官方术语似乎是generator是“generator function”的缩写。在过去,文档对术语的定义不一致,但幸运的是,这个问题已经得到了解决。
在没有进一步说明的情况下,精确地避免使用术语“生成器”可能仍然是一个好主意。
对于Stephan202的回答,我唯一能补充的是建议您看一看David Beazley的PyCon '08演示文稿“生成器技巧给系统程序员”,这是我所见过的关于如何以及为什么使用生成器的最好的解释。这就是让我从“Python看起来很有趣”变成“这就是我一直在寻找的东西”的原因。网址是http://www.dabeaz.com/generators/。
它有助于明确区分函数foo和生成器foo(n):
def foo(n):
yield n
yield n+1
Foo是一个函数。 Foo(6)是一个生成器对象。
使用生成器对象的典型方式是在循环中:
for n in foo(6):
print(n)
循环打印
# 6
# 7
可以将生成器视为可恢复函数。
Yield的行为类似于return,产生的值被生成器“返回”。然而,与return不同的是,下一次生成器被请求一个值时,生成器的函数foo将从它停止的地方恢复——在最后一个yield语句之后——并继续运行,直到遇到另一个yield语句。
在幕后,当您调用bar=foo(6)时,生成器对象bar为您定义了一个下一个属性。
你可以自己调用它来获取foo产生的值:
next(bar) # Works in Python 2.6 or Python 3.x
bar.next() # Works in Python 2.5+, but is deprecated. Use next() if possible.
当foo结束时(并且没有更多的输出值),调用next(bar)将抛出StopInteration错误。
这篇文章将使用斐波那契数作为工具来解释Python生成器的有用性。
这篇文章将同时介绍c++和Python代码。
斐波那契数列定义为:0,1,1,2,3,5,8,13,21,34,....
或者概括地说:
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2
这可以非常容易地转换为c++函数:
size_t Fib(size_t n)
{
//Fib(0) = 0
if(n == 0)
return 0;
//Fib(1) = 1
if(n == 1)
return 1;
//Fib(N) = Fib(N-2) + Fib(N-1)
return Fib(n-2) + Fib(n-1);
}
但是如果你想打印前六个斐波那契数,你将需要用上面的函数重新计算大量的值。
例如:Fib(3) = Fib(2) + Fib(1),但Fib(2)也会重新计算Fib(1)。你想计算的值越高,你的情况就越糟。
因此,人们可能会试图通过跟踪main中的状态来重写上面的内容。
// Not supported for the first two elements of Fib
size_t GetNextFib(size_t &pp, size_t &p)
{
int result = pp + p;
pp = p;
p = result;
return result;
}
int main(int argc, char *argv[])
{
size_t pp = 0;
size_t p = 1;
std::cout << "0 " << "1 ";
for(size_t i = 0; i <= 4; ++i)
{
size_t fibI = GetNextFib(pp, p);
std::cout << fibI << " ";
}
return 0;
}
但这是非常丑陋的,它使我们的逻辑变得复杂。在我们的main函数中不用担心状态会更好。
我们可以返回一个值的向量,并使用迭代器遍历该值集,但对于大量的返回值,这需要一次性占用大量内存。
回到我们以前的方法,如果我们想做一些除了打印数字之外的事情会发生什么?我们必须在main中复制并粘贴整个代码块,并将输出语句更改为我们想要做的任何事情。 如果你复制粘贴代码,你就会被枪毙。你不想中枪,对吧?
为了解决这些问题,并避免被击中,我们可以使用回调函数重写这段代码。每次遇到新的斐波那契数时,我们都会调用回调函数。
void GetFibNumbers(size_t max, void(*FoundNewFibCallback)(size_t))
{
if(max-- == 0) return;
FoundNewFibCallback(0);
if(max-- == 0) return;
FoundNewFibCallback(1);
size_t pp = 0;
size_t p = 1;
for(;;)
{
if(max-- == 0) return;
int result = pp + p;
pp = p;
p = result;
FoundNewFibCallback(result);
}
}
void foundNewFib(size_t fibI)
{
std::cout << fibI << " ";
}
int main(int argc, char *argv[])
{
GetFibNumbers(6, foundNewFib);
return 0;
}
这显然是一个改进,你在main中的逻辑不再那么混乱,你可以对斐波那契数做任何你想做的事情,简单地定义新的回调。
但这仍然不完美。如果你只想得到前两个斐波那契数,然后做一些事情,然后再得到更多,然后再做其他事情呢?
好吧,我们可以像之前那样继续,我们可以再次开始在main中添加state,允许GetFibNumbers从任意点开始。 但是这将进一步膨胀我们的代码,对于像打印斐波那契数这样的简单任务来说,它看起来已经太大了。
我们可以通过几个线程实现生产者和消费者模型。但是这会使代码更加复杂。
我们来讨论一下生成器。
Python有一个很好的语言特性,可以解决这些叫做生成器的问题。
生成器允许您执行一个函数,在任意点停止,然后在停止的地方再次继续。 每次返回一个值。
考虑下面使用生成器的代码:
def fib():
pp, p = 0, 1
while 1:
yield pp
pp, p = p, pp+p
g = fib()
for i in range(6):
g.next()
这给了我们结果:
0 1 1 2 3 5
yield语句与Python生成器一起使用。它保存函数的状态并返回生成的值。下次在生成器上调用next()函数时,它将继续执行yield停止的地方。
这比回调函数代码要简洁得多。我们有更干净的代码,更小的代码,更不用说更多的功能代码(Python允许任意大的整数)。
源
我给出了这段代码,解释了关于生成器的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)