生成器和迭代器

本文详细介绍了Python中的生成器和迭代器的概念,包括它们的工作原理、使用方式和优势。通过实例展示了如何创建迭代器类以及使用生成器函数。文章还探讨了生成器的yield语句、send方法、协程以及递归生成器,同时提供了多个练习及解决方案,帮助读者深入理解和运用Python的迭代和生成器机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍

风力发电机

什么是迭代器?迭代器是可以像我们在 for 循环中那样迭代的对象。我们也可以说迭代器是一个对象,它一次返回一个元素。也就是说,在我们明确要求他们的下一个项目之前,他们不会做任何工作。它们的工作原理在计算机科学中称为惰性求值。惰性求值是一种求值策略,它将表达式的求值延迟到真正需要它的值。由于 Python 迭代器的惰性,它们是处理无穷大的好方法,即可以永远迭代的可迭代对象。您几乎找不到不与迭代器配合使用的 Python 程序。

迭代器是 Python 的一个基本概念。您已经在第一个 Python 程序中了解到可以迭代容器对象,例如列表和字符串。为此,Python 创建了列表或字符串的迭代器版本。在这种情况下,迭代器可以被看作是一个指向容器的指针,它使我们能够迭代这个容器的所有元素。迭代器是一种抽象,它使程序员能够访问可迭代对象(集合、字符串、列表等)的所有元素,而无需深入了解该对象的数据结构。

生成器是一种特殊的函数,它使我们能够实现或生成迭代器。

大多数情况下,迭代器是隐式使用的,就像在 Python 的 for 循环中一样。我们在以下示例中演示了这一点。我们正在迭代一个列表,但您不要误会:列表不是迭代器,但它可以像迭代器一样使用:

城市 =  [ “巴黎” “柏林” “汉堡” “法兰克福” “伦敦” “维也纳” “阿姆斯特丹” “海牙” ]
 位置  城市:
    打印“位置:”  + 位置)

输出:

地点:巴黎
地点:柏林
地点:汉堡
地点:法兰克福
地点:伦敦
地点:维也纳
地点:阿姆斯特丹
地点:海牙

执行 for 循环时到底发生了什么?函数'iter' 应用于'in' 关键字之后的对象,例如for i in o:。可能有两种情况: o 是可迭代的或不可迭代的。如果 o 不可迭代,则会引发异常,表示对象的类型不可迭代。另一方面,如果 o 是可迭代的,则调用 iter(o) 将返回一个迭代器,让我们称其为 iterator_obj for 循环使用此迭代器通过使用 next 方法迭代对象 o。当 next(iterator_obj) 耗尽时 for 循环停止,这意味着它返回一个 StopIteration 异常。我们在以下代码示例中演示了这种行为:

专业知识 =  [ “Python 初学者” “Python 中级” “Python 精通” “Python 高级” ]
专业知识 迭代器=  iter (专业知识)
打印“第一次调用“下一步”:“ 下一步(专业知识迭代器))
打印“第二次调用‘next’:” next ( experts_iterator ))

输出:

第一次调用“next”:Python 初学者
第二次调用“next”:Python 中级

我们本可以再调用next两次,但在这之后我们会得到一个 StopIteration 异常。

我们可以在 while 循环中模拟 for 循环的这种迭代行为:您可能已经注意到我们的程序中缺少一些东西:我们必须捕获“停止迭代”异常:

other_cities  =  [ "Strasbourg" ,  "Freiburg" ,  "Stuttgart" ,  
                "Vienna / Wien" ,  "Hannover" ,  "Berlin" ,  
                "Zurich" ] 
city_iterator  =  iter ( other_cities )
 city_iterator : 
    try : 
        city  =  next ( city_iterator )
        打印(城市)
    除了 StopIteration break

输出:

斯特拉斯堡
弗莱堡
斯图加特
维也纳/维也纳
汉诺威
柏林
苏黎世

Python 标准库的顺序基类型以及大多数类都支持迭代。字典数据类型 dict 也支持迭代器。在这种情况下,迭代会遍历字典的键:

资本 =  {  
    “法国” “巴黎” “荷兰” “阿姆斯特丹” “德” “柏林” “瑞士” “伯尔尼” “奥地利” “维也纳” }
 国家  首都:
     印刷““  + 国家 +  ”的 首都是“ + 首都[国家])

输出:

法国的首都是巴黎
荷兰的首都是阿姆斯特丹
德国的首都是柏林
瑞士的首都是伯尔尼
奥地利的首都是维也纳

题外话:从我们的例子中了解到,荷兰的首都不是海牙(海牙)而是阿姆斯特丹,有些读者可能会感到困惑。根据宪法,阿姆斯特丹是荷兰的首都,尽管荷兰议会和荷兰政府以及最高法院和国务委员会都位于海牙。

将迭代器实现为类

在 Python 中创建迭代器的一种方法是定义一个实现方法__init____next__. 我们通过实现一个类循环来展示这一点,该循环可用于永远循环遍历一个可迭代对象。换句话说,这个类的一个实例返回一个可迭代的元素,直到它用完为止。然后它无限期地重复该序列。

