我开始学习Python,我遇到了生成器函数,那些在它们中有yield语句的函数.我想知道这些函数真正擅长解决哪些类型的问题.
发电机给你懒惰的评价.您可以通过迭代遍历它们,使用'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
引入概念的地方,yield
声明从function
左边的位置恢复.
def generate_integers(N): for i in xrange(N): yield i
In [1]: gen = generate_integers(3) In [2]: genIn [3]: gen.next() 0 In [4]: gen.next() 1 In [5]: gen.next()
这就是Python中return
和yield
语句之间的区别.
Yield语句是使函数成为生成函数的原因.
因此,生成器是一个用于创建迭代器的简单而强大的工具.它们像常规函数一样编写,但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
而不是yield
,则整个函数一旦到达返回就会结束.
return - returns only once yield - returns multiple times
如果函数使用关键字,yield
那么它就是一个生成器.
现在你可以像这样迭代:
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
关于生成器的可爱之处在于它们封装状态而无需经历创建对象的环节.思考它们的一种方式是记住其内部状态的"功能".
我从Python生成器获得了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
使用生成器,您还可以对无限序列进行迭代,这在迭代容器时当然是不可能的.
我最喜欢的用途是"过滤"和"减少"操作.
假设我们正在读取一个文件,并且只想要以"##"开头的行.
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示例类似.假设我们有一个文件,我们需要找到
行块.[不是HTML标签,但是看起来像标签的行.]
def reduceLocation( aSequence ): keep= False block= None for line in aSequence: if line.startswith("同样,我们可以在适当的for循环中使用此生成器.
source = file( ... ) for b in reduceLocation( source.readlines() ): print b source.close()我们的想法是生成器函数允许我们过滤或减少序列,一次产生一个值的另一个序列.
`fileobj.readlines()`会将整个文件读入内存中的列表,从而破坏了使用生成器的目的.由于文件对象已经可以迭代,你可以在your_generator(fileobject)中使用`for b:'代替.这样,您的文件将一次读取一行,以避免读取整个文件.
10> Pithikos..:您可以使用生成器的一个实际示例是,如果您有某种形状,并且想要迭代其角落,边缘或其他任何东西.对于我自己的项目(这里是源代码)我有一个矩形:
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()
.它使用__iter__
起来更优雅,因为我们可以直接在for
表达式中使用类实例名称.
11> MvdD..:在迭代输入维护状态时基本上避免回调函数.
请参阅此处和此处,了解使用生成器可以完成的操作.