“女娲娘娘,我想要个对象……”
女娲默不作声,从泥塘里拉过来一个小人……
“美丽的女娲娘娘,我也想要一个对象……”
女娲微微一笑,抓了一把泥,捏出一个俊俏的小人……
在上面的比喻中,女娲从泥塘取出一个对象就相当于在迭代,对象都是事先就做好的;而女娲捏泥人就相当于生成器,现场生成一个对象。
一、迭代器
上面的比方只是开玩笑,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
明显可见,在一千万个元素的求和时,迭代器比生成器占据更多的内存资源。
网友评论