• 3-可迭代对象 迭代器 生成器的原理剖析


    一、可迭代对象:有_ _iter_ _方法

    二、迭代器:同时有 _ _iter_ _和 _ _next_ _方法

    三、生成器 :yield / (i for in in range(n))

    四、字典、元祖、列表等可迭代对象

           可以被迭代若干次

           可以被转换成迭代器对象(使用iter方法)

    一、可迭代对象

    首先来看一下,python 2.7版本range的实现原理:

    from typing import Iterable
    
    
    def range_test(stop):
        start = 0
        result = []
        while start < stop:
            result.append(start)
            start += 1
        return result
    
    
    if __name__ == '__main__':
        print(isinstance(range_test(10),Iterable))  # 判断返回结果是不是可迭代,True
        print(range_test(10))                     # output:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    这种方式,需要多少数据,全部存在list中,在数据量比较大的情况下,是非常耗内存的。

    那么如何实现高性能(省内存)的呢?请看实现:

    省内存:延迟生成数据,节省内存

    from typing import Iterable
    
    
    # Next含有__next__()方法,每次变量+1,一次只占用一个数的内存
    class Next(object):
        """Next class"""
    
        def __init__(self, stop, start=-1):
            self.start = start
            self.stop = stop
    
        def __next__(self):
            if self.start >= self.stop - 1:
                raise StopIteration
            self.start += 1
            return self.start
    
    
    # 含有__iter__()方法,return调用Next
    class MyRange:
    
        def __init__(self, stop):
            self.stop = stop
    
        def __iter__(self):
            return Next(self.stop)
    
    
    if __name__ == '__main__':
        print(Next(10).__next__())  # 0

    print(isinstance(MyRange(10), Iterable)) # True (因为MyRange有__iter__()) print(isinstance(Next(10), Iterable)) # False (Next没有__iter__()) for i in MyRange(10): # for循环自动执行__iter__,__next__方法 print(i) # 0,1,2,3,4,5,6,7,8,9 # 下面用while循环,解释下运行的原理。(for的原理,for是一种语法,会自动执行__next__()) num = MyRange(10).__iter__() i = 0 while i < 10: print(num.__next__()) # 0,1,2,3,4,5,6,7,8,9 i += 1

    总结:

    1.可迭代对象:对象必须要有_ _iter_ _()方法

    (能被for迭代的对象 都是可迭代对象):这里给你们捋一捋思路,能够被for迭代是因为for会自动调用__iter__(),那有了__iter__,就符合可迭代对象的基本定义了。

    常见的可迭代对象:list,str,tuple

    我们也可以用dir()自省,来判断某个每个对象是不是可迭代对象。自省的结果中有_ _iter_ _(),说明是可迭代对象

    上面的例子可以用hasattr()验证,返回值是bool类型,可迭代对象是否有_ _iter_ _():--------hasattr() 函数用于判断对象是否包含对应的属性

     print(hasattr(list,"__iter__")) # True
     print(hasattr(str, "__iter__")) # True
     print(hasattr(tuple, "__iter__")) # True

    2.for 用在迭代的场景,能用for,就不用while。不会越界,比较安全。(for 循环不会越界,while循环很容易越界,while通常用在自由执行循环的场景

       

    二、迭代器

    内置函数next():

    首先来看下next()源码:

    def next(iterator, default=None): # real signature unknown; restored from __doc__
        """
        next(iterator[, default])
        
        Return the next item from the iterator. If default is given and the iterator
        is exhausted, it is returned instead of raising StopIteration.
        """
        pass

    看见没?想要使用next()函数,参数必须是个迭代器!!!

    来看看list,能不能使用next()方法:

    print(next([1,2]))
    """
        print(next([1,2]))
    TypeError: 'list' object is not an iterator
    """

    哎呀,报错了:list对象不是一个迭代器!!

    因为next([1,2])实际上执行的是[1,2]._ _next_ _(). 但是list没有_ _next_ _()属性,就会报错(list不是一个迭代器)

    print(hasattr(list,"__next__")) # False

    那什么是迭代器??

    迭代器:

    必须同时有 _ _iter_ _和 _ _next_ _方法,才是迭代器

    核心:通过_ _next_ _方法记住迭代的位置,有没有迭代完毕。

    举例:上面例子中的class Next,里面有_ _next_ _,是不是迭代器呢?

    class Next(object):
        """Next class"""
    
        def __init__(self, stop, start=-1):
            self.start = start
            self.stop = stop
    
        def __next__(self):
            if self.start >= self.stop - 1:
                raise StopIteration
            self.start += 1
            return self.start
    
    
    if __name__ == '__main__':
        print(isinstance(Next(2),Iterator))  #False 因为虽然含有__next__属性,但是没有__iter__属性,不是一个迭代器

    栗子2:

    class Fruit:
        """ class Fruit
        :param
        :raise Raise StopIteration if index out of len(friuts)
        """
    
        def __init__(self):
            self.fruit = ["apple","peach","banana","grape"]
            self.index = 0
    
        def __next__(self):
            if self.index >= len(self.fruit):
                raise StopIteration
            self.index += 1
            return self.fruit[self.index - 1]
    
    if __name__ == '__main__':
        fruit = Fruit()
        for item in fruit:      #对fruit对象进行迭代,不是一个可迭代对象,因为没有_ _iter_ _
            print(item)
    """
        for item in fruit:
    TypeError: 'Fruit' object is not iterable
    """

    下面加上一个_ _iter_ _方法,再来看一下

    class Fruit(object):
        """ class Fruit
        :param
        :raise Raise StopIteration if index out of len(friuts)
        """
    
        def __init__(self):
            self.fruit = ["apple", "peach", "banana", "grape"]
            self.index = 0
    
        def __iter__(self):
            return iter(self.fruit)
    
        def __next__(self):
            if self.index >= len(self.fruit):
                raise StopIteration
            self.index += 1
            return self.fruit[self.index - 1]
    
    
    if __name__ == '__main__':
        ft = Fruit()
        for item in ft:
            print(item)
    """
    apple
    peach
    banana
    grape
    """
    print(isinstance(ft,Iterator)) # True 是迭代器,同时含有__iter__ 和__next__

    iter()方法-----iter() 函数用来把可迭代对象生成迭代器

    上面的_ _iter_ _()方法的返回值要是一个“迭代器”---严格的说,返回值必须有_ _next_ _()方法,比如开始的例子,MyRange中的_ _iter__()的返回值,调用了Next,有_ _next_ _()方法。

     总结:

    ①迭代器是一个特殊的可迭代对象,特殊在它还有_ _next_ _()方法

    注意:上面的 _ _next_ _() 没有被for循环调用,for循环只是自动调用了_ _iter__()。因为自己写的__iter__(),不会去调用_ _next_ _()

    那么怎么才能调用_ _next_ _()方法呢?

    继承迭代器(直接继承迭代器(已经实现了_ _iter_ _),不需要自己写_ _iter__()方法了)

    from typing import Iterator
    
    
    class Fruit(Iterator):
        """ class Fruit
        :param
        :raise Raise StopIteration if index out of len(friuts)
        """
    
        def __init__(self):
            self.fruit = ["apple", "peach", "banana", "grape"]
            self.index = 0
    
        def __next__(self):
            print("这个是__next__ 方法")
            if self.index >= len(self.fruit):
                raise StopIteration
            self.index += 1
            return self.fruit[self.index - 1]
    
    
    if __name__ == '__main__':
        ft = Fruit()
        for item in ft:
            print(item)
    """
    这个是__next__ 方法
    apple
    这个是__next__ 方法
    peach
    这个是__next__ 方法
    banana
    这个是__next__ 方法
    grape
    这个是__next__ 方法
    """

     for 迭代迭代器时,会自动调用_ _next_ _()方法。自己写的那个算个假迭代器,不会执行_ _next_ _().

    在实际使用中,直接继承迭代器即可。

    总结:

    ①迭代器的_ _next_ _(),记住迭代的位置

    ②一次占用一个数的内存,省内存

    ③for原理:for循环自动调用_ _iter_ _(),而 _ _iter_ _()会调用 _ _next_ _().  ----------------核心

    三、生成器

    因为迭代器手写比较麻烦,所以python提供了生成器对象。

    生成器对象的目的就是获得迭代器对象。

    生成器的目的:就是获得迭代器对象(高效)

    1.如何创建一个生成器对象?----使用yield

    yield关键字----特殊功能,为了生成器而提供的关键字。

    即函数中使用yield,调用函数的时候,会先 生成一个生成器(而不是直接运行函数)

     ❤生成器是特殊的迭代器,迭代器是特殊的可迭代对象。(生成器是可迭代对象)

    def fun():
        print("在yield1前面")
        yield 1
        print("在yield1后面,yield2前面")
        yield 2
    
    
    if __name__ == '__main__':
        # 验证是否是生成器
        print(fun())   # <generator object fun at 0x104d00150> 结果是一个生成器对象,但是没有运行函数
        print(hasattr(fun(),"__iter__"))  # True
        print(hasattr(fun(),"__next__"))  # True
    
        # 运行生成器,必须要调用__next__
        print(fun().__next__())  # 1 / 在yield1前面
        
        # 运行出全部结果,for自动调用__next__
        for i in fun():
            print(i)
    """
    在yield1前面
    1
    在yield1后面,yield2前面
    2
    """

    当python解释器看到函数中有yield的时候,不会立即去调用这个函数,而是先创建一个生成器对象。而这个生成器对象会记住函数的状态(存代码,记住函数的执行状态:是否执行?执行到哪里了?)。

    ②原理解释:在for循环中,运行到第一个yield时,就暂时退出了(类似return,但区别是return是永久跳出不会再回去了)。再次运行时(__next__记住位置),会从yield1后面开始运行。

    ③ 当执行next(生成器对象)时,会执行生成器对象的yield.

         h = fun()  # h是一个生成器对象

      next(h),会执行g里面的yield

     ④直接调用函数,函数没有直接运行,而是先创建一个生成器对象?那什么时候才能运行yield呢?

        ❤调用_ _next_ _()方法是会运行yield。如上面例子中运行的for,for会自动调用_ _next_ _()。或者fun()._ _next_ _() ;

     ⑤生成器 就是 迭代器的抽象

     ⑥生成器的使用场景:协程 

    读写大文件

    (一边下载video 一边播放。下载的内容用yield 返回,记录下载返回的数据状态)

    ⑦生成器 省 内存的原理:就是因为__next__

       生成器是为了快速获得含有_ _next_ _()和_ _iter_ _()方法 ----就是为了获得迭代器。

       

     summary:

    1.可迭代对象(含有__iter__)

    2.迭代器的特点:

       记住遍历位置

       迭代器只能被迭代一遍

       同时含有__next__() 和__iter__()

    3.迭代器的应用场景:for循环

    4.有了循环为啥还要迭代器?循环需要判断便利到哪里,但是迭代器不需要判断

    5.什么对象可以转化为迭代器?可迭代对象,用iter()进行转化

    6.生成器(特殊的迭代器):

        ---简化迭代器创建过程,提供关键字yield 

        ---函数中有yield,调用函数时,函数会先创建一个生成器对象,这个对象就是一个迭代器

        ---yield 记住执行的位置;保存状态、恢复状态

     
  • 相关阅读:
    Java中遍历Set集合的方法
    分布式RPC框架Apache Dubbo
    CSS:页面美化和布局控制
    JavaScript实例
    Codeforces Round #604 题解
    洛谷P1533 可怜的狗狗题解
    Educational Codeforces Round 81 题解
    P1494 [国家集训队]小Z的袜子 题解
    洛谷P1283 平板涂色题解
    洛谷P1220 关路灯题解
  • 原文地址:https://www.cnblogs.com/ananmy/p/14038022.html
Copyright © 2020-2023  润新知