• 迭代器和生成器


    迭代器(iterator) 

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

    字符串,列表,元组,字典,集合都可以被for 循环,说明他们都是可迭代的。

    from collections import Iterable
    
    l=[1,2,3,4,5]
    t=(3,4,5,6,6)
    d={'a':1,'b':2}
    s={1,2,3,4,5}
    print(isinstance(l,Iterable))
    print(isinstance(t,Iterable))
    print(isinstance(d,Iterable))
    print(isinstance(s,Iterable))
    
    # True
    # True
    # True
    # True

    结合使用for循环时的现象,可以从字面上理解,迭代就是可以将某个数据集内的数据'一个挨着一个的取出来'。

    可迭代协议:内部含有__iter__方法的数据类型就是可迭代的

    验证可迭代协议:

    print(dir([1,2]))
    print(dir((2,3)))
    print(dir({'a':1,'b':2}))
    print(dir({1,2,3,4}))
    
    # ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__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__', '__init_subclass__', '__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__', '__init_subclass__', '__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__', '__init_subclass__', '__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 0x000001A68B0210B8>

    得到了一个list_iterator,此时我们又得到了一个新的名词——iterator。

    同样的,可以对字典、集合、元祖进行__iter__操作

    print((1,2,3).__iter__())
    print({'a':1,'b':2}.__iter__())
    print({1,2,3}.__iter__())
    
    # <tuple_iterator object at 0x0000021688F61080>
    # <dict_keyiterator object at 0x0000021688E8A4A8>
    # <set_iterator object at 0x0000021688F60630>

    同样的得到了tuple_iterator、dict_keyiterator、set_iterator,这就说明了它们都是可迭代的。

    可以通过集合求差集来看可迭代对象与迭代器质检的差别。

    print(set(dir([1,2].__iter__()))-set(dir([1,2])))
    
    # {'__next__', '__length_hint__', '__setstate__'}
    #可以看出可迭代对象比迭代器多了一个__next__功能

    对可迭代对象进行操作:

    iter_1=[1,2,3,4,5].__iter__()
    print(iter_1.__length_hint__())
    # 从索引为2的元素开始迭代
    print('*',iter_1.__setstate__(2))
    #一个一个的取值
    print('**',iter_1.__next__())
    print('***',iter_1.__next__())
    
    # 5
    # * None
    # ** 3
    # *** 4

    可以看到其中的__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)
    
    # 1
    # 2
    # 3
    # 4
    #   File "G:/群下载/2.闭包的概念.py", line 206, in <module>
    #     item=l_iter.__next__()
    # StopIteration

    上述方法确实将l中的每个元素都遍历且取出来了,但是当l中的元素个数不清楚 的时候我们会写很多行重复的代码,且当超出范围的时候还会像上面一样报错。

    这个时候,我们需要使用异常处理机制来将这个异常来处理掉。用while循环来模拟for循环的原理:———for循环时依赖迭代器,且也可以仿照写一个。

    for 循环就是想让我们更简单的使用迭代器

    用迭代器取值就不需要关心索引或者key的问题了。

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

    range()方法:

    # 查看__iter__方法中range()使用dir()方法之后是否还存在
    print('__iter__'in dir(range(12)))
    #查看__next__方法中range()使用dir()方法之后是否还存在
    print('__next__' in dir(range(12)))
    from collections import Iterable
    print(isinstance(range(100),Iterable))  # 验证range执行之后得到的结果是不是一个迭代器
    
    # True
    # False
    # True

    可迭代的必须要含有__iter__方法  # 可迭代协议

    迭代器比可迭代的多一个__next__方法

    迭代器:包含__next__,__iter__方法的就是迭代器。  #  迭代器协议

        包含__next__方法的可迭代对象就是迭代器

    迭代器是可迭代的一部分

    获得迭代器:可迭代的调用__iter__()

    使用迭代器:__next__()

    如何判断一个变量是不是迭代器或者可迭代的

    方法一:

    print('__iter__' in dir([1,2,3,4]))
    print('__next__' in dir([1,2,3,4]))
    
    # True
    # False
    # 由此可以看出[1,2,3,4]有__iter__用法,没有__next__用法,说明该列表是一个可迭代对象,
    #但不是一个迭代器。

    方法二:

    from collections import Iterable
    print(isinstance(([1,2,3,4]),Iterable))
    # True
    str_iter='abc'.__iter__()
    print(isinstance(str_iter,Iterable))
    # True
    print(isinstance('abc',Iterable))
    # True

    只是记录当前这个元素和下一个元素

    range_iter=range(10).__iter__()
    print(range_iter.__next__())
    print(range_iter.__next__())
    # 0
    # 1

    总结迭代器的特点:1、惰性运算;

             2、从前到后依次的取值,但是过程是不可逆的,不可重复的;

             3、节省内存

    生成器: 

    用生成器做监听文件的程序

    def tail():
        f = open('文件', 'r', encoding='utf-8')  # 打开文件
        f.seek(0, 2)  # 将光标移到文件内容的最后
        while True:
            line = f.readline()  # 读出文件中光标之后的内容
            if line:  # 如果有则输出新加的内容
                yield line
    g = tail()
    for i in g:
        print(i.strip())

    send用法:

    1、从哪一个yield开始接着执行,就把一个值传给了那个yield

    2、send不能用在第一个触发生成器

    3、生成器函数中有多少个yield就必须有多少个next+send

    def func():
        print('*'*10)
        a = yield 5
        print(a)
        b = yield 10
    g = func()
    num = g.__next__()
    print(num)
    num2 = g.send('alix')
    print(num2)
    
    # **********
    # 5
    # alix
    # 10

    利用send计算滚动平均值:

    def avarge():
        total=0.0
        count=0
        avarge=None
        while True:
            trem=yield avarge
            count+=1
            total+=trem
            avarge=total/count
    g=avarge()
    avg_num=g.__next__()
    avg_num=g.send(10)
    print(avg_num)
    avg_num=g.send(30)
    print(avg_num)
    avg_num=g.send(60)
    print(avg_num)
    
    # 10.0
    # 20.0
    # 33.333333333333336

    将上面的滚动平均值改成装饰器的形式:

    def init(func):  # 生成器的预激装饰器
        def inner(*args,**kwargs):
            g=func(*args,**kwargs)  # func=avarge
            g.__next__()
            return g
        return inner
    @init
    def avarge():
        total=0.0
        count=0
        avarge=None
        while True:
            trem = yield avarge
            count += 1
            total += trem
            avarge = total/count
    g = avarge()
    avg_num=g.send(10)
    print(avg_num)
    avg_num=g.send(30)
    print(avg_num)
    avg_num=g.send(60)
    print(avg_num)
    
    # 10.0
    # 20.0
    # 33.333333333333336
    # 将a='AB',b='CD'输出,输出成'A','B','C','D'
    def func():
        a = 'AB'
        b = 'CD'
        yield from a
        yield from b
    g=func()
    g_s=g.__next__()
    print(g_s)
    g_s=g.__next__()
    print(g_s)
    g_s=g.__next__()
    print(g_s)
    g_s=g.__next__()
    print(g_s)
    # A
    # B
    # C
    # D

    或将上面的代码简化

    # 将a='AB',b='CD'输出,输出成'A','B','C','D'
    def func():
        a = 'AB'
        b = 'CD'
        yield from a
        yield from b
    g=func()
    for i in g:
        print(i)
    # A
    # B
    # C
    # D

    触发执行的方式:
    next和send是执行几次拿几个数据,在取值的过程中不知道到底有多少个,可能会超出范围,当超出范围的时候会报错。

    其中send(None)==__next__(),send中next的基础上传一个值到生成器函数内部;send操作不能用在生成器使用的第一次。

    for循环每次取一个值,取完为止,不会报错

    def cloth():
        for i in range(100):
            yield '衣服%s'%i
    g = cloth()
    for c in g:
        print(c)
        if c.endswith('20'):  # 打印到衣服20
            break
    print(g.__next__())  # 再次触发生成器,输出衣服21到衣服99
    print('*'*20)
    for c in g:
        print(c)

    列表推导式:

    求出l=[1,2,3,4,5,6,7]中每个元素的平方并存入新的列表中

    #方法一:
    l=[1,2,3,4,5,6,7]
    li=[]
    for i in l:
        li.append(i*i)
    l=li
    print(l)
    # [1, 4, 9, 16, 25, 36, 49]
    
    
    #方法二:
    l=[1,2,3,4,5,6,7]
    x=[i*i for i in l]
    print(x)
    # [1, 4, 9, 16, 25, 36, 49]

    生成器表达式:

    l=[1,2,3,4,5,6,7]
    g=(i*i for i in l)
    for i in g:
        print(i)
    # 1
    # 4
    # 9
    # 16
    # 25
    # 36
    # 49

    使用生成器的优点:
    1、延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这在处理较多数据的时候可以节省内存空间。

    2、提高代码可读性

    字典的推导式:

    找出names中含有2个e的名字:

    names=[{'Tom','Billy','Jefferson','Andrew','Wesley','Steven','Joe'},
    {'Alice','Jill','Ana','Wendy','Jennifer','Sherry','Eva'}]
    names=[{'Tom','Billy','Jefferson','Andrew','Wesley','Steven','Joe'},
           {'Alice','Jill','Ana','Wendy','Jennifer','Sherry','Eva'}]
    ret=[name for name_list in names for name in name_list if name.count('e')>=2]  # 相当于两个for循环的嵌套,之后再加上一个if判断
    print(ret)
    
    # ['Steven', 'Jefferson', 'Wesley', 'Jennifer']
  • 相关阅读:
    js交互数据
    js字符串操作
    js数组操作
    hasattr ,setarrt, getattr属性
    装饰器
    redis数据库安装
    ubuntu中mysql数据库安装与删除
    装换器
    jinjia2
    Laravel框架与ThinkPHP框架的不同
  • 原文地址:https://www.cnblogs.com/hzhcdhm/p/7778216.html
Copyright © 2020-2023  润新知