yield 学习笔记
第三部分(先看)
先讲 iterator 和 iterable
可迭代对象 (Iterable) 是实现了__iter__()方法的对象, 通过调用iter()方法可以获得一个迭代器 (Iterator)。
迭代器 (Iterator) 是实现了__iter__()和__next__()的对象。
对于iterable, 我们该关注的是, 它是一个能一次返回一个成员的对象 (iterable is an object capable of returning its members one at a time), 一些iterable将所有值都存储在内存中, 比如list, 而另一些并不是这样, 比如我们下面将讲到的iterator.
iterator是实现了iterator.__iter__()
和iterator.__next__()
方法的对象iterator.__iter__()
方法返回的是iterator对象本身. 根据官方的说法, 正是这个方法, 实现了for ... in ...
语句. 而iterator.__next__()
是iterator区别于iterable的关键了, 它允许我们显式地获取一个元素. 当调用next()
方法时, 实际上产生了 2 个操作:
- 更新iterator状态, 令其指向后一项, 以便下一次调用
- 返回当前结果
正是__next__()
,使得iterator能在每次被调用时,返回一个单一的值 ,从而极大的节省了内存资源。另一点需要格外注意的是,iterator是消耗型的,即每一个值被使用过后,就消失了。因此,你可以将以上的操作 2 理解成pop。对iterator进行遍历之后,其就变成了一个空的容器了,但不等于None哦。因此,若要重复使用iterator,利用list()方法将其结果保存起来是一个不错的选择。
我们通过代码来感受一下。
>>> from collections import Iterable, Iterator
>>> a = [1,2,3]
# 众所周知,list是一个iterable
>>> b = iter(a)
# 通过iter()方法,得到iterator,iter()
实际上调用了__iter__(),此后不再多说
>>> isinstance(a, Iterable)
True
>>> isinstance(a, Iterator)
False
>>> isinstance(b, Iterable)
True
>>> isinstance(b, Iterator)
True
# iterator是消耗型的,用一次少一次.
对iterator进行变量,iterator就空了!
>>> c = list(b)
>>> c
[1, 2, 3]
>>> d = list(b)
>>> d
[]
# 空的iterator并不等于None.
>>> if b:
... print(1)
...
1
# 再来感受一下next()
>>> e = iter(a)
>>> next(e)
#next()实际调用了__next__()方法,此后不再多说
1
>>> next(e)
2
PS: iterator都是iterable,但iterable不都是iterator。
for循环的原理
我们对一个iterable用for ... in ...进行迭代时,实际是先通过调用iter()方法得到一个iterator,假设叫做 X。然后循环地调用 X 的next()方法取得每一次的值,直到 iterator 为空,返回的StopIteration作为循环结束的标志。for ... in ...会自动处理StopIteration异常,从而避免了抛出异常而使程序中断。
yield
我们常说的生成器,就是带有yield的函数,而generator iterator则是generator function的返回值,即一个generator对象,而形如(elem for elem in [1, 2, 3])的表达式,称为generator expression,实际使用与generator无异。
>>> a = (elem for elem in [1, 2, 3])
>>> a
<generator object <genexpr> at 0x7f0d23888048>
>>> def fib():
... a, b = 0, 1
... while True:
... yield b
... a, b = b, a + b
...
>>> fib
<function fib at 0x7f0d238796a8>
>>> b = fib()
<generator object fib at 0x7f0d20bbfea0>
其实说白了,generator就是iterator的一种,以更优雅的方式实现的iterator。
你完全可以像使用iterator一样使用generator。定义一个iterator,你需要分别实现__iter__()方法和__next__()方法,但generator只需要一个小小的yield。
前文讲到iterator通过__next__()方法实现了每次调用,返回一个单一值的功能。而yield就是实现generator的__next__()方法的关键!先来看一个最简单的例子:
>>> def g():
... print("1 is")
... yield 1
... print("2 is")
... yield 2
... print("3 is")
... yield 3
...
>>> z = g()
>>> z
<generator object g at 0x7f0d2387c8b8>
>>> next(z)
1 is1
>>> next(z)
2 is2
>>> next(z)
3 is3
>>> next(z)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
第一次调用next()方法时,函数似乎执行到yield 1,就暂停了。然后再次调用next()时,函数从yield 1之后开始执行的,并再次暂停。第三次调用next(),从第二次暂停的地方开始执行。第四次, 抛出StopIteration异常。
我们再来看另一段代码。
>>> def gen():
... while True:
... s = yield
... print(s)
...
>>> g = gen()
>>> g.send("kissg")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator
>>> next(g)
>>> g.send("kissg")
kissg
generator其实有第 2 种调用方法 (恢复执行),即通过send(value)方法将value作为yield表达式的当前值,你可以用该值再对其他变量进行赋值,这一段代码就很好理解了。
调用send(value)时要注意,要确保,generator是在yield处被暂停了,如此才能向yield表达式传值,否则将会报错 (如上所示),可通过next()方法或send(None)使generator执行到yield。
再来看一段yield更复杂的用法,或许能加深你对generator的next()与send(value)的理解:
In [1]: def echo(v=None):
...: while True:
...: print("line3")
...: v = yield v
...: if v is not None:
...: v += 1
...:
In [2]: g = echo(1)
In [3]: g
Out[3]: <generator object echo at 0x0000000003776C50>
In [4]: g.send(None)
line3
Out[4]: 1
In [5]: g.send(2)
line3
Out[5]: 3
yield 与 return
在一个生成器中,如果没有 return,则默认执行到函数完毕时返回 StopIteration;
>>> def g1():
... yield 1
...
>>> g=g1()
>>> next(g) #第一次调用 next(g) 时,会在执行完 yield 语句后挂起,所以此时程序并没有执行结束。
1
>>> next(g) #程序试图从 yield 语句的下一条语句开始执行,发现已经到了结尾,所以抛出 StopIteration 异常。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
>>> def g2():
... yield 'a'
... return
... yield 'b'
...
>>> g=g2()
>>> next(g) #程序停留在执行完 yield 'a'语句后的位置。
'a'
>>> next(g) #程序发现下一条语句是 return,所以抛出 StopIteration 异常,这样 yield 'b'语句永远也不会执行。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
如果在 return 后返回一个值,那么这个值为 StopIteration 异常的说明,不是程序的返回值。
生成器没有办法使用 return 来返回值。
>>> def g3():
... yield 'hello'
... return 'world'
...
>>> g=g3()
>>> next(g)
'hello'
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: world
第一部分
生成器有这么几个方法:
__next__
不用说了,每次 for 还有 next 都是调用这个方法。- send(value) 用 value 对 yield 语句赋值,再执行接下来的代码直到下个 yield。
- throw(type[, value[, traceback]]) 抛出错误,类似于 raise 吧。
- close() 告诉生成器,你已经死了。再调用会抛出 StopIteration。
- gi_running 查看生成器是否再运行。
实例1
def gen():
yield 1
yield 2
g = gen()
for i in g:
print(i)
打印:
1
2
实例 2
在调用send方法前,必须先调用一次__next__
,让生成器执行到 yield 语句处, 才能进行赋值。外面加上 while 循环是为了避免出现send之后, 生成器没有 yield 语句了,抛出 StopIteration 的情况。
def gen():
while 1:
h = yield
print(h)
g = gen()
g.send(None) #表示开始
g.send('aaa')
aaa
g.send('bbb')
bbb
g.close()
g.send('ccc')
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-11-b18ebbba6fe9> in <module>()
----> 1 g.send('ccc')
StopIteration:
g.gi_running
False
第二部分
yield
实例1:
def add(s, x):
return s + x
def gen():
for i in range(4):
yield i
base = gen()
for n in [1, 10]:
base = (add(i, n) for i in base)
print(list(base))
结果:
[20, 21, 22, 23]
核心语句就是:
for n in [1, 10]:
base = (add(i + n) for i in base)
在执行list(base)的时候,开始检索,然后生成器开始运算了。关键是,这个循环次数是2,也就是说,有两次生成器表达式的过程。必须牢牢把握住这一点。
生成器返回去开始运算,n = 10而不是1没问题吧,这个在上面提到的文章中已经提到了,就是add(i+n)绑定的是n这个变量,而不是它当时的数值。
然后首先是第一次生成器表达式的执行过程:base = (10 + 0, 10 + 1, 10 + 2, 10 +3),这是第一次循环的结果(形象表示,其实已经计算出来了(10,11,12,3)),然后第二次,base = (10 + 10, 11 + 10, 12 + 10, 13 + 10) ,终于得到结果了[20, 21, 22, 23].
实例2:
#coding: U8
def h():
print('Wen Chuan')
m = yield 5 #m是下一个yield传进来的参数,即send()的内容
print(m)
d = yield 12
# print(d) #这里d没有值,因为下面没有send了,d是下一个yield传进来的参数
print('We are together!')
c = h()
x = c.send(None) #send方法 给yield传参
#x 获取了 yield 5 的参数值 5
#相当于c.__next__(),第一次使用 send参数必须为None,表示启动生成器,直到下一个 yield 表达式处。
#m = Fighting! 是下一个send的参数
y = c.send('Fighting!') #y 获取了 yield 12 的参数值 12
print('We will never forget the date', x, '.', y)
打印:
Wen Chuan
Fighting!
('We will never forget the date', 5, '.', 12)
实例3: 生产-消费者模型
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
c = consumer()
produce(c)
打印:
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK
注意到consumer
函数是一个generator
,把一个consumer
传入produce
后:
首先调用
c.send(None)
启动生成器;然后,一旦生产了东西,通过
c.send(n)
切换到consumer
执行;consumer
通过yield
拿到消息,处理,又通过yield
把结果传回;produce
拿到consumer
处理的结果,继续生产下一条消息;produce
决定不生产了,通过c.close()
关闭consumer
,整个过程结束。
整个流程无锁,由一个线程执行,produce
和consumer
协作完成任务,所以称为 “协程”,而非线程的抢占式多任务。
实例4:
发送值到生成器函数在中
def mygen():
"""Yield 5 until something else is passed back via send()"""
a = 5
while True:
f = (yield a) #yield a and possibly get f in return
if f is not None:
a = f #store the new value
>>> g = mygen()
>>> g.next()
5
>>> g.next()
5
>>> g.send(7) #we send this back to the generator
7
>>> g.next() #now it will yield 7 until we send something else
7
yield 处理异常
import random
def get_record(line):
num = random.randint(0, 3)
if num == 3:
raise Exception("3 means danger")
return line
def parsefunc(stream):
for line in stream:
try:
rec = get_record(line)
except Exception as e:
yield (None, e)
else:
yield (rec, None)
if __name__ == '__main__':
with open('data/test.txt') as f:
for rec, e in parsefunc(f):
if e:
print("Got an exception %s" % e)
else:
print("Got a record %s" % rec)
yield 学习笔记的更多相关文章
- yield学习笔记
参考:http://www.dabeaz.com/finalgenerator/ from concurrent.futures import ThreadPoolExecutor import ti ...
- 【Python学习笔记之二】浅谈Python的yield用法
在上篇[Python学习笔记之一]Python关键字及其总结中我提到了yield,本篇文章我将会重点说明yield的用法 在介绍yield前有必要先说明下Python中的迭代器(iterator)和生 ...
- ES6学习笔记<三> 生成器函数与yield
为什么要把这个内容拿出来单独做一篇学习笔记? 生成器函数比较重要,相对不是很容易理解,单独做一篇笔记详细聊一聊生成器函数. 标题为什么是生成器函数与yield? 生成器函数类似其他服务器端语音中的接口 ...
- 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁
什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...
- Python学习笔记—Python基础1 介绍、发展史、安装、基本语法
第一周学习笔记: 一.Python介绍 1.Python的创始人为吉多·范罗苏姆.1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言 ...
- Caliburn.Micro学习笔记(五)----协同IResult
Caliburn.Micro学习笔记目录 今天说一下协同IResult 看一下IResult接口 /// <summary> /// Allows custom code to execu ...
- Java学习笔记-多线程-创建线程的方式
创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...
- 阅读《LEARNING HARD C#学习笔记》知识点总结与摘要三
最近工作较忙,手上有几个项目等着我独立开发设计,所以平时工作日的时候没有太多时间,下班累了就不想动,也就周末有点时间,今天我花了一个下午的时间来继续总结与整理书中要点,在整理的过程中,发现了书中的一些 ...
- Scala入门学习笔记三--数组使用
前言 本篇主要讲Scala的Array.BufferArray.List,更多教程请参考:Scala教程 本篇知识点概括 若长度固定则使用Array,若长度可能有 变化则使用ArrayBuffer 提 ...
随机推荐
- git shell 常用命令
git branch 查看本地所有分支 git status 查看当前状态 git commit 提交 git branch -a 查看所有的分支 git branch -r 查看远程所有分支 git ...
- apns关于APP数字角标的理解
前两天群里有兄弟在吐槽,做远程推送的时候:老板要求APP桌面图标的右上角显示红色未读数字(数字角标)要精准,有多少未读通知就显示数字几:但是后台的弟兄在发送推送通知的时候,每次的角标是1,然后要移动端 ...
- linux服务器开发二(系统编程)--进程相关
进程相关的概念 程序与进程 程序,是指编译好的二进制文件,在磁盘上,不占用系统资源(CPU.内存.打开的文件.设备.锁等等). 进程,是一个抽象的概念,与操作系统原理联系紧密.进程是活跃的程序,占用系 ...
- 使用spring-data-redis操作redis
redis.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="htt ...
- 安装MySQL -- SuSE Linux Enterprise Server 11 SP3
1.准备工作从MySQL官网上分别下载mysql服务器端于客户端包: MySQL-server-5.5.49-1.sles11.x86.rpm MySQL-client-5.5.49-1.sles11 ...
- ACM第五次积分赛
做出三道题,第二名,总积分上升到第八名,继续加油! SAU-ACM总比赛成绩 姓名 账号 上学期成绩 第一次成绩 第二次成绩 第三次成绩 第四次成绩 第五次成绩 总成绩 张国庆 143401 ...
- Git之”make sure you have the correct access…”
git 命令在windows下无法使用pull.fetch.push等命令,提示 “please make sure you have the correct access and the repos ...
- 无法定位程序输入点__gxx_personality_v0的一个解决方法
windows系统,使用mingw32-g++编译一个简单的工程,编译链接过程都没有错误提示,但是运行的时候会弹出提示框提示"无法定位程序输入点__gxx_personality_v0&qu ...
- Java笔试题目-my
1.BuildString 和 BefferedString 默认初始容量为 16 , 超出就是 16*2 + 2; 可以看源代码得知; 2.ArrayList 默认的构造方法是一个空的Ob ...
- DataGrid 导出数据到 Excel
Private Sub GridToExl_Click() On Error Resume Next If DataGrid1.Columns.Count = 0 Then MsgBox " ...