Python:协程的实现(迭代器实现for循环,生成器,yield→greenlet→gevent【图片下载器实例】)

tech2025-04-04  22

首先说下迭代器的优点:

●在迭代器中存储的是生成值的方法代码,并不会存储值本身 ●因而迭代器所占用的内存空间会非常小

·

一. 判断是否可以迭代的方法

● 判断一个变量是否是可以迭代的对象(可以用for循环遍历)

● 观察该变量是否与collections模块中的Iterable类有关(Iterable类与迭代有关)

● 用isinstance(对象, 类)去判断 (isinstance可用来判断两者之间的关系,返回True则表示有关联,False贼表示没有关联)

具体实现:

In [8]: from collections import Iterable In [9]: isinstance(100, Iterable) Out[9]: False In [10]: isinstance([11,22,33], Iterable) Out[10]: True

·

二. 类的对象的迭代

● 在默认情况下,类的对象即便是一个列表也无法进行迭代(即for循环遍历)

· ● 如果在类中定义了__iter__()方法时,则类的对象可以进行迭代

· ●在__iter__()方法中,必须返回一个新类,而在主函数中通过__iter__()方法返回的对象就是迭代器

# 判断一个对象是否是迭代器 isinstance(对象, Iterator)

· ●在这个新类中必须定义__iter__()方法和__next__()方法,其中在进行迭代的时候所输出的索引即为__next__()方法中的返回值

· ●为了使新类中__next__()方法能获得要迭代的类的数据内容,则在之前__iter__()方法中返回新类的对象时必须传入self对象。

· ● 也可以把要迭代的类直接变成迭代器,直接在类中添加__iter__()和__next__()方法,在__iter__()方法中返回自己即可

注:迭代器一定可以迭代,但可以迭代的不一定是迭代器

·

类的对象迭代实例1(依靠迭代器迭代):

import collections import time class classmate(object): def __init__(self): self.name = list() def add(self, name): self.name.append(name) def __iter__(self): return classiterator(self) class classiterator(): """迭代器""" def __init__(self, obj): self.obj = obj self.num = 0 def __iter__(self): pass def __next__(self): if self.num < len(self.obj.name): ret = self.obj.name[self.num] self.num += 1 return ret else: # 当输出列表所有的数值后需要扔出一个StopIteration异常,停止继续运行,否则循环不会终止 raise StopIteration student = classmate() student.add("张三") student.add("李四") student.add("王五") for i in student: print(i) time.sleep(1)

·

类的对象迭代实例2(改造成迭代器自身进行迭代):

import collections import time class classmate(object): def __init__(self): self.name = list() self.num = 0 def add(self, name): self.name.append(name) def __iter__(self): return self def __next__(self): if self.num < len(self.name): ret = self.name[self.num] self.num += 1 return ret else: # 当输出列表所有的数值后需要扔出一个StopIteration异常,停止继续运行,否则循环不会终止 raise StopIteration student = classmate() student.add("张三") student.add("李四") student.add("王五") for i in student: print(i) time.sleep(1)

·

四. 关于迭代器的作用

例:以输出斐波那契数列为例

利用迭代器:

# -*- coding=utf-8 -*- class Fibonacci(object): def __init__(self, num): self.num = num self.current_num = 0 self.a = 0 self.b = 1 def __iter__(self): return self def __next__(self): if self.current_num < self.num: ret = self.a self.a, self.b = self.b, self.a+self.b self.current_num += 1 return ret else: raise StopIteration def main(): f = Fibonacci(10) for i in f: print(i) if __name__=="__main__": main()

· 利用列表储存:

# -*- coding=utf-8 -*- def main(): f = list() a = 0 b = 1 for i in range(10): f.append(a) a,b = b,a+b for i in f: print(i) if __name__=="__main__": main()

实验结果: ·

区别:

当列表中所要存储的数据过大时,占用的内存空间会非常大,而如果通过迭代器在需要数据的时候就制造出来,占用的内存空间也只有迭代器的那几行代码。

·