class  Cycle ( object ): 
    def  __init__ ( self ,  iterable ): 
        self 可迭代 = 可迭代
        self iter_obj  =  iter ( iterable ) 
    def  __iter__ ( self ): 
        return  self 
    def  __next__ ( self ): 
        while  True : 
            try : 
                next_obj  =  next ( self . iter_obj ) 
                return next_obj
            除了 StopIteration : 
                self iter_obj  =  ITER (自我可迭代)
X  = 周期(“ABC”  范围(10 ):
    打印下一个(X ), 结束= “” 

输出:

a、b、c、a、b、c、a、b、c、a、 

尽管创建迭代器的面向对象方法可能非常有趣,但这不是 Pythonic 方法。

在 Python 中创建迭代器的常用且最简单的方法是使用生成器函数。您将在下一章中了解这一点。

发电机

从表面上看,Python 中的生成器看起来像函数,但在语法和语义上都存在差异。一个显着特征是产量声明。yield 语句将函数转换为生成器。生成器是一个返回生成器对象的函数。这个生成器对象可以看作是一个函数,它产生一系列结果而不是单个对象。这个值序列是通过迭代它产生的,例如使用 for 循环。可以迭代的值是通过使用 yield 语句创建的。yield 语句创建的值是 yield 关键字之后的值。当到达 yield 语句时,代码的执行停止。将返回 yield 背后的值。生成器的执行现在被中断。一旦在生成器对象上再次调用“next”,生成器函数将在最后一次调用的代码中的 yield 语句之后立即恢复执行。执行将在最后一次让步之后生成器离开的状态继续执行。换句话说,所有局部变量仍然存在,因为它们在调用之间自动保存。这是与函数的根本区别:函数总是在函数体的开头开始执行,而不管它们在之前的调用中离开了哪里。它们没有任何静态或持久值。生成器的代码中可能有多个 yield 语句,或者 yield 语句可能位于循环体内。如果生成器的代码中有 return 语句,当 Python 解释器执行此代码时,执行将停止并出现 StopIteration 异常错误。“发电机”这个词

可以用生成器完成的所有事情也可以用基于类的迭代器来实现。然而,生成器的关键优势在于自动创建方法__iter__() 和 next()。生成器提供了一种非常简洁的方式来生成巨大甚至无限的数据。

下面是一个简单的生成器示例,它能够生成各种城市名称。

可以用这个生成器创建一个生成器对象,它一个接一个地生成所有城市名称。

def  city_generator (): 
    yield ( "Hamburg" ) 
    yield ( "Konstanz" ) 
    yield ( "Berlin" ) 
    yield ( "Zurich" ) 
    yield ( "Schaffhausen" ) 
    yield ( "Stuttgart" )  

我们通过调用 city_generator() 创建了一个迭代器:

city  =  city_generator ()
打印下一个(城市))

输出:

汉堡
打印下一个(城市))

输出:

康斯坦茨
打印下一个(城市))

输出:

柏林
打印下一个(城市))

输出:

苏黎世
打印下一个(城市))

输出:

沙夫豪森
打印下一个(城市))

输出:

斯图加特
打印下一个(城市))

输出:

-------------------------------------------------- ------------------------- 
StopIteration                              Traceback (最近一次调用最后一次)
 <ipython-input-22-02870f953c83> in <module> 
---- > 1打印( next ( city ) ) 
StopIteration :

如我们所见,我们city在交互式 shell 中生成了一个迭代器。每次调用该方法都会next(city)返回另一个城市。在最后一个城市,即斯图加特创建完成后,另一个调用next(city)抛出异常,说迭代已经停止,即StopIteration. “我们可以向迭代器发送重置吗?” 是一个常见问题,因此它可以重新开始迭代。没有重置,但可以创建另一个生成器。这可以通过例如再次使用语句“city = city_generator()”来完成。虽然乍一看,yield 语句看起来像函数的 return 语句,但我们可以在这个例子中看到有很大的不同。如果我们有一个 return 语句而不是前一个例子中的 yield,它就是一个函数。但此函数将始终返回第一个城市“汉堡”,而不会返回任何其他城市,即“康斯坦茨”、“柏林”、“苏黎世”、“沙夫豪森”和“斯图加特”

操作方法

正如我们在本章的介绍中所阐述的,生成器提供了一种生成迭代器的舒适方法,这就是它们被称为生成器的原因。

 

工作方法:

  • 生成器就像函数一样被调用。它的返回值是一个迭代器,即生成器对象。生成器的代码不会在这个阶段执行。
  • 迭代器可以通过调用 next 方法来使用。第一次执行像函数一样开始,即迭代器主体内的第一行代码。代码会一直执行,直到到达 yield 语句。
  • yield返回表达式的值,它跟在关键字 yield 之后。这就像一个函数,但是 Python 会跟踪这个 yield 的位置,并且会存储局部变量的状态以备下次调用。在下一次调用时,继续执行 yield 语句之后的语句,并且变量的值与它们在前一次调用中的值相同。
  • 如果生成器主体已完全运行或程序流遇到没有值的返回语句,则迭代器已完成。

