• Python3-生成器&迭代器


     

     

     

    列表生成式

    列表[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],要求把列表里的每个值加1,如何实现?

    >>> a = [i+1 for i in range(10)]
    >>> a
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    这就叫列表生成。

    生成器

    通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万元素的列表,不仅占用很大的储存空间,如果我们仅仅需要访问前面几个元素,那后面绝大所述元素占用的空间都白白浪费了。

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

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

    >>> 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 0x1022ef630>

    创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。

    我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

    如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值:

    >>> next(g)
    0
    >>> next(g)
    1
    >>> next(g)
    4
    >>> next(g)
    9
    >>> next(g)
    16
    >>> next(g)
    25
    >>> next(g)
    36
    >>> next(g)
    49
    >>> next(g)
    64
    >>> next(g)
    81
    >>> next(g)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration

    我们讲过,generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

    上面不断调用next(g)太麻烦,正确的方式是使用for循环,因为generator也是可迭代对象:

    >>> g = (x * x for x in range(10))
    >>> for n in g:
    ...     print(n)
    ...
    0
    1
    4
    9
    16
    25
    36
    49
    64
    81

    generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

    比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

    1, 1, 2, 3, 5, 8, 13, 21, 34, ...

    斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

    def fib(max):
        n, a, b = 0, 0, 1
        while n < max:
            print(b)
            a, b = b, a + b
            n = n + 1
        return 'done'
    a, b = b, a + b
    
    #相当于
    t = (b, a + b) # t是一个tuple
    a = t[0]
    b = t[1]

    上面的函数可以输出斐波那契数列的前N个数:

    >>> fib(10)
    1
    1
    2
    3
    5
    8
    13
    21
    34
    55
    done

    上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

    def fib(max):
        n,a,b = 0,0,1
    
        while n < max:
            #print(b)
            yield  b
            a,b = b,a+b
    
            n += 1
    
        return 'done'

    这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

    >>> f = fib(6)
    >>> f
    <generator object fib at 0x104feaaa0>

    generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

    data = fib(10)
    print(data)
    
    print(data.__next__())
    print(data.__next__())
    print("干点别的事")
    print(data.__next__())
    print(data.__next__())
    print(data.__next__())
    print(data.__next__())
    print(data.__next__())
    
    #输出
    <generator object fib at 0x101be02b0>
    1
    1
    干点别的事
    2
    3
    5
    8
    13

    更多应用

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

    send

    def generator():
        print(123)
        content = yield 1
        print('======', content)
        print(456)
        yield 2
    
    g = generator()
    ret = g.__next__()       # 跟 ret = g.send(None) 一样效果 
    print('***', ret)
    ret = g.send('hello')   # send的效果和next一样
    print(ret)
    
    # next的时候,把yield 的值返回到外面
    # send的时候,把send 的值返回到里面!
    def averager():
        total = 0.0
        count = 0
        average = None
        while True:
            num = yield average
            count += 1
            total += num
            average = total / count
    
    
    g_avg = averager()
    g_avg.send(None)
    print(g_avg.send(10))
    print(g_avg.send(30))
    print(g_avg.send(5))
    计算移动平均值(1)
    def init(func):     #在调用被装饰生成函数的时候先用next激活生成器
        def inner():
            g = func()
            next(g)
            return g
        return inner
    
    
    @init
    def averager():
        total = 0.0
        count = 0
        average = None
        while True:
            num = yield average
            count += 1
            total += num
            average = total / count
    
    
    g_avg = averager()
    # g_avg.send(None)
    print(g_avg.send(10))
    print(g_avg.send(30))
    print(g_avg.send(5))
    计算移动平均值(2) 带装饰器

    yield from

    yield from iterable 本质上等于 for item in iterable: yield item的缩写版

    def gen1():
        for c in 'AB':
            yield c
        for i in range(3):
            yield i
    
    print(list(gen1()))
    
    def gen2():
        yield from 'AB'
        yield from range(3)
    
    print(list(gen2()))

    均输出:

    ['A', 'B', 0, 1, 2]

    迭代器

    python中的for循环

    要了解for循环是怎么回事儿,咱们还是要从代码的角度出发。

    首先,我们对一个列表进行for循环。

    for i in [1,2,3,4]:  
        print(i)

    上面这段代码肯定是没有问题的,但是我们换一种情况,来循环一个数字1234试试

    for i in 1234
        print(i) 
    
    结果:
    Traceback (most recent call last):
      File "test.py", line 4, in <module>
        for i in 1234:
    TypeError: 'int' object is not iterable

    看,报错了!报了什么错呢?“TypeError: 'int' object is not iterable”,说int类型不是一个iterable,那这个iterable是个啥?

    迭代和可迭代协议

    什么叫迭代

    现在,我们已经获得了一个新线索,有一个叫做“可迭代的”概念

    首先,我们从报错来分析,好像之所以1234不可以for循环,是因为它不可迭代。那么如果“可迭代”,就应该可以被for循环了。

    这个我们知道呀,字符串、列表、元组、字典、集合都可以被for循环,说明他们都是可迭代的

    from collections import Iterable
                                 
    l = [1,2,3,4]                
    t = (1,2,3,4)                
    d = {1:2,3:4}                
    s = {1,2,3,4}                
                                 
    print(isinstance(l,Iterable))
    print(isinstance(t,Iterable))
    print(isinstance(d,Iterable))
    print(isinstance(s,Iterable))



    #from collections import Iterator
                                 
    #l = [1,2,3,4] 
    #print(isinstance(l,Iterator))

    结合我们使用for循环取值的现象,再从字面上理解一下,其实迭代就是我们刚刚说的,可以将某个数据集内的数据“一个挨着一个的取出来”,就叫做迭代

    可迭代协议

    我们现在是从结果分析原因,能被for循环的就是“可迭代的”,但是如果正着想,for怎么知道谁是可迭代的呢?

    假如我们自己写了一个数据类型,希望这个数据类型里的东西也可以使用for被一个一个的取出来,那我们就必须满足for的要求。这个要求就叫做“协议”。

    print(dir([1,2]))
    print(dir((2,3)))
    print(dir({1:2}))
    print(dir({1,2}))
    ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
    ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
    ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
    ['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
    结果

    总结一下我们现在所知道的:可以被for循环的都是可迭代的,要想可迭代,内部必须有一个__iter__方法。

    接着分析,__iter__方法做了什么事情呢?

    print([1,2].__iter__())
    
    结果
    <list_iterator object at 0x1024784a8>

    执行了list([1,2])的__iter__方法,我们好像得到了一个list_iterator,现在我们又得到了一个新名词——iterator。

    iterator,这里给我们标出来了,是一个计算机中的专属名词,叫做迭代器。 

    迭代器

    什么叫“可迭代”之后,又一个历史新难题,什么叫“迭代器”?

    虽然我们不知道什么叫迭代器,但是我们现在已经有一个迭代器了,这个迭代器是一个列表的迭代器。

    我们来看看这个列表的迭代器比起列表来说实现了哪些新方法,这样就能揭开迭代器的神秘面纱了吧?

    '''
    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__())

    这三个方法中,能让我们一个一个取值的神奇方法是谁?

    没错!就是__next__

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

    那接下来我们就用迭代器的next方法来写一个不依赖for的遍历。

    
    

    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取到迭代器里已经没有元素了,就会抛出一个异常StopIteration,告诉我们,列表中已经没有有效的元素了。

    这个时候,我们就要使用异常处理机制来把这个异常处理掉。

    l = [1,2,3,4]
    l_iter = l.__iter__()
    while True:
        try:
            item = l_iter.__next__()
            print(item)
        except StopIteration:
            break

    那现在我们就使用while循环实现了原本for循环做的事情,我们是从谁那儿获取一个一个的值呀?是不是就是l_iter?好了,这个l_iter就是一个迭代器。

    迭代器遵循迭代器协议:必须拥有__iter__方法和__next__方法。

    小结:

    可迭代对象:

      拥有__iter__方法  (遵循可迭代协议),也就是说可以进行for循环的对象

      例如:range(),str,list,tuple,dict,set

      特点:惰性运算

    迭代器Iterator:

      拥有__iter__方法和__next__方法

      例如:iter(range), iter(str), iter(tuple)    生成器,(列表生成式,带yield函数)

      #iter()函数可以将可迭代对象变为迭代器

    生成器Generator:

      本质:迭代器,所以拥有__iter__方法和__next__方法

      特点:惰性运算,开发者自定义

    使用生成器的优点:

     1。延迟计算,一次返回一个结果,也就是说,他不会一次生成所有的结果,这对于大数据量处理,将会非常有用。

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

      

  • 相关阅读:
    Windows10下Opencv4+CMake+MinGW64+VSC安装教程
    相机标定问题-实践操作流程
    eNSP仿真学习,网络入门!
    SFTP服务的使用!!
    树莓派B+使用入门&RPI库安装&wringPi库安装
    Python基本语法初试
    基于51单片机+DAC0832的信号发生器
    各种标志位的含义
    根文件系统ramdisk.image.gz && uramdisk.image.gz
    Linux中/etc/inittab文件
  • 原文地址:https://www.cnblogs.com/Xuuuuuu/p/10179724.html
Copyright © 2020-2023  润新知