• python函数-迭代器和生成器


    一 迭代器

    1.1 认识迭代器

    什么是迭代 什么是迭代器

    迭代器
    如何从列表、字典中取值的
        index索引 ,key
        for循环
    凡是可以使用for循环取值的都是可迭代的
    可迭代协议 :内部含有__iter__方法的都是可迭代的
    迭代器协议 :内部含有__iter__方法和__next__方法的都是迭代器 

    一个生成器 只能取一次
    生成器在不找它要值的时候始终不执行
    当他执行的时候,要以执行时候的所有变量值为准
    l = [1,2,3]
    while True:
        lst = l.__iter__()   ## iter 方法自带一个next方法,所以第一次可以取值输出,并取到下一个值保存
        print(next(lst))
    
    
    l = [1,2,3]
    lst = l.__iter__()          ##当输出最后一个值的时候,仍旧执行next方法会报错
    while True:
        print(next(lst))
    
    l = [1,2,3]
    lst = l.__iter__()
    while True:
        try:
            print(next(lst))     #  测试错误
        except StopIteration:    #   当遇到报错  StopIteration  的时候,执行break,停止执行next方法。
    
    
    
    
    break

    什么是迭代器  迭代器 = iter(可迭代的),自带一个__next__方法

    可迭代 最大优势是 节省内存

    我们可以从range学习

    from collections import Iterable,Iterator   #可迭代的 迭代器
    print(range(100000000))
    print(isinstance(range(100000000),Iterable))   # True 是可迭代的
    print(isinstance(range(100000000),Iterator))    # False  不是迭代器
    py2 range 不管range多少 会生成一个列表 这个列表将用来存储所有的值
    py3 range 不管range多少 都不会实际的生成任何一个值

    迭代器的优势:
    节省内存
    取一个值就能进行接下来的计算 ,而不需要等到所有的值都计算出来才开始接下来的运算 —— 快
    迭代器的特性:惰性运算

    二 生成器

    生成器,Genenator

    2.1 什么是生成器

    自己写的迭代器,就是一个生成器

    写迭代器的两种机制:

    生成器函数和生成器表达式

    # 凡是带有yield的函数就是一个生成器函数
    def func():
        print('****')
        yield 1
        print('^^^^')
        yield 2   # 记录当前所在的位置,等待下一次next来触发函数的状态
    生成器函数的调用不会触发代码的执行,而是会返回一个生成器(迭代器)
    想要生成器函数执行,需要用next
    g = func()   # g = 函数执行之后生成的一个生成器
    print('---',g)  # 函数执行不输出任何内容
    print('--',next(g))   # 调用一次next,会执行一次取值,到下一次遇到yield停止
    print('--',next(g))

    调用两次next返回的结果:

    ****
    -- 1
    ^^^^
    -- 2

    2.1.2使用生成器监听文件输入

    import time
    def listen_file():
        with open('userinfo') as f:
            while True:
                line = f.readline()  #使用readline监听文件,它不会自动结束
                if line.strip():
                    yield line.strip()
                time.sleep(0.1)
    
    g = listen_file()
    for line in g:
        print(line)

    # 使用mac的时候,需要用echo向文件追加内容测试。win手动输入 使用ctrl+s保存即可

    2.1.3 send关键字

    理解send

    def func():
        print(11111)
        ret1 = yield 1
        print(22222,'ret1 :',ret1)
        ret2 = yield 2
        print(33333,'ret2 :',ret2)
        yield 3
    
    g = func()
    ret = next(g)  # 第一次next,返回11111 和 yield 1 。并记录当前位置,等待下一个next来触发函数状态     
    print(ret)
    print(g.send('alex'))  # 在执行next的过程中 传递一个参数 给生成器函数的内部,同时接收yield的返回值。
    print(g.send('金老板'))
    
    ##接收send发送的参数的是上次次结束的yield。第一次执行next(g),后,返回yield 1。yield会记录当前所在位置,等待下一个next来触发函数的状态。当执行send(alex)的时候的时候,相当于又执行一个next,且yield 1首先执行,接收send的内容。
    # send 不能在第一次next之前。第一次必须用next触发这个生成器,有一个激活的过程

        send  不能用在生成器的第一步。必须先用next触发激活生成器

    例子: 计算移动平均值

    要求,每次给你一个数,都计算之前所有拿到的数的平均值。例如计算月度的天平均收入

    def average():
        sum_money = 0
        day = 0
        avg = 0
        while True:
            money = yield avg
            sum_money += money
            day += 1
            avg = sum_money/day
    
    g = average()
    print(g.__next__())  #第一步 先触发生成器,之后才能send
    print(g.send(20))
    print(g.send(40))
    print(g.send(60))

    2.2 预激生成器

    个人理解:生成器send的时候需要用next激活,才能使用send传值。这里用到的是装饰器来进行生成器函数的预激活

    def init(func):
        def inner(*args,**kwargs):
            ret = func(*args,**kwargs)
            next(ret)   # 预激活
            return ret  #  需要返回生成器的结果给send
        return inner
    
    
    @init
    def average():
        sum_money = 0
        day = 0
        avg = 0
        while True:
            money = yield avg
            sum_money += money
            day += 1
            avg = sum_money/day
    #
    g = average()
    print(g.send(200))
    print(g.send(300))
    print(g.send(600))

    2.3 生成器的使用

    2.3.1 yield from

    # yield from
    def generator_func():
        for i in range(5):
            yield i
        for j in 'hello':
            yield j
    
    def generator_func():
        yield from range(5)
        yield from 'hello'
    
    
    以上两种写法完全是完全相同的

    2.3.2 迭代器只能生成一次

    我们看下这两段代码

    # 定义函数
    # # yield from
    def generator_func():
        for i in range(5):
            yield i
    
    # 代码调用第一种方式
    g = generator_func()
    while True:
        print(next(g))
    
    #输出内容:
    0
    1
    2
    3
    4
      File "/Users/wph/PycharmProjects/learn/Day4/8.复习练习.py", line 194, in <module>
        print(next(g))
    StopIteration
    
    # 代码调用第二种方式
    while True:
        print(next(generator_func()))
    
    # 执行结果是一直输出 0

     我们想一下,为什么会出现这样的情况呢?

    来继续看下面的代码:

    def generator_func():
        for i in range(5):
            yield i
    
    g1 = generator_func()
    g2 = generator_func()
    print(g1,g2)
    #输出结果 <generator object generator_func at 0x104c2b0a0> <generator object generator_func at 0x104c2b0f8> 可以看到 g1和g2的内存地址是不一样的。也就是说每次调用generator_func,都会生成一个全新的生成器。那么每次从里面取值,都是会从头开始的。

    2.3.3 生成器函数定义

    生成器函数 是我们python程序员实现迭代器的一种手段

    主要特种是 在函数中 含有 yield

    调用一个生成器函数 不会执行这个函数中的代码,知识会获得一个生成器(迭代器)

    只有从生成器中取值的时候,才会执行函数内部的代码。且每获取一个数据才执行得到这个数据的代码

    获取数据的方式包括 next send 循环 数据类型的强制转换

    yield返回值的简便方法,如果本身就是一个可迭代的,且要把可迭代数据中的每一个元素都返回,可以用 yield from

    使用send的时候,在生成器创造出来自后要进行预激,这一步可以使用装饰器完成。

    生成器的特点:节省内存 惰性计算

    生成器用来解决内存问题和程序功能之间的解耦

    2.3.4 生成器表达式

    先了解一下列表推导式:

    # 列表推导式
    new_lst = []
    for i in range(10):
        new_lst.append(i**2)
    print(new_lst)
    
    print([i**2 for i in range(10)])
    
    # 求列表中的数对2取余
    l = [1,2,3,-5,6,20,-7]
    print([i%2 for i in range(10)])
    
    # 取出列表中的奇数
    l = [1,2,3,-5,6,20,-7]
    print([num for num in l if num%2 == 1])
    
    30以内所有能被3整除的数
    print([i for i in range(30) if i%3 ==0])
    
    30以内所有能被3整除的数的平方
    print([i**2 for i in range(30) if i%3 ==0])
    
    
    找到嵌套列表中名字含有两个‘e’的所有名字
    names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],
             ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]
    print([name for name_lst in names for name in name_lst if name.count('e') == 2])

    生成器表达式

    l = [1,2,3,-5,6,20,-7]
    30以内所有能被3整除的数
    
    l = [i for i in range(30) if i%3 ==0]   # 列表推导式 排序的时候
    g = (i for i in range(30) if i%3 ==0)   # 生成器表达式 庞大数据量的时候 使用生成器表达式   #与列表推导式相比,只是把[]换成了()
    print(l) #结果是一个列表
    print(g) ## 结果只是拿到一个生成器  <generator object <genexpr> at 0x1043100a0>
    for i in g:print(i)  # 从这个生成器中取值

    总结:

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

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

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

    print(sum(x ** 2 for x in range(4)))

    2.3.5 生成器面试题

     # 面试题  请确认这段代码的打印内容
    def demo():
        for i in range(4):
            yield i
    
    g=demo()
    
    g1=(i for i in g)   ### 生成器表达式 不执行!
    g2=(i for i in g1)
    
    print(list(g1))  #执行生成器表达式  #结果  [0,1,2,3]  
    print(list(g2))                  #结果  []

    解析:因为g=demo()的使用调用生成器函数,g是一个生成器。g1 是一个生成器推导式,并不会执行。

    当print从g1 中取值完成后,由于一个生成器只能取值一次。,g2取不到值了。

    面试题2: 很蛋疼的,慢慢看吧 ,不信你能懂。。(重点在于 生成器是什么时候执行的)

    # 判断下列代码输出内容
    
    def add(n,i):
        return n+i
    
    def test():
        for i in range(4):
            yield i
    
    g=test()
    for n in [1,3,10]:
        g=(add(n,i) for i in g)
    
    print(list(g))

     输出内容

    10 11 12 13

    2.3.5 生成器准则

    一个生成器 只能取值一次。

    生成器在不找他要值的时候,始终不执行

    当他执行的时候,要以执行时的所有变量值为准  

  • 相关阅读:
    机器学习第一练(铁达尼号罹难者预测)
    Codewars题记 :Find the missing letter
    Codewars题记 :Take a Ten Minute Walk
    Codewars题记 :Some numbers have funny properties.
    Codewars题记 :Count the number of Duplicates
    Java图片合成工具类
    解决Libreoffice在Linux服务器上,重启Tomcat但是Libreoffice8100端口还一直占用的问题
    Java对Linux进程关闭
    Java多张图片合成PDF
    java下载文件到本地磁盘
  • 原文地址:https://www.cnblogs.com/wangph/p/8946609.html
Copyright © 2020-2023  润新知