美文网首页Python与数据分析
【Python学习笔记】迭代器与生成器简析

【Python学习笔记】迭代器与生成器简析

作者: 清梦载星河 | 来源:发表于2020-02-25 17:22 被阅读0次

“女娲娘娘,我想要个对象……”

女娲默不作声,从泥塘里拉过来一个小人……

“美丽的女娲娘娘,我也想要一个对象……”

女娲微微一笑,抓了一把泥,捏出一个俊俏的小人……

在上面的比喻中,女娲从泥塘取出一个对象就相当于在迭代,对象都是事先就做好的;而女娲捏泥人就相当于生成器,现场生成一个对象。

一、迭代器

上面的比方只是开玩笑,Python迭代器的正规定义为:

迭代器是一种对象,该对象包含值的可计数数字。

迭代器是可迭代的对象,这意味着您可以遍历所有值。

从技术上讲,在 Python 中,迭代器是实现迭代器协议的对象,它包含方法 __iter__()__next__()

——摘自w3cschool

看描述可能有点不理解,但其实我们早有接触,比如:

for i in [1,2,3,4,5]:
    print(i)

绝大部分简单学习过Python编程的人对于上面两行代码肯定都不陌生,就是简单的for循环。在上面的代码中,列表[1,2,3,4,5]就是一个可迭代对象。其实在Python中,所有集合都是可迭代的。在Python语言内部,迭代器用于支持:

  • for循环;
  • 构建和扩展集合类型;
  • 逐行便利文本文件;
  • 列表推导、字典推导和集合推导;
  • 元组拆包;
  • 调用函数时,使用 * 拆包参数。

可迭代的依据

主要因素:

  • __iter__方法:返回self,以便在应该使用可迭代对象的地方使用迭代器;
  • __getitem__方法:类似__iter__,但属于遗留方法;
  • __next__方法:返回下一个可用的元素,如果没有元素了,抛出StopIteration异常。

如何判断对象能否迭代

从Python3.4开始,检查对象能否迭代最准确的方法是调用iter()函数,如果不可迭代,再处理TypeError异常。

也可以使用isinstance()函数,但iter()函数会考虑遗留的__getitem__方法,而isinstance()则不会考虑。

如何构建一个迭代器

若要构建一个迭代器,在类中实现__next____iter__就行了。

class NewIterator:
    def __iter__(self):
        self.a = 1
        return self
    def __next__(self):
        x = self.a
        self.a += 1
        return x
    
tmp1 = NewIterator()
tmpiter = iter(tmp1)

print(next(tmpiter))
print(next(tmpiter))
print(next(tmpiter))

### 输出
1
2
3

二、生成器

只要函数的定义体中有yield关键字,该函数就是生成器函数。所有的生成器都是迭代器,因为生成器完全实现了迭代器的接口。与迭代器不同的是,生成器是随需随创随用,而迭代器是先创再随需随用。因为需要的时候再创建数据,所以相比于迭代器,生成器更节省资源。

import os
import psutil # 可能需要使用pip安装

def show_memory_info(hint):
    '''显示当前Python程序占用的内存大小'''
    pid = os.getpid()
    p = psutil.Process(pid)
    info = p.memory_full_info()
    memory = info.uss / 1024. / 1024
    print('{} memory used: {} MB'.format(hint, memory))
    
def test_iterator(): 
    show_memory_info('initing iterator') 
    list_1 = [i for i in range(10000000)] 
    show_memory_info('after iterator initiated') 
    print(sum(list_1)) 
    show_memory_info('after sum called')
    
def test_generator(): 
    show_memory_info('initing generator') 
    list_2 = (i for i in range(10000000)) 
    show_memory_info('after generator initiated') 
    print(sum(list_2)) 
    show_memory_info('after sum called')

上面代码中,test_iterator()test_generator()之间的区别只有推导式之间的区别。

我们可以使用timeit这个魔法函数来测试:

%timeit test_iterator()

### 迭代器测试的输出
initing iterator memory used: 33.89453125 MB
after iterator initiated memory used: 226.04296875 MB
49999995000000
after sum called memory used: 226.04296875 MB
Wall time: 1.36 s
    
# ----------------#

%timeit test_generator()

### 生成器测试的输出
initing generator memory used: 33.89453125 MB
after generator initiated memory used: 33.89453125 MB
49999995000000
after sum called memory used: 33.89453125 MB
Wall time: 1.49 s

明显可见,在一千万个元素的求和时,迭代器比生成器占据更多的内存资源。

相关文章

网友评论

    本文标题:【Python学习笔记】迭代器与生成器简析

    本文链接:https://www.haomeiwen.com/subject/bsmxchtx.html