Python中的迭代器,生成器,协程(coroutine)--整理自CSDN微信公开课

本文整理自CSDN Python学习班的微信公开课,探讨了Python中的迭代器、生成器及其区别,强调生成器按需生成值的高效性。此外,还介绍了协程的概念,它是用户空间的线程,用于数据消费,通过send方法控制执行流程,并给出了简单的协程过滤输入的例子。协程的轻量级特性使其在资源管理和并发执行方面具有优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文整理自CSDN Python学习班2017年3月16日的微信公开课,授课老师陈舸。

注:示例代码中用了print()函数,所以,如果在Python2下运行,需要在第一行添加:

from __future__ import print_function


一 迭代器

像很多语言一样,Python中也有for语句。for语句可以迭代许多不同的对象,如:

for char in 'hello':
	print char
for i in [1,2,3]:
	print x
Python可以迭代不同对象的原因是,Python中有一个特定的迭代协议。任何对象,只要满足迭代协议,就是可迭代的。因此,只要实现__iter__()和next()方法,就能实现自己的可迭代对象。例如:

class Letters(object):
	def __init__(self):
		self.current='a'
	def next(self):
		if self.current > 'z':
			raise StopIteration
		result =self.current
		self.current=chr(ord(result)+1)
		return result
	def __iter__(self):
		return self

注:这是Python2中的写法,如果在Python3中运行,第4行要改为:

def __next__(self):

这里定义了一个Letters类,实现了next和__iter__(),因此Letters的实例是可迭代的。
letters=Letters()
for i in letters:
	print(i)

结果是输出a-z。


二 生成器

首先给出一个例子(我稍微改动了下,便于理解):

def countdown(n):
	print("Counting down from", n)
	while n>0:
		yield n
		print n
		n-=1
然后执行:

 x=countdown(10)
可以看到,x=countdown(10)执行后,并没有输出。因为x只是一个生成器对象,而生成器只会在对它调用next()时才开始执行。

>>> x.next()
('Counting down from', 10)
10
>>> x.next()
10
9

函数的执行结果如上所示。出现这种结果的原因在于yield。

yield会产生一个值,但是却会挂起函数的执行。也就是说,函数运行完yield后就暂时不运行了,直到下一次调用next时,才继续执行。


总结一下生成器与迭代器的区别:

生成器产生的值是按需产生的,是每次迭代时按需生成,不会事先算好放在内存里。迭代器用的是事先存储起来的值。

比如Python2中的range和xrange,前者返回的是list,是迭代器;而xrange返回的是生成器。现在终于明白xrange为什么高效了。因此,Python3中取消了xrange,只保留range,而且行为和Python2中的xrange一致。


三 协程

协程与生成器都有yield。区别是,生成器产生数据,通常用于迭代。协程消费数据,使用数据。

下面是一个简单的例子,作用是过滤发送给它的输入。

def grep(pattern):
	print('start coroutine')
	while True:
		line=yield
		if pattern in line:
			print(line)
就像生成器函数一样,我们调用这个函数之后,它不会马上执行。要想让协程正常运行,必须先对其调用一次next()或send(None)。这一步叫做启动协程。由于这个步骤固定,且容易忘记,所以我们可以用装饰器来自动处理(知乎有对Python装饰器的解释,链接在这里:https://www.zhihu.com/question/26930016  ps:装饰器是一种语法糖,听名字就对它很有好感==):

def coroutine(func):
	def start(*args,**kwargs):
		c=func(*args,**kwargs)
		c.next()
		return c
	return start
然后代码可以改成这样:

 @coroutine
def grep(pattern):
	print('start coroutine')
	while True:
		line=yield
		if pattern in line:
			print(line)
调用:

>>> search=grep('coroutine')
start coroutine
>>> search.send('co')
>>> search.send('coroutines')
coroutines
为了加深理解,我修改了上面的函数:

>>> @coroutine
def grep(pattern):
	print('start coroutine')
	while True:
		print("true")
		line=yield
		print('a')
		print(yield)
		if pattern in line:
			print(line)

调用函数:

>>> search=grep('abc')
start coroutine
true
>>> search.send('abcd')
a
>>> search.send('abc')
abc
abcd
true
>>> search.send('abcabc')
a
通过这个例子可以看出,函数在执行过程中,只要碰到yield就会自动挂起,直到下一次调用。具体来说,这个例子的调用过程如下:

由于我们加了装饰器,所以第一次调用

search=grep('abc')
就能启动协程。函数运行到第一个yield处,挂起。此时我们没有send东西给函数,所以line此时没有赋值。

然后我们send一个值:

>>> search.send('abcd')
这时line被赋值为‘abcd’。继续向下执行,输出‘a’。下一个print的参数是yield,所以此时挂起函数。

然后我们再send一个值:

>>> search.send('abc')
这时print中的yield有了值,所以输出‘abc’,然后执行下面的if,发现满足条件,于是输出line,也就是‘abcd’。

然后又send了一个值:

>>> search.send('abcabc')
这时与上面讲的一样,line被赋值,然后向下执行输出‘a’,然后遇到函数中的第二个yield,挂起。

根据上面几个例子,应该能了解迭代器,生成器,协程的概念了。迭代器就是依次运用现有的存储好的数据;生成器的标志是yield n,根据函数被调用的次数动态生成数据;协程的标志是yield,不生成数据,只传输数据。(我觉得,叫什么名字不重要,明白yield的作用就够了)


协程的应用场景:

协程是用户空间的线程,这种协作式任务调度可以让用户自己控制使用CPU的时间,除非自己放弃,否则不会被其他协程抢占CPU。而线程和进程的调度由os决定,都需要内核调度。协程不需要内核参与,完全用户态,更加轻量级,占用内存少。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值