我们将在以下示例中说明这种行为。生成器计数创建一个迭代器,它通过从起始值 'firstval' 开始计数并使用 'step' 作为计数的增量来创建一系列值:

def  count ( firstval = 0 ,  step = 1 ): 
    x  =  firstval 
    while  True : 
        yield  x 
        x  +=  step 
counter  =  count ()  # 计数将从 0 开始
for  i  in  range ( 10 ): 
    print ( next ( counter ),  end = ", " ) 
start_value  =  2.1 
stop_value  =  0.3
打印(" \n新计数器:" ) 
counter  =  count ( start_value ,  stop_value ) 
for  i  in  range ( 10 ): 
    new_value  =  next ( counter ) 
    print ( f " { new_value : 2.2f } " ,  end = ", " )

输出:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
新柜台:
2.10, 2.40, 2.70, 3.00, 3.30, 3.60, 3.90, 4.20, 4.50, 4.80, 

斐波那契作为生成器

斐波那契数列以比萨的列奥纳多命名,他被称为斐波那契(filius Bonacci,“博纳乔之子”的缩写)。在他的教科书 Liber Abaci 中,出现在 1202 年)他有一个关于兔子及其繁殖的练习:它从一对新生的兔子开始,即雄性和雌性。他们需要一个月的时间才能交配。在第二个月末,雌性生下了一对新兔子。现在让我们假设每只母兔在第一个月结束后每个月都会生另一对兔子。我们不得不提到斐波那契的兔子永远不会死。他们的问题是在一段时间后人口将有多大。

这会产生一系列数字:0, 1, 1, 2, 3, 5, 8, 13

这个序列可以用数学术语定义如下:

Fn=Fn-1+Fn-2 与种子值: F0=0 和 F1=1

def  fibonacci ( n ): 
    """ 用于创建斐波那契数列的生成器 """ 
    a ,  b ,  counter  =  0 ,  1 ,  0 
    while  True : 
        if  ( counter  >  n ):  
            return 
        yield  a 
        a ,  b  =  b ,  a  +  b
        计数器 +=  1 
f  =  fibonacci ( 5 ) 
for  x  in  f :
    打印( x ,  " " ,  end = "" )  #
打印()

输出:

0 1 1 2 3 5  

上面的生成器可用于创建由空格分隔的前 n 个斐波那契数字,或者更好的 (n+1) 个数字,因为还包括第 0 个数字。在下一个示例中,我们展示了一个能够返回无限迭代器的版本。当我们使用这个迭代器时,我们必须注意使用终止条件:

def  fibonacci (): 
    """按需生成无限序列的斐波那契数列""" 
    a ,  b  =  0 ,  1 
    while  True : 
        yield  a 
        a ,  b  =  b ,  a  +  b 
f  =  fibonacci () 
counter  =  0 
for  x  in  f : 
    print ( x ,  " " ,  end = "" ) 
    counter  +=  1 
    if  (counter  >  10 ): 
        中断 
打印()

输出:

0 1 1 2 3 5 8 13 21 34 55  

在生成器中使用“返回”

从 Python 3.3 开始,生成器也可以使用 return 语句,但是生成器仍然需要至少一个 yield 语句才能成为生成器!生成器内部的 return 语句等价于 raise StopIteration()

让我们看一下我们在其中引发 StopIteration 的生成器:

def  gen (): 
    yield  1 
    raise  StopIteration ( 42 ) 
    yield  2
g  =  gen ()
下一个( g )

输出:

1
下一个( g )

输出:

-------------------------------------------------- ------------------------- 
StopIteration                              Traceback(最近一次调用最后一次)
 <ipython-input-26-6735830131e8> in gen () 
      2      yield  1 
- ---> 3 raise StopIteration ( 42 ) 
      4      yield  2 
StopIteration : 42
上述异常是以下异常的直接原因:
RuntimeError                               Traceback(最近一次调用最后一次)
 <module>中的<ipython-input-29-e734f8aca5ac > 
----> 1 next ( g ) 
RuntimeError : generator raise StopIteration

我们现在证明这return“几乎”等同于引发“StopIteration”异常。

def  gen (): 
    yield  1 
    return  42 
    yield  2
g  =  gen ()
下一个( g )

输出:

1
下一个( g )

输出:

-------------------------------------------------- ------------------------- 
StopIteration                              Traceback(最近一次调用最后一次)
 <ipython-input-32-e734f8aca5ac> in <module> 
---- > 1 next ( g ) 
StopIteration : 42

发送方法/协程

生成器不仅可以发送对象,还可以接收对象。向生成器发送消息,即一个对象,可以通过对生成器对象应用send 方法来实现。请注意, send 既向生成器发送一个值,又返回生成器生成的值。我们将在以下简单的协程示例中演示这种行为:

def  simple_coroutine (): 
    print ( "coroutine has been started!" ) 
    while  True : 
        x  =  yield  "foo" 
        print ( "coroutine received:" ,  x ) 
cr  =  simple_coroutine () 
cr

输出:

<位于 0x7fa2a8049ac0 的生成器对象 simple_coroutine>
下一个( cr )

输出:

协程已经启动!
ret_value  =  cr 发送(“嗨” 打印“'发送'返回:” , ret_value )

