第三部分(先看)

先讲 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后:

  1. 首先调用c.send(None)启动生成器;

  2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

  3. consumer通过yield拿到消息,处理,又通过yield把结果传回;

  4. produce拿到consumer处理的结果,继续生产下一条消息;

  5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produceconsumer协作完成任务,所以称为 “协程”,而非线程的抢占式多任务。

实例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 学习笔记的更多相关文章

  1. yield学习笔记

    参考:http://www.dabeaz.com/finalgenerator/ from concurrent.futures import ThreadPoolExecutor import ti ...

  2. 【Python学习笔记之二】浅谈Python的yield用法

    在上篇[Python学习笔记之一]Python关键字及其总结中我提到了yield,本篇文章我将会重点说明yield的用法 在介绍yield前有必要先说明下Python中的迭代器(iterator)和生 ...

  3. ES6学习笔记<三> 生成器函数与yield

    为什么要把这个内容拿出来单独做一篇学习笔记? 生成器函数比较重要,相对不是很容易理解,单独做一篇笔记详细聊一聊生成器函数. 标题为什么是生成器函数与yield? 生成器函数类似其他服务器端语音中的接口 ...

  4. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  5. Python学习笔记—Python基础1 介绍、发展史、安装、基本语法

    第一周学习笔记: 一.Python介绍      1.Python的创始人为吉多·范罗苏姆.1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言 ...

  6. Caliburn.Micro学习笔记(五)----协同IResult

    Caliburn.Micro学习笔记目录 今天说一下协同IResult 看一下IResult接口 /// <summary> /// Allows custom code to execu ...

  7. Java学习笔记-多线程-创建线程的方式

    创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...

  8. 阅读《LEARNING HARD C#学习笔记》知识点总结与摘要三

    最近工作较忙,手上有几个项目等着我独立开发设计,所以平时工作日的时候没有太多时间,下班累了就不想动,也就周末有点时间,今天我花了一个下午的时间来继续总结与整理书中要点,在整理的过程中,发现了书中的一些 ...

  9. Scala入门学习笔记三--数组使用

    前言 本篇主要讲Scala的Array.BufferArray.List,更多教程请参考:Scala教程 本篇知识点概括 若长度固定则使用Array,若长度可能有 变化则使用ArrayBuffer 提 ...

随机推荐

  1. git shell 常用命令

    git branch 查看本地所有分支 git status 查看当前状态 git commit 提交 git branch -a 查看所有的分支 git branch -r 查看远程所有分支 git ...

  2. apns关于APP数字角标的理解

    前两天群里有兄弟在吐槽,做远程推送的时候:老板要求APP桌面图标的右上角显示红色未读数字(数字角标)要精准,有多少未读通知就显示数字几:但是后台的弟兄在发送推送通知的时候,每次的角标是1,然后要移动端 ...

  3. linux服务器开发二(系统编程)--进程相关

    进程相关的概念 程序与进程 程序,是指编译好的二进制文件,在磁盘上,不占用系统资源(CPU.内存.打开的文件.设备.锁等等). 进程,是一个抽象的概念,与操作系统原理联系紧密.进程是活跃的程序,占用系 ...

  4. 使用spring-data-redis操作redis

    redis.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="htt ...

  5. 安装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 ...

  6. ACM第五次积分赛

    做出三道题,第二名,总积分上升到第八名,继续加油! SAU-ACM总比赛成绩 姓名     账号  上学期成绩 第一次成绩 第二次成绩 第三次成绩 第四次成绩 第五次成绩 总成绩 张国庆 143401 ...

  7. Git之”make sure you have the correct access…”

    git 命令在windows下无法使用pull.fetch.push等命令,提示 “please make sure you have the correct access and the repos ...

  8. 无法定位程序输入点__gxx_personality_v0的一个解决方法

    windows系统,使用mingw32-g++编译一个简单的工程,编译链接过程都没有错误提示,但是运行的时候会弹出提示框提示"无法定位程序输入点__gxx_personality_v0&qu ...

  9. Java笔试题目-my

    1.BuildString  和 BefferedString  默认初始容量为 16 ,  超出就是 16*2 + 2;   可以看源代码得知; 2.ArrayList 默认的构造方法是一个空的Ob ...

  10. DataGrid 导出数据到 Excel

    Private Sub GridToExl_Click() On Error Resume Next If DataGrid1.Columns.Count = 0 Then MsgBox " ...