五. 并不是只有for循环能接收可迭代对象

除了for循环,list, tuple等也能接收

l = list(Fibonacci(15)) print(l) # 通过迭代器取出数据后再转换成列表 t = tuple(Fibonacci(15)) print(t)

·

六. 生成器

在实现迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,可以使用更简单的生成器。

生成器是一类特殊的迭代器

·

1. 创建生成器

1)将列表中括号改成小括号(不常用)

In [1]: nums = (i for i in range(10)) In [2]: nums Out[2]: <generator object <genexpr> at 0x7fac130da4d0>

这里返回的 <generator object <genexpr> at 0x7fac130da4d0>是一个对象,即为生成器

·

2)通过yield定义生成器

当一个函数中出现yield时,那么这个就不是函数,而是一个生成器模板。

当调用这个函数,发现函数中有yield时,则不是在调用这个函数,而是在创建一个生成器对象。

·

实例:以输出斐波那契数列为例

# -*- coding=utf-8 -*- def Fibonacci(num): a,b = 0,1 current_num = 0 while current_num < num: yield a a,b = b,a+b current_num += 1 f = Fibonacci(10) for i in f: print(i)

当运行到yield a这句话时,系统会暂时保存a的值,并传到下面for循环中的i值中,然后继续循环。

而yield保存的值就是next()所返回的值

·

2 . 启动生成器的两种方法

· 1)通过返回next()值得到yield暂停程序保存的值

2)通过调用对象的send()方法,可以传入一个新的实参作为yield返回的值,并且得到下一次yield暂停程序保存的值

实例:

# -*- coding=utf-8 -*- def Fibonacci(num): a,b = 0,1 current_num = 0 while current_num < num: f = yield a # 当函数中存在yield,那么这个就不是函数,而是一个生成器 print(f) a,b = b,a+b current_num += 1 obj = Fibonacci(10) # 生成器必须返回一个对象 ret = next(obj) # 生成器中next()保存的值是当前运行到yield后,程序暂停并返回的a的值 print(ret) ret = next(obj) # 在调用第一次next()方法并返回yield返回的值后,程序继续运行循环到第二次运行到yield,暂停程序返回a的值 print(ret) obj2 = Fibonacci(5) print("-----调用第二个生成器对象-----") ret = next(obj2) print(ret) ret = next(obj2) print(ret) print("-----再次调用第一个生成器对象----") ret = next(obj) print(ret) print("-----第一个生成器对象调用send()方法") ret = obj.send("哈哈哈") # send()方法和next()方法的作用一样,但是send()方法必须通过对象调用且可以传入参数,赋值给f print(ret)

实验结果: ·

**当想要得到生成器返回的值而不是yield返回的值时

代码:

# -*- coding=utf-8 -*- def Fibonacci(num): a,b = 0,1 current_num = 0 while current_num < num: yield a # 当函数中存在yield,那么这个就不是函数,而是一个生成器 a,b = b,a+b current_num += 1 return "函数结束返回。" obj = Fibonacci(10) # 生成器必须返回一个对象 try: while True: ret = next(obj) # 生成器中next()保存的值是当前运行到yield后,程序暂停并返回的a的值 print(ret) # 当循环输出所有数后,会报出StopIteration异常,因此抓取异常后可输出生成器最终返回的值 except Exception as ret: print(ret.value)

实验结果:

·

七. 小结(迭代器,生成器)

· ● 拥有一个iter()方法,next()方法这样的一个对象就是迭代器,通过for循环实现遍历出结果

● 迭代器保存了生成数据的代码而不是结果,可以用极小的内存空间,极少的代码去生成庞大的数据

● 生成器是一个含有yield的特殊的迭代器,里面没有iter()方法,next()方法但可以直接通过对象调用next()方法,send()方法

● 生成器保证代码执行了一部分后可以暂停执行并将数据返回给对象,在下一次调用之前会一直维持所保存的数据还在,通过next()方法再次调用的时候会从原来的状态中继续运行,不会从新开始运行。

·