输出:

收到协程:嗨
“发送”返回:foo

我们必须先在生成器上调用 next,因为生成器需要启动。使用发送到尚未启动的生成器会导致异常。

要使用send 方法,生成器必须等待yield 语句,以便发送的数据可以被处理或分配给左边的变量。到目前为止我们还没有说:下一个调用也发送和接收。它总是发送一个 None 对象。“next”和“send”发送的值被分配给生成器内的一个变量:这个变量new_counter_val在下面的例子中被调用。

下面的示例通过添加一个send功能来修改上一章节中的生成器“计数” 。

发送的另一个例子

from  random  import  choice 
def  song_generator ( song_list ): 
    new_song  =  None 
    while  True : 
        if  new_song  !=  None : 
            if  new_song  not  in  song_list : 
                song_list 追加(new_song )
            new_song  =  yield  new_song 
        else :
            new_song  =  yield (选择(song_list ))           
songs  =  [“她的 Şeyi Yak - Sezen Aksu” “Bluesette - Toots Thielemans” “六个马林巴琴 - Steve Reich” “Riverside - Agnes Obel” “Not for Radio - Nas” “What's going on - Taste” “On Stream” - Nils Petter Molvær” “La' Inta Habibi - Fayrouz “Ik Leef Niet Meer Voor Jou - Marco Borsato” “Δέκα λεπτά - Αθηνά Ανδρεάδη” ]
radio_program  =  song_generator (歌曲)
下一个( radio_program )

输出:

'河畔 - 艾格尼丝奥贝尔'
对于 范围(3 ):
    打印下一个(radio_program ))

输出:

Bluesette - Toots Thielemans
六个马林巴斯 - 史蒂夫赖希
六个马林巴斯 - 史蒂夫赖希
广播节目发送(“扭曲的天使 - 存档” 

输出:

'扭曲的天使 - 档案'
歌曲

输出:

['她的Şeyi Yak - Sezen Aksu',
 'Bluesette - Toots Thielemans',
 '六个马林巴斯 - 史蒂夫赖希',
 '河畔 - 艾格尼丝奥贝尔',
 '不适合广播 - Nas',
 “这是怎么回事 - 味道”,
 'On Stream - Nils Petter Molvær',
 "La' Inta Habibi - Fayrouz",
 'Ik Leef Niet Meer Voor Jou - Marco Borsato',
 'Δέκα λεπτά - Αθηνά Ανδρεάδη',
 '扭曲的天使 - 档案']

到目前为止,我们可以通过发送一首新歌曲来改变歌曲交互器的行为,即标题加上表演者。我们现在将扩展生成器。我们可以发送新的歌曲列表。我们必须向新迭代器发送一个元组,或者是一个元组:

(, ) 或者

("-歌曲列表-", )

 随机 导入 选择
def  song_generator ( song_list ): 
    new_song  =  None 
    while  True : 
        if  new_song  !=  None : 
            if  new_song [ 0 ]  ==  "-songlist-" : 
                song_list  =  new_song [ 1 ] 
                new_song  =  yield ( choice ( song_list )) 
            否则:
                标题, 表演者 =  new_song 
                new_song = 标题 +  “ - ”  + 表演
                ,如果 new_song 没有  song_list :
                    song_list 追加(new_song )
                new_song  =  yield  new_song 
        else :
            new_song  =  yield (选择(song_list ))   
歌曲 1  =  [ “Après un Rêve - Gabriel Fauré” 
         “On Stream - Nils Petter Molvær” “Der Wanderer Michael - Michael Wollny” “Les barricades mystérieuses - Barbara Thompson” “Monday - Ludovico Einaudi” ]
歌曲 2  =  [ “Dünyad” Uszak - Pinhani” “Again - Archive” “If I 
          have a Hear - Fever Ray”、“每个你,每个我 - 安慰剂” “熟悉的 - Angnes Obel” ]
radio_prog  =  song_generator ( song1 )
对于 范围(5 ):
    打印下一个(radio_prog ))

输出:

Les barricades mystérieuses - 芭芭拉·汤普森
Après un Rêve - Gabriel FauréOn Stream - Nils Petter Molvær
Les barricades mystérieuses - 芭芭拉·汤普森
Les barricades mystérieuses - 芭芭拉·汤普森
Après un Rêve - Gabriel FauréOn Stream - Nils Petter Molvær

我们现在通过交换歌曲列表来改变广播节目:

radio_prog 发送((“-songlist-” , song2 ))

输出:

《熟悉——安涅斯·奥贝尔》
对于 范围(5 ):
    打印下一个(radio_prog ))

输出:

再次 - 存档
如果我听到了 - 发烧射线每一个你,每一个我 - 安慰剂
Dünyadan Uszak - Pinhani
Dünyadan Uszak - Pinhani
如果我听到了 - 发烧射线每一个你,每一个我 - 安慰剂

抛出方法

throw() 方法在生成器暂停时引发异常,并返回生成器生成的下一个值。如果生成器退出而没有产生另一个值,它会引发 StopIteration。生成器必须捕获传入的异常,否则异常将传播给调用者。

