在Python中使用
例如,我试图理解这段代码1:
1 2 3 4 5 | def _get_child_candidates(self, distance, min_dist, max_dist): if self._leftchild and distance - max_dist < self._median: yield self._leftchild if self._rightchild and distance + max_dist >= self._median: yield self._rightchild |
这是来电者:
1 2 3 4 5 6 7 8 | result, candidates = [], [self] while candidates: node = candidates.pop() distance = node._get_dist(obj) if distance <= max_dist and distance >= min_dist: result.extend(node._values) candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) return result |
调用方法
<子>1. 代码来自Jochen Schulz (jrschulz),他为度量空间创建了一个很棒的Python库。这是到完整源代码的链接:Module mspace.
要理解
iterable
当您创建一个列表时,您可以逐个读取它的项。逐项读取其项称为迭代:
1 2 3 4 5 6 | >>> mylist = [1, 2, 3] >>> for i in mylist: ... print(i) 1 2 3 |
1 2 3 4 5 6 | >>> mylist = [x*x for x in range(3)] >>> for i in mylist: ... print(i) 0 1 4 |
您可以在上面使用"
这些迭代器非常方便,因为您可以随心所欲地读取它们,但是您将所有的值都存储在内存中,当您有很多值时,这并不总是您想要的。
<发电机/ hh2 >
生成器是迭代器,这种迭代器只能迭代一次。生成器不会将所有值都存储在内存中,它们会动态生成这些值:
1 2 3 4 5 6 | >>> mygenerator = (x*x for x in range(3)) >>> for i in mygenerator: ... print(i) 0 1 4 |
除了使用
产量
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> def createGenerator(): ... mylist = range(3) ... for i in mylist: ... yield i*i ... >>> mygenerator = createGenerator() # create a generator >>> print(mygenerator) # mygenerator is an object! <generator object createGenerator at 0xb7555c34> >>> for i in mygenerator: ... print(i) 0 1 4 |
在这里,这是一个无用的例子,但是当您知道函数将返回一组只需要读取一次的值时,这是很方便的。
要掌握
然后,每次
现在是最难的部分:
一旦函数运行,生成器就被认为是空的,但是不再命中
代码解释
发电机:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # Here you create the method of the node object that will return the generator def _get_child_candidates(self, distance, min_dist, max_dist): # Here is the code that will be called each time you use the generator object: # If there is still a child of the node object on its left # AND if distance is ok, return the next child if self._leftchild and distance - max_dist < self._median: yield self._leftchild # If there is still a child of the node object on its right # AND if distance is ok, return the next child if self._rightchild and distance + max_dist >= self._median: yield self._rightchild # If the function arrives here, the generator will be considered empty # there is no more than two values: the left and the right children |
打电话者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # Create an empty list and a list with the current object reference result, candidates = list(), [self] # Loop on candidates (they contain only one element at the beginning) while candidates: # Get the last candidate and remove it from the list node = candidates.pop() # Get the distance between obj and the candidate distance = node._get_dist(obj) # If distance is ok, then you can fill the result if distance <= max_dist and distance >= min_dist: result.extend(node._values) # Add the children of the candidate in the candidates list # so the loop will keep running until it will have looked # at all the children of the children of the children, etc. of the candidate candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) return result |
这段代码包含几个智能部分:
循环在一个列表上迭代,但是当循环被迭代时,列表会扩展:-)这是一种简洁的方法来遍历所有这些嵌套数据,即使它有点危险,因为您可能会得到一个无限循环。在本例中,
通常我们传递一个列表给它:
1 2 3 4 5 | >>> a = [1, 2] >>> b = [3, 4] >>> a.extend(b) >>> print(a) [1, 2, 3, 4] |
但在你的代码中,它有一个生成器,这很好,因为:
您不需要读取值两次。你可能有很多孩子,你不想把他们都存储在内存中。它之所以有效是因为Python并不关心方法的参数是否为列表。Python希望使用迭代器,所以它可以处理字符串、列表、元组和生成器!这就是所谓的duck typing,这也是Python如此酷的原因之一。但这是另一个故事,另一个问题……
你可以停在这里,或者读一点,看看发电机的高级用法:
控制生成器耗尽1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | >>> class Bank(): # Let's create a bank, building ATMs ... crisis = False ... def create_atm(self): ... while not self.crisis: ... yield"$100" >>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want >>> corner_street_atm = hsbc.create_atm() >>> print(corner_street_atm.next()) $100 >>> print(corner_street_atm.next()) $100 >>> print([corner_street_atm.next() for cash in range(5)]) ['$100', '$100', '$100', '$100', '$100'] >>> hsbc.crisis = True # Crisis is coming, no more money! >>> print(corner_street_atm.next()) <type 'exceptions.StopIteration'> >>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs >>> print(wall_street_atm.next()) <type 'exceptions.StopIteration'> >>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty >>> print(corner_street_atm.next()) <type 'exceptions.StopIteration'> >>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business >>> for cash in brand_new_atm: ... print cash $100 $100 $100 $100 $100 $100 $100 $100 $100 ... |
注意:对于python3,使用
它可以用于控制对资源的访问等各种事情。
Itertools,你最好的朋友itertools模块包含一些特殊的函数来操作iterables。有没有想过复制一个生成器?链两个发电机?用一行程序将嵌套列表中的值分组?
一个例子吗?让我们来看看四马比赛的可能顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | >>> horses = [1, 2, 3, 4] >>> races = itertools.permutations(horses) >>> print(races) <itertools.permutations object at 0xb754f1dc> >>> print(list(itertools.permutations(horses))) [(1, 2, 3, 4), (1, 2, 4, 3), (1, 3, 2, 4), (1, 3, 4, 2), (1, 4, 2, 3), (1, 4, 3, 2), (2, 1, 3, 4), (2, 1, 4, 3), (2, 3, 1, 4), (2, 3, 4, 1), (2, 4, 1, 3), (2, 4, 3, 1), (3, 1, 2, 4), (3, 1, 4, 2), (3, 2, 1, 4), (3, 2, 4, 1), (3, 4, 1, 2), (3, 4, 2, 1), (4, 1, 2, 3), (4, 1, 3, 2), (4, 2, 1, 3), (4, 2, 3, 1), (4, 3, 1, 2), (4, 3, 2, 1)] |
了解迭代的内部机制
迭代是一个包含迭代器(实现
在本文中有更多关于
到Grokking
当你看到一个带有
这个技巧可以让您了解函数背后的逻辑,但是使用
首先,迭代器协议——当您编写时
1 2 | for x in mylist: ...loop body... |
Python执行以下两个步骤:
获取
调用
(这是大多数人忘记告诉你的一步)
使用迭代器循环项目:
在步骤1返回的迭代器上继续调用
事实上,Python在任何时候想要循环对象的内容时都会执行上面的两个步骤——所以它可以是for循环,但也可以是像
这里
这就是迭代器协议,很多对象都实现了这个协议:
内置列表,字典,元组,集,文件。实现注意,
1 2 3 4 5 6 7 | def f123(): yield 1 yield 2 yield 3 for item in f123(): print item |
如果在
因此生成器对象有点像适配器——在一端,它通过公开
为什么使用发电机?
通常可以编写不使用生成器但实现相同逻辑的代码。一个选择是使用我之前提到的临时列表"技巧"。这并不是在所有情况下都有效,例如,如果您有无限循环,或者当您有一个非常长的列表时,它可能会使内存的使用效率降低。另一种方法是实现一个新的迭代类
这样想:
迭代器只是一个好听的术语,用来表示具有next()方法的对象。屈服函数最终是这样的:
原始版本:
1 2 3 4 5 6 | def some_function(): for i in xrange(4): yield i for i in some_function(): print i |
这基本上就是Python解释器对上面代码所做的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class it: def __init__(self): # Start at -1 so that we get 0 when we add 1 below. self.count = -1 # The __iter__ method will be called once by the 'for' loop. # The rest of the magic happens on the object returned by this method. # In this case it is the object itself. def __iter__(self): return self # The next method will be called repeatedly by the 'for' loop # until it raises StopIteration. def next(self): self.count += 1 if self.count < 4: return self.count else: # A StopIteration exception is raised # to signal that the iterator is done. # This is caught implicitly by the 'for' loop. raise StopIteration def some_func(): return it() for i in some_func(): print i |
要了解幕后发生了什么,
1 2 3 4 5 6 | iterator = some_func() try: while 1: print iterator.next() except StopIteration: pass |
这更有意义还是让你更困惑?:)
我应该指出,出于说明的目的,这是一种过度简化。:)
简而言之:生成器是一个延迟的、增量挂起的列表,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | generator = myYieldingFunction(...) x = list(generator) generator v [x[0], ..., ???] generator v [x[0], x[1], ..., ???] generator v [x[0], x[1], x[2], ..., ???] StopIteration exception [x[0], x[1], x[2]] done list==[x[0], x[1], x[2]] |
例子
让我们定义一个与Python的
1 2 3 4 5 6 7 8 9 | def makeRange(n): # return 0,1,2,...,n-1 i = 0 while i < n: yield i i += 1 >>> makeRange(5) <generator object makeRange at 0x19e4aa0> |
要强制生成器立即返回它的挂起值,您可以将它传递到
1 2 | >>> list(makeRange(5)) [0, 1, 2, 3, 4] |
比较示例与"只是返回一个列表"
上面的例子可以被认为仅仅是创建一个列表,你添加并返回:
1 2 3 4 5 6 7 8 9 10 11 12 | # list-version # # generator-version def makeRange(n): # def makeRange(n): """return [0,1,2,...,n-1]""" #~ """return 0,1,2,...,n-1""" TO_RETURN = [] #> i = 0 # i = 0 while i < n: # while i < n: TO_RETURN += [i] #~ yield i i += 1 # i += 1 ## indented return TO_RETURN #> >>> makeRange(5) [0, 1, 2, 3, 4] |
不过有一个主要的区别;参见最后一节。
如何使用生成器iterable是列表理解的最后一部分,所有的生成器都是iterable的,所以它们通常是这样使用的:
1 2 3 | # _ITERABLE_ >>> [x+10 for x in makeRange(5)] [10, 11, 12, 13, 14] |
为了更好地理解生成器,您可以尝试使用
请注意:生成器实际上可以用于更多的事情,例如实现协程、非确定性编程或其他优雅的事情。然而,我在这里提出的"懒惰列表"观点是您会发现的最常见的用法。
幕后这就是"Python迭代协议"的工作原理。也就是说,当您执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | >>> x=iter(range(5)) >>> next(x) 0 >>> next(x) 1 >>> next(x) 2 >>> next(x) 3 >>> next(x) 4 >>> next(x) Traceback (most recent call last): File"<stdin>", line 1, in <module> StopIteration |
内置函数
细节
正常情况下,大多数人不会在意下面的区别,可能想要停止阅读这里。
在python语言中,迭代器是任何"理解for循环概念"的对象,比如列表
当您从列表中请求一个迭代器时,它将创建一个新的迭代器。然而,当您从迭代器请求一个迭代器时(您很少这样做),它只会给您一个自身的副本。
因此,在不太可能的情况下,您不能做这样的事情……
1 2 3 4 5 | > x = myRange(5) > list(x) [0, 1, 2, 3, 4] > list(x) [] |
…然后记住生成器是迭代器;也就是说,它是一次性的。如果您想重用它,您应该再次调用
What does the
yield keyword do in Python?
<
回答提纲/总结/ hh1 >调用带有yield 的函数时,将返回生成器。生成器是迭代器,因为它们实现了迭代器协议,所以您可以对它们进行迭代。生成器还可以发送信息,使其在概念上成为协同程序。在python3中,您可以使用yield from 在两个方向上从一个生成器委托给另一个生成器。(附录中给出了几个答案,包括最上面的一个,并讨论了在生成器中使用return 。)
发电机:
生成器的概念来自其他语言(参见脚注1),它们的实现各不相同。在Python的生成器中,代码的执行被冻结在生成点。当调用生成器(方法将在下面讨论)时,执行将恢复,然后在下一个yield中冻结。
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> def func(): ... yield 'I am' ... yield 'a generator!' ... >>> type(func) # A function with yield is still a function <type 'function'> >>> gen = func() >>> type(gen) # but it returns a generator <type 'generator'> >>> hasattr(gen, '__iter__') # that's an iterable True >>> hasattr(gen, 'next') # and with .next (.__next__ in Python 3) True # implements the iterator protocol. |
生成器类型是迭代器的子类型:
1 2 3 | >>> import collections, types >>> issubclass(types.GeneratorType, collections.Iterator) True |
如果有必要,我们可以这样打字检查:
1 2 3 4 | >>> isinstance(gen, types.GeneratorType) True >>> isinstance(gen, collections.Iterator) True |
1 2 3 4 | >>> list(gen) ['I am', 'a generator!'] >>> list(gen) [] |
如果你想再次使用它的功能,你必须再做一个(见脚注2):
1 2 | >>> list(func()) ['I am', 'a generator!'] |
可以通过编程方式生成数据,例如:
1 2 3 | def func(an_iterable): for item in an_iterable: yield item |
上面的简单生成器也等价于下面的——从Python 3.3开始(在Python 2中不可用),您可以使用
1 2 | def func(an_iterable): yield from an_iterable |
但是,
协同程序:
这里有一个例子,注意
1 2 3 4 5 6 7 8 9 | def bank_account(deposited, interest_rate): while True: calculated_interest = interest_rate * deposited received = yield calculated_interest if received: deposited += received >>> my_account = bank_account(1000, .05) |
首先,我们必须使用内置函数
1 2 3 | >>> first_year_interest = next(my_account) >>> first_year_interest 50.0 |
现在我们可以把数据发送到生成器。(发送
1 2 3 | >>> next_year_interest = my_account.send(first_year_interest + 1000) >>> next_year_interest 102.5 |
与
现在,回想一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | def money_manager(expected_rate): under_management = yield # must receive deposited value while True: try: additional_investment = yield expected_rate * under_management if additional_investment: under_management += additional_investment except GeneratorExit: '''TODO: write function to send unclaimed funds to state''' finally: '''TODO: write function to mail tax info to client''' def investment_account(deposited, manager): '''very simple model of an investment account that delegates to a manager''' next(manager) # must queue up manager manager.send(deposited) while True: try: yield from manager except GeneratorExit: return manager.close() |
现在我们可以把功能委托给子生成器,它可以被使用由一个如上所述的发电机:
1 2 3 4 5 6 7 8 | >>> my_manager = money_manager(.06) >>> my_account = investment_account(1000, my_manager) >>> first_year_return = next(my_account) >>> first_year_return 60.0 >>> next_year_return = my_account.send(first_year_return + 1000) >>> next_year_return 123.6 |
您可以在PEP 380中阅读更多关于
1 | >>> my_account.close() |
您还可以抛出一个可以在生成器中处理的异常或传送回用户:
1 2 3 4 5 6 7 8 9 10 | >>> import sys >>> try: ... raise ValueError ... except: ... my_manager.throw(*sys.exc_info()) ... Traceback (most recent call last): File"<stdin>", line 4, in <module> File"<stdin>", line 2, in <module> ValueError |
结论
我认为我已经涵盖了以下问题的所有方面:
What does the
yield keyword do in Python?
结果是
评论顶部/接受的答案**它混淆了什么使一个迭代,仅仅使用一个列表作为例子。请参阅上面的参考文献,但总的来说:iterable有一个返回迭代器的
语法目前允许列表理解中的任何表达式。
1 2 3 4 5 | expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) | ('=' (yield_expr|testlist_star_expr))*) ... yield_expr: 'yield' [yield_arg] yield_arg: 'from' test | testlist |
由于yield是一个表达式,一些人认为在理解或生成器表达式中使用它很有趣——尽管没有引用特别好的用例。
CPython核心开发人员正在讨论取消它的许可。以下是邮件列表中的相关邮件:
On 30 January 2017 at 19:05, Brett Cannon wrote:
On Sun, 29 Jan 2017 at 16:39 Craig Rodrigues wrote:
I'm OK with either approach. Leaving things the way they are in Python 3
is no good, IMHO.我的意见是,这是一个SyntaxError,因为您没有得到您期望的结果语法。
我同意这是我们结束的一个明智的地方,就像任何代码一样依赖当前的行为实在是太聪明了易于维护。
为了达到这个目标,我们可能会想:
3.7中的SyntaxWarning或DeprecationWarning2.7.x中的Py3k警告SyntaxError在3.8干杯,尼克。
——Nick Coghlan | ncoghlan在澳大利亚布里斯班| gmail.com上报道
此外,还有一个突出的问题(10544),它似乎指向了一个方向,即这从来都不是一个好主意(PyPy是用Python编写的Python实现,它已经发出了语法警告)。
底线是,除非CPython的开发人员告诉我们其他情况:不要将
在Python中2:
In a generator function, the
return statement is not allowed to include anexpression_list . In that context, a barereturn indicates that the generator is done and will causeStopIteration to be raised.
在Python 3:
In a generator function, the
return statement indicates that the generator is done and will causeStopIteration to be raised. The returned value (if any) is used as an argument to constructStopIteration and becomes theStopIteration.value attribute.
脚注
提案中引用了CLU、Sather和Icon三种语言将生成器的概念引入Python。总的思路是一个函数可以保持内部状态并产生中间状态数据点根据用户的需要而定。这在性能上肯定会更好到其他方法,包括Python线程,这在某些系统上甚至不可用。
例如,这意味着
<子>
在您的代码中,函数
还有一件事要提一下:一个函数的收益率实际上不必终止。我写的代码是这样的:
1 2 3 4 5 | def fib(): last, cur = 0, 1 while True: yield cur last, cur = cur, last + cur |
然后我可以在其他代码中这样使用它:
1 2 3 | for f in fib(): if some_condition: break coolfuncs(f); |
它确实有助于简化一些问题,使一些事情更容易处理。
对于那些喜欢最小工作示例的人,请考虑一下这个交互式Python会话:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | >>> def f(): ... yield 1 ... yield 2 ... yield 3 ... >>> g = f() >>> for i in g: ... print i ... 1 2 3 >>> for i in g: ... print i ... >>> # Note that this time nothing was printed |
博士TL;
而不是这样的:
1 2 3 4 5 6 | def square_list(n): the_list = [] # Replace for x in range(n): y = x * x the_list.append(y) # these return the_list # lines |
这样做:
1 2 3 4 | def square_yield(n): for x in range(n): y = x * x yield y # with this one. |
无论什么时候,当你发现自己从头开始构建一个列表时,
这是我第一次屈服地"啊哈"。
build a series of stuff
同样的行为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> for square in square_list(4): ... print(square) ... 0 1 4 9 >>> for square in square_yield(4): ... print(square) ... 0 1 4 9 |
不同的行为:
Yield是单遍:您只能迭代一次。当一个函数有一个收益率时,我们称它为生成器函数。迭代器就是它返回的东西。这些术语很能说明问题。我们失去了容器的便利性,但获得了按需要计算的级数的幂,而且任意长。
收益率是懒惰的,它推迟了计算。一个有收益率的函数在你调用它的时候根本不会执行。它返回一个迭代器对象,该对象会记住它在哪里停止。每次在迭代器上调用
收益率是多才多艺的。数据不必全部存储在一起,可以一次存储一个。它可以是无限的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> def squares_all_of_them(): ... x = 0 ... while True: ... yield x * x ... x += 1 ... >>> squares = squares_all_of_them() >>> for _ in range(4): ... print(next(squares)) ... 0 1 4 9 |
如果您需要多个遍历,且级数不太长,只需调用
1 2 | >>> list(square_yield(4)) [0, 1, 4, 9] |
选择
yield — produce or provide (as in agriculture)
…提供本系列中的下一个数据。
yield — give way or relinquish (as in political power)
…在迭代器前进之前放弃CPU执行。
屈服给你一个发电机。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def get_odd_numbers(i): return range(1, i, 2) def yield_odd_numbers(i): for x in range(1, i, 2): yield x foo = get_odd_numbers(10) bar = yield_odd_numbers(10) foo [1, 3, 5, 7, 9] bar <generator object yield_odd_numbers at 0x1029c6f50> bar.next() 1 bar.next() 3 bar.next() 5 |
可以看到,在第一种情况下,
在第二种情况下,
再一次,这是一个很不自然的例子,如果你真的想计算到500亿,你可能会使用itertools。:)
这是生成器最简单的用例。正如您所说,它可以用来编写有效的排列,使用yield将内容向上推入调用堆栈,而不是使用某种堆栈变量。生成器还可以用于特殊的树遍历,以及其他所有方式。
它返回一个发电机。我对Python不是特别熟悉,但是如果您熟悉的话,我相信它和c#的迭代器块是一样的。
关键的思想是编译器/解释器/不管做什么,只要调用者关心,他们可以继续调用next(),它将继续返回值——就像生成器方法被暂停一样。现在很明显,你不能真正地"暂停"一个方法,所以编译器会为你建立一个状态机,让你记住你现在在哪里,以及局部变量是什么样子的等等。这比自己编写迭代器容易得多。
在描述如何使用生成器的众多答案中,有一种类型的答案我觉得还没有给出。下面是编程语言理论的答案:
Python中的
在编程语言理论中,延续是一种更为基础的计算,但它们并不经常被使用,因为它们非常难以推理,也非常难以实现。但延拓的概念很简单:它是尚未完成的计算的状态。在这种状态下,保存变量的当前值、尚未执行的操作等等。然后在程序稍后的某个时刻可以调用continuation,这样程序的变量就会重置为该状态,并执行保存的操作。
延续,以这种更一般的形式,可以通过两种方式实现。在
在延续传递风格(CPS)中,延续只是普通函数(仅在函数为第一类的语言中),程序员显式地管理这些函数并将其传递给子例程。在这种风格中,程序状态由闭包(以及恰好在其中编码的变量)表示,而不是驻留在堆栈上某个位置的变量。管理控制流的函数接受延续作为参数(在CPS的某些变体中,函数可能接受多个延续),并通过简单地调用它们并在调用后返回来操作控制流。延续传递风格的一个非常简单的例子如下:
1 2 3 4 5 | def save_file(filename): def write_file_continuation(): write_stuff_to_file(filename) check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation) |
在这个(非常简单的)示例中,程序员将实际编写文件的操作保存为一个延续(这可能是一个非常复杂的操作,有许多细节需要编写),然后传递这个延续(i。e,作为一级闭包)到另一个操作符,该操作符执行更多的处理,然后在必要时调用它。(我在实际的GUI编程中经常使用这种设计模式,因为它可以节省代码行,更重要的是,可以在GUI事件触发后管理控制流。)
这篇文章的其余部分将把延续概念化为CPS,而不会失去概括性,因为它非常容易理解和阅读。
现在让我们讨论Python中的生成器。生成器是延续的特定子类型。而延续通常能够保存计算的状态(例如。,生成器只能通过迭代器保存迭代的状态。不过,对于生成器的某些用例,这个定义有点误导。例如:
1 2 3 | def f(): while True: yield 4 |
这显然是一个合理的迭代器,它的行为定义得很好——每次生成器对它进行迭代时,它都会返回4(并且永远如此)。但是,当想到迭代器(即迭代器)时,它可能不是脑海中出现的可迭代的原型类型。,
重申:延续可以保存程序堆栈的状态,生成器可以保存迭代的状态。这意味着延续要比生成器强大得多,但生成器也要简单得多。它们对于语言设计人员更容易实现,对于程序员也更容易使用(如果您有一些时间,请尝试阅读和理解关于延续和调用/cc的这一页)。
但你可以很容易地实现(和概念化)生成器作为一个简单的,特定的情况下的延续传递风格:
无论何时调用
1 2 3 4 5 6 7 8 | class Generator(): def __init__(self,iterable,generatorfun): self.next_continuation = lambda:generatorfun(iterable) def next(self): value, next_continuation = self.next_continuation() self.next_continuation = next_continuation return value |
其中
1 2 3 4 5 | def generatorfun(iterable): if len(iterable) == 0: raise StopIteration else: return (iterable[0], lambda:generatorfun(iterable[1:])) |
请记住,这只是伪代码,Python中生成器的实际实现更加复杂。但是,作为理解正在发生的事情的练习,尝试使用延续传递样式来实现生成器对象,而不使用
这里有一个简单语言的例子。我将提供高级人类概念与低级Python概念之间的对应关系。
我想对一串数字进行运算,但我不想在创建这个数列时麻烦自己,我只想专注于我想做的运算。所以,我这样做:
我打电话给你,告诉你我想要一个数字序列,它以特定的方式产生,我让你知道算法是什么。这个步骤对应于
此步骤对应于调用生成器函数,该函数返回生成器对象。注意,你还没有告诉我任何数字;你只要拿起纸和铅笔。我问你,"告诉我下一个数字",你告诉我第一个数字;之后,你等着我问你下一个号码。你的工作是记住你在哪里,你已经说过哪些数字,以及下一个数字是什么。我不在乎细节。
此步骤对应于对生成器对象调用
此步骤对应生成器对象结束其任务,并引发
这就是生成器所做的(包含
迭代器协议最著名的用户是Python中的
1 | for item in sequence: |
无论
注意,
要获得更准确的信息,请阅读Python文档中的迭代器类型、yield语句和生成器。
虽然有很多答案显示了为什么要使用
为了帮助理解
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | >>> def coroutine(): ... i = -1 ... while True: ... i += 1 ... val = (yield i) ... print("Received %s" % val) ... >>> sequence = coroutine() >>> sequence.next() 0 >>> sequence.next() Received None 1 >>> sequence.send('hello') Received hello 2 >>> sequence.close() |
还有另一个
1 | yield from <expr> |
PEP 380—用于委托给子生成器的语法:
A syntax is proposed for a generator to delegate part of its operations to another generator. This allows a section of code containing 'yield' to be factored out and placed in another generator. Additionally, the subgenerator is allowed to return with a value, and the value is made available to the delegating generator.
The new syntax also opens up some opportunities for optimisation when one generator re-yields values produced by another.
此外,这将介绍(自Python 3.5以来):
1 2 3 | async def new_coroutine(data): ... await blocking_action() |
为了避免协程与常规生成器混淆(现在在两者中都使用
所有的答案都很好,但是对于新手来说有点难。
我想你已经学过
作为类比,
Try to get a num_list with
return .
1 2 3 | def num_list(n): for i in range(n): return i |
运行该程序:
1 2 | In [5]: num_list(3) Out[5]: 0 |
看,你只得到一个数字,而不是它们的列表。
There comes
yield
将
1 2 3 4 5 6 7 8 9 10 | In [10]: def num_list(n): ...: for i in range(n): ...: yield i ...: In [11]: num_list(3) Out[11]: <generator object num_list at 0x10327c990> In [12]: list(num_list(3)) Out[12]: [0, 1, 2] |
现在,你赢了,得到所有的数字。
与只运行一次并停止的
One more step we can rewrite
yield statement withreturn
1 2 3 4 5 6 7 8 | In [15]: def num_list(n): ...: result = [] ...: for i in range(n): ...: result.append(i) ...: return result In [16]: num_list(3) Out[16]: [0, 1, 2] |
它是关于
列表
您将始终从列表对象中获取[0,1,2],但只能从'the object
最后,作为对grok的隐喻:
下面是一些Python示例,演示如何实际实现生成器,就好像Python没有为它们提供语法糖一样:
作为Python生成器:
1 2 3 4 5 6 7 8 9 | from itertools import islice def fib_gen(): a, b = 1, 1 while True: yield a a, b = b, a + b assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5)) |
使用词法闭包而不是生成器
1 2 3 4 5 6 7 8 9 10 11 12 13 | def ftake(fnext, last): return [fnext() for _ in xrange(last)] def fib_gen2(): #funky scope due to python2.x workaround #for python 3.x use nonlocal def _(): _.a, _.b = _.b, _.a + _.b return _.a _.a, _.b = 0, 1 return _ assert [1,1,2,3,5] == ftake(fib_gen2(), 5) |
使用对象闭包而不是生成器(因为closuresandobjectsareequal)
1 2 3 4 5 6 7 8 9 10 | class fib_gen3: def __init__(self): self.a, self.b = 1, 1 def __call__(self): r = self.a self.a, self.b = self.b, self.a + self.b return r assert [1,1,2,3,5] == ftake(fib_gen3(), 5) |
我本打算发布"阅读Beazley's 'Python: Essential Reference'的第19页来快速描述生成器",但是其他很多人已经发布了很好的描述。
另外,请注意
生成器和协程是设置数据流类型应用程序的一种很酷的方法。我认为了解
从编程的角度来看,迭代器被实现为thunks。
要将迭代器、生成器和线程池作为thunks(也称为匿名函数)来实现并发执行,可以使用发送到闭包对象的消息,该对象有一个dispatcher, dispatcher回答"messages"。
http://en.wikipedia.org/wiki/Message_passing
"next"是发送到由"iter"调用创建的闭包的消息。
有很多方法可以实现这种计算。我使用了变异,但是不使用变异很容易做到,方法是返回当前值和下一个yield。
下面是一个使用R6RS结构的演示,但是其语义与Python完全相同。这是相同的计算模型,只需要修改语法就可以用Python重写它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 Welcome to Racket v6.5.0.3.
-> (define gen
(lambda (l)
(define yield
(lambda ()
(if (null? l)
'END
(let ((v (car l)))
(set! l (cdr l))
v))))
(lambda(m)
(case m
('yield (yield))
('init (lambda (data)
(set! l data)
'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->
下面是一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def isPrimeNumber(n): print"isPrimeNumber({}) call".format(n) if n==1: return False for x in range(2,n): if n % x == 0: return False return True def primes (n=1): while(True): print"loop step ---------------- {}".format(n) if isPrimeNumber(n): yield n n += 1 for n in primes(): if n> 10:break print"wiriting result {}".format(n) |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | loop step ---------------- 1 isPrimeNumber(1) call loop step ---------------- 2 isPrimeNumber(2) call loop step ---------------- 3 isPrimeNumber(3) call wiriting result 3 loop step ---------------- 4 isPrimeNumber(4) call loop step ---------------- 5 isPrimeNumber(5) call wiriting result 5 loop step ---------------- 6 isPrimeNumber(6) call loop step ---------------- 7 isPrimeNumber(7) call wiriting result 7 loop step ---------------- 8 isPrimeNumber(8) call loop step ---------------- 9 isPrimeNumber(9) call loop step ---------------- 10 isPrimeNumber(10) call loop step ---------------- 11 isPrimeNumber(11) call |
我不是Python开发人员,但在我看来,
这似乎是一个有趣的和良好的能力:D
这是
我喜欢把线程看作是一个堆栈(即使它没有以这种方式实现)。
当调用一个普通函数时,它将其局部变量放在堆栈上,执行一些计算,然后清除堆栈并返回。它的局部变量的值再也看不到了。
对于一个
所以这是一个固定的函数,这个函数是生成器所依赖的。
当随后调用
比较以下例子:
1 2 3 4 5 6 7 8 9 | def normalFunction(): return if False: pass def yielderFunction(): return if False: yield 12 |
当我们调用第二个函数时,它的行为与第一个函数非常不同。
1 2 | >>> yielderFunction() <generator object yielderFunction at 0x07742D28> |
调用
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> gen = yielderFunction() >>> dir(gen) ['__class__', ... '__iter__', #Returns gen itself, to make it work uniformly with containers ... #when given to a for loop. (Containers return an iterator instead.) 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', #The method that runs the function's body. 'send', 'throw'] |
正如每个答案所暗示的,
1 2 3 | def getNextLines(): while con.isOpen(): yield con.read() |
你可以在你的代码中使用它,如下:
1 2 | for line in getNextLines(): doSomeThing(line) |
执行控制转移收到
在执行yield时,执行控制将从getNextLines()转移到
因此,简而言之,函数具有以下代码
1 2 3 4 5 6 7 8 | def simpleYield(): yield"first time" yield"second time" yield"third time" yield"Now some useful value {}".format(12) for i in simpleYield(): print i |
将打印
1 2 3 4 | "first time" "second time" "third time" "Now some useful value 12" |
屈服是一个对象
函数中的
如果希望函数返回大量值,请使用
更重要的是,
like barrier in the CUDA language, it will not transfer control until it gets
completed.
也就是说,它将从头运行函数中的代码,直到到达
然后,每个其他调用将再次运行您在函数中编写的循环,返回下一个值,直到没有任何值可以返回为止。
(我下面的回答只是从使用Python生成器的角度,而不是生成器机制的底层实现的角度,后者涉及堆栈和堆操作的一些技巧。)
当在python函数中使用
(现在我想谈谈
据我所知,当我们想要处理一堆数据时,我们通常首先把数据存储在某个地方,然后一个一个地处理它。但这种天真的做法是有问题的。如果数据量很大,那么预先将它们作为一个整体存储将非常昂贵。因此,与其直接存储
有两种方法可以包装这样的元数据。
在OO方法中,我们封装元数据无论哪种方法,都会创建一个迭代器,即某个对象,它可以提供您想要的数据。OO方法可能有点复杂。无论如何,使用哪一个取决于您。
总之,
1 2 3 4 5 6 7 | def simple_generator(): yield 'one' yield 'two' yield 'three' for i in simple_generator(): print i |
简单地输出
1 2 3 | one two three |
能量来自于使用带有计算序列的循环的生成器,生成器每次执行停止循环以"生成"下一个计算结果,这样它就可以动态地计算一个列表,好处是为特别大型的计算节省了内存
假设你想创建一个你自己的
1 2 3 4 5 6 7 | def myRangeNaive(i): n = 0 range = [] while n < i: range.append(n) n = n + 1 return range |
像这样使用它;
1 2 | for i in myRangeNaive(10): print i |
但这是低效的,因为
创建一个只使用一次的数组(这会浪费内存)这段代码实际上在数组上循环了两次!:(幸运的是,圭多和他的团队足够慷慨,开发了发电机,所以我们可以这样做;
1 2 3 4 5 6 7 8 9 | def myRangeSmart(i): n = 0 while n < i: yield n n = n + 1 return for i in myRangeSmart(10): print i |
现在,在每次迭代时,生成器上的一个名为
许多人使用
下面是一个例子,
return (in function)
1 2 3 4 5 6 7 8 | import random def return_dates(): dates = [] # With 'return' you need to create a list then return it for i in range(5): date = random.choice(["1st","2nd","3rd","4th","5th","6th","7th","8th","9th","10th"]) dates.append(date) return dates |
yield (in function)
1 2 3 4 5 | def yield_dates(): for i in range(5): date = random.choice(["1st","2nd","3rd","4th","5th","6th","7th","8th","9th","10th"]) yield date # 'yield' makes a generator automatically which works # in a similar way. This is much more efficient. |
Calling functions
1 2 3 4 5 6 7 8 9 | dates_list = return_dates() print(dates_list) for i in dates_list: print(i) dates_generator = yield_dates() print(dates_generator) for i in dates_generator: print(i) |
这两个函数都做同样的事情,但是
This is the result from the code:
可以看到,这两个函数做的是相同的事情。唯一的区别是
一个实际的例子可能是逐行读取文件,或者只是想生成一个生成器。
这里有一个简单的基于
1 2 3 4 5 | def fib(limit=50): a, b = 0, 1 for i in range(limit): yield b a, b = b, a+b |
当您在REPL中输入这个并尝试调用它时,您将得到一个令人困惑的结果:
1 2 | >>> fib() <generator object fib at 0x7fa38394e3b8> |
这是因为
那么,如何生成这些值呢?这可以通过使用内置函数
使用内置的
1 2 3 4 5 6 7 8 9 10 11 | >>> g = fib() >>> next(g) 1 >>> next(g) 1 >>> next(g) 2 >>> next(g) 3 >>> next(g) 5 |
间接地,如果你提供
1 2 3 4 5 | results = [] for i in fib(30): # consumes fib results.append(i) # can also be accomplished with results = list(fib(30)) # consumes fib |
类似地,使用
1 2 | >>> tuple(fib(5)) # consumes fib (1, 1, 2, 3, 5) |
生成器与函数的区别在于它是惰性的。它通过维护它的本地状态并允许您在需要时随时恢复来实现这一点。
当您第一次调用
1 | f = fib() |
Python编译函数,遇到
然后,当您请求它生成第一个值时,它直接或间接地执行它找到的所有语句,直到遇到
1 2 3 4 5 6 7 | def yielder(value): """ This is an infinite generator. Only use next on it""" while 1: print("I'm going to generate the value for you") print("Then I'll pause for a while") yield value print("Let's go through it again.") |
现在,请输入REPL:
1 | >>> gen = yielder("Hello, yield!") |
您现在有一个生成器对象,它正在等待一个命令来生成一个值。使用
1 2 3 4 | >>> next(gen) # runs until it finds a yield I'm going to generate the value for you Then I'll pause for a while 'Hello, yield!' |
未引用的结果是打印出来的。引用的结果是从
1 2 3 4 5 | >>> next(gen) # continues from yield and runs again Let's go through it again. I'm going to generate the value for you Then I'll pause for a while 'Hello, yield!' |
生成器记得它在
一个很容易解释的简单例子:
1 2 3 4 5 6 7 8 | def f123(): for _ in range(4): yield 1 yield 2 for i in f123(): print i |
的输出是:
1 | 1 2 1 2 1 2 1 2 |
另一个TL,博士
list上的迭代器:
迭代器生成器:
您可以通过调用
注意:生成器不是一个正常的函数。它像本地变量(堆栈)一样记住以前的状态。详细说明请参阅其他答案或文章。生成器只能迭代一次。您可以不使用
收益率与收益率相似。不同之处在于:
yield使函数可迭代(在下面的示例中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def isprime(n): if n == 1: return False for x in range(2, n): if n % x == 0: return False else: return True def primes(n = 1): while(True): if isprime(n): yield n n += 1 for n in primes(): if n > 100: break print(n) |
在上面的例子中,如果
1 | n += 1 |
这里所有的答案都很棒;但是其中只有一个(投票最多的一个)与代码的工作方式有关。另一些则与生成器有关,以及它们是如何工作的。
我不会重复生成器是什么,收益率是什么;我认为这些都被现有的答案所覆盖。然而,在花了几个小时试图理解与您的代码类似的代码之后,我将分解它的工作原理。
您的代码将遍历一个二叉树结构。以这棵树为例:
1 2 3 4 5 | 5 / \ 3 6 / \ \ 1 4 8 |
另一个更简单的二进制搜索树遍历实现:
1 2 3 4 5 6 7 8 9 10 11 12 | class Node(object): .. def __iter__(self): if self.has_left_child(): for child in self.left: yield child yield self.val if self.has_right_child(): for child in self.right: yield child |
执行代码在
1 2 3 4 5 6 7 8 9 | def __iter__(self): class EmptyIter(): def next(self): raise StopIteration if self.root: return self.root.__iter__() return EmptyIter() |
1 2 3 | it = iter(TreeObj) # returns iter(self.root) which calls self.root.__iter__() for element in it: .. process element .. |
因为
这里的问题是,每次迭代都创建子迭代器来遍历树,并保存当前迭代器的状态。一旦到达终点,它将遍历堆栈,并以正确的顺序返回值(首先返回最小的yield值)。
您的代码示例用一种不同的技术做了类似的事情:它为每个子对象填充一个元素列表,然后在下一次迭代时弹出它并在当前对象上运行函数代码(因此是
我希望这对这个传奇的话题有所贡献。我花了好几个小时来画这个过程来理解它。
简而言之,yield的用法类似于关键字return,只是它返回一个生成器。生成器对象只遍历一次。
收益有两个好处:
您不需要读取这些值两次;您可以获得许多子节点,而不需要将它们全部放入内存。在Python中,
因此,我们可以在每次
这里有一个实例:
1 2 3 4 5 6 7 8 9 10 11 | def getPrimes(number): while True: if isPrime(number): number = yield number # a miracle occurs here number += 1 def printSuccessivePrimes(iterations, base=10): primeGenerator = getPrimes(base) primeGenerator.send(None) for power in range(iterations): print(primeGenerator.send(base ** power)) |
产量
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> def create_generator(): ... my_list = range(3) ... for i in my_list: ... yield i*i ... >>> my_generator = create_generator() # create a generator >>> print(my_generator) # my_generator is an object! <generator object create_generator at 0xb7555c34> >>> for i in my_generator: ... print(i) 0 1 4 |
简而言之,您可以看到循环不会停止,甚至在发送对象或变量之后还会继续运行(不像
这里有一个链接,看看产量的真正作用,也是在代。发电机,Yield关键字- Python中央(PC)
此外,
一个类比可以帮助我们理解这个概念:
想象一下,你创造了一台神奇的机器,它能够每天产生成千上万个灯泡。这台机器用一个独特的序列号在盒子里产生这些灯泡。你没有足够的空间同时储存所有这些灯泡。,由于存储空间的限制,您无法跟上机器的速度),因此您希望调整此机器以按需生成灯泡。
Python生成器与这个概念没有太大区别。
假设您有一个函数
机的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def barcode_generator(): serial_number = 10000 # Initial barcode while True: yield serial_number serial_number += 1 barcode = barcode_generator() while True: number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate?")) barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)] print(barcodes) # function_to_create_the_next_batch_of_lightbulbs(barcodes) produce_more = input("Produce more? [Y/n]:") if produce_more =="n": break |
正如您所看到的,我们有一个自包含的"函数"来每次生成下一个惟一的序列号。这个函数返回一个生成器!正如您所看到的,我们不是每次需要一个新的序列号时都调用该函数,而是使用
输出:
1 2 3 4 5 6 7 8 9 | How many lightbulbs to generate? 5 [10000, 10001, 10002, 10003, 10004] Produce more? [Y/n]: y How many lightbulbs to generate? 6 [10005, 10006, 10007, 10008, 10009, 10010] Produce more? [Y/n]: y How many lightbulbs to generate? 7 [10011, 10012, 10013, 10014, 10015, 10016, 10017] Produce more? [Y/n]: n |
简单地说,yield类似于return值,但它对Generator有效。
在简单的yield中,返回生成器对象而不是值。
下面简单的例子将有所帮助!
1 2 3 4 5 6 7 8 | def sim_generator(): for i in range(3): yield(i) obj = sim_generator() next(obj) # another way is obj.__next__() next(obj) next(obj) |
上面的代码返回0,1,2
甚至短
1 2 | for val in sim_generator(): print(val) |
返回0,1,2
希望这有助于
1 2 3 4 5 6 | In [4]: def make_cake(numbers): ...: for i in range(numbers): ...: yield 'Cake {}'.format(i) ...: In [5]: factory = make_cake(5) |
在这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | In [7]: next(factory) Out[7]: 'Cake 0' In [8]: next(factory) Out[8]: 'Cake 1' In [9]: next(factory) Out[9]: 'Cake 2' In [10]: next(factory) Out[10]: 'Cake 3' In [11]: next(factory) Out[11]: 'Cake 4' |
他们吃光了所有的蛋糕,但又要了一块。
1 2 3 4 5 6 7 | In [12]: next(factory) --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-12-0f5c45da9774> in <module> ----> 1 next(factory) StopIteration: |
他们被告知不要再问了。所以一旦你消耗了一个发电机,你就完成了。如果你想要更多的蛋糕,你需要再打一次电话。这就像下了另一个杯子蛋糕的订单。
1 2 3 4 5 6 7 8 | In [13]: factory = make_cake(3) In [14]: for cake in factory: ...: print(cake) ...: Cake 0 Cake 1 Cake 2 |
您还可以使用for循环和上面的生成器。
再举一个例子:假设你想要一个随机密码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | In [22]: import random In [23]: import string In [24]: def random_password_generator(): ...: while True: ...: yield ''.join([random.choice(string.ascii_letters) for _ in range(8)]) ...: In [25]: rpg = random_password_generator() In [26]: for i in range(3): ...: print(next(rpg)) ...: FXpUBhhH DdUDHoHn dvtebEqG In [27]: next(rpg) Out[27]: 'mJbYRMNo' |
这里是一个生成器
一个简单的生成器函数
1 2 3 4 5 6 7 8 9 10 11 12 13 | def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n |
yield语句暂停保存函数的所有状态,然后在以后的调用中继续。
https://www.programiz.com/python-programming/generator