• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

11 迭代器|生成器|协程

武飞扬头像
Micoreal
帮助1

迭代器

迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

可迭代对象

我们已经知道可以对listtuplestr 等类型的数据使用 for…in…的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。

我们把可以通过 for…in…这类语句迭代读取一条数据供我们使用的对象称之为可迭代对象(Iterable)。

可以使用 isinstance() 判断该对象是否是一个可迭代的对象

In [50]: from collections.abc import Iterable
In [51]: isinstance([], Iterable)
Out[51]: True
In [52]: isinstance({}, Iterable)
Out[52]: True
In [53]: isinstance('abc', Iterable)
Out[53]: True
In [54]: isinstance(mylist, Iterable)
Out[54]: False
In [55]: isinstance(100, Iterable)
Out[55]: False

可迭代对象的本质

可迭代对象通过__iter__方法向我们提供一个迭代器,我们在迭代一个可迭代对
象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据. 那么也就是说,一个具备了__iter__方法的对象,就是一个可迭代对象,所以关键就在于我们要如何去重写这一个__iter__方法。

iter()函数与 next()函数

list、tuple 等都是可迭代对象,我们可以通过 iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用 next()函数来获取下一条数据。

iter()函数实际上就是调用了可迭代对象的__iter__方法

注意,当我们已经迭代完最后一个数据之后,再次调用 next()函数会抛出StopIteration 的异常,来告诉我们所有数据都已迭代完成,不用再执行 next()函数了

迭代器 Iterator

通过上面的分析,我们已经知道,迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用 next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用 next()函数的时候,调用的就是迭代器对象的__next__方法(Python3 中是对象的__next__方法)。所以,我们要想构造一个迭代器,就要实现它的__next__方法。但这还不够,python 要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可

一个实现了__iter__方法和__next__方法的对象,就是迭代器。

样例

from collections.abc import Iterable
# 自定义可迭代对象
class MyList:
    def __init__(self):
        self.mylist = []

    def add(self,num):
        self.mylist.append(num)

    # 此处是关键,定义了这一个之后,就相当于是让这个对象是可迭代对象
    def __iter__(self):
        return MyIterator(self)


# 自定义迭代器
class MyIterator:
    def __init__(self,mylist):
        self.mylist:MyList = mylist
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current > len(self.mylist.mylist) - 1:
        	# 此处一定要实现抛出StopIteration异常
            raise StopIteration
        else:
            current = self.current
            self.current = self.current   1
            return self.mylist.mylist[current]


if __name__ == '__main__':
    mylist = MyList()
    mylist.add(1)
    mylist.add(2)
    mylist.add(3)
    for i in mylist:
        print(i)

    print(isinstance(mylist,Iterable))
学新通

for…in…循环的本质

for item in Iterable 循环的本质就是先通过 iter()函数获取可迭代对象 Iterable 的迭代器,然后对获取到的迭代器不断调用 next()方法来获取下一个值并将其赋值给item,当遇到 StopIteration 的异常后循环

使用的场景–斐波那契数列


class FibIterator(object):
	"""斐波那契数列迭代器"""
	def __init__(self, n):
		"""
			:param n: int, 指明生成数列的前 n 个数
		"""
		self.n = n
		# current 用来保存当前生成到数列中的第几个数了
		self.current = 0
		# num1 用来保存前前一个数,初始值为数列中的第一个数 0
		self.num1 = 0
		# num2 用来保存前一个数,初始值为数列中的第二个数 1
		self.num2 = 1
		
	def __next__(self):
		"""被 next()函数调用来获取下一个数"""
		if self.current < self.n:
			num = self.num1
			self.num1, self.num2 = self.num2, self.num1 self.num2
			self.current  = 1
			return num
		else:
			raise StopIteration
			
	def __iter__(self):
		"""迭代器的__iter__返回自身即可"""
		return self

if __name__ == '__main__':
	fib = FibIterator(10)
	for num in fib:
		print(num, end=" ")
学新通

list和tuple也可以接收可迭代对象

除了 for 循环能接收可迭代对象,list、tuple 等也能接收

li = list(FibIterator(15))
print(li)
tp = tuple(FibIterator(6))
print(tp)

生成器

简介

