zoukankan      html  css  js  c++  java
  • Python之路(第十篇)迭代器协议、for循环机制、三元运算、列表解析式、生成器

    一、迭代器协议

    a迭代的含义

      迭代器即迭代的工具,那什么是迭代呢?
    #迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值

    b为何要有迭代器?

    对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器

    c可迭代对象

    可迭代对象指的是内置有iter方法的对象,即字符串、元组、列表、集合、字典、文件,

      
    'hello'.__iter__
    (1,2,3).__iter__
    [1,2,3].__iter__
    {'a':1}.__iter__
    {'a','b'}.__iter__
    open('a.txt').__iter__

    d迭代器对象

      
    可迭代对象执行obj.__iter__()得到的结果就是迭代器对象
    而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象

    可迭代对象(字符串、元组、列表、集合、字典、文件)通过调用

      
    __iter__()

    方法,这里是遵循迭代器协议,将可迭代对象转为一个迭代器,这时既可以调用

      
    __iter__()方法又内置有__next__()方法

    即为迭代器对象。迭代器对象是一个内存地址。

    迭代器对象本身也可以使用__iter__()方法

    迭代器对象再次使用__iter__()方法生成的还是迭代器对象。

    例子

      
      dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
      iter_dic = dic.__iter__()
      print(iter_dic)
      v =iter_dic.__iter__()
      print(v)
    

      

    输出结果

      
      <dict_keyiterator object at 0x02191600>
      <dict_keyiterator object at 0x02191600>
    

      



    分析:这里可以看到,对字典dic调用了__iter__()方法,使其变成迭代器对象,再次对这个迭代器对象使用__iter__()方法还是其本身。

    e迭代器协议

    1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退)

    2.可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个iter()方法)

    3.协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。

    f注意:

    迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象。

    例子

      s  = "hello"
      iter_s = s.__iter__() #将字符串用__iter__()方法转换为迭代器对象
      print(iter_s.__next__())  #调用__next__()方法依次按照顺序打印每个字符
      print(iter_s.__next__())
      print(iter_s.__next__())
      print(iter_s.__next__())
      print(iter_s.__next__())
      print(iter_s.__next__()) #抛出异常StopIteration,或者说结束标志
    

      


    输出结果

      
      h
      e
      l
      l
      o
      #抛出异常StopIteration,或者说结束标志,StopIteration
    

      

    这里等同于用for循环打印

      
      s  = "hello"
      for i in s:  #for i in s.__iter__()
          print(i)  #print(iter_s.__next__())直到出现StopIteration,然后结束循环
    

      

    分析:这里的for 循环里的for i in s,s调用了__iter__()方法,将s变为一个迭代器对象,同时对这个迭代器对象使用

    __next__()方法打印出来,循环访问,并处理了最后的StopIteration,结束了循环。

    小知识

    next()方法是调用python解释器的,等同于某个可迭代对象下的__next__()方法

      
      #print(next(iter_s))等同于print(iter_s.__next__())
    

      

    例子2

      
      dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
      iter_dic = dic.__iter__()
      print(iter_dic.__next__())
      print(iter_dic.__next__())
      print(iter_dic.__next__())
      print(iter_dic.__next__())
      # print(iter_dic.__next__())  产生StopIteration停止标志
    

      

    输出结果

      
      k1
      k2
      k3
      k4
    

      

    改成for循环

      
      dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
      for i in dic:  #dic调用了__iter__方法,将其改成迭代器对象
          print(i)   #使用__next__()方法挨个去打印,直到出现StopIteration结束
    

      

    用while循环实现

      
      dic = {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"}
      iter_dic = dic.__iter__()
      while True:
          try:
              print(iter_dic.__next__())
          except StopIteration:
              print("迭代结束了,循环终止")
              break
              
    

      

    输出结果

      
      k1
      k2
      k3
      k4
      #迭代结束了,循环终止
    

      

    g迭代器的优缺点

    优点:

    • 提供一种统一的、不依赖于索引的迭代方式

    • 惰性计算,节省内存

    缺点:

    • 无法获取长度(只有在next完毕才知道到底有几个值)

    • 一次性的,只能往后走,不能往前退

    二、三元运算

    三元表达式的格式

      
    为真时的结果 if 判定条件 else 为假时的结果

    如果条件成立,返回if前面的结果,否则else 返回else后的结果

    例子

      
      a = 2
      b = 3
      s  = a if a < b else b   #这里的if语句后不加冒号
      print(s)
    

      

    输出结果

      
      2
    

      

    例子2

      
      name = input('姓名>>: ')
      res = 'SB' if name == 'ken' else 'NB'
      print(name,res)
     
    

      

    三、列表解析式

    列表解析是Python迭代机制的一种应用,它常用于实现创建新的列表,返回的是一个列表,因此用在[]中。

    例子

    生成1-100以内的偶数

    普通使用for循环的方式

      
      li  = []
      for i in range(1,101):
          if i % 2 == 0:
              li.append(i)
          else:
              pass
      print(li)
    

      

    使用列表解析式

      
      li  = []
      res = [i for i in range(1,101)if i % 2 == 0 ]
      print(res)
     
    

      

    例子2

    将字符串变大写组成列表

    普通方式

      
      s  = "nicholas"
      li = []
      for i in s :
          res = i.upper()
          li.append(res)
      print(li)
    

      

    列表解析式

      
      s  = "nicholas"
      li = [i.upper() for i in s ]
      print(li)
    

      

    四、生成器

    如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

    要创建一个generator,有很多种方法。

    第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator,即生成器表达式:

    生成器表达式能做的事情列表解析基本都能处理,只不过在需要处理的序列比较大时,列表解析比较费内存。

    例子

      
      li = [i*i for i in range(10) ]
      print(li)
    

      

    输出结果

      
      [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    

      

    分析:这里是生成了一个0到9的平方的列表。

    这里要改成生成器只要把列表生成式的[]改成()

      
      li = [i*i for i in range(10) ]
      g = (i*i for i in range(10) )
      print(li)
      print(g)
    

      

    输出结果

      
      [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
      <generator object <genexpr> at 0x02221600>
    

      

    分析:这里的<generator object <genexpr> at 0x02221600>就是一个生成器。这是生成器的第一种形式。

    生成器是包含有__iter__()__next__()方法的,所以可以直接使用for来迭代

    在这里调用__next__()方法或者用next()直接打印

    
    
      li = [i*i for i in range(5) ]
      g = (i*i for i in range(5) )
      print(li)
      print(g)
      print(g.__next__())
      print(g.__next__())
      print(g.__next__())
      print(next(g))
      print(next(g))
      #print(next(g))    #执行到此处就会产生一个StopIteration错误,类似可迭代对象调用__next__()一样
    

      

    输出结果

      
      [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
      <generator object <genexpr> at 0x02211600>
      0
      1
      4
      9
      16
    

      

    这里的g生成器就是一个迭代器。

    这里也可以用for循环直接打印

    li = [i*i for i in range(5) ]
    g = (i*i for i in range(5) )
    print(li)
    for i in g:
        print(i)
    

      

    输出结果

    [0, 1, 4, 9, 16]
    0
    1
    4
    9
    16
    

     

    分析:同样的这里的对生成器g的for循环自动处理了StopIteration错误,结束了for循环。与处理可迭代对象的方式类似。

    第二种是生成器函数:

    在函数中如果出现了yield关键字,那么该函数就不再是普通函数,而是生成器函数。生成器函数可以生产一个无线的序列,这样列表根本没有办法进行处理。

    yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator。函数名+括号就变成了生成器。

    例子

    下面为一个可以生产奇数的生成器函数。

    def num():
        n=1
        while True:
            print("函数内的第一处",n)
            yield n
            print("函数内奇数", n)
            n+=2
            print("函数内的第二处", n)
    new_num = num()
    print(new_num)
    next(new_num)
    print("函数外面的",next(new_num))
    

      

    输出结果

    <generator object num at 0x02421660>
    函数内的第一处 1
    函数内奇数 1
    函数内的第二处 3
    函数内的第一处 3
    函数外面的 3
    

      

    分析:执行print(new_num)语句可以看到,这里的自定义函数是一个生成器,通过next()方法调用执行函数内部语句,这里的yield相当于return的功能,每次next()返回一个迭代值,下次next()继续执行循环,于是函数继续执行,直到再次遇到 yield,再次返回。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。而不是在while True语句下一次性执行完语句。

    yield 与 return

    return 是返回并中止函数

    yield 是返回数据,并冻结当前的执行过程

    next唤醒冻结的函数执行过程,继续执行,直到遇到下一个yield

    例子

    def g():
       yield 1
       yield 2
       yield 3
    new_g = g()
    print(next(new_g)) 
    #第一次调用next(new_g)时,会在执行完yield语句后挂起,所以此时程序并没有执行结束,函数返回数据1,并冻结当前执行过程,等待下一个next()唤醒执行过程
    print(next(new_g))
    #通过next()唤醒执行过程,yield返回数据2,冻结执行过程,等待下一个next()
    print(next(new_g))
    # print(next(new_g))  
    #这里如果运行上面这条语句,程序试图从yield语句的下一条语句开始执行,发现已经到了结尾,所以抛出StopIteration异常。
    

      

    输出结果

    1
    2
    3
    

      

    分析:在一个生成器中,如果没有return,则默认执行到函数完毕时返回StopIteration;
    如果遇到return,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

    例子

    def g():
       yield 1
       yield 2
       return "a"
       yield 3
    new_g = g()
    print(next(new_g))#通过next()执行函数,得到返回数据1,冻结当前过程,等待下一个next()唤醒
    print(next(new_g))#通过next()执行函数,得到返回数据1,冻结当前过程,等待下一个next()唤醒
    print(next(new_g))#通过next()执行函数,遇到return语句,直接抛出StopIteration 终止迭代,这样yield '3'语句永远也不会执行。
    print(next(new_g))
    

      

    输出结果

    1
    Traceback (most recent call last):
    2
     File "D:/exercise/test1.py", line 11, in <module>
        print(next(new_g))
    StopIteration: a  #如果在return后返回一个值,那么这个值为StopIteration异常的说明,不是程序的返回值。
    

      

    如果在return后返回一个值,那么这个值为StopIteration异常的说明,不是程序的返回值。

    生成器没有办法使用return来返回值。

    例子

    与上面的例子基本相同,只是修改了return的返回值

    def g():
       yield 1
       yield 2
       return "some"
       yield 3
    new_g = g()
    print(next(new_g))
    print(next(new_g))
    print(next(new_g))
    print(next(new_g))
    
    

    输出结果

    1
    Traceback (most recent call last):
    2
     File "D:/exercise/test1.py", line 11, in <module>
        print(next(new_g))
    StopIteration: some    #生成器return返回的值是为StopIteration异常的说明,不是程序的返回值。
    

      

    生成器支持的方法
    close()

    手动关闭生成器函数,后面的调用会直接返回StopIteration异常。

    例子

    def g():
       yield 1
       yield 2
       yield 3
    
    new_g = g()
    print(next(new_g))
    print(next(new_g))
    new_g.close()   #这里通过cloes()方法直接关闭了生成器,后续通过next()也无法唤醒生成器继续执行返回数据,也就是说无法返回数据3,在这里直接抛出StopIteration异常
    print(next(new_g))
    print(next(new_g))
    

     

    输出结果

    1
    Traceback (most recent call last):
    2
      File "D:/exercise/test1.py", line 11, in <module>
        print(next(new_g))
    StopIteration
    

      

    send()方法

    生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。

    例子

    def gen():
          value = 0
          while True:
              receive = yield value
              if receive == "stop":
                  break
              value = 'got: %s' % receive
      ​
      g=gen()
      print(g.send(None))
      print(g.send('aaa'))
      print(g.send(3))
      print(g.send('stop'))
    

      

    输出结果

      
      Traceback (most recent call last):
        File "D:/exercise/test6.py", line 18, in <module>
          print(g.send('stop'))
      StopIteration
      0
      got: aaa
      got: 3
    

      

    分析:

    执行过程:

    1、首先g.send(None)或者g.next()唤醒生成器,并执行到receive = yield value语句,冻结执行过程,等下一个next()、g.send()、g.close(),此时,执行完了yield语句,但是没有给receive赋值。

    注意:在启动生成器函数时只能send(None)或者next(g),如果试图输入其它的值都会得到错误提示信息。

    2、通过g.send('aaa'),会传入aaa,并赋值给receive,然后计算出value的值,并回到while头部,执行yield value语句有停止。

    此时yield value会输出"got: aaa",然后冻结执行状态。

    3、通过g.send(3),会重复第2步,最后输出结果为"got: 3"

    4、通过g.send('stop'),传入生成器函数,赋值给receive,执行if receive == "stop":break 退出循环,整个函数执行完毕,最后出现StopIteration异常

    throw()

    throw()用来向生成器函数送入一个异常,可以结束系统定义的异常,或者自定义的异常。throw()后直接抛出异常并结束程序,或者消耗掉一个yield,或者在没有下一个yield的时候直接进行到程序的结尾。

    例子

    def gen():
          while True:
              try:
                  yield 'normal value'
                  yield 'normal value 2'
                  print('here')
              except ValueError:
                  print('we got ValueError here')
              except TypeError:
                  break
      g=gen()
      print(next(g))
      print(g.throw(ValueError))
      print(next(g))
      print(next(g))
      print(next(g))
      print(g.throw(TypeError))
    

      

    输出结果

      normal value
      we got ValueError here
      normal value    
      normal value 2
      here
      normal value
      normal value 2
      Traceback (most recent call last):
       File "D:/exercise/test6.py", line 22, in <module>
       print(g.throw(TypeError))
      StopIteration
    

      

    分析:

    执行过程

    1、通过print(next(g))唤醒生成器,执行到yield 'normal value',返回"normal value"被输出,冻结生成器函数执行过程

    2、执行print(g.throw(ValueError))语句,出现了ValueError,这里直接执行了except ValueError下的内容,循环继续,返回try语句,执行了yield 'normal value',这里又输出了一个“normal value”,之后冻结执行过程

    3、执行了print(next(g)),函数里执行yield 'normal value 2'语句,输出“normal value 2”,冻结执行过程,

    4、执行了print(next(g)),函数里开始继续执行,输出“here”,之后调到开始执行yield 'normal value',输出“normal value”,冻结执行过程

    5、执行了print(next(g)),函数里执行yield 'normal value 2',输出“normal value 2”,冻结执行过程

    6、通过print(g.throw(TypeError)),跳出try语句,出现TypeError,直接执行except TypeError:

    跳出while循环,到达生成器函数结尾,所以抛出StopIteration异常。

     

     
  • 相关阅读:
    SJTU T4143 推箱子
    Markdown基本语法
    命令行的操作——cd
    C++ ------- 类和对象
    数据结构------栈和队列
    MySQL------ 子查询
    MySQL------ SQL99语法
    C++------内存分区模型
    第三章------数据链路层
    MySQL------ SQL92语法
  • 原文地址:https://www.cnblogs.com/Nicholas0707/p/8908009.html
Copyright © 2011-2022 走看看