zoukankan      html  css  js  c++  java
  • python之路-----迭代器 和生成器

    迭代器

    迭代器 和 可迭代对象

    for 循环的本质就是一个python给我们提供的一个便捷迭代器

    for循环的本质就是在内部调用了__next__方法才能取到一个一个的值。

    #for 循环的对象可以是一个迭代对象 也可以是一个迭代器
    #for[1,2,3]
    # [1,2,3].__iter__()
    # __next__()
    for

    能被for循环的就是可迭代的

    可迭代协议

     --  可迭代协议
    就是数据类型和python解释器定下来的协议
     '.__iter__'这个方法导致了一个数据类型的可迭代
    只要包含了'双下iter'方法的数据类型就是可迭代的

    在python里  你学过的所有的可以被for循环的 基本数据类型 都是可迭代的  而不是迭代器

    迭代器----iterator

     迭代器协议

     迭代器必须要满足两点才可以叫做可迭代器

     :  内部实现了__iter__和__next__方法

    迭代器的本质   就是for循环调用的底层内置函数的运用

    可迭代对象:字符串、列表、元组、字典、集合都是可迭代的对象

     .............................

    Iterable -- 可迭代的 形容词
    iterator -- 迭代器 名词
    ....................................
    迭代器包含可迭代对象
    迭代器=可迭代对象.__iter__()

    执行可迭代的Iterable方法就得到一个iterator迭代器

    为什么要用迭代器?

    第一个功能,能够对python中的基本数据类型进行统一的遍历,不需要关心每一个值是什么
    第二个功能,他可以节省内存
    第三,思想--惰性运算 只有在你要的时候__next__时才会执行调用

    重要点''''只能迭代出当前和下一个数据 不能反向迭代 ,
    文件句柄就是一个迭代器  f=open('','')
    range 就是一个可迭代的对象 他也是为了节省内存存在的
     

    通过代码来理解

    '''
    dir([1,2].__iter__())是列表迭代器中实现的所有方法,dir([1,2])是列表中实现的所有方法,都是以列表的形式返回给我们的,为了看的更清楚,我们分别把他们转换成集合,
    然后取差集。
    '''
    #print(dir([1,2].__iter__()))
    #print(dir([1,2]))
    print(set(dir([1,2].__iter__()))-set(dir([1,2])))
    
    结果:
    {'__length_hint__', '__next__', '__setstate__'}
    迭代器方法

    迭代器中的三个方法的作用

    iter_l = [1,2,3,4,5,6].__iter__()
    #获取迭代器中元素的长度
    print(iter_l.__length_hint__())
    #根据索引值指定从哪里开始迭代
    print('*',iter_l.__setstate__(4))
    #一个一个的取值
    print('**',iter_l.__next__())
    print('***',iter_l.__next__())
    方法的作用

    在for循环中,就是在内部调用了__next__方法才能取到一个一个的值,,但是取不到值的时候会报错

    经典  一眼就能看出来
    l = [1,2,3,4]
    l_iter = l.__iter__()
    item = l_iter.__next__()
    print(item)
    item = l_iter.__next__()
    print(item)
    item = l_iter.__next__()
    print(item)
    item = l_iter.__next__()
    print(item)
    item = l_iter.__next__()
    print(item)经典
    经典

    内置函数:next和iter方法

    print('__next__' in dir(range(12)))  #查看'__next__'是不是在range()方法执行之后内部是否有__next__
    print('__iter__' in dir(range(12)))  #查看'__next__'是不是在range()方法执行之后内部是否有__next__
    
    from collections import Iterator
    print(isinstance(range(100000000),Iterator))  #验证range执行之后得到的结果不是一个迭代器
    
    range函数的返回值是一个可迭代对象
    range函数的返回值是一个可迭代对象

    生成器--本质上就是一个'自己写的可实现迭代器功能的东西'

    目的是为了节省内存 ,不会在内存中生成太多数据

    生成器 里面的值是有限的 只能被顺序取用一次, 取完就没有了
    生成器里面的值只在调用的时候才取值

      

    1.生成器函数:只要包含yield关键字的函数就是一个生成器函数

    使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

    import time
    def genrator_fun1():
        a = 1
        print('现在定义了a变量')
        yield a
        b = 2
        print('现在又定义了b变量')
        yield b
    
    g1 = genrator_fun1()
    print('g1 : ',g1)       #打印g1可以发现g1就是一个生成器
    print('-'*20)   #我是华丽的分割线
    print(next(g1))
    time.sleep(1)   #sleep一秒看清执行过程
    print(next(g1))
    
    初识生成器函数
    初始生成器函数
    #初识生成器二
    
    def produce():
        """生产衣服"""
        for i in range(2000000):
            yield "生产了第%s件衣服"%i
    
    product_g = produce()
    print(product_g.__next__()) #要一件衣服
    print(product_g.__next__()) #再要一件衣服
    print(product_g.__next__()) #再要一件衣服
    num = 0
    for i in product_g:         #要一批衣服,比如5件
        print(i)
        num +=1
        if num == 5:
            break
    
    #到这里我们找工厂拿了8件衣服,我一共让我的生产函数(也就是produce生成器函数)生产2000000件衣服。
    #剩下的还有很多衣服,我们可以一直拿,也可以放着等想拿的时候再拿
    
    初识生成器二
    生成器2

    1.3 生成器Generator的本质:开发者自定义的迭代器   ..惰性运算 ...节省内存..

    1.4生成器函数: 一个包含yield关键字的函数就是一个 生成器函数, yield可以为我们从函数中返回值,但其又不同于return,return意味着程序结束    而其是程序停在那里    需要调用的时候再调用 就可以继续运行

    代码体现生成器不占内存

    import time
    def genrator_fun1():
        a = 1
        print('现在定义了a变量')
        yield a
        b = 2
        print('现在又定义了b变量')
        yield b
    
    g1 = genrator_fun1()
    print('g1 : ',g1)       #打印g1可以发现g1就是一个生成器
    print('-'*20)   #我是华丽的分割线
    print(next(g1))
    time.sleep(1)   #sleep一秒看清执行过程
    print(next(g1))
    
    初识生成器函数
    体现生成器不占内存

    监控文件这点自己不是太懂记得问一下

    import time
    
    
    def tail(filename):
        f = open(filename)
        f.seek(0, 2) #从文件末尾算起
        while True:
            line = f.readline()  # 读取文件中新的文本行
            if not line:
                time.sleep(0.1)
                continue
            yield line
    
    tail_g = tail('tmp')
    for line in tail_g:
        print(line)
    
    生成器监听文件输入的例子
    监控文件

    小结和代码练习

    #可迭代对象   内部有__iter__
    #迭代器    内部有__iter__ __next__
    #生成器    就是迭代器
        #自己写的迭代器
        #生成器函数   yield /yield from
        #生成器表达式
    def generator_func():  #生成器函数
        print(123)
        yield 'aaa'   #return
        print(456)
        yield 'bbb'
    
    def get_clothing():
        for cloth in range(1,2000000):
            yield '第%s件衣服'%cloth
    
    import time
    def tail(filename):
        f = open(filename,encoding='utf-8')
        f.seek(0,2)   #将文件的光标移到最后
        while True:
            line = f.readline()
            if not line:
                time.sleep(0.1)
                continue
            yield line
    
    tail_g = tail('demo')
    for line in tail_g:
        print(line,end='')
    

    yield form 就相当于for i in 然后yield 返回值
    def func2(): yield from [1,2,3] yield from 'ABC' g = func2() for i in g: print(i) # 生成器 # 处理文件,从一个文件中找到包含自己要查找的值的那一行,并打印出来 # python

    生成器函数进阶与关键字send的用法

    生成器函数 用代码 来具体表现一下  他的惰性  和 取值

    # def func():
    #     yield 1
    #     yield 2
    #     yield 3
    #
    # g=func()
    # for i in g:
    #     print(i)
    #     print(list(g))
    yield和list

    send :文字面意思就是发送 , 在生成器函数中表达的就是传值     

    send(进去的值 )=yield+返回值

    代码表达就是:

        def func():
          print(123)
          value=yield 1
          print(value)
          print(456)
          yield '***'+value+'***'

    g=func()
    print(g.__next__())
    print(g.send('aaa'))
     

    #执行结果会拿到 123,1,aaa,456,***aaa***

    send 存在的作用就是 往生成器函数中发送一个值

    第一小段练习
    
    def func():
        print(123)
        value=yield 1
        yield value
    
    g=func()
    g.__next__()
    print(g.send('aaa'))
    
    第二小段练习
    def func():
        print('*')
        value=yield 1
        print('**',value)
        yield 2
    
    g=func()
    print(g.__next__())
    print(g.send('aaa'))
    
    第三小段练习
    def func():
        print('*')
        value=yield 1
        print('**',value)
        v=yield 2
        print(v)
        yield 3
    
    g=func()
    print(g.__next__())
    print(g.send('aaa'))
    print(g.send('bbb'))
    
    
    第四小段练习
    def func():
        print(1)
        yield 2
        print(3)
        value=yield 4
        print(5)
        yield value
    
    g=func()
    print(g.__next__())
    print(g.send('100'))
    print(g.__next__())
    
    
    虽然在调用阶段 send进去一个值   但是send进去的值没有yield来接收  所以返回的是一个空
    第四小段结果是 1 2 3 4 5 none



    第五小段练习
      def func():
      print(1)
      a=yield 2
      yield a
      print(3)
      value=yield 4
      print(5)
      yield value

    g=func()
    print(g.__next__())
    a=g.send('100')
    print(a)
    print(g.__next__())


    得到的结果是 1 2 100 3 4
    如果注释掉 a=yield 2 那就是返回 1 2 3 4 5 none


    代码体现send关键字在实际中的书写:

      计算移动平均值:总值/个数  (他们不是一个固定值)

    def averager():
        total = 0.0
        count = 0
        average = 0
        while True:
            term = yield average  #term依次=10=30=5 
            total += term
            count += 1
            average = total/count
    
    
    g_avg = averager()
    next(g_avg)
    print(g_avg.send(10))
    print(g_avg.send(30))
    print(g_avg.send(5))
    
    计算移动平均值(1)
    

    加装饰器的计算平均值(2)_预激活

    def init(func):  #在调用被装饰生成器函数的时候首先用next激活生成器
        def inner(*args,**kwargs):
            g = func(*args,**kwargs)
            next(g)
            return g
        return inner
    
    @init
    def averager():
        total = 0.0
        count = 0
        average = None
        while True:
            term = yield average
            total += term
            count += 1
            average = total/count
    
    
    g_avg = averager()
    # next(g_avg)   在装饰器中执行了next方法
    print(g_avg.send(10))
    print(g_avg.send(30))
    print(g_avg.send(5))
    
    计算移动平均值(2)_预激协程的装饰器
    装饰器激活生成器

     生成器进阶小结

    send :首先send和next工作的起止位置是完全相同的
    send可以把一个值作为信号量传递到函数中去
    在生成器伊始,只能用next
    只要用send传递参数的时候,必须在生成器中还有一个未被返回的yield
    在平时生活当中 没有必须的点 面试时用到

    列表推导式和生成器表达式

    生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列

    #***由于*哥的强势加盟很快走上了上市之路,***思来想去决定下几个鸡蛋来报答峰哥
    
    egg_list=['鸡蛋%s' %i for i in range(10)] #列表解析
    
    #*哥瞅着alex下的一筐鸡蛋,捂住了鼻子,说了句:哥,你还是给我只母鸡吧,我自己回家下
    
    laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式
    print(laomuji)
    print(next(laomuji)) #next本质就是调用__next__
    print(laomuji.__next__())
    print(next(laomuji))
    
    *哥与****的故事

    (总结的知识点  希望大家能看懂  )

    #列表解析
    sum([i for i in range(100000000)])#内存占用大,机器容易卡死
     
    #生成器表达式
    sum(i for i in range(100000000))#几乎不占内存
    

     列表推导式的目的是为了 : 简化代码

    列表推导式: print([i*i for i in[1,3,9]] )#结果必须是列表[1,9,81]
          for前面就是新列表里面的值 和 一些附加条件 , 也可以在末尾加一些if条件判断

    问1:用range取7以内能被2整除的数字   ,   一列表推导式表现出来

          print([i//2 for i in range(0,7,2)])

    问2:加上字符串拼接 :   print(['egg%s'%i for i in range(10)])

    问3:加上一些条件判断: print([i for i in range(30) if i % 3 is 0])

     问4:  找到嵌套列表中名字含有两个‘e’的所有名字

    names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],
             ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]
    
    print([name for lst in names for name in lst if name.count('e') >= 2])  # 注意遍历顺序,这是实现的关键
    
    for i in (name for lst in names for name in lst if name.count('e') >= 2):
         print(i)
    

    小结: 写代码时注意点:

        写代码尽量多的让列表推导式[]--默认变成生成式表达式()
        让推导式简化你的操作,增强你的代码可读性
        如果推导式过于复杂,应该转换成普通的python代码
        所有的列表推导式都可以转化成生成器表达式 ,在你的代码中尽量多的用到生成器表达式

    禁忌:  在代码里多层嵌套的for循环是禁忌--会大幅度增加代码复杂程度

    生成器表达式 -- 节省内存 , 简化代码  就是把 列表推导式的[]去掉

    laomuji=('egg%d'%i for i in range(10))
    print(laomuji)
    1.__next__()
    2.for
    for i in laomuji:
    print(i)

    laomuji 是一个可迭代对象  不是一个迭代器/生成器

    总结:

    1.把列表解析的[]换成()得到的就是生成器表达式

    2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存

    3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:

    sum(x ** 2 for x in xrange(4))

    而不用多此一举的先构造一个列表:

    sum([x ** 2 for x in xrange(4)]) 

    字典推导式

    例一:将一个字典的key和value对调

    mcase = {'a': 10, 'b': 34}
    mcase_frequency = {mcase[k]: k for k in mcase}
    print(mcase_frequency)
    字典推导

    例二:合并大小写对应的value值,将k统一成小写

    mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}
    mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys()}
    print(mcase_frequency)
    字典推导2

    集合推导式

    例:计算列表中每个值的平方,自带去重功能

    squared = {x**2 for x in [1, -1, 2]}
    print(squared)
    # Output: set([1, 4])
    集合推导

    练习题:

    例1:  过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母

    例2:  求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表

    例3:  求M中3,6,9组成的列表M = [[1,2,3],[4,5,6],[7,8,9]]

    1.[name.upper() for name in names if len(name)>3] 
    2.[(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1] 
    3. [row[2] for row in M] 
    答案

                 

  • 相关阅读:
    48. Rotate Image
    47. Permutations II
    46. Permutations
    45. Jump Game II
    44. Wildcard Matching
    43. Multiply Strings
    42. Trapping Rain Water
    Python_匿名函数
    Python_内置函数之map()
    Python_面向对象_单例模式
  • 原文地址:https://www.cnblogs.com/zgd1234/p/7275562.html
Copyright © 2011-2022 走看看