Python迭代器和可迭代对象详解

Python迭代器

  • 迭代器
  • 迭代(iter)
  • 可迭代(iterable)
  • 可迭代对象
  • 迭代器(iterator)
  • 遍历
  • iter函数
  • next函数
  • 自定义可迭代对象与自定义迭代器
  • Python for循环的运行过程
  • 迭代器

    迭代(iter)

    我们经常听说过"版本迭代"这个词,意思是在原来版本的基础上,再提升一个版本的过程。那么我们仅仅看看"迭代"这个词,会发现迭代就是一个根据原来的状态决定本次状态的过程
    迭代应用于Python中,迭代具体是指根据原来的数据输出(并不一定是要打印,也可能仅仅是取出数据),决定本次数据输出的过程

    可迭代(iterable)

    就可以根据原来的数据输出(并不一定是要打印,也可能仅仅是取出数据),决定本次数据输出,即输出数据时候具有迭代的能力,即为可迭代

    可迭代对象

    即输出数据时候具有迭代的能力的对象为可迭代对象

    迭代器(iterator)

    使得可迭代对象具有迭代的能力的对象,也就是每一个可迭代对象就是因为具有一个迭代器,所以才具有迭代的能力,即对应一个可迭代对象来说,必须具有迭代器
    本质上每一次对可迭代对象进行迭代操作的时候,可迭代对象都是让迭代器去执行这个迭代操作,即迭代器就是一个打工人,为可迭代对象打工,使得可迭代对象可以进行迭代操作

    遍历

    遍历,这个词我相信读者一定不陌生,遍历就是一个将所有数据依次取出的过程,结合"迭代"的概念来看,遍历中每一次将数据取出的操作,都是要先知晓上一次取出的哪一个数据,然后才能根据上一次取出的数据,再取出这一次应该取出的数据,这就是一个迭代的过程
    所以,遍历就是一个不断迭代的过程,迭代可以看作是遍历的一个子过程,可以进行迭代操作是可以进行遍历的基础,可以实现的遍历操作的对象必定是一个可迭代对象
    Python中,遍历操作的方式就是进行for循环

    iter函数

    上面提到可迭代对象必须具有迭代器,否则就不具有迭代的能力,而使用iter函数可以将一个可迭代对象的迭代器取出(迭代器:“我 free(免费)啦”)

    lst = [1, 2, 3]
    for num in lst:		#对列表进行遍历,如果可以成功完成,就说明列表类型对象为可迭代对象
        print(num)
    
    iterator = iter(lst)	#取出列表的迭代器
    print(iterator)
    
    #输出结果:
    """
    1
    2
    3
    <list_iterator object at 0x000002A0E654ACD0>
    """
    

    上面也提到过,可迭代对象进行迭代操作的本质就是让它的迭代器去完成迭代操作,所以可迭代对象可被遍历,那么我们也可以尝试一下,对于迭代器本身,是否可遍历

    lst = [1, 2, 3]
    
    iterator = iter(lst)
    for num in iterator:	#对迭代器进行遍历
        print(num)
    
    #输出结果:
    """
    1
    2
    3
    """
    

    结果是迭代器对象是可遍历的
    可迭代对象的定义是具有迭代能力的对象,而迭代器现在我们尝试出来是可以被遍历,而遍历的本质就是可迭代,即迭代器是具有迭代能力的,所以,迭代器实际上也是可迭代对象

    那既然可迭代对象可以被调用iter函数,那迭代器是否可以调用呢?

    lst = [1, 2, 3]
    
    iterator_1 = iter(lst)
    iterator_2 = iter(iterator_1)   #针对迭代器调用iter函数
    print(iterator_2)
    
    #输出结果:
    """
    <list_iterator object at 0x0000021EED8BACD0>
    """
    

    从结果来看,迭代器是可以调用iter函数的

    next函数

    使用next函数可以对一个迭代器完成一次迭代操作,即可以根据上次取出的数据,决定这一次取出数据

    lst = [1, 2, 3]
    
    iterator = iter(lst)
    
    print(next(iterator))	#对迭代器进行一次迭代操作
    print(next(iterator))	#对迭代器进行一次迭代操作
    print(next(iterator))	#对迭代器进行一次迭代操作
    
    #输出结果:
    """
    1
    2
    3
    """
    

    其实可迭代对象被遍历的本质就是一次次对其对应的迭代器进行这样的操作完成的
    这个时候就会有读者会问了,如果再对迭代器调用一次next函数会咋样嘞?以及如果内部是通过一次次对迭代器调用next函数,那怎么知道何时结束调用呢?毕竟可迭代对象遍历的时候,我们看到的现象是正正好把所有的数据都取出了,不多一个,也不少一个呀

    其实这两个问题可以一起回答
    我们先看看连续对一个迭代器调用next函数的次数超过了可迭代对象的数据总数会怎么样

    lst = [1, 2, 3]
    
    iterator = iter(lst)
    
    print(next(iterator))
    print(next(iterator))
    print(next(iterator))
    
    print(next(iterator))   #连续地,第四次调用next函数
    
    #引发异常:没有任何异常信息,异常类型为StopIteration
    

    引发了异常,而且必定是第四次调用导致的
    这就表明了,对于迭代器的迭代操作,并不是循环式的(也就是取出最后一个元素后,并不会再从头开始取数据),而且结束的方式是抛出异常,即一个迭代器只能实现一次遍历,不能二次遍历(可以猜测,一个可迭代对象是可以实现多次遍历的,而一个迭代器只能实现一次遍历,所以,一个可迭代对象可以产生多个迭代器,每一次遍历过程都要消耗一个迭代器,读者有兴趣的可以自行测试,我这里就不再展示了)
    这是对第一个问题的回答

    既然看到了这样的结果,第二个问题也就迎刃而解了,只需要写一个while死循环,然后添加上异常处理机制,如果出现了异常,表明数据全部取出,就可以结束打破循环出去了,就像下面这个代码一样

    lst = [1, 2, 3]
    iterator = iter(lst)
    
    print('对迭代器的遍历开始')
    
    while True:
        try:
            print(next(iterator))
        except StopIteration as ret:
            break
    
    print('对迭代器的遍历结束')
    
    #输出结果:
    """
    对迭代器的遍历开始
    1
    2
    3
    对迭代器的遍历结束
    """
    

    可以看到完美实现了对于一个迭代器的遍历过程,这也就是对一个可迭代对象的遍历过程,其实这也就是for循环的本质,不过具体的验证要在实现自定义可迭代对象与自定义迭代器后才可以实现

    自定义可迭代对象与自定义迭代器

    上面提到过,可迭代对象就是具有迭代能力的对象,而迭代能力指的是根据上一次的取出的数据,决定这一次应该取出的数据。迭代器就是可迭代对象具有迭代能力的本质,即迭代工作实际上是由迭代器完成的,遍历也就是进行了多次迭代操作,于是可迭代对象的遍历实际上也是迭代器负责的

    想要使用关键字class去自定义可迭代对象和迭代器,就必须深刻了解可迭代对象的本质和迭代器的本质,就必须要使用一个方法来准确判断自己定义的这个对象,是否为一个可迭代对象,或者是一个迭代器

  • 针对于可迭代对象与迭代器的判断,提供一个途径
  • 可以导入collections.abc模块中的Iterable和Iterator,配合函数isinstance,实现准确判断
  • 对于可迭代对象与迭代器判断工具的题外话(不重要)
    可能在一些地方,读者会看到是从collections模块导入Iterable和Iterator的,但是建议写from collections.abc import Iterable,因为from collections import Iterable在python 3.8及更高级版本停止使用,如果使用会抛出这样的异常信息:
    DeprecationWarning: Using or importing the ABCs from ‘collections’ instead of from ‘collections.abc’ is deprecated, and in 3.8 it will stop working from collections import Iterable

    from collections.abc import Iterator, Iterable
    
    lst = [1, 2, 3]
    iterator = iter(lst)
    
    print(isinstance(lst, Iterable))		#判断列表对象是否为可迭代对象
    print(isinstance(iterator, Iterator))	#判断列表的迭代器是否为迭代器
    print(isinstance(iterator, Iterable))	#判断列表的迭代器是否为可迭代对象
    
    #输出结果:
    """
    True
    True
    True
    """
    
  • 针对于可迭代对象与迭代器的构造,提供两个方法
  • __iter__和__next__
  • 单单从方法名来看,就很容易让读者联想到iter函数和next函数
    它们的联系请看下面两个案例

    class C:
        def __iter__(self):
            print('__iter__方法被调用')
            return 10
    
    ins = C()
    iter(ins)
    
    #输出结果:__iter__方法被调用
    #打印上面的结果以后,引发异常:iter() returned non-iterator of type 'int'
    

    我们可以看出,__iter__方法是在对一个实例对象调用iter函数的时候触发的
    根据异常信息(即iter函数的返回值不是一个迭代器,而是一个int类型对象,这个int类型对象就是那个10),以及上面我们对于iter函数的使用,我们可以断定,iter函数的返回值必须是一个迭代器,否则就会像上面这样抛出异常,以及之前我们反复使用iter函数,得到一个迭代器,实际上就是可迭代对象自动调用了这个__iter__方法,然后这个方法返回一个迭代器实现的

    class C:
        def __next__(self):
            print('__next__方法被调用')
            return 10
    
    ins = C()
    
    data = next(ins)
    print(data)
    
    #输出结果:
    """
    __next__方法被调用
    10
    """
    

    我们可以看出,和上面iter函数和__iter__的关系类似,所谓调用next函数实际上就是自动调用__next__方法实现的,而且__next__方法就是next函数的返回值
    所以我们可以断定,next函数对迭代器进行一次迭代操作,必定是通过调用迭代器的__next__方法,然后该方法返回一个数据实现的

    现在我们已经具有了判断一个对象是否为可迭代对象,是否为迭代器的工具了,并且我们知道了调用next函数和iter函数的本质,我们就可以聊一聊如何构造一个可迭代对象和迭代器对象了

    首先我们先尝试构造一个基本的可迭代对象和迭代器对象,先不管内部的代码,即至少让判断工具可以认定我们构造出的就是可迭代对象和迭代器对象

    一个可迭代对象,我们前面对其调用过iter函数,而iter函数的本质就是去自动调用__iter__方法,于是我们可以先这样构造

    from collections.abc import Iterator, Iterable
    
    class C:
        def __iter__(self):
            pass
    
    ins = C()
    print(isinstance(ins, Iterable))		#判断对象是否为可迭代对象
    print(isinstance(ins, Iterator))		#判断对象是否为迭代器
    
    #输出结果:
    """
    True
    False
    """
    

    从输出结果可以看出,具有__iter__方法的对象就是一个可迭代对象,但不是一个迭代器

    一个迭代器,我们前面对其调用过iter函数和next函数,而iter函数的本质就是去自动调用__iter__方法,next函数的本质就是去自动调用__next__方法,于是我们可以先这样构造

    from collections.abc import Iterator, Iterable
    
    class C:
        def __iter__(self):
            pass
    
        def __next__(self):
            pass
    
    ins = C()
    print(isinstance(ins, Iterable))		#判断对象是否为可迭代对象
    print(isinstance(ins, Iterator))		#判断对象是否为迭代器
    
    #输出结果:
    """
    True
    True
    """
    

    从输出结果可以看出,同时具有__iter__方法和__next__方法的对象就是一个迭代器,而且是一个可迭代对象

    综上所述:

  • iter函数的本质就是调用对象中的__iter__方法,next函数的本质就是调用对象中的__next__方法
  • 迭代器的本质就是一个同时具有__iter__方法和__next__方法的对象,而可迭代对象的本质就是一个具有__iter__方法的对象,并且可迭代对象和迭代器的关系为:可迭代对象不一定为一个迭代器,而迭代器必定是一个可迭代对象
  • 现在除了对于迭代器和可迭代对象可以使用collections.abc模块中的Iterable和Iterator,配合函数isinstance,实现准确判断,也可以直接查看该对象是否具有__iter__方法和__next__方法,从而实现准确判断
  • 进一步地,可以进行可迭代对象和迭代器的代码完善了
    这里先写一个可以实现基本功能可迭代对象,以及一个不太完善的迭代器(这个对象其实都不能称为迭代器,因为其只有__next__方法)

    class my_iterable:
        def __init__(self):
            self.lst = list()
    
        def add(self, data):        #实现数据的添加
            self.lst.append(data)
    
        def __iter__(self):         #返回一个'迭代器'对象
            return my_iterator(self.lst)
    
    
    class my_iterator:
        def __init__(self, lst):
            self.lst = lst
            self.count = 0
    
        def __next__(self):
            data = self.lst[self.count]
            self.count += 1
            return data
    
    
    Iterable = my_iterable()		#创建一个可迭代对象
    Iterable.add(1)					#向该可迭代对象中添加数据
    Iterable.add(2)
    Iterable.add(3)
    
    Iterator = iter(Iterable)		#取出可迭代对象的迭代器
    print(next(Iterator))			#对迭代器进行进行迭代操作
    print(next(Iterator))
    print(next(Iterator))
    print(next(Iterator))
    
    #输出结果:
    """
    1
    2
    3
    """
    #打印完上面的结果以后,抛出异常:list index out of range
    

    基本是实现了作为迭代器和可迭代对象的基本功能了,但是还是有一些问题,比如最后的异常抛出的异常类型不对,以及迭代器的部分的代码需要完善一下
    在完善代码之前,先看这两个代码

    lst = [1,2,3]
    lst_iterator_1 = iter(lst)                #取出迭代器
    print(lst_iterator_1)   
    print(next(lst_iterator_1))               #对迭代器进行一次迭代操作
    
    lst_iterator_2 = iter(lst)       #针对迭代器取出迭代器
    print(lst_iterator_2) 
    print(next(lst_iterator_2))               #对迭代器进行一次迭代操作
    
    #输出结果:
    """
    <list_iterator object at 0x0000015D14C0ACD0>
    1
    <list_iterator object at 0x0000015D14C0AAC0>
    1
    """
    

    从结果可以看出,每一次对可迭代对象调用iter函数,取出的迭代器是不一样的,即创建了新的迭代器

    lst = [1,2,3]
    lst_iterator_1 = iter(lst)                #取出迭代器
    print(lst_iterator_1)   
    print(next(lst_iterator_1))               #对迭代器进行一次迭代操作
    
    lst_iterator_2 = iter(lst_iterator_1)       #针对迭代器取出迭代器
    print(lst_iterator_2) 
    print(next(lst_iterator_2))               #对迭代器进行一次迭代操作
    
    #输出结果:
    """
    <list_iterator object at 0x0000022D1CD5ACD0>
    1
    <list_iterator object at 0x0000022D1CD5ACD0>
    2
    """
    

    从结果可以看出,如果对一个迭代器调用iter函数,取出的迭代器就是这个迭代器本身,即并没有创建一个新的迭代器

    根据这两个代码的运行结果,我们就可以更加真实地去还原迭代器了

    class my_iterable:
        def __init__(self):
            self.lst = list()
    
        def add(self, data):        #实现数据的添加
            self.lst.append(data)
    
        def __iter__(self):         #返回一个迭代器对象(创建一个新的迭代器返回)
            return my_iterator(self.lst)
    
    class my_iterator:
        def __init__(self, lst):
            self.lst = lst
            self.count = 0
    
        def __iter__(self):         #返回一个迭代器对象(即返回自身)
            return self
    
        def __next__(self):
            if self.count < len(self.lst):      
                data = self.lst[self.count]
                self.count += 1
                return data
            else:                   #如果下标超出范围,抛出StopIteration异常
                raise StopIteration
    
    
    Iterable = my_iterable()		#创建一个可迭代对象
    Iterable.add(1)					#向该可迭代对象中添加数据
    Iterable.add(2)
    Iterable.add(3)
    
    Iterator_1 = iter(Iterable)		#取出可迭代对象的迭代器
    print(next(Iterator_1))			#对迭代器进行进行迭代操作
    print(next(Iterator_1))
    
    Iterator_2 = iter(Iterator_1)	#取出迭代器的迭代器
    print(next(Iterator_2))			#对迭代器进行进行迭代操作
    print(next(Iterator_2))
    
    #输出结果:
    """
    1
    2
    3
    """
    #打印上面的这些信息后引发异常:没有任何异常信息,异常类型为StopIteration
    

    Python for循环的运行过程

    其实我们上面构造的迭代器和可迭代对象是可以使用for循环进行遍历的

    class my_iterable:
        def __init__(self):
            self.lst = list()
    
        def add(self, data):        #实现数据的添加
            self.lst.append(data)
    
        def __iter__(self):         #返回一个迭代器对象(创建一个新的迭代器返回)
            return my_iterator(self.lst)
    
    class my_iterator:
        def __init__(self, lst):
            self.lst = lst
            self.count = 0
    
        def __iter__(self):         #返回一个迭代器对象(即返回自身)
            return self
    
        def __next__(self):
            if self.count < len(self.lst):      
                data = self.lst[self.count]
                self.count += 1
                return data
            else:                   #如果下标超出范围,抛出StopIteration异常
                raise StopIteration
    
    
    Iterable = my_iterable()
    Iterable.add(1)
    Iterable.add(2)
    Iterable.add(3)
    
    for num in Iterable:	#使用for循环遍历自定义的可迭代对象
        print(num)
    
    #输出结果:
    """
    1
    2
    3
    """
    

    现在,我们可以解析一下for循环的运行过程

    from time import sleep
    
    class my_iterable:
        def __init__(self):
            self.lst = list()
    
        def add(self, data):        #实现数据的添加
            self.lst.append(data)
    
        def __iter__(self):         #返回一个迭代器对象(创建一个新的迭代器返回)
            print('my_iterable的__iter__被调用')
            return my_iterator(self.lst)
    
    class my_iterator:
        def __init__(self, lst):
            print('my_iterator的__init__被调用')
            self.lst = lst
            self.count = 0
    
        def __iter__(self):         #返回一个迭代器对象(即返回自身)
            print('my_iterator的__iter__被调用')
            return self
    
        def __next__(self):
            print('my_iterator的__next__被调用')
            if self.count < len(self.lst):      
                data = self.lst[self.count]
                self.count += 1
                return data
            else:                   
                print('遍历结束')	#使用print标识程序运行的进程
                sleep(2)			#使用减缓打印速度
    
    Iterable = my_iterable()
    Iterable.add(1)
    Iterable.add(2)
    Iterable.add(3)
    
    for num in Iterable:
        print(num)
    
    #输出结果:
    """
    my_iterable的__iter__被调用
    my_iterator的__init__被调用
    my_iterator的__next__被调用
    1
    my_iterator的__next__被调用
    2
    my_iterator的__next__被调用
    3
    my_iterator的__next__被调用
    遍历结束
    None
    """
    #打印完上面的结果以后,不断打印,程序不会停止
    """
    my_iterator的__next__被调用
    遍历结束
    None
    """
    

    从上面代码的运行结果可以知道

  • for循环内部首先会提取可迭代对象的迭代器(通过调用iter函数,触发__iter__方法的自动调用)
  • 接着for循环内部会对这个迭代器不断(这个不断是用死循环实现的)进行迭代操作,取出数据(通过调用next函数,触发__next__方法的自动调用)
  • 最后for循环内部停止对这个迭代器进行迭代操作,是通过捕获StopIteration异常实现的(读者可以尝试将抛出异常的类型进行改变,会发现程序最后会抛出那个异常,说明for循环内部仅仅捕获StopIteration异常,其他类型的异常是不会被捕获的)
  • 即上面for循环的等效代码为

    Iterator = iter(Iterable)
    
    while True:
        try:
            num = next(Iterator)
            print(num)
    
        except StopIteration as ret:
            print(ret)
    

    实际上除了for循环,在Python的很多地方,底层都是迭代器
    比如list函数,tuple函数,dict函数,set函数的实现实际上也是调用了iter函数和next函数,进行遍历,然后转化为对应类型的序列对象,这里演示一下set函数

    class my_iterable:
        def __init__(self):
            self.lst = list()
    
        def add(self, data):        #实现数据的添加
            self.lst.append(data)
    
        def __iter__(self):         #返回一个迭代器对象(创建一个新的迭代器返回)
            print('my_iterable的__iter__被调用')
            return my_iterator(self.lst)
    
    class my_iterator:
        def __init__(self, lst):
            print('my_iterator的__init__被调用')
            self.lst = lst
            self.count = 0
    
        def __iter__(self):         #返回一个迭代器对象(即返回自身)
            print('my_iterator的__iter__被调用')
            return self
    
        def __next__(self):
            print('my_iterator的__next__被调用')
            if self.count < len(self.lst):      
                data = self.lst[self.count]
                self.count += 1
                return data
            else:                   #如果下标超出范围,抛出StopIteration异常
                raise StopIteration
    
    Iterable = my_iterable()
    Iterable.add(1)
    Iterable.add(2)
    Iterable.add(1)
    
    
    
    st = set(Iterable)
    print(st)
    print(type(st))
    
    #输出结果:
    """
    my_iterable的__iter__被调用
    my_iterator的__init__被调用
    my_iterator的__next__被调用
    my_iterator的__next__被调用
    my_iterator的__next__被调用
    my_iterator的__next__被调用
    {1, 2}
    <class 'set'>
    """
    

    可以看到set函数是先进行遍历,然后将取出的相同元素仅保留一个,最后得到集合类型对象

    包括推导式中的for循环也是这个底层也是这个机制

    class my_iterable:
        def __init__(self):
            self.lst = list()
    
        def add(self, data):        #实现数据的添加
            self.lst.append(data)
    
        def __iter__(self):         #返回一个迭代器对象(创建一个新的迭代器返回)
            print('my_iterable的__iter__被调用')
            return my_iterator(self.lst)
    
    class my_iterator:
        def __init__(self, lst):
            print('my_iterator的__init__被调用')
            self.lst = lst
            self.count = 0
    
        def __iter__(self):         #返回一个迭代器对象(即返回自身)
            print('my_iterator的__iter__被调用')
            return self
    
        def __next__(self):
            print('my_iterator的__next__被调用')
            if self.count < len(self.lst):      
                data = self.lst[self.count]
                self.count += 1
                return data
            else:                   #如果下标超出范围,抛出StopIteration异常
                raise StopIteration
    
    Iterable = my_iterable()
    Iterable.add(1)
    Iterable.add(2)
    Iterable.add(3)
    
    
    
    lst = [i for i in Iterable]
    print(lst)
    
    #输出结果:
    """
    my_iterable的__iter__被调用
    my_iterator的__init__被调用
    my_iterator的__next__被调用
    my_iterator的__next__被调用
    my_iterator的__next__被调用
    my_iterator的__next__被调用
    [1, 2, 3]
    """
    

    序列解包也是这个机制

    class my_iterable:
        def __init__(self):
            self.lst = list()
    
        def add(self, data):        #实现数据的添加
            self.lst.append(data)
    
        def __iter__(self):         #返回一个迭代器对象(创建一个新的迭代器返回)
            print('my_iterable的__iter__被调用')
            return my_iterator(self.lst)
    
    class my_iterator:
        def __init__(self, lst):
            print('my_iterator的__init__被调用')
            self.lst = lst
            self.count = 0
    
        def __iter__(self):         #返回一个迭代器对象(即返回自身)
            print('my_iterator的__iter__被调用')
            return self
    
        def __next__(self):
            print('my_iterator的__next__被调用')
            if self.count < len(self.lst):      
                data = self.lst[self.count]
                self.count += 1
                return data
            else:                   #如果下标超出范围,抛出StopIteration异常
                raise StopIteration
    
    Iterable = my_iterable()
    Iterable.add(1)
    Iterable.add(2)
    Iterable.add(3)
    
    
    a, b, c = Iterable
    print(a, b, c)
    
    
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » Python迭代器和可迭代对象详解

    发表回复