python学习:迭代器和生成器
在理解迭代器和生成器之前,首先需要了解可迭代对象。我们都知道列表list,字典dict,元组tuple,集合set都是属于容器,都是可迭代的,我们可以通过for obj in 容器去遍历容器内的对象。那是因为所有的容器都有实现一个迭代器,有两个实现两个重要的方法:__iter__和__next__。我们可以通过isinstance(obj, Iterable)来判断一个对象是否可迭代。
from collections.abc import Iterable
l = [1,2,3,4] #数组
t = (1,2,3,4)#元组
d = {"a":1,"b":2} #字典
s = {1,3,4,5} #集合
string = "this is string"
print(isinstance(l,Iterable)) #True
print(isinstance(s,Iterable)) #True
print(isinstance(d,Iterable)) #True
print(isinstance(s,Iterable)) #True
迭代器
迭代,是访问集合中的对象元素的一种方法。
而迭代器是可以帮助记住访问位置的对象。它能从集合的第一个元素开始访问,直到最后一个元素结束。只能向前不能后退。
如集合set,列表list,字典dict,字符串我们都可以通过for循环迭代。因为它们都是实现了迭代器需要的两个基本方法:__iter__()和__next__().
我们通过集合set,列表list,字典dict,字符串都可以生成迭代器。
l = [1,2,3,4]
it = iter(l) #通过列表生成迭代器
for x in it: #访问迭代器,依次输出
print(x,end=" ")
1 2 3 4
d = {"a":1,"b":2}
it = iter(d) #字典的迭代器,是根据key值生成。
for key in it:
print(key,end=" ") #字典的key值
a b
迭代器只能向前不能后退,如果在访问之后,我们还继续next,则会抛出一个StopIteration的异常
l = [1,2,3,4]
it = iter(l) #通过列表生成迭代器
for x in it: #访问迭代器,依次输出
print(x,end=" ")
print("\n迭代完成后,调用next:")
next(it)
1 2 3 4
迭代完成后,调用next:
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-13-f4f6d96eba47> in <module>
5
6 print("\n迭代完成后,调用next:")
----> 7 next(it)
StopIteration:
我们自定义的类,也可以通过实现__iter__和__next__两个方法而可迭代。在生成迭代器的时候和首次开始迭代的时候会调用__iter__()方法,而在每次迭代的时候调用next()方法。
#定义一个水果类
class Fruit:
def __init__(self,type):
self.type = type
#苹果类
class Apple(Fruit):
def __init__(self):
super().__init__("Apple")
#橘子类
class Orange(Fruit):
def __init__(self):
super().__init__("Orange")
#定义一个水果盒子
class FruitBox():
def __init__(self):
self.fruit_list = []
def add_fruit(self,fruit):
self.fruit_list.append(fruit)
#初始化一个水果盒子
box = FruitBox()
#往水果盒子加水果
box.add_fruit(Apple())
box.add_fruit(Orange())
box.add_fruit(Apple())
it = iter(box)
for fruit in box:
print("this is :{}".format(fruit.type))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-69-b7d2655962a9> in <module>
27 box.add_fruit(Apple())
28
---> 29 it = iter(box)
30 for fruit in box:
31 print("this is :{}".format(fruit.type))
TypeError: 'FruitBox' object is not iterable
#定义一个水果类
class Fruit:
def __init__(self,type):
self.type = type
#定义一个水果盒子
class FruitBox():
def __init__(self):
self.fruit_list = []
def add_fruit(self,fruit):
self.fruit_list.append(fruit)
def __iter__(self):
print("======iter=====")
self.position = 0
return self
def __next__(self):
print("======next=====")
if self.position < len(self.fruit_list):
fruit = self.fruit_list[self.position]
self.position += 1
return fruit
else:
raise StopIteration
#初始化一个水果篮子
print("start init fruit box")
box = FruitBox()
#往水果篮子加水果
print("start add fruit to box")
box.add_fruit(Fruit("Apple"))
box.add_fruit(Fruit("Orange"))
box.add_fruit(Fruit("Apple"))
print("start generate a iterator")
it = iter(box) #生成一个水果篮子的迭代器,然后从通过迭代器从水果篮子取水果
print("loop for iterator to get fruit")
for fruit in it:
print("this is :{}".format(fruit.type))
start init fruit box
start add fruit to box
start generate a iterator
======iter=====
loop for iterator to get fruit
======iter=====
======next=====
this is :Apple
======next=====
this is :Orange
======next=====
this is :Apple
======next=====
生成器
什么是生成器?在 Python 中,使用了 yield 的函数被称为生成器(generator)。
它是一个函数,调用生成器函数返回的是一个迭代器。只能用于迭代操作。可以理解为生成器是一个懒惰版的迭代器。只有在调用next()才会生成一个对象。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
def fibonacci(n): # 生成器函数 - 斐波那契
a, b, counter = 0, 1, 0
while True:
if (counter > n):
return
yield a
print("after yield")
a, b = b, a + b
counter += 1
f = fibonacci(5) # f 是一个迭代器,由生成器返回生成
print(f)
for i in range(5):
print("start next")
print(next(f))
print("end next")
#生成器对象也可以通过list直接转换成列表
#list(f)
<generator object fibonacci at 0x7f977fadc970>
start next
0
end next
start next
after yield
1
end next
start next
after yield
1
end next
start next
after yield
2
end next
start next
after yield
3
end next
迭代器 vs 生成器
如果我们要计算从1加到1000000,迭代器需要首先生成把1000000元素先加载到内存中,而生成器只需要在计算的时候生成,拿来用即可,无需为1000000个元素申请内存。生成器在 Python 的写法是用小括号括起来,(i for i in range(1000000)),即初始化了一个生成器。如下:
import os
import psutil
# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
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("start iterator")
iterator = [i for i in range(1000000)]
print(show_memory_info("end iterator"))
result = sum(iterator)
print("result is:{}".format(result))
print(show_memory_info("after call sum"))
def test_generator():
show_memory_info("start generator")
generator = (i for i in range(1000000))
show_memory_info("end generator")
result = sum(generator)
print("result is:{}".format(result))
show_memory_info("after call sum")
def main():
test_iterator()
print("=========")
test_generator()
main()
start iterator memory used 129.66015625 MB
end iterator memory used 159.1171875 MB
None
result is:499999500000
after call sum memory used 159.1171875 MB
None
=========
start generator memory used 127.16796875 MB
end generator memory used 127.16796875 MB
result is:499999500000
after call sum memory used 127.16796875 MB
如果水果篮子变成哆啦A梦的口袋,只要想就可以从里面取出水果。那么显然水果篮子已经不够用了,此时需要一个可以无限生成的生成器
#定义一个水果类
class Fruit:
def __init__(self,tag):
self.tag = tag #每一个都有一个编号
#哆啦A梦的口袋,水果生成器
def doraemon_pocket(n):
tag = 0
while True:
yield Fruit(tag)
tag += 1
fruit_generator = doraemon_pocket(0)
for i in range(10):
f = next(fruit_generator)
print("this is number of {} Fruit".format(f.tag))
#可以一直取下去
this is number of 0 Fruit
this is number of 1 Fruit
this is number of 2 Fruit
this is number of 3 Fruit
this is number of 4 Fruit
this is number of 5 Fruit
this is number of 6 Fruit
this is number of 7 Fruit
this is number of 8 Fruit
this is number of 9 Fruit
总结:生成器相比迭代器,在无需保存元素的情况下,内存方面是很占优势的。
yield 返回值在下次next()函数调用的时候才继续执行,这个特性可以很好的利用。
THE END!