首页 文章

“yield”关键字有什么作用?

提问于
浏览
8739

Python中 yield 关键字的用途是什么?它有什么作用?

例如,我试图理解这个代码1:

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

这是来电者:

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

调用方法 _get_child_candidates 时会发生什么?列表是否返回?单个元素?它又被召唤了吗?后续通话何时停止?


1.代码来自Jochen Schulz(jrschulz),他为度量空间创建了一个很棒的Python库 . 这是完整源代码的链接:模块mspace .

30 回答

  • 273

    yield 关键字简化为两个简单的事实:

    • 如果编译器在函数内的任何位置检测到 yield 关键字,则该函数不再通过 return 语句返回 . Instead ,它 immediately 返回 lazy "pending list" object 称为生成器

    • 生成器是可迭代的 . 什么是可迭代的?它类似于 listsetrange 或dict-view,具有用于按特定顺序访问每个元素的内置协议 .

    简而言之: a generator is a lazy, incrementally-pending listyield statements allow you to use function notation to program the list values 生成器应逐渐吐出 .

    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]]
    

    示例

    让我们定义一个函数 makeRange ,'s just like Python' s range . 呼叫 makeRange(n) 退回发电机:

    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>
    

    要强制生成器立即返回其挂起值,您可以将其传递给 list() (就像您可以任意迭代一样):

    >>> list(makeRange(5))
    [0, 1, 2, 3, 4]
    

    比较“只返回列表”的示例

    上面的例子可以被认为只是创建一个你追加并返回的列表:

    # 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_
    >>> [x+10 for x in makeRange(5)]
    [10, 11, 12, 13, 14]
    

    为了更好地感受生成器,您可以使用 itertools 模块(确保在保证时使用 chain.from_iterable 而不是 chain ) . 例如,您甚至可以使用生成器来实现无限长的惰性列表,如 itertools.count() . 您可以实现自己的 def enumerate(iterable): zip(count(), iterable) ,或者在while循环中使用 yield 关键字实现 .

    请注意:生成器实际上可以用于更多的东西,例如implementing coroutines或非确定性编程或其他优雅的东西 . 但是,我在这里提出的观点是你会发现的最常见的用途 .


    在幕后

    这就是"Python iteration protocol"的工作原理 . 也就是说,当你做 list(makeRange(5)) 时会发生什么 . 这就是我之前描述的"lazy, incremental list" .

    >>> 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
    

    内置函数 next() 只调用对象 .next() 函数,它是"iteration protocol"的一部分,可以在所有迭代器上找到 . 您可以手动使用 next() 函数(以及迭代协议的其他部分)来实现花哨的东西,通常以牺牲可读性为代价,因此尽量避免这样做......


    Minutiae

    通常情况下,大多数人不会关心以下区别,可能想在这里停止阅读 .

    在Python语言中,iterable是"understands the concept of a for-loop"像列表 [1,2,3] 的任何对象,迭代器是所请求的for循环的特定实例,如 [1,2,3].__iter__() . 生成器与任何迭代器完全相同,除了它的编写方式(使用函数语法) .

    从列表中请求迭代器时,它会创建一个新的迭代器 . 但是,当您从迭代器(您很少这样做)请求迭代器时,它只会为您提供自身的副本 .

    因此,万一你没有做到这样的事情......

    > x = myRange(5)
    > list(x)
    [0, 1, 2, 3, 4]
    > list(x)
    []
    

    ...然后记住发电机是一个迭代器;也就是说,它是一次性的 . 如果要重复使用它,则应再次调用 myRange(...) . 如果需要使用结果两次,请将结果转换为列表并将其存储在变量 x = list(myRange(5)) 中 . 那些绝对需要克隆生成器的人(例如,谁正在做可怕的hackish元编程)如果绝对必要的话可以使用itertools.tee,因为可复制的迭代器Python PEP标准提案已被推迟 .

  • 57

    像每个答案所暗示的那样, yield 用于创建序列生成器 . 它用于动态生成一些序列 . 例如,在网络上逐行读取文件时,可以使用 yield 函数,如下所示:

    def getNextLines():
       while con.isOpen():
           yield con.read()
    

    您可以在代码中使用它,如下所示:

    for line in getNextLines():
        doSomeThing(line)
    

    Execution Control Transfer gotcha

    执行yield时,执行控件将从getNextLines()传送到 for 循环 . 因此,每次调用getNextLines()时,执行都从上次暂停的位置开始 .

    因此简而言之,具有以下代码的功能

    def simpleYield():
        yield "first time"
        yield "second time"
        yield "third time"
        yield "Now some useful value {}".format(12)
    
    for i in simpleYield():
        print i
    

    将打印

    "first time"
    "second time"
    "third time"
    "Now some useful value 12"
    
  • 191

    Grokking收益率的快捷方式

    当你看到一个带有 yield 语句的函数时,应用这个简单的技巧来理解会发生什么:

    • 在函数开头插入一行 result = [] .

    • result.append(expr) 替换每个 yield expr .

    • 在底部插入一条 return result 行功能 .

    • 耶 - 没有 yield 陈述!阅读并找出代码 .

    • 将功能与原始定义进行比较 .

    这个技巧可以让你了解函数背后的逻辑,但 yield 实际发生的情况与基于列表的方法中发生的情况明显不同 . 在许多情况下,yield方法将更高效,更快 . 在其他情况下,即使原始函数工作得很好,这个技巧也会让你陷入无限循环 . 请继续阅读以了解更多信息...

    不要混淆你的Iterables,Iterators和Generators

    首先, iterator protocol - 你写的时候

    for x in mylist:
        ...loop body...
    

    Python执行以下两个步骤:

    • 获取 mylist 的迭代器:

    调用 iter(mylist) - >返回一个带有 next() 方法的对象(或Python 3中的 __next__() ) .

    [这是大多数人忘记告诉你的步骤]

    • 使用迭代器循环遍历项目:

    继续在步骤1返回的迭代器上调用 next() 方法 . 将 next() 的返回值赋给 x 并执行循环体 . 如果从 next() 中引发异常 StopIteration ,则表示迭代器中没有更多值,并且退出循环 .

    事实是Python只要想要循环对象的内容就执行上述两个步骤 - 所以它可能是一个for循环,但它也可能是像 otherlist.extend(mylist) 这样的代码(其中 otherlist 是一个Python列表) .

    这里 mylist 是一个可迭代的,因为它实现了迭代器协议 . 在用户定义的类中,您可以实现 __iter__() 方法以使类的实例可迭代 . 此方法应返回迭代器 . 迭代器是一个带有 next() 方法的对象 . 可以在同一个类上实现 __iter__()next() ,并使 __iter__() 返回 self . 这适用于简单的情况,但是当您希望两个迭代器同时循环遍历同一个对象时 .

    所以这是迭代器协议,许多对象实现了这个协议:

    • 内置列表,词典,元组,集合,文件 .

    • 用户定义的实现 __iter__() 的类 .

    • 发电机 .

    注意 for 循环没有't know what kind of object it'处理它 - 它只是遵循迭代器协议,并且很高兴得到项目,因为它调用 next() . 内置列表逐个返回它们的项目,字典逐个返回键,文件一个接一个地返回行等 . 然后生成器返回......那就是 yield 所在的位置:

    def f123():
        yield 1
        yield 2
        yield 3
    
    for item in f123():
        print item
    

    而不是 yield 语句,如果在 f123() 中有三个 return 语句,则只会执行第一个语句,并且该函数将退出 . 但 f123() 不是普通的功能 . 调用 f123() 时,它不会返回yield语句中的任何值!它返回一个生成器对象 . 此外,该功能并没有真正退出 - 它进入暂停状态 . 当 for 循环尝试遍历生成器对象时,该函数在之前返回的 yield 之后的最后一行从其挂起状态恢复,执行下一行代码,在本例中为 yield 语句,并将其返回为下一个项目 . 这一过程发生,直到函数退出,此时生成器引发 StopIteration ,循环退出 .

    所以生成器对象有点像适配器 - 在一端它展示了迭代器协议,通过暴露 __iter__()next() 方法来保持 for 循环满意 . 然而,在另一端,它运行该功能足以从中获取下一个值,并将其重新置于挂起模式 .

    为什么使用发电机?

    通常你可以编写一些我之前提到过的代码 . 这并不适用于所有情况,例如如果你有无限循环,或者当你有一个非常长的列表时它可能会低效地使用内存 . 另一种方法是实现一个新的可迭代类 SomethingIter ,它将状态保存在实例成员中,并在它的 next() (或Python3中的 __next__() )方法中执行下一个逻辑步骤 . 根据逻辑, next() 方法中的代码可能看起来非常复杂并且容易出错 . 这里的发电机提供了一个简洁的解决方案

  • 142

    我将发布“阅读Beazley的'Python:Essential Reference'第19页,以便快速描述发生器”,但是很多其他人已经发布了很好的描述 .

    另外,请注意 yield 可以在协同程序中用作它们在生成器函数中使用的对偶 . 虽然它与您的代码片段的用法不同,但 (yield) 可以用作函数中的表达式 . 当调用者使用 send() 方法向方法发送值时,协程将执行,直到遇到下一个 (yield) 语句 .

    生成器和协同程序是设置数据流类型应用程序的一种很酷的方法 . 我认为在函数中知道 yield 语句的其他用法是值得的 .

  • 82

    所有伟大的答案,但是新手有点困难 .

    我假设你已经学会了 return 声明 .

    作为类比, returnyield 是双胞胎 . return 表示'return and stop'而'yield` means '返回,但继续'

    尝试使用return返回num_list .

    def num_list(n):
        for i in range(n):
            return i
    

    运行:

    In [5]: num_list(3)
    Out[5]: 0
    

    看,你只得到一个数字而不是它们的列表 . return 永远不会让你高兴,只执行一次并退出 .

    收益率

    return 替换为 yield

    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]
    

    现在,你赢了所有的数字 .

    比较 return 运行一次并停止, yield 运行您计划的时间 . 您可以将 return 解释为 return one of them ,将 yield 解释为 return all of them . 这叫做 iterable .

    还有一步我们可以用return重写yield语句

    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]
    

    这是 yield 的核心 .

    列表 return 输出与对象 yield 输出之间的区别是:

    您将始终从列表对象获取[0,1,2],但只能从“对象 yield 输出”中检索一次 . 因此,它具有 Out[11]: <generator object num_list at 0x10327c990> 中显示的新名称 generator 对象 .

    总之,作为一个隐喻它的隐喻:

    • returnyield 是双胞胎

    • listgenerator 是双胞胎

  • 138

    这是 yield 所做的心理形象 .

    我喜欢将一个线程视为具有堆栈(即使它没有以这种方式实现) .

    当调用普通函数时,它将其局部变量放在堆栈上,进行一些计算,然后清除堆栈并返回 . 其局部变量的值再也看不到了 .

    使用 yield 函数,当其代码开始运行时(即在调用函数之后,返回一个生成器对象,然后调用其 next() 方法),它同样将其局部变量放入堆栈并计算一段时间 . 但是,当它到达 yield 语句时,在清除其部分堆栈并返回之前,它会获取其局部变量的快照并将它们存储在生成器对象中 . 它还会在其代码中写下它当前所处的位置(即特定的 yield 语句) .

    所以它是发电机悬挂的一种冻结功能 .

    当随后调用 next() 时,它会将函数的所有物检索到堆栈中并重新设置动画 . 该功能继续从它停止的地方进行计算,不知道它刚刚在冷库中度过了一个永恒的事实 .

    比较以下示例:

    def normalFunction():
        return
        if False:
            pass
    
    def yielderFunction():
        return
        if False:
            yield 12
    

    当我们调用第二个函数时,它的行为与第一个函数的行为非常不同 . yield 语句可能无法访问,但如果's present anywhere, it changes the nature of what we'正在处理 .

    >>> yielderFunction()
    <generator object yielderFunction at 0x07742D28>
    

    为了便于阅读,使用 yielder 前缀来调用 yielderFunction() 不是't run its code, but makes a generator out of the code. (Maybe it'是个好主意 . )

    >>> 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']
    

    gi_codegi_frame 字段是存储冻结状态的位置 . 用 dir(..) 探索它们,我们可以确认我们上面的心理模型是可信的 .

  • 1709

    对于那些喜欢最小工作示例的人,请冥想这个交互式的Python会话:

    >>> 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
    
  • 102

    还有另一个 yield 用法和含义(自Python 3.3起):

    yield from <expr>
    

    来自PEP 380 -- Syntax for Delegating to a Subgenerator

    建议生成器将其部分操作委托给另一个生成器 . 这允许将包含'yield'的一段代码分解出来并放在另一个生成器中 . 此外,允许子生成器返回一个值,该值可供委派生成器使用 . 当一个生成器重新生成另一个生成器生成的值时,新语法也会为优化提供一些机会 .

    此外this将介绍(自Python 3.5):

    async def new_coroutine(data):
       ...
       await blocking_action()
    

    避免协程与常规生成器混淆(今天 yield 用于两者) .

  • 42

    总之, yield 语句将您的函数转换为一个工厂,该工厂生成一个名为 generator 的特殊对象,它包裹原始函数的主体 . 当 generator 被迭代时,它会执行你的函数,直到它到达下一个 yield 然后暂停执行并计算传递给 yield 的值 . 它在每次迭代时重复此过程,直到执行路径退出函数 . 例如,

    def simple_generator():
        yield 'one'
        yield 'two'
        yield 'three'
    
    for i in simple_generator():
        print i
    

    简单的输出

    one
    two
    three
    

    电源来自使用带有计算序列的循环的发生器,发生器每次执行循环停止以“产生”下一个计算结果,这样它就可以动态计算列表,其好处是内存保存用于特别大的计算

    假设你想创建一个自己的 range 函数,它产生一个可迭代的数字范围,你可以这样做,

    def myRangeNaive(i):
        n = 0
        range = []
        while n < i:
            range.append(n)
            n = n + 1
        return range
    

    并像这样使用它;

    for i in myRangeNaive(10):
        print i
    

    但这是低效的,因为

    • 您创建一个只使用一次的数组(这会浪费内存)

    • 这段代码实际上循环遍历该数组两次! :(

    幸运的是,Guido和他的团队足够慷慨地开发发电机,所以我们可以做到这一点;

    def myRangeSmart(i):
        n = 0
        while n < i:
           yield n
           n = n + 1
        return
    
    for i in myRangeSmart(10):
        print i
    

    现在每个人迭代一个名为 next() 的生成器上的函数执行该函数,直到它到达'yield'语句,在该语句中它停止并且'yields'该值或到达函数的结尾 . 在这种情况下,在第一次调用时, next() 执行到yield语句并产生'n',在下一次调用时它将执行增量语句,跳回到'while',对其进行评估,如果为true,它将停止并再次产生'n' ,它将继续这种方式,直到while条件返回false并且生成器跳转到函数的末尾 .

  • 80

    这是一个简单的基于_134822的方法,用于计算斐波纳契数列,解释如下:

    def fib(limit=50):
        a, b = 0, 1
        for i in range(limit):
           yield b
           a, b = b, a+b
    

    当你在REPL中输入它然后尝试调用它时,你会得到一个神秘的结果:

    >>> fib()
    <generator object fib at 0x7fa38394e3b8>
    

    这是因为您希望创建一个生成器,即根据需要生成值的对象,向Python发出 yield 信号 .

    那么,你如何生成这些值?这可以通过使用内置函数 next 直接完成,也可以通过将其提供给消耗值的构造来间接完成 .

    使用内置的 next() 函数,直接调用 .next / __next__ ,强制生成器生成一个值:

    >>> g = fib()
    >>> next(g)
    1
    >>> next(g)
    1
    >>> next(g)
    2
    >>> next(g)
    3
    >>> next(g)
    5
    

    间接地,如果您将 fib 提供给 for 循环, list 初始值设定项, tuple 初始化程序或任何其他需要生成/生成值的对象的东西,那么您将生成生成器,直到它不再生成值(并且它回报):

    results = []
    for i in fib(30):       # consumes fib
        results.append(i) 
    # can also be accomplished with
    results = list(fib(30)) # consumes fib
    

    同样,使用 tuple 初始值设定项:

    >>> tuple(fib(5))       # consumes fib
    (1, 1, 2, 3, 5)
    

    生成器与函数的不同之处在于它是惰性的 . 它通过维护本地状态并允许您随时恢复来实现此目的 .

    当您第一次通过调用它来调用 fib 时:

    f = fib()
    

    Python编译函数,遇到 yield 关键字并简单地返回一个生成器对象 . 似乎不是很有帮助 .

    然后,当您请求它直接或间接生成第一个值时,它会执行它找到的所有语句,直到遇到 yield ,然后它会返回您提供给 yield 的值并暂停 . 有关更好地演示此示例的示例,让我们使用一些 print 调用(如果在Python 2上,则替换为 print "text" ):

    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:

    >>> gen = yielder("Hello, yield!")
    

    你有一个生成器对象现在正在等待命令让它生成一个值 . 使用 next 并查看打印内容:

    >>> 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!'
    

    未加引号的结果是印刷的 . 引用的结果是从 yield 返回的结果 . 现在再次致电 next

    >>> 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!'
    

    发电机记得它在 yield value 暂停并从那里恢复 . 打印下一条消息并再次执行搜索 yield 语句以暂停它(由于 while 循环) .

  • 35

    Yield is an object

    函数中的 return 将返回单个值 .

    如果需要 a function to return a huge set of values ,请使用 yield .

    更重要的是, yieldbarrier .

    就像CUDA语言中的障碍一样,它不会在控制完成之前进行转移 .

    也就是说,它将从头开始运行函数中的代码,直到它达到 yield . 然后,它将返回循环的第一个值 .

    然后,每隔一个调用将再次运行您在函数中编写的循环,返回下一个值,直到没有任何值返回 .

  • 35

    要了解 yield 的作用,您必须了解生成器是什么 . 在发电机出现之前 .

    Iterables

    创建列表时,您可以逐个阅读其项目 . 逐个读取它的项称为迭代:

    >>> mylist = [1, 2, 3]
    >>> for i in mylist:
    ...    print(i)
    1
    2
    3
    

    mylist 是可迭代的 . 当您使用列表推导时,您创建一个列表,因此是一个可迭代的:

    >>> mylist = [x*x for x in range(3)]
    >>> for i in mylist:
    ...    print(i)
    0
    1
    4
    

    您可以使用“ for... in... ”的所有内容都是可迭代的; listsstrings ,文件......

    这些迭代很方便,因为您可以根据需要阅读它们,但是您将所有值存储在内存中,当您拥有大量值时,这并不总是您想要的 .

    发电机

    生成器是迭代器,是一种可迭代的 you can only iterate over once . 生成器不会将所有值存储在内存中, they generate the values on the fly

    >>> mygenerator = (x*x for x in range(3))
    >>> for i in mygenerator:
    ...    print(i)
    0
    1
    4
    

    除了你使用 () 而不是 [] 之外,它是一样的 . 但是,你 cannot 第二次执行 for i in mygenerator ,因为生成器只能使用一次:它们计算0,然后忘记它并计算1,然后逐个结束计算4 .

    收益率

    yield 是一个像 return 一样使用的关键字,除了该函数将返回一个生成器 .

    >>> 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
    

    这是一个无用的例子,但是当你知道你的函数将返回一组你只需要阅读一次的大量值时它会很方便 .

    要掌握 yield ,你必须明白 when you call the function, the code you have written in the function body does not run. 该函数只返回生成器对象,这有点棘手:-)

    然后,每次 for 使用生成器时,您的代码将从它停止的位置继续 .

    现在困难的部分:

    for 第一次调用从你的函数创建的生成器对象,它将从头开始运行函数中的代码直到它命中 yield ,然后它将返回循环的第一个值 . 然后,每个其他调用将再次运行您在函数中写入的循环,并返回下一个值,直到没有值返回 .

    一旦函数运行,生成器被认为是空的,但是不再命中 yield . 这可能是因为循环已经结束,或者因为你不再满足 "if/else" .


    您的代码已解释

    发电机:

    # 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
    

    呼叫者:

    # 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
    

    此代码包含几个智能部分:

    • 循环遍历列表,但是循环迭代时列表会扩展:-)它有点危险,因为你最终可以得到一个无限循环 . 在这种情况下, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) 耗尽了生成器的所有值,但 while 不断创建新的生成器对象,这些对象将生成与之前的值不同的值,因为它不应用于同一节点 .

    • extend() 方法是一个列表对象方法,它需要一个iterable并将其值添加到列表中 .

    通常我们将列表传递给它:

    >>> a = [1, 2]
    >>> b = [3, 4]
    >>> a.extend(b)
    >>> print(a)
    [1, 2, 3, 4]
    

    但是在你的代码中它得到了一个生成器,这很好,因为:

    • 您不需要两次读取值 .

    • 您可能有很多孩子,并且您不希望它们都存储在内存中 .

    它的工作原理是因为Python不关心方法的参数是否是列表 . Python期望iterables所以它将适用于字符串,列表,元组和生成器!这叫做鸭子打字,这也是Python如此酷的原因之一 . 但这是另一个故事,另一个问题......

    你可以在这里停下来,或者阅读一下看看发电机的高级用途:

    控制发电机耗尽

    >>> 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
    ...
    

    Note: 对于Python 3,请使用 print(corner_street_atm.__next__())print(next(corner_street_atm))

    它可以用于控制对资源的访问等各种事情 .

    Itertools,你最好的朋友

    itertools模块包含操作iterables的特殊函数 . 曾经希望复制发电机吗?链两个发电机?使用单行分组嵌套列表中的值? Map / Zip 没有创建另一个列表?

    然后只是 import itertools .

    一个例子?让我们来看看四匹马比赛的可能到达顺序:

    >>> 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)]
    

    了解迭代的内在机制

    迭代是一个暗示迭代(实现 __iter__() 方法)和迭代器(实现 __next__() 方法)的过程 . Iterables是可以从中获取迭代器的任何对象 . 迭代器是允许您迭代迭代的对象 .

    关于how for loops work,这篇文章还有更多内容 .

  • 32

    yield 就像 return - 它返回你告诉它的任何东西(作为生成器) . 区别在于下次调用生成器时,执行从最后一次调用 yield 语句开始 . 与回归不同, the stack frame is not cleaned up when a yield occurs, however control is transferred back to the caller, so its state will resume the next time the function.

    对于代码,函数 get_child_candidates 的作用类似于迭代器,因此当您扩展列表时,它会一次向新列表添加一个元素 .

    list.extend 调用迭代器直到它耗尽 . 对于您发布的代码示例,只返回一个元组并将其附加到列表中将更加清晰 .

  • 12780

    yield 就像一个函数的返回元素 . 区别在于 yield 元素将函数转换为生成器 . 生成器的行为就像函数一样,直到'yielded'为止 . 发电机停止,直到下一次调用,并从它开始的完全相同的点继续 . 您可以通过调用 list(generator()) 将一个所有'yielded'值的序列合二为一 .

  • 28

    yield 关键字只是收集返回的结果 . 想想 yield 就像 return +=

  • 425

    yield关键字在Python中有什么作用?

    答案大纲/摘要

    • yield的函数,调用时, returns a Generator.

    • 生成器是迭代器,因为它们实现iterator protocol,因此您可以迭代它们 .

    • 生成器也可以是 sent information ,从概念上讲它是 coroutine .

    • 在Python 3中,您可以使用 yield from 在两个方向上从一个生成器 delegate 到另一个生成器 .

    • (附录批评了几个答案,包括前一个答案,并讨论了在发电机中使用 return . )

    发电机:

    yield 仅在函数定义内合法,并且 the inclusion of yield in a function definition makes it return a generator.

    生成器的想法来自其他语言(见脚注1),具有不同的实现 . 在Python的Generators中,代码的执行在yield的时候是frozen . 当调用生成器时(下面讨论方法),执行将恢复,然后在下一次生成时冻结 .

    yield 提供了一种简单的implementing the iterator protocol方式,由以下两种方法定义: __iter__next (Python 2)或 __next__ (Python 3) . 这两个方法都使对象成为一个迭代器,您可以使用 collections 中的 Iterator Abstract Base Class进行类型检查 . 模块 .

    >>> 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.
    

    生成器类型是迭代器的子类型:

    >>> import collections, types
    >>> issubclass(types.GeneratorType, collections.Iterator)
    True
    

    如有必要,我们可以像这样打字检查:

    >>> isinstance(gen, types.GeneratorType)
    True
    >>> isinstance(gen, collections.Iterator)
    True
    

    Iterator is that once exhausted的一个功能,您无法重复使用或重置它:

    >>> list(gen)
    ['I am', 'a generator!']
    >>> list(gen)
    []
    

    如果您想再次使用其功能,则必须制作另一个(参见脚注2):

    >>> list(func())
    ['I am', 'a generator!']
    

    可以以编程方式生成数据,例如:

    def func(an_iterable):
        for item in an_iterable:
            yield item
    

    上面的简单生成器也等同于下面 - 从Python 3.3开始(在Python 2中不可用),你可以使用yield from

    def func(an_iterable):
        yield from an_iterable
    

    但是, yield from 也允许委托给子发电机,这将在下面关于与协同程序的协同授权的部分中解释 .

    协同程序:

    yield 形成一个表达式,允许将数据发送到生成器(见脚注3)

    下面是一个示例,请注意 received 变量,该变量将指向发送到生成器的数据:

    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)
    

    首先,我们必须使用内置函数next排队生成器 . 它将调用适当的 next__next__ 方法,具体取决于您使用的Python版本:

    >>> first_year_interest = next(my_account)
    >>> first_year_interest
    50.0
    

    现在我们可以将数据发送到生成器 . (Sending None is the same as calling next . ):

    >>> next_year_interest = my_account.send(first_year_interest + 1000)
    >>> next_year_interest
    102.5
    

    协同代表团参与Sub-Coroutine的收益率

    现在,回想一下 yield from 在Python 3中可用 . 这允许我们将协同程序委托给子协会:

    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()
    

    现在我们可以将功能委托给子生成器,它可以像生成器一样使用,如上所述:

    >>> 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.中阅读有关 yield from 的精确语义的更多信息

    其他方法:关闭并抛出

    close 方法在函数执行被冻结时引发 GeneratorExit . 这也将由 __del__ 调用,因此您可以将任何清理代码放在处理 GeneratorExit 的位置:

    >>> my_account.close()
    

    您还可以抛出一个异常,该异常可以在生成器中处理或传播回用户:

    >>> 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
    

    结论

    我相信我已经涵盖了以下问题的所有方面:

    yield关键字在Python中有什么作用?

    事实证明, yield 做了很多 . 我相信我可以为此添加更全面的例子 . 如果您想要更多或有一些建设性的批评,请通过下面的评论告诉我 .


    附录:

    对最高/已接受答案的批评**

    • 关于什么使 iterable 感到困惑,只是以列表为例 . 请参阅上面的参考资料,但总结一下:iterable的 __iter__ 方法返回 iterator . iterator 提供 .next (Python 2或 .__next__ (Python 3)方法,由 for 循环隐式调用,直到它引发 StopIteration ,一旦它发生,它将继续这样做 .

    • 然后使用生成器表达式来描述生成器是什么 . 由于生成器只是创建 iterator 的简便方法,因此只会混淆事情,我们仍然没有进入 yield 部分 .

    • Controlling a generator exhaustion 中,他调用了 .next 方法,而他应该使用内置函数 next . 这将是一个适当的间接层,因为他的代码在Python 3中不起作用 .

    • Itertools?这与 yield 完全无关 .

    • 没有讨论 yield 提供的方法以及Python 3中的新功能 yield from . The top/accepted answer is a very incomplete answer.

    对答案的批判,表明在生成器表达或理解中的屈服 .

    语法当前允许列表理解中的任何表达 .

    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
    

    由于屈服是一种表达,因此在一些理解或生成器表达中使用它已被一些人吹捧为有趣 - 尽管没有引用特别好的用例 .

    CPython核心开发人员是discussing deprecating its allowance . 这是邮件列表中的相关帖子:

    2017年1月30日19:05,Brett Cannon写道:在Sun,2017年1月29日16:39 Craig Rodrigues写道:我对任何一种方法都没关系 . 恕我直言,将它们放在Python 3中的方式并不好 . 我的投票是它是一个SyntaxError,因为你没有得到你对语法的期望 . 我同意这对我们来说是一个明智的地方,因为任何依赖当前行为的代码实在太聪明而无法维护 . 在达到目标方面,我们可能会想要:3.8中的SyntaxWarning或DeprecationWarning 2.7s中的3.7x SyntaxError中的Py3k警告,Nick . - Nick Coghlan | ncoghlan at gmail.com |澳大利亚布里斯班

    此外,有一个outstanding issue (10544)似乎指向了这个永远不是一个好主意的方向(PyPy,一个用Python编写的Python实现,已经提出了语法警告 . )

    最重要的是,直到CPython的开发者告诉我们: Don't put yield in a generator expression or comprehension.

    生成器中的return语句

    Python 2

    在生成器函数中,不允许return语句包含expression_list . 在该上下文中,裸返回表示生成器已完成并将导致StopIteration被引发 .

    expression_list 基本上是任意数量的用逗号分隔的表达式 - 实质上,在Python 2中,您可以使用 return 停止生成器,但不能返回值 .

    Python 3

    在生成器函数中,return语句指示生成器已完成并将导致引发StopIteration . 返回值(如果有)用作构造StopIteration的参数,并成为StopIteration.value属性 .

    脚注

    • 在提案中引用了语言CLU,Sather和Icon,以便将生成器的概念引入Python . 一般的想法是函数可以维持内部状态并根据用户的要求产生中间数据点 . 这承诺在性能上优于其他方法,包括Python线程,在某些系统上甚至不可用 .

    • 这意味着,例如,xrange对象(Python 3中的范围)不是迭代器,即使它们是可迭代的,因为它们可以被重用 . 与列表一样,它们的__iter__方法返回迭代器对象 .

    • yield最初是作为语句引入的,这意味着它只能出现在代码块中一行的开头 . 现在yield产生了一个yield表达式 . https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt此更改旨在允许用户将数据发送到生成器,就像人们可能会收到它一样 . 要发送数据,必须能够将其分配给某些内容,为此,语句将无效 .

  • 247

    下面是一些如何实际实现生成器的Python示例,就好像Python没有为它们提供语法糖:

    As a Python generator:

    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))
    

    Using lexical closures instead of generators

    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)
    

    Using object closures instead of generators (因为ClosuresAndObjectsAreEquivalent

    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)
    
  • 162

    产量为您提供发电机 .

    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
    

    如您所见,在第一种情况下,foo会立即将整个列表保存在内存中 . 对于包含5个元素的列表来说,这不是什么大问题,但是如果你想要一个500万的列表呢?这不仅是一个巨大的内存消耗者,而且在调用函数时也需要花费大量时间来构建 . 在第二种情况下,bar只给你一个发电机 . 生成器是可迭代的 - 这意味着您可以在for循环等中使用它,但每个值只能被访问一次 . 所有值也不会同时存储在内存中;生成器对象“记住”上次调用它时循环的位置 - 这样,如果你使用一个可迭代(比如说)数到500亿,那么你不需要数到500亿全部立刻存储500亿个数字 . 再次,这是一个非常人为的例子,如果你真的想要数到500亿,你可能会使用itertools . :)

    这是生成器最简单的用例 . 正如你所说,它可以用来编写有效的排列,使用yield来通过调用堆栈推送,而不是使用某种堆栈变量 . 生成器也可以用于专门的树遍历,以及其他各种方式 .

  • 130

    虽然很多答案都说明了为什么要使用 yield 来创建生成器,但 yield 还有更多用途 . 它重复了有关使用 yield 创建生成器的任何精细示例 .

    为了帮助理解 yield 在以下代码中的作用,您可以用手指在任何具有 yield 的代码中跟踪循环 . 每当你的手指击中 yield 时,你必须等待输入 nextsend . 当调用 next 时,您将跟踪代码,直到您点击 yield ... yield 右侧的代码被评估并返回给调用者...然后您等待 . 再次调用 next 时,您将通过代码执行另一个循环 . 但是,您会注意到在协程中, yield 也可以与 send 一起使用,它将从调用者发送一个值到yielding函数中 . 如果给出 send ,则 yield 接收发送的值,并将其从左侧吐出......然后通过代码的跟踪进行直到再次击中 yield (在结束时返回值,就好像 next 被调用一样) .

    例如:

    >>> 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()
    
  • 116

    许多人使用 return 而不是 yield ,但在某些情况下 yield 可以更高效,更容易使用 .

    以下是 yield 绝对最适合的示例:

    返回(在功能中)

    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
    

    产量(功能)

    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.
    

    调用函数

    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)
    

    两个函数都做同样的事情,但 yield 使用三行而不是五行,并且需要担心的变量少一个 .

    这是代码的结果:

    正如您所看到的,两个函数都做同样的事情 . 唯一的区别是 return_dates() 给出一个列表, yield_dates() 给出一个生成器 .

    一个现实生活中的例子就像是逐行读取文件或者只是想制作一个生成器 .

  • 94

    从编程的角度来看,迭代器实现为thunks .

    为并发实现迭代器,生成器和线程池执行等作为thunks(也称为匿名函数),使用发送给具有调度程序的闭包对象的消息,并且调度程序回答“消息” .

    http://en.wikipedia.org/wiki/Message_passing

    “下一个" is a message sent to a closure, created by the " iter”电话 .

    有很多方法可以实现这个计算 . 我使用了变异,但通过返回当前值和下一个yielder,很容易做到没有变异 .

    这是一个使用R6RS结构的演示,但语义与Python完全相同 . 它是相同的计算模型,只需要在Python中重写它就需要改变语法 .

    欢迎使用Racket v6.5.0.3 .

    • (定义gen
      (lambda(l)
      (定义产量
      (lambda()
      (if(null?l)
      '结束
      (让((v(car l)))
      (设置!l(cdr l))
      V))))
      (拉姆达(米)
      (案例m
      ('收益率(收益率))
      ('init(lambda(数据)
      (设置数据)
      '好))))))

    • (定义流(gen'(1 2 3)))

    • (流'产量)
      1

    • (流'产量)
      2

    • (流'产量)
      3

    • (流'产量)
      '结束

    • ((stream'init)'(a b))
      '好

    • (流'产量)
      '一个

    • (流'产量)
      “b

    • (流'产量)
      '结束

    • (流'产量)
      '结束

  • 75

    (我的下面的答案仅从使用Python生成器的角度讲,而不是underlying implementation of generator mechanism,它涉及堆栈和堆操作的一些技巧 . )

    当在python函数中使用 yield 而不是 return 时,该函数将变成一个名为 generator function 的特殊函数 . 该函数将返回 generator 类型的对象 . The yield keyword is a flag to notify the python compiler to treat such function specially. 正常函数将在从其返回某个值后终止 . 但是在编译器的帮助下,生成器函数 can be thought of 可以恢复 . 也就是说,将恢复执行上下文,并且执行将从上次运行继续 . 直到您显式调用return,这将引发 StopIteration 异常(也是迭代器协议的一部分),或者到达函数的末尾 . 我找到了很多关于 generator 的参考资料,但 functional programming perspective 中的one是最可消化的 .

    (现在我想谈谈 generator 背后的基本原理,以及 iterator 基于我自己的理解 . 我希望这可以帮助你掌握迭代器和生成器的 essential motivation . 这样的概念也出现在其他语言中,比如C# . )

    据我所知,当我们想要处理大量数据时,我们通常首先将数据存储在某处,然后逐个处理 . 但这种天真的方法是有问题的 . 如果数据量很大,那么事先将它们作为一个整体存储起来是很昂贵的 . So instead of storing the data itself directly, why not store some kind of metadata indirectly, i.e. the logic how the data is computed .

    有两种方法来包装这样的元数据 .

    • OO方法,我们包装元数据 as a class . 这就是所谓的 iterator ,它实现了迭代器协议(即 __next__()__iter__() 方法) . 这也是常见的iterator design pattern .

    • 功能方法,我们包装元数据 as a function . 这就是所谓的 generator function . 但在引擎盖下,返回的 generator object 仍然是 IS-A 迭代器,因为它还实现了迭代器协议 .

    无论哪种方式,都会创建一个迭代器,即一些可以为您提供所需数据的对象 . OO方法可能有点复杂 . 无论如何,使用哪一个取决于你 .

  • 68

    这是一个简单的例子:

    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)
    

    输出:

    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开发人员,但它在我看来 yield 保持程序流的位置,下一个循环从"yield"位置开始 . 似乎它正在等待那个位置,就在此之前,将值返回到外部,然后下一次继续工作 .

    这似乎是一个有趣而且很好的能力:D

  • 47

    想一想:

    对于具有next()方法的对象,迭代器只是一个奇特的声音术语 . 因此,屈服函数最终会像这样:

    原始版本:

    def some_function():
        for i in xrange(4):
            yield i
    
    for i in some_function():
        print i
    

    这基本上是Python解释器对上面代码的作用:

    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
    

    为了更深入地了解幕后发生的事情,可以将 for 循环重写为:

    iterator = some_func()
    try:
        while 1:
            print iterator.next()
    except StopIteration:
        pass
    

    这更有意义还是只是让你感到困惑? :)

    我应该注意到,这仅仅是为了说明目的而过于简单化 . :)

  • 42

    在我描述如何使用发电机的许多重要答案中,有一种我认为尚未给出的答案 . 这是编程语言理论的答案:

    Python中的 yield 语句返回一个生成器 . Python中的生成器是一个返回continuation的函数(特别是一种coroutine,但continuation代表了更常用的机制来理解正在发生的事情) .

    编程语言理论的延续是一种更为基础的计算,但它们并不经常使用,因为它们极难推理并且也很难实现 . 但是,延续的概念很简单:计算的状态还没有完成 . 在此状态下,将保存变量的当前值,尚未执行的操作等 . 然后在程序的某个时间点继续可以调用,以便程序的变量重置为该状态,并执行保存的操作 .

    以这种更一般的形式,可以以两种方式实现连续 . 在 call/cc 方式中,程序的堆栈实际上已保存,然后在调用continuation时,将恢复堆栈 .

    在连续传递样式(CPS)中,continuation只是普通函数(仅在函数是第一类的语言中),程序员明确地管理它并传递给子例程 . 在这种风格中,程序状态由闭包(以及碰巧在其中编码的变量)表示,而不是驻留在堆栈中某处的变量 . 管理控制流的函数接受继续作为参数(在CPS的某些变体中,函数可以接受多个延续)并通过简单地调用它们并在之后返回来调用它们来操纵控制流 . 延续传递样式的一个非常简单的例子如下:

    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)
    

    在这个(非常简单的)示例中,程序员将实际写入文件的操作保存到一个延续中(这可能是一个非常复杂的操作,需要写出许多细节),然后传递该延续(即,作为第一个 - class closure)到另一个执行更多处理的运算符,然后在必要时调用它 . (我在实际的GUI编程中经常使用这种设计模式,因为它节省了我的代码行,或者更重要的是,在GUI事件触发后管理控制流 . )

    本文的其余部分将不失一般性地将延续概念化为CPS,因为它更容易理解和阅读 .

    现在让我们谈谈Python中的生成器 . 生成器是延续的特定子类型 . 而 continuations are able in general to save the state of a computation (即程序的调用堆栈), generators are only able to save the state of iteration over an iterator . 虽然这个定义对于某些发电机的使用情况略有误导 . 例如:

    def f():
      while True:
        yield 4
    

    这显然是一个合理的迭代,其行为很明确 - 每次生成器迭代它,它返回4(并且永远这样做) . 但是在考虑迭代器时(即 for x in collection: do_something(x) ),它可能不是想到的典型迭代类型 . 这个例子说明了生成器的强大功能:如果有什么是迭代器,生成器可以保存其迭代的状态 .

    重申:Continuations可以保存程序堆栈的状态,生成器可以保存迭代状态 . 这意味着continuation比生成器更强大,但是生成器也很多,更容易 . 它们对于语言设计者来说更容易实现,并且程序员更容易使用它们(如果你有时间刻录,尝试阅读和理解this page about continuations and call/cc) .

    但是您可以轻松地实现(和概念化)生成器作为连续传递样式的简单,特定情况:

    每当调用 yield 时,它会告诉函数返回一个延续 . 再次调用该函数时,它从它停止的任何地方开始 . 因此,在伪伪代码(即,不是伪代码,而不是代码)中,生成器的 next 方法基本上如下:

    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
    

    其中 yield 关键字实际上是真正的生成器函数的语法糖,基本上类似于:

    def generatorfun(iterable):
      if len(iterable) == 0:
        raise StopIteration
      else:
        return (iterable[0], lambda:generatorfun(iterable[1:]))
    

    请记住,这只是伪代码,Python中生成器的实际实现更复杂 . 但是作为一个理解正在发生的事情的练习,尝试使用延续传递样式来实现生成器对象而不使用 yield 关键字 .

  • 41

    一个简单的例子,它很容易解释: yield

    def f123():
        for _ in range(4):
            yield 1
            yield 2
    
    
    for i in f123():
        print i
    

    输出是:

    1 2 1 2 1 2 1 2
    
  • 38

    还有一件事需要提及:一个实际上不会终止收益的函数 . 我编写了这样的代码:

    def fib():
        last, cur = 0, 1
        while True: 
            yield cur
            last, cur = cur, last + cur
    

    然后我可以在其他代码中使用它,如下所示:

    for f in fib():
        if some_condition: break
        coolfuncs(f);
    

    它确实有助于简化一些问题,并使一些事情更容易使用 .

  • 143

    这是一个简单语言的例子 . 我将提供高级人类概念与低级Python概念之间的对应关系 .

    我想对一系列数字进行操作,但我不想因为创建该序列而烦恼我自己,我只想专注于我想要做的操作 . 所以,我做了以下事情:

    • 我打电话给你并告诉你我想要一个以特定方式产生的数字序列,我告诉你算法是什么 .
      This step corresponds to defining the generator function, i.e. the function containing a yield.

    • 过了一会儿,我告诉你,"OK, get ready to tell me the sequence of numbers" .
      This step corresponds to calling the generator function which returns a generator object. 请注意,您还没有告诉我任何数字;你 grab 你的纸和铅笔 .

    • 我问你,"tell me the next number",你告诉我第一个号码;之后,你等我问你下一个号码 . 它关心细节 .
      This step corresponds to calling .next() on the generator object.

    • ...重复上一步,直到......

    • 最终,你可能会走到尽头 . 你没告诉我一个号码;你只是喊,"hold your horses! I'm done! No more numbers!"
      This step corresponds to the generator object ending its job, and raising a StopIteration exception 生成器函数不需要引发异常 . 它被提升了当函数结束或发出 return 时自动执行 .

    这就是生成器的作用(包含 yield 的函数);它开始执行,只要它执行 yield 就会暂停,当被要求输入 .next() 时,它会从最后一点继续 . 它完全符合Python的迭代器协议设计,它描述了如何顺序请求值 .

    迭代器协议最着名的用户是Python中的 for 命令 . 所以,每当你做一个:

    for item in sequence:
    

    如果 sequence 是如上所述的列表,字符串,字典或生成器对象,则无关紧要;结果是一样的:你逐个读取序列中的项目 .

    注意,包含 yield 关键字的函数中的 def 不是创建生成器的唯一方法;这只是创建一个最简单的方法 .

    有关更准确的信息,请阅读Python文档中的iterator typesyield statementgenerators .

  • 81

    它不是特别熟悉Python,但我相信它与C#'s iterator blocks是一样的,如果你熟悉它们的话 .

    关键的想法是编译器/解释器/无论做什么都有一些技巧,因此就调用者而言,他们可以继续调用next()并且它将保持返回值 - 就好像生成器方法被暂停一样 . 现在很明显你不能真正实现一个方法,所以编译器会构建一个状态机,让你记住你当前的位置以及局部变量等 . 这比自己编写迭代器容易得多 .

  • 368

    TL;DR

    而不是:

    def squares_list(n):
        the_list = []                         # Replace
        for x in range(n):
            y = x * x
            the_list.append(y)                # these
        return the_list                       # lines
    

    这样做:

    def squares_the_yield_way(n):
        for x in range(n):
            y = x * x
            yield y                           # with this one.
    

    每当你发现自己从头开始构建一个列表时,每个部分都会改为 .

    这是我第一次有收益的“啊哈”时刻 .


    yield 是一种含糖的方式

    Build 一系列的东西

    相同的行为:

    >>> for square in squares_list(4):
    ...     print(square)
    ...
    0
    1
    4
    9
    >>> for square in squares_the_yield_way(4):
    ...     print(square)
    ...
    0
    1
    4
    9
    

    不同的行为:

    收益率为 single-pass :您只能迭代一次 . 当函数有一个yield时,我们将其称为generator function . 而iterator就是它的回报 . 这是揭示 . 我们失去了容器的便利性,但却获得了任意长篇系列的力量 .

    收益率是 lazy ,它推迟了计算 . 当你调用它时,一个带有yield的函数实际上根本不会执行 . 它返回的迭代器对象使用magic来维护函数的内部上下文 . 每次在迭代器上调用 next() (这发生在for循环中)执行时,前进到下一个yield . ( return 引发 StopIteration 并结束该系列 . )

    收益率为 versatile . 它可以做无限循环:

    >>> 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
    

    如果你需要 multiple passes 且系列不太长,只需在上面调用 list()

    >>> list(squares_the_yield_way(4))
    [0, 1, 4, 9]
    

    yield 这个词的精彩选择,因为both meanings适用:

    产量 - 生产环境 或提供(如农业)

    ...提供系列中的下一个数据 .

    收益 - 放弃或放弃(如政治权力)

    ...放弃CPU执行直到迭代器前进 .

相关问题