python学习:迭代器和生成器

2021-11-12  本文已影响0人  倔犟的贝壳

在理解迭代器和生成器之前,首先需要了解可迭代对象。我们都知道列表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!

上一篇下一篇

猜你喜欢

热点阅读