利用迭代器,我们可以在每次迭代获取数据(通过 next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。

创建生成器

方法一

实际上就是把一个列表生成式的 [ ] 改成 ( )

my_generator = ( x*2 for x in range(5))
print(my_generator)
print(next(my_generator))
print(next(my_generator))
print(next(my_generator))
print(next(my_generator))
print(next(my_generator))

对于生成器来说,我们可以按照迭代器的使用方法来使用,即可以通过 next()函数、for 循环、list()等方法进行迭代

方法二

generator 非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。

在使用生成器实现的方式中,我们将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的 return 换成了 yield,此时新定义的函数便不再是函数,而是一个生成器了。简单来说:只要在 def 中有yield 关键字的 就称为 生成器此时按照调用函数的方式( 案例中为 F = fib(5) )使用生成器就不再是执行函数体了,而是会返回一个生成器对象( 案例中为 F ),然后就可以按照使用迭代器的方式来使用生成器了。

# 含有yield的函数称为生成器
def fib(n):
    current = 0
    num1, num2 = 0, 1
    while current < n:
        num = num1
        num1, num2 = num2, num1   num2
        current  = 1
        # print(num,end=' ')
        yield num
    return 'done'

# F是一个生成器,支持next
F=fib(10)

print(F)

# for i in F:
#     print(i,end=' ')

# 迭代生成
l=[ i for i in F ]
print(l)

# 想要拿取return当中的值
try:
    next(F)
except StopIteration as e:
    print("\n返回值为:{}".format(e.value))
学新通

用 for 循环调用 generator 时,发现拿不到 generator 的 return 语句的返回
值。如果想要拿到返回值,必须捕获 StopIteration 错误,返回值包含在
StopIteration 的 value 中

这里还有一个小细节,读者可能回想为什么我们在使用已经做好的list时候对于list进行for in处理,可以多次处理多次拿值,但是对于我们上文写的迭代器为什么不行?原因就在于我们实际上list叫做可迭代对象,它实际上所采用的是我们最上面的那种手法,定义两个类的手法,每次迭代,都会有一个新的迭代器来运行。

总结

  1. 使用了 yield 关键字的函数不再是函数,而是生成器。(使用了 yield 的函数
    就是生成器)
  2. yield 关键字有两点作用:
    – 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
    – 将 yield 关键字后面表达式的值作为返回值返回,此时可以理解为起到了 return 的作用
  3. 可以使用 next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
  4. Python3 中的生成器可以使用 return 返回最终运行的返回值

使用 send 唤醒

我们除了可以使用 next()函数来唤醒生成器继续执行外,还可以使用 send()函数来唤醒执行。使用 send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据

def gen():
    i=0
    while i<5:
        temp=yield i
        print(temp)
        i =1

g=gen()

next(g)
g.send('hello')

协程

协程,又称微线程,纤程。英文名 Coroutine。

协程是 python 个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。 为啥说它是一个执行单元,因为它自带 CPU 上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU 上下文那么程序还是可以运行的。

通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。

协程和线程差异

在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU 上下文这么简单。操作系统为了程序运行的高效性每个线程都有自己缓存 Cache 等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作 CPU 的上下文,所以一秒钟切换个上百万次系统都抗的住。

简单实现协程

import time

def work1():
	while True:
		print("----work1---")
		yield
		time.sleep(0.5)
		
def work2():
	while True:
		print("----work2---")
		yield
		time.sleep(0.5)
		
def main():
	w1 = work1()
	w2 = work2()
	while True:
		next(w1)
		next(w2)

if __name__ == "__main__":
	main()
学新通

greenlet

基本的使用方法:

from greenlet import greenlet
import time

def test1():
	while True:
		print("---A--")
		gr2.switch()
		time.sleep(0.5)

def test2():
	while True:
		print("---B--")
		gr1.switch()
		time.sleep(0.5)

gr1 = greenlet(test1)
gr2 = greenlet(test2)
#切换到 gr1 中运行
gr1.switch()

学新通

gevent

greenlet 已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,python 还有一个比 greenlet 更强大的并且能够自动切换任务的模块 gevent,其原理是当一个 greenlet 遇到 IO(指的是 input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的 greenlet,等到 IO 操作完成,再在适当的时候切换回来继续执行。

由于 IO 操作非常耗时,经常使程序处于等待状态,有了 gevent 为我们自动切换协程,就保证总有协程在运行,而不是等待 IO时间

参考链接

安装

pip install gevent -i https://pypi.tuna.tsinghua.edu.cn/simple

gevent 的使用方法

gevent.spawn 接口使用方法

gevent.spawn(函数名,传参)
import gevent

def f(n):
	for i in range(n):
		print(gevent.getcurrent(), i)
		#用来模拟一个耗时操作,注意不是 time 模块中的 sleep
		gevent.sleep(1)
		
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()

运行结果:

<Greenlet at 0x7fa70ffa1c30: f(5)> 0
<Greenlet at 0x7fa70ffa1870: f(5)> 0
<Greenlet at 0x7fa70ffa1eb0: f(5)> 0
<Greenlet at 0x7fa70ffa1c30: f(5)> 1
<Greenlet at 0x7fa70ffa1870: f(5)> 1
<Greenlet at 0x7fa70ffa1eb0: f(5)> 1
<Greenlet at 0x7fa70ffa1c30: f(5)> 2
<Greenlet at 0x7fa70ffa1870: f(5)> 2
<Greenlet at 0x7fa70ffa1eb0: f(5)> 2
<Greenlet at 0x7fa70ffa1c30: f(5)> 3
<Greenlet at 0x7fa70ffa1870: f(5)> 3
<Greenlet at 0x7fa70ffa1eb0: f(5)> 3
<Greenlet at 0x7fa70ffa1c30: f(5)> 4
<Greenlet at 0x7fa70ffa1870: f(5)> 4
<Greenlet at 0x7fa70ffa1eb0: f(5)> 4

学新通

给程序打补丁

猴子补丁作用:
monkey patch 指的是在执行时动态替换,通常是在 startup 的时候. 用过 gevent 就会知道,会在最开头的地方 gevent.monkey.patch_all();把标准库中的 thread/socket 等给替换掉.这样我们在后面使用 socket的时候能够跟寻常一样使用,无需改动不论什么代码,可是它变成非堵塞的了

from gevent import monkey
import gevent
import random
import time

# 这句话是关键
monkey.patch_all()

def coroutine_work(coroutine_name):
    for i in range(10):
        print(coroutine_name, i)
        time.sleep(random.random())

gevent.joinall([
        gevent.spawn(coroutine_work, "work1"),
        gevent.spawn(coroutine_work, "work2")
])
学新通

gevent的常用方法

常用方法 说明
gevent.spawn() 创建一个普通的 Greenlet 对象并切换
gevent.spawn_later(seconds=3) 延时创建一个普通的 Greenlet 对象并切换
gevent.spawn_raw() 创建的协程对象属于一个组
gevent.getcurrent() 返回当前正在执行的 greenlet
gevent.joinall(jobs) 将协程任务添加到事件循环,接收一个任务列表
gevent.wait() 可以替代 join 函数等待循环结束,也可以传入协程对象列表
gevent.kill() 杀死一个协程
gevent.killall() 杀死一个协程列表里的所有协程
monkey.patch_all() 非常重要,会自动将 python 的一些标准模块替换成 gevent框架

python最新接口

链接

官方文档:
链接
链接

最后的示例

并行下载

from gevent import monkey
import gevent
import urllib.request

# 有耗时操作时需要
monkey.patch_all()

def my_downLoad(url):
    print('GET: %s' % url)
    resp = urllib.request.urlopen(url)
    data = resp.read()
    print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([
        gevent.spawn(my_downLoad, 'http://www.百度.com/'),
        gevent.spawn(my_downLoad, 'http://www.cskaoyan.com/'),
        gevent.spawn(my_downLoad, 'http://www.qq.com/'),
])
学新通

以及

from gevent import monkey
import gevent
import urllib.request

#有 IO 才做时需要这一句
monkey.patch_all()

def my_downLoad(file_name, url):
	print('GET: %s' % url)
	resp = urllib.request.urlopen(url)
	data = resp.read()
	with open(file_name, "wb") as f:
		f.write(data)
	print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([gevent.spawn(my_downLoad,"7a082c0dde36eac2205a088397aaf295.jpg",'http://qzs.qq.com/qzone/v6/v6_config/upload/7a082c0dde36eac2205a088397aaf295.jpg'),gevent.spawn(my_downLoad,"da8e974dc_is.jpg",'https://pic1.zhimg.com/da8e974dc_is.jpg'),])

学新通

上面的 url 可以换为自己需要下载视频、音乐、图片等url

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhiahfki
系列文章
更多 icon
同类精品
更多 icon
继续加载