题
我开始学习Python和我遇到了发电机的功能,那些具有产率的发言。我想要知道什么类型的问题,这些功能是真的好解决。
解决方案
生成器为您提供惰性评估。您可以通过迭代遍历它们,使用'for'显式地或通过将其传递给任何迭代的函数或构造来隐式地使用它们。您可以将生成器视为返回多个项目,就好像它们返回一个列表一样,但不是一次性返回它们,而是一个一个地返回它们,并且生成器函数暂停,直到请求下一个项目为止。
生成器适用于计算大量结果(特别是涉及循环本身的计算),其中您不知道是否需要所有结果,或者您不希望为所有结果分配内存同一时间。或者对于生成器使用另一个生成器或消耗其他一些资源的情况,如果发生的时间更晚,则更方便。
生成器的另一个用途(实际上是相同的)是用迭代替换回调。在某些情况下,您希望函数执行大量工作,并偶尔向调用者报告。传统上你会使用回调函数。您将此回调传递给工作函数,它会定期调用此回调。生成器方法是工作函数(现在是生成器)对回调一无所知,只是在它想要报告某些内容时产生。调用者不是编写单独的回调并将其传递给工作函数,而是在生成器周围进行一些“for”循环。
例如,假设你写了一个'文件系统搜索'程序。您可以完整地执行搜索,收集结果,然后一次显示一个。在显示第一个结果之前,必须收集所有结果,并且所有结果将同时存储在内存中。或者您可以在找到结果时显示结果,这样可以提高内存效率并且对用户更友好。后者可以通过将结果打印功能传递给文件系统搜索功能来完成,也可以通过将搜索功能作为生成器并迭代结果来完成。
如果您想查看后两种方法的示例,请参阅os.path.walk()(带回调的旧文件系统 - 行走函数)和os.walk()(新文件系统 - 行走生成器)。当然,如果你真的想在列表中收集所有结果,那么生成器方法很容易转换为大名单方法:
big_list = list(the_generator)
其他提示
使用发电机的一个原因是为某种解决方案使解决方案更清晰。
另一种方法是一次处理一个结果,避免构建大量的结果列表,无论如何都要处理这些结果。
如果你有像这样的fibonacci-up-to-n函数:
# function version
def fibon(n):
a = b = 1
result = []
for i in xrange(n):
result.append(a)
a, b = b, a + b
return result
您可以更轻松地编写此功能:
# generator version
def fibon(n):
a = b = 1
for i in xrange(n):
yield a
a, b = b, a + b
功能更清晰。如果你使用这样的功能:
for x in fibon(1000000):
print x,
在此示例中,如果使用生成器版本,则根本不会创建整个1000000项目列表,一次只创建一个值。使用列表版本时不会出现这种情况,首先会创建一个列表。
参见<!>“动机<!>”; PEP 255 中的部分。
生成器的非显而易见的用途是创建可中断的功能,这使您可以执行更新UI或运行多个作业<!>同时<!>“; (实际上是交错的),而不使用线程。
我发现这个解释清除了我的怀疑。因为有可能不知道的人Generators
也不知道yield
<强>返回强>
return语句是销毁所有局部变量并将结果值返回(返回)给调用者的地方。如果稍后调用相同的函数,该函数将获得一组新的变量。
收益强>
但是如果退出函数时局部变量没有丢弃怎么办?这意味着我们可以resume the function
我们离开的地方。这是引入generators
概念的地方,function
语句在return
停止的地方重新开始。
def generate_integers(N):
for i in xrange(N):
yield i
In [1]: gen = generate_integers(3)
In [2]: gen
<generator object at 0x8117f90>
In [3]: gen.next()
0
In [4]: gen.next()
1
In [5]: gen.next()
这就是Python中<=>和<=>语句之间的区别。
Yield语句是使函数成为生成函数的原因。
因此,生成器是一个用于创建迭代器的简单而强大的工具。它们像常规函数一样编写,但只要想要返回数据,它们就会使用<=>语句。每次调用next()时,生成器都会从它停止的位置恢复(它会记住所有数据值以及上次执行的语句)。
真实世界示例
假设您的MySQL表中有1亿个域名,并且您希望更新每个域的Alexa排名。
您首先需要从数据库中选择您的域名。
假设您的表名是domains
且列名是domain
。
如果使用SELECT domain FROM domains
,它将返回1亿行,这会占用大量内存。所以你的服务器可能会崩溃。
所以你决定批量运行程序。假设我们的批量大小是1000。
在我们的第一批中,我们将查询前1000行,检查每个域的Alexa排名并更新数据库行。
在我们的第二批中,我们将处理接下来的1000行。在我们的第三批中,它将从2001年到3000年,依此类推。
现在我们需要一个生成我们批次的生成器函数。
这是我们的发电机功能:
def ResultGenerator(cursor, batchsize=1000):
while True:
results = cursor.fetchmany(batchsize)
if not results:
break
for result in results:
yield result
正如您所看到的,我们的功能可以保持yield
结果。如果您使用关键字return
而不是<=>,则整个函数一旦返回就会结束。
return - returns only once
yield - returns multiple times
如果函数使用关键字<=>,那么它就是生成器。
现在你可以像这样迭代:
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
doSomethingWith(result)
db.close()
缓冲。如果以大块的形式获取数据是有效的,但是以小块的形式处理它,那么生成器可能有所帮助:
def bufferedFetch():
while True:
buffer = getBigChunkOfData()
# insert some code to break on 'end of data'
for i in buffer:
yield i
以上功能可让您轻松地将缓冲与处理分开。消费者功能现在可以逐个获取值,而无需担心缓冲。
我发现生成器在清理代码方面非常有用,并且为您提供了一种非常独特的方法来封装和模块化代码。在某种情况下,您需要根据自己的内部处理不断吐出值,并且需要从代码中的任何位置调用某些内容(例如,不仅仅是在循环或块中),生成器要使用的功能。
一个抽象的例子是Fibonacci数字生成器,它不在一个循环中,当从任何地方调用它时总会返回序列中的下一个数字:
def fib():
first = 0
second = 1
yield first
yield second
while 1:
next = first + second
yield next
first = second
second = next
fibgen1 = fib()
fibgen2 = fib()
现在您有两个Fibonacci数字生成器对象,您可以从代码中的任何位置调用它们,它们将始终按顺序返回更大的Fibonacci数字:
>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5
关于生成器的可爱之处在于它们封装状态而无需经历创建对象的环节。考虑它们的一种方法是<!> quot; functions <!> quot;记得他们的内心状态。
我从 Python Generators - 获得了Fibonacci示例它们是什么? 并且只需要一点点想象力,您就可以提出许多其他情况,其中生成器可以替代for
循环和其他传统迭代结构。
简单的解释:考虑一个 for
声明
for item in iterable:
do_stuff()
很多的时间,所有项目 iterable
并不需要有从一开始,但可以产生在飞行因为他们必需的。这可以是一个很大的更有效的在这两个
- 空间(你永远不需要储存的所有项目的同时)和
- 时间(迭代可能之前完成所有项目都需要)。
其他时间,你甚至不知道的所有项目的时间提前。例如:
for command in user_input():
do_stuff_with(command)
你有没有办法知道的所有用户的事先命令,但是可以使用一个很好的环喜欢这个,如果你有一个发生器交给你的命令:
def user_input():
while True:
wait_for_command()
cmd = get_command()
yield cmd
有发电机还可以有迭代过无限的序列,这当然是不可能的,当迭代的集装箱。
我最喜欢的用途是<!> quot; filter <!> quot;和<!> quot; reduce <!> quot;操作
假设我们正在读取一个文件,并且只想要以<!>“## <!>”开头的行。
def filter2sharps( aSequence ):
for l in aSequence:
if l.startswith("##"):
yield l
然后我们可以在适当的循环中使用生成器函数
source= file( ... )
for line in filter2sharps( source.readlines() ):
print line
source.close()
reduce示例类似。假设我们有一个文件,我们需要找到<Location>...</Location>
行的块。 [不是HTML标签,而是恰好看起来像标签的行。]
def reduceLocation( aSequence ):
keep= False
block= None
for line in aSequence:
if line.startswith("</Location"):
block.append( line )
yield block
block= None
keep= False
elif line.startsWith("<Location"):
block= [ line ]
keep= True
elif keep:
block.append( line )
else:
pass
if block is not None:
yield block # A partial block, icky
同样,我们可以在适当的for循环中使用此生成器。
source = file( ... )
for b in reduceLocation( source.readlines() ):
print b
source.close()
这个想法是生成器函数允许我们过滤或减少一个序列,一次产生一个值的另一个序列。
一个可以使用生成器的实际示例是,如果您有某种形状,并且想要迭代其角落,边缘或其他任何东西。对于我自己的项目(源代码这里)我有一个矩形:
class Rect():
def __init__(self, x, y, width, height):
self.l_top = (x, y)
self.r_top = (x+width, y)
self.r_bot = (x+width, y+height)
self.l_bot = (x, y+height)
def __iter__(self):
yield self.l_top
yield self.r_top
yield self.r_bot
yield self.l_bot
现在我可以创建一个矩形并在其角上循环:
myrect=Rect(50, 50, 100, 100)
for corner in myrect:
print(corner)
而不是__iter__
您可以使用方法iter_corners
并使用for corner in myrect.iter_corners()
调用该方法。使用for
更优雅,因为我们可以直接在<=>表达式中使用类实例名称。
一些很好的答案在这里,但是,我也建议一个完整的读的蟒蛇 编程功能的教程 这有助于解释的一些更有效的使用情况的发电机。
- 特别有意思的是,它现在可以 更新的产量变量,从外部发电机的功能, 因此,使它能够创造的动态和相互交织的协同程序相对较少的努力。
- 也见 PEP342:协同程序通过增强的发电机 更多的信息。
当我们的Web服务器充当代理时,我使用生成器:
- 客户端从服务器请求代理网址
- 服务器开始加载目标网址
- 服务器收到后立即将结果返回给客户 醇>
由于没有提到发生器的发送方法,这里有一个例子:
def test():
for i in xrange(5):
val = yield
print(val)
t = test()
# Proceed to 'yield' statement
next(t)
# Send value to yield
t.send(1)
t.send('2')
t.send([3])
它显示了向正在运行的生成器发送值的可能性。以下视频中有关生成器的更高级课程(包括yield
来自探索,并行处理的生成器,转发递归限制等)
成堆的东西。任何时候你想要生成一系列项目,但不想一次将它们“实现”到一个列表中。例如,您可以使用一个返回素数的简单生成器:
def primes():
primes_found = set()
primes_found.add(2)
yield 2
for i in itertools.count(1):
candidate = i * 2 + 1
if not all(candidate % prime for prime in primes_found):
primes_found.add(candidate)
yield candidate
然后,您可以使用它来生成后续素数的产品:
def prime_products():
primeiter = primes()
prev = primeiter.next()
for prime in primeiter:
yield prime * prev
prev = prime
这些是相当简单的例子,但你可以看到它如何在不事先生成它们的情况下处理大型(可能无限!)数据集,这只是一个更明显的用途。
也适用于打印素数到n:
def genprime(n=10):
for num in range(3, n+1):
for factor in range(2, num):
if num%factor == 0:
break
else:
yield(num)
for prime_num in genprime(100):
print(prime_num)