count之前示例中生成器的无限循环不断产生顺序数据的元素,但我们没有关于“计数器”变量的索引或状态的任何信息。我们可以count通过在throw方法中抛出异常来获取这些信息,即变量的状态。我们在生成器内部捕获此异常并打印“count”值的状态:

def  count ( firstval = 0 ,  step = 1 ): 
    counter  =  firstval 
    while  True : 
        try : 
            new_counter_val  =  yield  counter 
            if  new_counter_val  is  None : 
                counter  +=  step 
            else : 
                counter  =  new_counter_val 
        except  Exception : 
            yield  ( firstval ,  step ,  counter )

在下面的代码块中,我们将展示如何使用这个生成器:

c  =  count () 
for  i  in  range ( 6 ): 
    print ( next ( c )) 
print ( "让我们看看迭代器的状态是什么:" ) 
state_of_count  =  c throw ( Exception ) 
print ( state_of_count ) 
print ( "now, we can continue:" ) 
for  i  in  range ( 3 ): 
    print ( next ( c ))

输出:

0
1
2
3
4
5
让我们看看迭代器的状态是什么:
(0, 1, 5)
现在,我们可以继续:
5
6
7

我们可以通过定义我们自己的异常类 StateOfGenerator 来改进前面的例子:

class  StateOfGenerator ( Exception ): 
     def  __init__ ( self ,  message = None ): 
         self message  =  message 
def  count ( firstval = 0 ,  step = 1 ): 
    counter  =  firstval 
    while  True : 
        try : 
            new_counter_val  =  yield  counter 
            if  new_counter_val  is  None : 
                counter  +=  step
            else : 
                counter  =  new_counter_val
        除了 StateOfGenerator : 
            yield  ( firstval ,  step ,  counter )

我们可以像这样使用之前的生成器:

c  =  count () 
for  i  in  range ( 3 ): 
    print ( next ( c )) 
print ( "让我们看看迭代器的状态是什么:" ) 
i  =  c throw ( StateOfGenerator ) 
print ( i ) 
print ( "now, we can continue:" ) 
for  i  in  range ( 3 ): 
    print ( next ( c ))

输出:

0
1
2
让我们看看迭代器的状态是什么:
(0, 1, 2)
现在,我们可以继续:
2
3
4

产量来自

“yield from”自 Python 3.3 起可用!该yield from <expr>语句可以在生成器的主体内使用。<expr>必须是一个评估为可迭代的表达式,从中提取迭代器。迭代器运行到耗尽,即直到遇到StopIteration异常。该迭代器向生成器的调用者(即包含 yield from 语句的调用者)生成和接收值。

我们可以通过查看两台发电机从下面的例子中学习“GEN1”和“第二代”的是,yield fromgen2 被替换为“GEN1”的循环:

def  gen1 (): 
    for  char  in  "Python" : 
        yield  char 
    for  i  in  range ( 5 ): 
        yield  i 
def  gen2 (): 
    yield from  "Python" 
    yield from  range ( 5 ) 
g1  =  gen1 () 
g2  =  gen2 ()
打印“G1” , 结束= “”  X  G1 :
    打印(X,  end = ", " )
打印( " \n g2: " ,  end = ", " ) 
for  x  in  g2 : 
    print ( x ,  end = ", " ) 
print ()

输出:

g1: , P, y, t, h, o, n, 0, 1, 2, 3, 4, 
g2: , P, y, t, h, o, n, 0, 1, 2, 3, 4, 

我们可以从输出中看到两个生成器是相同的。

yield from 语句的好处可以看作是将生成器拆分为多个生成器的一种方式。这就是我们在前面的示例中所做的,我们将在以下示例中更明确地演示这一点:

DEF 城市():
    对于 城市  [ “柏林” “汉堡” “慕尼黑” “弗赖堡” ]:
        收率 城市
DEF 平方():
    对于 范围(10 ):
        产量**  2 
DEF  generator_all_in_one () :
    对于 城市 中的 城市():
        方块为单位的数量的产生 城市
    ():产生数量def   
         
 generator_splitted ():
    产率从 城市()
    从产率 平方()
LST1  =  [ EL  EL  generator_all_in_one ()] 
LST2  =  [ EL  EL  generator_splitted ()]
打印(LST1  ==  LST2 )

输出:

真的

前面的代码返回 True 因为生成器generator_all_in_onegenerator_splitted yield元素相同。这意味着如果<expr> fromyield from是另一个生成器,则效果与子生成器的主体在yield from语句点内联一样。此外,子生成器可以执行return带有值的语句,该值成为yield from表达式的值。我们用以下小脚本演示这一点:

def  subgenerator (): 
    yield  1 
    return  42 
def  delegating_generator (): 
    x  =  yield from  subgenerator () 
    print ( x ) 
for  x  in  delegating_generator (): 
    print ( x )

输出:

1
42

