• 迭代器 生成器, 可迭代对象以及应用场景


     

    可迭代对象:

    实现了迭代器协议的对象就是可迭代对象(实现方式是,实现iter方法)

    迭代器

    迭代器对象就是实现了iter() 和 next()方法的对象.其中iter()返回迭代器本身,而next()返回容器的下一个元素,在结尾处引发StopInteration异常.

    迭代器有两个方法:
    iter() 和 next()

    it  = iter(iterable)  # 将一个可迭代对象转换为迭代器
    next(it)  # 获取下一个迭代器中的下一个值
    
    `注意`
    list dic tuple string 并不是迭代器,它们只是可迭代对象.但是可以通过iter(list)的方法
    将它们转换为迭代器.
    

    你可能会问,为什么list、dict、str等数据类型不是Iterator?

    这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

    Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

    for x in [1,2,3,4,5]
    pass
    
    `完全等价于`
    
    # 首先获得Iterator对象:
    it = iter([1, 2, 3, 4, 5])
    # 循环:
    while True:
        try:
            # 获得下一个值:
            x = next(it)
        except StopIteration:
            # 遇到StopIteration就退出循环
            break
    

    自定义迭代器

    # encoding:utf-8
    __author__ = 'Fioman'
    __date__ = '2018/11/20 20:15'
    
    from itertools import islice
    from collections import Iterator,Iterable
    # 自定义一个迭代器,求斐波那契序列
    class Fib(object):
        def __init__(self):
            self.prev = 0
            self.curr = 1
    
        def __iter__(self):
            return self
    
        def __next__(self):
            value = self.curr
            self.curr += self.prev
            self.prev = value
            return value
    
    # 迭代器对象
    f = Fib()
    print(isinstance(f,Iterator)) # true
    L = list(islice(f,0,10)) # islice对可迭代对象进行切片
    print(L)
    # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
    
    
    class Container:
        def __init__(self, start, end):
            self.start = start
            self.end = end
    
        def __iter__(self):
            print("调用了 __iter__(self) 方法")
            return self  # 返回迭代器对象本身
    
        def __next__(self):
    
            if self.start < self.end:
                i = self.start
                self.start = self.start + 1
                return i
            else:
                raise StopIteration
    
    
    Cont = iter(Container(0, 10))
    
    for i in Cont:
        print(i)
    
    

    总结

    • 所有的iterable都可以通过内置函数iter()转换为iterator
    • 迭代器的优点:省内存.它是一种通过延时创建的方式生成一个序列,只有在需要的时候才被创建.
    • 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问结束,只能往前不能后退
    • 迭代器有两个基本的方法:iter,text方法
    • 内置函数iter(),next(),本质上都是用的对象的iter()和next()方法.

    生成器

    Python使用生成器对延迟操作提供了支持.所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果.
    这也是生成器的主要好处.

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

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

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

     
     

    创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
    我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
    如果要一个一个打印出来,可以通过generator的next()方法:

    Python有两种不同的方式提供生成器:

    • 生成器表达式

    类似于列表推导式,是用()代替了原来的[].生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

    • 生成器函数

    和常规函数定义一样,但是返回语句return被yield语句代替了.yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行.

    • 使用生成器返回自然数的平方
    def gensquares(N):
        for i in range(N):
            yield i ** 2
    
    
    for item in gensquares(5):
        print(item)
    
    • 使用普通的函数
    # 使用普通的函数
    
    def squares_list(N):
        ret = []
        for i in range(N):
            ret.append(i * i)
        return ret
    
    
    for item in squares_list(5):
        print(item)
    

    再看生成器

    1. 语法上和普通的函数非常相似,都是用def进行定义.唯一的不同是普通的函数是用return返回,而生成器是通过yield语句返回一个值
    2.自动实现迭代器协议:对于生成器,Python会自动实现它的可迭代协议,以便用在可以迭代的地方.所以我们可调用它的next方法,获取下一个元素,并且在没有值可以返回的时候,生成器会自动产生StopIteration异常
    3.状态挂起:生成器使用yield语句返回一值.yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行.

    示例

    首先,生成器的好处是延时计算,一次返回一个结果.也就是所它不会一次返回所有的结果,可以节省内存.

    sum([i for i in range(1000000000)])
    sum((i for i in range(1000000000)))
    

    注意:使用生成器的注意事项

     
     

    列表生成器,迭代器和生成器的区别?

    列表生成器

    现在有个需求,看列表[1,2,3,4,5,6,7,8,9],要求你把列表里面的每个值都加1,你怎么实现呢?

    方法1: 简单,循环遍历,然后用另外一个列表来实现

    # encoding:utf-8
    __author__ = 'Fioman'
    __date__ = '2018/12/6 11:26'
    
    
    # 现在有一个需求,[1,2,3,4,5,6],要求你将里面的数每个数都加1,你怎么办?
    
    # 方法1: 遍历,然后添加
    def func1(num_list):
        # # 使用append
        # b = []
        # for i in num_list:
        #     b.append(num_list[i])
        for index, i in enumerate(num_list):
            num_list[i] += 1
    
        print(num_list)
    
    # 方法2: 使用map,匿名函数.map的作用,就是前面的参数是一个函数,然后是一个可迭代对象
    # 将可迭代对象逐个的带入到前面的函数中,返回一个列表
    def func2(num_list):
        a = map(lambda x:x+1,num_list)
        print(a)
    
    # 方法3: 高级,直接使用列表推导式
    
    def func3(num_list):
        a = [x+1 for x in num_list]  # 列表推导式,前面是一个表达式,表示结果,后面跟一个for加一个可迭代的对象,再后面还可以跟
        # 一个if else 语句进行判断
        print(a)
    
    
    
    if __name__ == '__main__':
        num_list = [1, 2, 3, 4, 5, 6]
        func1(num_list)
    
    

    生成器

    什么是生成器

    通过列表推导式,我们可以直接创建一个列表,但是如果这个列表的长度很大,就很占用内存,比如我们想要创建一个100万个元素的列表,就会占用很大的内存空间.

    如果列表元素可以按照某种算法推算出来,我们可不可以在一边循环的过程中不断的推算出后续的元素呢?这样就不需要一次性分配出一个list所需要的全部的内存空间,答案是肯定的,在Python中我们通过生成器的机制,可以实现一边循环一边分配空间.

    生成器是迭代器的一种,Python中有两种方法来实现生成器,一个是生成器表达式,一个是生成器函数.

    生成器表达式:
    将列表推导式的[] 换成圆括号就是生成器表达式.

    sum([i for i in range(1000000000)])
    sum((i for i in range(1000000000)))
    

    生成器函数,带yield的函数

    生成器是一个特殊的函数,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield代替return返回,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器.

    生成器类似于返回值为一个数组的一个函数,这个函数可以接收参数,可以被调用,但是不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,生成器可以被看作是一个函数,但是它本质上也算是一个迭代器.

    小结

    • 凡是可以用作for循环的对象都是可迭代对象,都遵循了可迭代协议
    • 凡是可以用next()取出下一个元素,使用iter()返回自身的的迭代器类型
    • 可迭代数据类型如list,dict,str等都是Iteralbe但不是迭代器Iterator,不过可以通过iter(list)函数获取一个迭代器对象

    对于yield的总结

    • 通常的for ... in ..循环中,后面的是一个列表或者是字典,或者是字符串.它的缺点是很明显的,就是在迭代的时候一下就分配了全部的内存,这样数据比较大,将会占用很大的内存.

    • 生成器是可以迭代的,但是只可以读取它一次,因为用的时候才生成.生成器表达式和列表推导式的区别就是生成器表达式使用小括号而列表推导式使用[]

    • 生成器(generator)能够迭代的关键是他有next()方法,工作原理是通过重复调用next()方法,直到捕获一个StopIteration异常

    • 带有yield的函数不再是一个普通的函数,而是一个生成器函数.可以用于迭代

    • yield就是return返回的一个值,并且记住这个返回的位置.下一次迭代就从整个位置开始

    • send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式的值



    作者:莫辜负自己的一世韶光
    链接:https://www.jianshu.com/p/411352426841
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 相关阅读:
    linux基础学习-6.4-Linux无法上网排查流程
    [JSOI2007][BZOJ1029] 建筑抢修
    [HNOI2003][BZOJ1216] 操作系统
    [Apio2009][BZOJ1179] Atm
    [Tjoi2013][BZOJ3172] 单词
    AC自动机学习笔记
    [转]一个比较通俗的KMP算法讲解
    [HAOI2008][BZOJ1042] 硬币购物
    [NOI2007][BZOJ1491] 社交网络
    [SCOI2009][BZOJ1295] 最长距离
  • 原文地址:https://www.cnblogs.com/liurenli/p/10127818.html
Copyright © 2020-2023  润新知