python之迭代器(Iterator)、生成器(yield)、协程(gevent)

一,迭代器

迭代器最大的好处是:储存生成数据的方式,较储存生成数据的结果占用较少的内存

若一个对象是可迭代对象,此对象不一定可以进行迭代;若一个对象是迭代器,则此对象一定可以进行迭代。迭代对象也可以同时为迭代器。

1,可迭代对象

如何判断对象是否为可迭代对象呢?在python中万物皆对象,只要在对象内部实现__iter__方法,则对象是可迭代对象,即可以使用for循环。
代码中判断:

from collections import Iterable
isinstance(对象名,Iterable)                # 返回布尔值进行判断
2,迭代器

如何判断对象是否为迭代器对象呢?在认定对象为可迭代对象(对应类中有__iter__方法)后,调用iter方法,对象自动调用__iter__中return,return的返回值应该为一个迭代器。迭代器中应有__iter__方法和__next__方法。调用next(对象)方法并触发对象中的__next__方法中的返回值。若迭代器出现报错 StopIteration时,for循环将自动结束迭代,并不报错。

代码中判断:
from collections import Iterator
isinstance(对象名,Iterator)                  # 返回布尔值进行判断
3,总结

迭代一个对象的步骤为:
1)判断对象是否为可迭代对象(对象内部实现__iter__方法)。
2)调用iter方法并触发对象的__iter__方法的返回值(__iter__的返回值应该为一个迭代器)。
3)判断返回对象是否为一个迭代器(迭代器中应有__iter__方法和__next__方法。调用next(对象)方法并触发对象中的__next__方法中的返回值)。
4)判断迭代器如何结束(当迭代器 raise StopIteration时,for循环语句将自动处理,正常结束循环)。

4,为了更好的理解,附上代码
from collections import Iterable,Iterator

class Classmate():
    def __init__(self):
        self.names = list()
        self.current_num = 0
        
    def add(self,name):
        self.names.append(name)
    
    def __iter__(self):
        """只要在对象内部实现__iter__方法,
        则对象是可迭代对象,即可以使用for循环"""
        return self  # 可迭代对象可以返回自己作为迭代器,但是要保证实现了__iter__、__next__方法。
    
    def __next__(self):
        if self.current_num < len(self.names):
            ret = self.names[self.current_num]
            self.current_num += 1 
            return ret
        else:
            raise StopIteration # 当迭代器报错StopIteration时,for循环语句将自动处理,正常结束循环。
            
classmate = Classmate()
classmate.add("老大")
classmate.add("老二")
classmate.add("老三")

print("判断classmate是否是可以迭代对象:",isinstance(classmate,Iterable))
# classmate_iterator = iter(classmate)
print("判断classmate_iterator是否是迭代器:",isinstance(classmate_iterator,Iterator))
print(next(classmate))

for name in classmate:
    print(name)

二,生成器

生成器的一大好处是:储存生成数据的方式。较return可返回结果时暂定程序,而后继续执行。

1,什么是生成器

生成器是一类特殊的迭代器, 它不需要再像上面的类一样写__iter__()和__next__()方法了,使用更加方便,它依然可以使用next函数和for循环取值,而当调用next函数并触发__next__返回值无参数时,将会自动报错StopIteration (迭代器无此功能)

2,创建生成器的方法
1)将列表推导式的[] 换成()
nums = (x*2 for x in range(10))
       
next(nums)    /   for i in nums:
2) 函数创建生成器

只要在def中有yield关键字的 就称为 生成器。在函数中只要有yield,那个这就不是一个函数,而是一个生成器模板,在调用函数时就是在创建一个生成器对象。

3,yield生成器在代码中如何执行

当代码执行到yield语句时,将暂停程序,并返回数据,依然可以使用next函数和for循环取值。
当调用next函数并触发__next__返回值无参数时,将自动报错StopIteration。多个生成器对象先后执行,相互无影响。
为便于理解,附上实现 “斐波那契”生成器实例:

def create_num(all_num):
    a,b = 0,1
    current_num = 0
    while current_num < all_num:
        yield a  # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板。
        a,b = b,a+b
        current_num += 1 
# 如果在调用create_num的时候,发现这个函数中有yield那么此时,不是调用函数,而是创建一个生成器对象。
obj = create_num(10)
obj2 = create_num(10)

# ret = next(obj)
# print("obj:",ret)

# ret = next(obj2)
# print("obj2:",ret)

for index in obj:
    print(index)

for index in obj2:
    print(index)
3,return 在 yield生成器中的作用

当yield生成器报错时,(Exception的错误原因.value)将返回return数据,如下所示:

def create_num(all_num):
    a,b = 0,1
    current_num = 0
    while current_num < all_num:
        # print(a)
        yield a # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板。
        a,b = b,a+b
        current_num += 1 
    return "OK..."

obj = create_num(5)

while True : 
    try:
        ret = next(obj)
        print(ret)
    except Exception as e:
        print(e,e.value)
        break