“PEP 380 -- 委派给子生成器的语法”中的六点根据生成器协议描述了 yield from 表达式的完整语义:

  • 迭代器产生的任何值都直接传递给调用者。
  • 使用 send() 发送到委托生成器的任何值都会直接传递给迭代器。如果发送的值为 None,则调用迭代器的next () 方法。如果发送的值不是 None,则调用迭代器的 send() 方法。如果调用引发 StopIteration,则恢复委托生成器。任何其他异常都会传播到委托生成器。
  • 除了 GeneratorExit 被抛出到委托生成器中的异常被传递给迭代器的 throw() 方法。如果调用引发 StopIteration,则恢复委托生成器。任何其他异常都会传播到委托生成器。
  • 如果在委托生成器中抛出 GeneratorExit 异常,或者委托生成器的 close() 方法被调用,则迭代器的 close() 方法如果有则调用。如果此调用导致异常,则将其传播到委托生成器。否则,在委托生成器中引发 GeneratorExit。
  • yield from 表达式的值是迭代器终止时引发的 StopIteration 异常的第一个参数。
  • 在生成器中返回 expr 会导致在退出生成器时引发 StopIteration(expr)。

递归生成器

以下示例是一个生成器,用于创建给定项目列表的所有排列。

对于那些不知道什么是排列的人,我们有一个简短的介绍:

正式定义:术语“排列”源自拉丁语动词“permutare”,意思是排列是有序列表元素的重新排列。换句话说:n 个元素的每一种排列都称为排列。

在以下几行中,我们向您展示了字母 a、b 和 c 的所有排列:

abc
ab
bac
bca
cab
cba

一组 n 个元素的排列数由 n!

啊!= n (n-1) (n-2) ... 2 * 1

啊!称为 n 的阶乘。

可以使用任意对象列表调用置换生成器。此生成器返回的迭代器生成所有可能的排列:

def  permutations ( items ): 
    n  =  len ( items ) 
    if  n == 0 :  yield  [] 
    else : 
        for  i  in  range ( len ( items )): 
            for  cc  in  permutations ( items [: i ] + items [ i + 1 ) :]):
                产量 [ items [ i ]] + cc 
for  p  置换([ 'R' 'E' 'd' ): 打印'' 加入(p ))
 p  排列(列表“游戏” )): 打印'' 加入(p ) +  ", " ,  end = "" )

输出:

红色的
数据
erd
编辑
dre
德
游戏,gaem,gmae,gmea,geam,gema,agme,agem,amge,ameg,aegm,aemg,mgae,mgea,mage,maeg,mega,meag,egam,egma,eagm,eamg,emga,emag, 

前面的例子对于新手来说可能很难理解。一如既往,Python 提供了一个方便的解决方案。为此,我们需要模块itertools。Itertools 是一个非常方便的工具来创建和操作迭代器。

使用 itertools 创建排列:

导入 itertools 
perms  =  itertools 排列([ 'r' , 'e' , 'd' ])
列表( perms )

输出:

[('红色的'),
 ('r', 'd', 'e'),
 ('e', 'r', 'd'),
 ('e', 'd', 'r'),
 ('d', 'r', 'e'),
 ('d', 'e', 'r')]

术语“排列”有时可以以较弱的含义使用。置换可以在这个较弱的含义中表示元素序列,其中每个元素只出现一次,但不需要包含给定集合的所有元素。所以从这个意义上说 (1,3,5,2) 是一组数字 {1,2,3,4,5,6} 的排列。例如,我们可以构建固定长度 k 的元素的所有序列,这些序列取自给定的大小为 n 的集合,其中 k ≤ n。

这些是集合 {"a","b","c","d"} 的所有 3 排列。

这些非典型排列也称为无重复序列。通过使用这个术语,我们可以避免与术语“排列”混淆。n 的这种 k 排列的数量表示为磷n,克 其值由乘积计算:

n·(n-1)·…(n-克+1)

通过使用阶乘符号,上述表达式可以写为:

磷n,克=n!/(n-克)!

用于创建 n 个对象的 k 排列的生成器看起来与我们之前的排列生成器非常相似:

def  k_permutations ( items ,  n ): 
    if  n == 0 :  
        yield  [] 
    else : 
        for  item  in  items : 
            for  kp  in  k_permutations ( items ,  n - 1 ): 
                if  item  not  in  kp : 
                    yield  [ item ]  +  kp 
for  kp  k_permutations ( "abcd" ,  3 ) 中:
    打印( kp ) 

输出:

['a', 'b', 'c']
['a', 'b', 'd']
['a', 'c', 'b']
['a', 'c', 'd']
['a', 'd', 'b']
['a', 'd', 'c']
['b', 'a', 'c']
['坏的']
['b', 'c', 'a']
['b', 'c', 'd']
['b', 'd', 'a']
['b', 'd', 'c']
['出租车']
['c', 'a', 'd']
['c', 'b', 'a']
['c', 'b', 'd']
['c', 'd', 'a']
['c', 'd', 'b']
['d', 'a', 'b']
['d', 'a', 'c']
['d', 'b', 'a']
['d', 'b', 'c']
['d', 'c', 'a']
['d', 'c', 'b']

发电机的发电机

我们斐波那契数列示例的第二个生成器生成一个迭代器,理论上可以生成所有的斐波那契数,即无限数。但是您不应该尝试使用以下行在列表中生成所有这些数字。

 列表(斐波那契())

