深入理解Python迭代器和可迭代对象

  • Post author:
  • Post category:python




1 迭代器

    迭代是指对集合元素遍历的一种方式,迭代器是可以实现对集合从前向后依次遍历的一个对象



2 可迭代对象

  • 定义(表面理解)

      表面来看,只要可以用 for...in...进行遍历的对象就是可迭代对象    
    
  • 自定义可迭代对象(本质)

      语法层面,如果一个对象实现了__iter__方法,那么这个对象就是可迭代对象
    
  • 判断是否是可迭代对象(Iterable)

      通过调用Python内置的isinstance来判断是否是Iterable的实例
      
      In [9]: isinstance([],Iterable)
      Out[9]: True
      
      In [10]: isinstance('',Iterable)
      Out[10]: True
    
      In [11]: isinstance({},Iterable)
      Out[11]: True
    
      In [12]: class MyList(object):
      ....:     def __init__(self):
      ....:         self.list = []
      ....:     def add(self,item):
      ....:         self.list.append(item)
      ....:         
    
      In [13]: mylist = MyList()
    
      In [14]: isinstance(mylist,Iterable)
      Out[14]: False
    

通过上面例子可以看出,Python中的内置类型dict,list,str的对象都是可迭代的,我们自定义了一个类MyList,由于这个类没有实现__iter__方法,所以这个类的实例不是可迭代对象


接下来实现一个自定义的可迭代对象,只需要给MyList类添加一个__iter__方法就好。

    In [16]: class MyList(object):
    ....:     def __init__(self):
    ....:         self.list = []
    ....:     def add(self,item):
    ....:         self.list.append(item)
    ....:     def __iter__(self):
    ....:         pass
    ....:     

In [17]: from collections import Iterable

In [18]: mylist = MyList()

In [19]: isinstance(mylist,Iterable)
Out[19]: True

至此,我们已经实现了一个自定义的可迭代对象,但是这个可迭代对象还不能通过for…in…语法去迭代,因为我们还缺一个迭代器去帮我们遍历。for…in…遍历的本质就是调用对象的__iter__方法返回一个迭代器,然后通过这个迭代器去依次取得可迭代对象的每一个元素。Python中的内置可迭代对象用到的迭代器Python已经帮我们实现了,我们自定义的可迭代对象的迭代器只有我们自己动手实现了。




给自定义可迭代对象插上迭代器翅膀
1 from collections import Iterable
2                  
3 class MyList(object):
4     def __init__(self):
5         self.items = []
6                  
7     def add(self,val):
8         self.items.append(val)
9                  
10    def __iter__(self):
11                  
12        myiterator = MyIterator(self)
13        return myiterator
14                  
15 class MyIterator(object):
16                  
17     def __init__(self,mylist):
18         self.mylist = mylist
19         self.current= 0
20                  
21     def __next__(self):
22         if self.current < len(self.mylist.items):
23             item = self.mylist.items[self.current]
24             self.current += 1
25             return item
26         else:    
27             raise StopIteration
28     def __iter__(self):
29         return self                                                                                                                                                           
30                  
31 if __name__ == '__main__':
34     mylist = MyList()
35     mylist.add(1)
36     mylist.add(2)
37     mylist.add(3)
38     mylist.add(4)
39     mylist.add(5)
40     for num in mylist:
41         print(num)

for…in…遍历mylist时,先通过mylist的__iter__方法拿到一个迭代器对象myiterator,然后不断调用myiterator的__next__的方法依次取到遍历的每一个元素。也许会困惑的一点是” MyIterator中的__iter__方法有什么卵用 ? 里面为什么还要return self, 毕竟我们只需要迭代器的__next__方法啊!”。其实上面这种写法,完全可以不实现__iter__这个方法,但是上面例子这种写法在实际项目中是很少见的,通常我们创建一个自定义的可迭代对象不会写两个类这么臃肿,而是写一个类,让这个类既是可迭代对象又是迭代器,此时__iter__方法需要返回一个迭代器,而自身又是一个迭代器,所以只需要返回自身即可。




