本文整理自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决定,都需要内核调度。协程不需要内核参与,完全用户态,更加轻量级,占用内存少。