这将非常快速地向您显示计算机的极限。在大多数实际应用中,我们只需要n和其他“无限”迭代器的第一个元素。在我们的示例中firstn,我们可以使用另一个生成器来创建n生成器的第一个元素generator

def  firstn ( generator ,  n ): 
    g  =  generator () 
    for  i  in  range ( n ): 
        yield  next ( g )

以下脚本返回斐波那契数列的前 10 个元素:

def  fibonacci (): 
    """ A Fibonacci number generator """ 
    a ,  b  =  0 ,  1 
    while  True : 
        yield  a 
        a ,  b  =  b ,  a  +  b 
print ( list ( firstn ( fibonacci ,  10 )))   

输出:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

练习

练习 1

编写一个计算运行平均值的生成器。

练习 2

编写一个 generator frange,它的行为类似于range但接受float值。

练习 3

3)编写一个生成器 trange,它生成一个从开始到停止逐步递增的时间元组序列。时间元组是一个由整数组成的 3 元组:(小时、分钟、秒)所以对 的调用trange可能如下所示:

trange((10, 10, 10), (13, 50, 15), (0, 15, 12) )

练习 4

写一个之前生成器的版本“rtrange”,它可以接收消息来重置起始值。

练习 5

编写一个程序,使用新编写的生成器“trange”,创建一个文件“times_and_temperatures.txt”。此文件的行包含格式中的时间hh::mm::ss和 10.0 到 25.0 度之间的随机温度。时间应该从 6:00:00 开始以 90 秒的步长递增。例如:

06:00:00 20.1
06:01:30 16.1
06:03:00 16.9
06:04:30 13.4
06:06:00 23.7
06:07:30 23.6
06:09:00 17.5
06:10:30 11.0

零和一的比特流

练习 6

编写一个名为“random_ones_and_zeroes”的生成器,它在每次迭代中返回一个比特流,即零或一。返回 1 的概率 p 在变量 p 中定义。生成器会将此值初始化为 0.5。换句话说,零和一将以相同的概率返回。

练习 7

我们Cycle在 Python 教程的本章开头编写了一个类。编写一个cycle执行相同任务的生成器。

我们练习的解决方案

练习 1 的解决方案

def  running_average (): 
    total  =  0.0 
    counter  =  0 
    average  =  None 
    while  True : 
        term  =  yield  average 
        total  +=  term 
        counter  +=  1 
        average  =  total  /  counter 
ra  =  running_average ()   # 初始化协程
next ( ra )                 # 我们有启动协程
 [ 7 , 13 17, ,  231 ,  12 ,  8 ,  3 ]: 
    out_str  =  "sent: {val:3d} , new average: {avg:6.2f} " 
    print ( out_str . format ( val = value ,  avg = ra . send ( value )) )

输出:

发送:7,新平均值:7.00
发送:13,新平均值:10.00
发送:17,新平均:12.33
发送:231,新平均值:67.00
发送:12,新平均:56.00
发送:8,新平均:48.00
发送:3,新平均:41.57

练习 2 的解决方案

DEF  FRANGE (* ARGS ): 
    “” “dfgdg ”“” 
    startval  =  0
    步长 =  1    
    如果 len个(ARGS ) ==  1 :
        endval  =  ARGS [ 0 ]
    的elif  LEN (ARGS ) ==  2 :
        startval , endval  =  ARGS 
    的elif  LEN (ARGS ) ==  3 :
        startval , endval , 步长 =  ARGS
    否则:
        TXT  =  “范围预期至多3个参数,得到了”  +  LEN (ARGS )
        加注 类型错误(TXT )
    值 =  startval
    因子 =  - 1 如果 步长 <  0 别的 1
     (值 -  endval ) *  (因子) <  0 :
        屈服 值
        值 + = 步长

使用frange可能像这样:

for  i  in  frange ( 5.6 ): 
    print ( i ,  end = ", " ) 
print () 
for  i  in  frange ( 0.3 ,  5.6 ): 
    print ( i ,  end = ", " ) 
print () 
for  i  in  frange ( 0.3 ,  5.6 ,  0.8 ):
    打印( i ,  end = ", " )
打印()

输出:

0, 1, 2, 3, 4, 5, 
0.3, 1.3, 2.3, 3.3, 4.3, 5.3, 
0.3, 1.1, 1.9000000000000001, 2.7, 3.5, 4.3, 5.1, 

练习 3 的解答

def  trange ( start ,  stop ,  step ): 
    """ 
    trange(stop) -> 时间作为三元组(小时、分钟、秒)
    trange(start, stop[, step]) -> time tuple 
    start: time tuple (hours, minute, seconds) 
    stop: time tuple 
    step: time tuple
    返回从 start 到 stop 递增的时间元组序列
    """         
    current  =  list ( start ) 
    while  current  <  list ( stop ): 
        yield  tuple ( current )
        秒 = 步骤[ 2 ]  + 当前[ 2 ] 
        min_borrow  =  0 
        hours_borrow  =  0 
        if  seconds  <  60 : 
            current [ 2 ]  =  seconds 
        else : 
            current [ 2 ]  =  seconds  -  60 
            min_borrow  =  1 
        minutes  =  step [ 1 ]  +  current [ 1 ]  +  min_borrow
        如果 分钟 <  60 :
            当前[ 1 ]  = 分钟 
        else :
            当前[ 1 ]  = 分钟 -  60 
            hours_borrow  =  1 
        hours  =  step [ 0 ]  +  current [ 0 ]  +  hours_borrow
        如果 hours  <  24 : 
            current [ 0 ]  =  hours  
        else : 
            current [ 0 ]  = 小时 -  24

