• 10.迭代器/生成器/协程函数/列表生成器


    迭代器

    为什么要用迭代器?

    提供了一种不依赖索引的取值方式,使一些不具有索引属性的对象也能遍历输出
    相比列表,迭代器的惰性计算更加节约内存。
    但是它无法有针对性地指定取某个值,并且只能向后取值。

    >>> from collections import Iterable
    >>> isinstance([],Iterable)
    True
    
    >>> from collections import Iterator
    >>> isinstance((x for x in range(10)),Iterator)
    True
    >>> isinstance([],Iterator)
    False
    >>> isinstance({},Iterator)
    False
    >>> isinstance('abx',Iterator)
    False

    注意这两个单词:‘Iterable’和‘Iterator’

    老师区分可迭代对象和迭代器的方法,就是对象是否内置.__iter__方法。而这个方法的运行,也就是xxx.__iter__(),被赋值给一个i。i = xxx.__iter__
    那么这个i就成为一个迭代器。这个过程也可以表示为i = iter(xxx)
    迭代器本身就有一个i.__next__方法,其实就相当于next(i)

    • Iterable:表示这个对象是“可以被迭代的”。比如说list、dict、str都是Iterable,但是根据上面代码可以发现,list、dict、str并不能算迭代器。

    • Iterator:迭代器对象。定义迭代器对象的时候,要有“数据流”的概念。迭代器和可迭代对象的差别就在于,迭代器是一个数据流,可以看成是一个有序序列,我们不知道序列的长度,只能通过next()函数对他进行不断迭代,直到爆出StopIteration的错误提示.

    其实这个StopIteration并不能算一个错误,只是一个提示,表示这个迭代器已经被迭代完了。这时候就需要使用try……except……函数来规避掉这个错误,在出现StopIteration的时候自动跳出迭代。

    可迭代对象转换成迭代器: iter()

    小结:

    凡是可以使用for循环的,都是Iterabale;
    凡是可以使用next()方法的都是Iterator,迭代器代表一个惰性计算(无限有限皆可)序列,比方说:全体自然数集合就是一个Iterator;
    一个迭代器被iter()之后,仍然是一个迭代器

    for x in [1,2,3,4,5,6]:
        print (x)
    
    print('======================')
    
    it = iter([1,2,3,4,5,6])
    while True:
        try:
            print(next(it))
        except StopIteration:
            break

    以上两段代码输出结果相同,所以我们就能理解for方法的运行原理了

    生成器

    • 关键字:yield

    为什么要使用生成器,什么是生成器?

    可循环的类型(Iteratable)比如list,占用内存空间较大,当你想要其中的一个元素的时候只能将list放在内存里,根据索引去获取,生成器本质就是一个迭代器,但是我们知道迭代器是可以循环出来的,每次都可以只输出一个值,节约了内存空间。
    如果一个迭代对象,他后面的每个值都是可以根据一定计算获得的。我们是否可以在循环的过程中不断推算出后续的元素,这样就不必创建list,节省了大部分空间。
    这种一边循环一边计算的机制,就是生成器:generator

    如何创建一个生成器

    • 将列表生成式的[]改成()
    >>> l = [x*x for x in range(10)]
    >>> l
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    >>> g = (x*x for x in range(10))
    >>> g
    <generator object <genexpr> at 0x0000006CC7E0D1A8>

    列表生成后,list可以直接打印出来,但是generator的迭代器属性,每个元素需要用next()函数获取

     >>> next(g)
    0
    >>> next(g)
    1
    >>> next(g)
    4
    >>> next(g)
    9

    以上这种调用方式,手工操作部分太多了,所以正确的调用应该是使用for循环:

    >>> next(g)
    0
    >>> next(g)
    1
    >>> next(g)
    4
    >>> next(g)
    9
    >>> for i in g:
    …     print(i)
    …
    16
    25
    36
    49
    64
    81

    啊哈哈哈哈,侧面反映了迭代器的单项输出性质,只能迭代下一个。这里还可以注意到,用for调用generator的时候,不需要担心StopIteration错误

    举个栗子:斐波拉契数列

    函数生成斐波拉契数列,并注释逻辑:

    # 函数生成斐波拉契数列
    def fibonacci(max):
        n, a, b = 0, 0, 1   #这里的n用于计数,取最大数用
                            #a是前置数,用于配合b获取最开始的两个数字
                            #b是第一个数
        while n < max:      #当n未达到max的时候
            print(b)        #打印数字b,后面可以看到,b的值是前两个数相加的和
            a, b = b, a + b #对a,b分别进行赋值,a是原来的b,b是前两数相加获得后的和
            n = n + 1       #n的计数加一
        return 'done'

    这时候执行fibonacci(num),得到的结果就是一个最大长度为num的fibonacci数列了。
    而想要把这个函数改成一个generator,其实只要通过一句yield就可以:

    # 函数生成斐波拉契生成器
    def fibonacci(max):
        n, a, b = 0, 0, 1
        while n < max:
            yield b
            a, b = b, a + b
            n = n + 1
            return 'done'
    print(fibonacci(10))

    结果为:

    <generator object fibonacci at 0x000000D75546D1A8>
    
    Process finished with exit code 0

    这里的fibonacci已经不再是一个函数了,而是一个生成器,他的结果需要被循环出来,而他本身就作为一个方法被储存在某个变量里。

    f = fibonacci(6)
    for i in f :
        print(i)

    用yield返回结果的执行流程

    使用了yield之后,函数就变成了生成器。我们原先认为函数运行到return、或者函数的最后一行语句就返回。但是添加了yield之后的生成器不一样,在每次调用了next()执行,遇到yield就返回,再次执行时,从上次返回的yield开始继续执行

    在一个函数中,可以有多个yield,但是这种不太常见,在我的理解里,yield就有点像IDE当中的debug断点,在指定断点输出一个值。但是这个断点越多越复杂,反而会不好,因为他最终返回的生成器是一个同类数据流,当你这个数据流中的数据出现两种,就不太适合被再次加工了。
    所以一般用一个yield来定义一束数据流(生成器),然后通过生成器具有的迭代器特性来逐个输出处理数据。起到节约内存的作用。

    注意:yield有时候会作为一个传参工具(下文会细说,搭配.send()使用,被称为协程函数),这时候的yield会被放在一个函数中比较靠前位置,但是函数本身暂时没有可以输出的值,这时候就需要提前使用next()方法,将生成器初始化到yield的位置
    比较优秀的方法,是使用装饰器,提前将函数next()或者g.send(None)一次,并且方便后面的生成器运行调用:

    def init(func):
        def wrapper(*args,**kwargs):
            res = func(*args,**kwargs)
            next(res)
            return res
        return wrapper
    
    @init

    如果上次的yield是最后一个,并且生成器是一个被while包围的函数,就从上次结束的yield处继续下次循环,仍然遇到这个yield就输出返回。

    >>> def odd():
    …     print('step1')
    …     yield 1
    …     print('step2')
    …     yield 2
    …     print('step3')
    …     yield 3
    >>> o = odd()
    >>> next(o)
    step1
    1
    >>> next(o)
    step2
    2
    >>> next(o)
    step3
    3

    实验记录:

    >>> def foo():
    …     print('llllll')
    …     yield 3
    …     print('wwwwww')
      File "<stdin>", line 4
        print('wwwwww')
                      ^
    IndentationError: unindent does not match any outer indentation level

    yield后面跟返回的值如果不用括号括起来,容易报错,注意养成习惯

    作业代码以及注释:

    #模拟管道功能,将cat的处理结果作为grep的输入
    #从文件中获取想要的数据
    def cat(file_name):         #传参获得目标文件名
        with open(file_name,'r') as f:
            res = iter(f.readlines())
        return res
    #通过readlines方法获取文件的行集合list,
    #并且将这个集合生成为一个迭代器返回给函数
    
    
    #传参获取目标关键字,文件内容迭代器,
    def grep(key_str,iterator):
        for i in iterator:
        #迭代文件内容并且匹配关键字
            if key_str in i.strip():
                #匹配到后输出关键字
                print (i.strip())
    
    # 调用函数阶段:
    # grep('apple',cat('a.txt'))

    协程函数

    如果函数内yield的表达形式为var= yield,那么必须在往生成器函数中传参前,next(g)或者e.send(None)
    因为协程函数中,需要将函数初始化暂停至yield所在点,然后再进行生成器轮巡运算。

    面向过程编程

    在提协程函数的时候,还需要提一个面向过程编程思想
    流水式的变成思想,在设计程序时,需要把整个流程设计出来。
    这种思想的优缺点:

    • 优点:
      体系结构更加清晰
      简化程序的复杂度
    • 缺点:
      可扩展性差,一条流程只能给一组功能使用。

    作业以及代码注解:

    # 定义一个可以不断传入(send方法)网址来进行爬取数据的生成器函数
    # 定义一个配合协程函数的装饰器
    def yield_next(func):
        def wrapper(*arg,**kwargs):
            res = func(*arg,**kwargs)
            next(res)
            #注意,这里仍然需要返回res给函数wrapper
            #如果缺少这步,下方调用return wrapper的时候无效
            return res
        return wrapper
    #模块加载
    from urllib.request import urlopen
    
    @yield_next
    def get_url():
        while True:
            url = yield
            #外部传值给yield 相当于yield 统一资源定位器,并且url能传入新的值
            url_res = urlopen(url)      #爬取url指定的页面内容
            webLine = url_res.read()    # 读取html页面
            print(webLine)              #输出url页面
    
    g = get_url()
    #由于之前使用了装饰器,这里不用next()
    g.send('http://www.baidu.com')
    

    典型范例以及代码解析:

    实现linux中grep -rl 'python' dir_path效果的代码

    # 想了半天觉得这个代码好蠢,不放了。

    列表生成式

    范例:

    l = ['egg%s'%i for i in range(100) if i >50]
    print(l)
    
    g = os.walk(c:\scott)
    l1 = ['%s\%s'%(i[0],j)for i in g for j in i[-1]]

    返回结果放在最前,列表生成的for运算放在中间,后面可加判断语句
    如果直接调用这个列表生成式的结果给一个函数进行运算,可以不用添加[]

    生成器表达式

    就是把列表生成的[]换成()
    l = ('egg%s'%i for i in range(100) if i >50)

    作业和练习

    # 今日作业
    # 有两个列表,
    # 分别存放来老男孩报名学习linux和python课程的学生名字
    
    linux = ['钢弹', '小壁虎', '小虎比', 'alex', 'wupeiqi', 'yuanhao']
    python = ['dragon', '钢弹', 'zhejiangF4', '小虎比']
    # 问题一:得出既报名linux又报名python的学生列表
    l1 = [x for x in linux if  x in python]
    print (l1)
    # 问题二:得出只报名linux,而没有报名python的学生列表
    l2 = [x for x in linux if x not in python]
    print(l2)
    # 问题三:得出只报名python,而没有报名linux的学生列表
    l3 = [x for x in python if x not in linux]
    print(l3)
    
    
    shares = {
    
        'IBM': 36.6,
    
        'lenovo': 27.3,
    
        'huawei': 40.3,
    
        'oldboy': 3.2,
        'ocean':20.1
    }
    
    # 问题一:得出股票价格大于30的股票名字列表
    list_1 = [i for i in shares if shares[i]>30]
    print(list_1)
    # 问题二:求出所有股票的总价格
    list_sum = sum (shares[i] for i in shares)
    print(list_sum)
    
    
    l = [10, 2, 3, 4, 5, 6, 7]
    
    # 得到一个新列表l1,
    # 新列表中每个元素是l中对应每个元素值的平方
    l1 = [(x*x) for x in l]
    print(l1)
    # 过滤出l1中大于40的值,然后求和
    l1_sum = sum(y for y in l1 if y >40)
    print(l1_sum)

    <wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">

  • 相关阅读:
    mysql中bigint、int、mediumint、smallint 和 tinyint的取值范围
    centos6.5下安装samba服务器与配置
    centos 6.5 安装图形界面【转】
    Linux 下添加用户,修改权限
    Linux下自动调整时间和时区与Internet时间同步
    C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)
    Unity脚步之NetworkBehaviour下前进、后退、左右转向的简单移动
    Token 在 Ajax 请求头中,服务端过滤器跨域问题
    【游戏】【暗黑2】重置属性点和技能点
    ASCII
  • 原文地址:https://www.cnblogs.com/scott-lv/p/7468941.html
Copyright © 2020-2023  润新知