八. 协程(用生成器实现多任务)

·

● 函数中出现yield时,那他就不是函数而是一个生成器,可返回一个对象

● 通过yield暂停运行,将此时的值保存并返回给生成器对象,通过next()方法可返回暂停的状态并在原有状态下继续执行直至再次出现yield,以此可并发实现多任务。

代码实例:

# -*- coding:utf-8 -*- import time def test1(): while True: print("-----1----") time.sleep(0.1) yield def test2(): while True: print("-----2----") time.sleep(0.1) yield def main(): t1 = test1() t2 = test2() while True: next(t1) next(t2) if __name__ == "__main__": main()

` 实验结果:

·

---------------------------------------------------------------------------------yield进化👇---------------------------------------------------------------------- ·

为了防止每次都要调一个next()函数过于麻烦这里使用进化版本 ·

greenlet

代码实例:

# -*- coding:utf-8 -*- import time from greenlet import greenlet # greenlet 类中对yield进行了封装 def test1(): while True: print("-----1----") gr2.switch() time.sleep(0.1) def test2(): while True: print("-----2----") gr1.switch() time.sleep(0.1) gr1 = greenlet(test1) gr2 = greenlet(test2) # 切换到gr1中运行 gr1.switch()

运行结果:

相比较yield,greenlet简化了手动切换的代码,也不必创建生成器,实现多任务更简单

·

--------------------------------------------------------------------greenlet进化(协程的最优化版本)👇-----------------------------------------------------

gevent

greenlet已经实现了协程,但是还是需要手动切换任务,而gevent则足够强大到可以自动切换协程任务

代码实例:

# -*- coding:utf-8 -*- import time import gevent def f1(n): for i in range(n): print(gevent.getcurrent(), n) gevent.sleep(0.5) # 在用gevent完成多任务时,所有的延时代码都必须是gevent模块中的 def f2(n): for i in range(n): print(gevent.getcurrent(), n) gevent.sleep(0.5) def f3(n): for i in range(n): print(gevent.getcurrent(), n) gevent.sleep(0.5) g1 = gevent.spawn(f1, 5) g2 = gevent.spawn(f2, 5) g3 = gevent.spawn(f3, 5) g1.join() # 等待g1对象运行结束 g2.join() # 等待g2对象运行结束 g3.join() # 等待g3对象运行结束

将上面代码优化后👇:

# -*- coding:utf-8 -*- import time import gevent from gevent import monkey monkey.patch_all() # 检测代码中如果有出现延时操作的代码会将所有延时操作代码前面的模块名改成gevent模块名 def f1(n): for i in range(n): print(gevent.getcurrent(), n) time.sleep(0.5) def f2(n): for i in range(n): print(gevent.getcurrent(), n) time.sleep(0.5) def f3(n): for i in range(n): print(gevent.getcurrent(), n) time.sleep(0.5) # 程序等待列表中所有的协程都运行完之后再结束,省去了一个对象就要写一个join()的麻烦 gevent.joinall([ gevent.spawn(f1, 5), gevent.spawn(f2, 5), gevent.spawn(f3, 5) ])

实验结果:

·

利用协程做图片下载器实例:

import urllib.request import gevent from gevent import monkey monkey.patch_all() def download(img_name, img_url): req = urllib.request.urlopen(img_url) content = req.read() with open(img_name, "wb") as f: f.write(content) def main(): gevent.joinall([ gevent.spawn(download, "file1.jpg","http://pic.sc.chinaz.com/files/pic/pic9/202009/bpic21233.jpg"), gevent.spawn(download, "file2.jpg", "http://pic.sc.chinaz.com/files/pic/pic9/202009/bpic21235.jpg") ]) if __name__ == "__main__": main()

· 实验结果:

·

协程的特点:

● 协程是利用在程序运行发生的延时里面自动切换任务来实现多任务

● 调用一个任务就像调用一个函数一样

● 切换的资源在进程,线程,协程中,协程最少,进程最多

● 依赖于线程,在单线程下完成了多任务

最新回复(0)