将上面两个类优化成一个类
1 class MyList(object):
2      
3     def __init__(self):
4         self.items = []
5         self.current = 0 
6      
7     def add(self,val):
8         self.items.append(val)
9      
10    def __next__(self):
11        if self.current < len(self.items):
12            item = self.items[self.current]
13            self.current += 1
14            return item
15        else:
16            raise StopIteration
17      
18    def __iter__(self): 
19        return self   #可迭代对象本身就是一个迭代器
20      
21 if __name__ == '__main__':
22     mylist = MyList()                                                                                                                                                         
23     mylist.add(1)
24     mylist.add(2)
25     mylist.add(3)
26     mylist.add(4)
27     mylist.add(5)
28     for num in mylist:
29         print(num)



3. 迭代器(可迭代对象)的应用

因为通常可迭代对象本身就是一个迭代器,所以二者的用途一般是一致的。我们发现迭代器的本质就是通过next()函数(next本质是调用迭代器的___next__方法)不断得到下一个数据,假如需要遍历的数据是可以通过计算规则依次得到的,这时候通过迭代器(升级版本是生成器)去遍历就会避免传统遍历需要依赖已有数据集合从而需要占用大量内存的弊端。下面通过一个经典例子求斐波那契数列的前n项来说明迭代器的优势。



斐波那契数列
数列中第一个数为0,第二个数为1,其后的每一个数都是前两个数相加之和:
    
0,1,1,2,3,5,8,13,21,34...

通过迭代器实现输出前n项的代码:

 1 #coding:utf-8              
2 class FibIterator(object):                                            
3     def __init__(self,n):  
4         self.n = n       #需要遍历的元素个数
5         self.current = 0 #当前遍历的元素个数
6         self.num1 = 0 #第一个元素
7         self.num2 = 1 #第二个元素
8                            
9     def __next__(self):    
10         if self.current < self.n:
11             self.num1, self.num2 = self.num2, self.num1+self.num2
12             self.current+=1
13             return self.num1
14         else:              
15             raise StopIteration
16                            
17     def __iter__(self):    
18         return self        
19                            
20 if __name__ == '__main__': 
21     fibiterator = FibIterator(10)
22     for num in fibiterator:
23         print(num)         

可以看出遍历的元素是通过即时运算生成的,而没有事先占用任何存储空间。迭代器和可迭代对象的知识到这里差不多已经全介绍完了,下篇将会分析Python另一个比较重要的知识点生成器。


2022年7月17日更新。

几年前写的文章,后面因为个人的一些原因,很惭愧博客没有坚持写下去,偶然回来看评论确实帮助到了很多同学,内心还是有一点高兴,这篇文章说的知识点并不是那些所谓的面试秒杀,高并发等对我们这些大多数日常在公司打螺丝的人来说只有面试时用到的大而虚的东西,但我想当时把这些说明白在今天看来还是有它的意义。我自己几年前也注册过一个公众号,后面没弄下去的原因是自己的知识输入确实无法支撑起持续的输出,渐渐放弃了。最近又重新申请了一个公众号,但是纯打算扯扯蛋,聊聊自己的日常生活、学习理财的体会,关于持续学习以及感兴趣的一些事情,因为纯技术的号我自己也清楚自己搞不动。我自己个人情况大体是:本科:计算机,研究生:人工智能。因为是菜狗,调参实在学不明白,毕业后目前还是在某大厂干开发大头兵,有些刚工作的同学如果需要内推,我很乐意帮忙,毕竟现在确实就业大环境不好。我最近也在思考,如果有一天自己真的被毕业了怎么办?毕竟 heros come and go,but 裁员 forever. 后来想了想一直焦虑也没啥用,保持持续学习,生活上开源节流,认真学习下理财知识,合理安排自己的工资,是自己接下来要做的事情,因为自己真的也没其它能搞钱的特长。大家如果现阶段比较迷茫也不要焦虑,一起加油。

公众号名称:苇小院



版权声明:本文为kobebryantlin0原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。