输出:

覆盖时间范围.py
from  timerange  import  trange 
for  time  in  trange (( 10 ,  10 ,  10 ),  ( 19 ,  53 ,  15 ),  ( 1 ,  24 ,  12 )  ): 
    print ( time )   

输出:

(10, 10, 10)
(11, 34, 22)
(12, 58, 34)
(14, 22, 46)
(15, 46, 58)
(17, 11, 10)
(18, 35, 22)

练习 4 的解答

def  rtrange ( start ,  stop ,  step ): 
        """ 
        trange(stop) -> 时间作为三元组(小时、分钟、秒)
        trange(start, stop[, step]) -> 时间元组
        开始:时间元组(hours, minute, seconds) 
        stop: time tuple 
        step: time tuple
        返回一个
时间元组的序列,从 start 到 stop 逐步递增        可以通过发送一个新的“start”值来重置生成器。
        """         
        current  =  list ( start )
         当前 < 列表(停止):
            new_start  =  yield  tuple ( current ) 
            if  new_start  !=  None : 
                current  =  list ( new_start ) 
                continue 
            seconds  =  step [ 2 ]  +  current [ 2 ] 
            min_borrow  =  0 
            hours_borrow  =  0 
            if  seconds  <  60 : 
                current [ 2 ]  =  seconds 
            else :
                当前[ ] 2  =-  60 
                min_borrow  =  1
            分钟 = 步[ 1 ]  + 当前[ 1 ]  +  min_borrow
            如果 分钟 <  60 :
                当前[ 1 ]  = 分钟 
            否则:
                当前[ 1 ]  = 分钟 -  60 
                hours_borrow  =  1
            小时 = 步[ 0 ]  + 当前[0 ] +  hours_borrow
            如果 hours  <  24 : 
                current [ 0 ]  =  hours  
            else : 
                current [ 0 ]  =  hours  - 24

输出:

覆盖 rtimerange.py
from  rtimerange  import  rtrange 
ts  =  rtrange (( 10 ,  10 ,  10 ),  ( 17 ,  50 ,  15 ),  ( 1 ,  15 ,  12 )  )   
for  _  in  range ( 3 ): 
    print ( next ( ts )) 
print ( ts .发送(( 8 ,  5 ,  50 ))) 
for  _  范围( 3 ):
    打印( next ( ts ))

输出:

(10, 10, 10)
(11, 25, 22)
(12, 40, 34)
(8, 5, 50)
(9, 21, 2)
(10, 36, 14)
(11, 51, 26)

练习 5 的解答

from  timerange  import  trange 
import  random 
fh  =  open ( "times_and_temperatures.txt" ,  "w" ) 
for  time  in  trange (( 6 ,  0 ,  0 ),  ( 23 ,  0 ,  0 ),  ( 0 ,  1 ,  30 )  ): 
    random_number  = 随机randint ( 100 ,  250 )  /  10 
    lst = 时间 +  ( random_number ,)
    输出 =  " {:02d} : {:02d} : {:02d} {:4.1f} \n " 格式( * lst ) 
    fh 写(输出)

您可以在我们关于加权概率的章节中找到有关此练习的更多详细信息和数学背景

练习 6 的解答

导入 随机
def  random_ones_and_zeros (): 
    p  =  0.5
     True : 
        x  =  random 随机()
        消息 = 收率 1 如果 X  <  p 别的 0
        如果 消息 =! 无:
            p  = 消息
X  =  random_ones_and_zeros ()
下一个(X )  #我们没有在返回值中感兴趣
 p  [ 0.2 , 0.8 ]: 
    print ( " \n我们将概率改为:"  +  str ( p )) 
    x send ( p )     
    for  i  in  range ( 20 ): 
        print ( next ( x ),  end = " " ) 
print ()

输出:

我们将概率更改为:0.2
1 0 0 0 1 0 0 1 1 0 1 0 1 0 0 0 1 1 0 0 
我们将概率更改为:0.8
0 1 1 0 1 0 1 1 0 0 1 1 1 1 1 0 1 1 1 1 

练习 7 的解答

“循环”生成器是“itertools”模块的一部分。以下代码是在itertools中的实现:

def  cycle ( iterable ): 
    # cycle('ABCD') --> ABCDABCDABCD ... 
    saved  =  [] 
    for  element  in  iterable : 
        yield  element 
        saved . 追加(元件)
    ,同时 保存:
        用于 元件  保存的:
              收率 元件
国家 =  [ “德国” “瑞士” “奥” ] 
country_iterator  = 周期(国家)
对于 i  in  range ( 7 ):
    打印( next ( country_iterator ))

输出:

德国
瑞士
奥地利
德国
瑞士
奥地利
德国
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值