#---output--------------
0
1
1
2
3
OK... OK...
4,send

因为send在应用于功能上于next相接近,在这里我们将send与next进行比较。

1)在传参上的区别:

next只能接受yield传过来的参数。
send能接受yield传过来的参数,同时将send的参数传给yield处。
注意:send不能接收第一次的数据,因为程序从第一行执行,send的参数无处可传。但可以使用生成器名.send(None).

2)在调用上的区别:

next(生成器名)
生成器名.send(None)

3)通过实例:
#生成器的send用法 generator.send(value)
def test():
    i = 1
    while i < 5:
        temp = yield i**2
        print("temp value is : %s "%temp)
        i += 1

t = test()
#第一次运行只能使用next或者send(None)
# print(t.__next__())
print(next(t)) 
#send的作用相当于使生成器继续运行,并且传递的参数为yield的返回值(程序中即temp的值)
print(t.send("Hello World"))
print(next(t))    #相当于send(None) 此时temp = None

#---output--------------
1
temp value is : Hello World 
4
temp value is : None 
9

三,协程

1,yield

在yield生成器中,利用next()方法,实现协程的多任务。遇到延时将等待延时,不切换任务。

import time 

def task_1():
    while True:
        print("-----1-----")
        time.sleep(1)
        yield
        
def task_2():
    while True:
        print("-----2-----")
        time.sleep(1)
        yield

def main():
    t1 = task_1()
    t2 = task_2()
    # 先让t1运行一会儿,当t1中遇到yield的时候,再返回到next(t1)处,然后再执行t2,
    # 当他遇到yield的时候,再次切换到t1中。这样t1、t2、t1...的交替运行,最终实现了多任务。。协程
    while True:
        next(t1)
        next(t2)

if __name__ == "__main__":
    main()
2,greenlet

greenlet 是对于yield的一个简单的封装,将yield利用next控制线程执行替换成了自动执行(像穿针引线)。遇到延时将等待延时,不切换任务。
实现原理与yield近乎一样,只是更加方便了。


import time 
import greenlet

# 任务1
def work1():
    for i in range(5):
        print("work1...")
        time.sleep(1)
        # 切换到协程2里面执行对应的任务
        g2.switch()
# 任务2        
def work2():
    for i in range(5):
        print("work2...")
        time.sleep(1)
        # 切换到第一个协程执行对应的任务
        g1.switch()


if __name__ == "__main__":
    # 创建协程制定对应的任务
    g1 = greenlet.greenlet(work1)
    g2 = greenlet.greenlet(work2)
    g1.switch()
3,gevent
  • gevent 是对于greenlet的内部封装,而遇到延时就切换到下一个任务继续执行。
  • gevent只识别自己的框架延时,为了让gevent框架识别其他延时操作则需要转换。
  • gevent在线程延时的时候才会自动执行,可使用join阻塞使gevent执行程序。

为方便理解,实现两示例代码如下:
示例1:

import gevent
import time
from gevent import monkey
# 打补丁,让gevent框架识别耗时操作,比如:time.sleep,网络请求延时
monkey.patch_all()
# 任务1
def work1(num):
    for i in range(num):
        print("work1....")
        time.sleep(1)
        # gevent.sleep(0.2)
# 任务2
def work2(num):
    for i in range(num):
        print("work2....")
        time.sleep(1)
        # gevent.sleep(0.2)
def main():
    gevent.joinall([
        gevent.spawn(work1, 5),
        gevent.spawn(work2, 5)
    ])
    print('end')

    # 创建协程指定对应的任务
    # g1 = gevent.spawn(work1, 6)
    # g2 = gevent.spawn(work2, 6)
    # while True:
    #     # print("主线程中执行")
    #     time.sleep(1)'

if __name__ == '__main__':
    main()

示例2:

import urllib.request
import gevent
from gevent import monkey
import re
url_list = list()
monkey.patch_all()
# def get_url():
#   files = open('cat_url.txt','r').read()
#   list1 = re.findall(r'http://img0.*?\.jpg',files,re.S)
#    if list1:
#     print(list1,len(list1))
#   else:
#      print('no match')
#    return list1
lists = ('http://img06.tooopen.com/images/20180916/tooopen_sl_21540854823136.jpg','http://img06.tooopen.com/images/20180911/tooopen_sl_145217521750944.jpg',
  'http://img08.tooopen.com/20181010/tooopen_sl_20520752729360.jpg')
def downloader(img_name, img_url):
  req = urllib.request.urlopen(img_url)
  img_content = req.read()
  with open('./test_cat/'+img_name, "wb") as f:
    f.write(img_content)
def main():
  for index in range(len(lists)):
    url_list.append(gevent.spawn(downloader,'cat'+str(index)+'.jpg',lists[index]))
  gevent.joinall(url_list)

if __name__ == '__main__':
  main()

猜你喜欢

转载自blog.csdn.net/TFATS/article/details/108051750
今日推荐