第三部分(先看)

先讲 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()方法将其结果保存起来是一个不错的选择。

我们通过代码来感受一下。

  1. >>> from collections import Iterable, Iterator
  2. >>> a = [1,2,3]
  3. # 众所周知,list是一个iterable
  4. >>> b = iter(a)
  5. # 通过iter()方法,得到iterator,iter()
  6. 实际上调用了__iter__(),此后不再多说
  7. >>> isinstance(a, Iterable)
  8. True
  9. >>> isinstance(a, Iterator)
  10. False
  11. >>> isinstance(b, Iterable)
  12. True
  13. >>> isinstance(b, Iterator)
  14. True
  15. # iterator是消耗型的,用一次少一次.
  16. iterator进行变量,iterator就空了!
  17. >>> c = list(b)
  18. >>> c
  19. [1, 2, 3]
  20. >>> d = list(b)
  21. >>> d
  22. []
  23. # 空的iterator并不等于None.
  24. >>> if b:
  25. ... print(1)
  26. ...
  27. 1
  28. # 再来感受一下next()
  29. >>> e = iter(a)
  30. >>> next(e)
  31. #next()实际调用了__next__()方法,此后不再多说
  32. 1
  33. >>> next(e)
  34. 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无异。

  1. >>> a = (elem for elem in [1, 2, 3])
  2. >>> a
  3. <generator object <genexpr> at 0x7f0d23888048>
  4. >>> def fib():
  5. ... a, b = 0, 1
  6. ... while True:
  7. ... yield b
  8. ... a, b = b, a + b
  9. ...
  10. >>> fib
  11. <function fib at 0x7f0d238796a8>
  12. >>> b = fib()
  13. <generator object fib at 0x7f0d20bbfea0>

其实说白了,generator就是iterator的一种,以更优雅的方式实现的iterator。

你完全可以像使用iterator一样使用generator。定义一个iterator,你需要分别实现__iter__()方法和__next__()方法,但generator只需要一个小小的yield。

前文讲到iterator通过__next__()方法实现了每次调用,返回一个单一值的功能。而yield就是实现generator的__next__()方法的关键!先来看一个最简单的例子:

  1. >>> def g():
  2. ... print("1 is")
  3. ... yield 1
  4. ... print("2 is")
  5. ... yield 2
  6. ... print("3 is")
  7. ... yield 3
  8. ...
  9. >>> z = g()
  10. >>> z
  11. <generator object g at 0x7f0d2387c8b8>
  12. >>> next(z)
  13. 1 is1
  14. >>> next(z)
  15. 2 is2
  16. >>> next(z)
  17. 3 is3
  18. >>> next(z)
  19. Traceback (most recent call last):
  20. File "<stdin>", line 1, in <module>
  21. StopIteration

第一次调用next()方法时,函数似乎执行到yield 1,就暂停了。然后再次调用next()时,函数从yield 1之后开始执行的,并再次暂停。第三次调用next(),从第二次暂停的地方开始执行。第四次, 抛出StopIteration异常。

我们再来看另一段代码。

  1. >>> def gen():
  2. ... while True:
  3. ... s = yield
  4. ... print(s)
  5. ...
  6. >>> g = gen()
  7. >>> g.send("kissg")
  8. Traceback (most recent call last):
  9. File "<stdin>", line 1, in <module>
  10. TypeError: can't send non-None value to a just-started generator
  11. >>> next(g)
  12. >>> g.send("kissg")
  13. kissg

generator其实有第 2 种调用方法 (恢复执行),即通过send(value)方法将value作为yield表达式的当前值,你可以用该值再对其他变量进行赋值,这一段代码就很好理解了。

调用send(value)时要注意,要确保,generator是在yield处被暂停了,如此才能向yield表达式传值,否则将会报错 (如上所示),可通过next()方法或send(None)使generator执行到yield。

再来看一段yield更复杂的用法,或许能加深你对generator的next()与send(value)的理解:

  1. In [1]: def echo(v=None):
  2. ...: while True:
  3. ...: print("line3")
  4. ...: v = yield v
  5. ...: if v is not None:
  6. ...: v += 1
  7. ...:
  8. In [2]: g = echo(1)
  9. In [3]: g
  10. Out[3]: <generator object echo at 0x0000000003776C50>
  11. In [4]: g.send(None)
  12. line3
  13. Out[4]: 1
  14. In [5]: g.send(2)
  15. line3
  16. Out[5]: 3

yield 与 return

在一个生成器中,如果没有 return,则默认执行到函数完毕时返回 StopIteration;

  1. >>> def g1():
  2. ... yield 1
  3. ...
  4. >>> g=g1()
  5. >>> next(g) #第一次调用 next(g) 时,会在执行完 yield 语句后挂起,所以此时程序并没有执行结束。
  6. 1
  7. >>> next(g) #程序试图从 yield 语句的下一条语句开始执行,发现已经到了结尾,所以抛出 StopIteration 异常。
  8. Traceback (most recent call last):
  9. File "<stdin>", line 1, in <module>
  10. StopIteration
  11. >>>

如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

  1. >>> def g2():
  2. ... yield 'a'
  3. ... return
  4. ... yield 'b'
  5. ...
  6. >>> g=g2()
  7. >>> next(g) #程序停留在执行完 yield 'a'语句后的位置。
  8. 'a'
  9. >>> next(g) #程序发现下一条语句是 return,所以抛出 StopIteration 异常,这样 yield 'b'语句永远也不会执行。
  10. Traceback (most recent call last):
  11. File "<stdin>", line 1, in <module>
  12. StopIteration

如果在 return 后返回一个值,那么这个值为 StopIteration 异常的说明,不是程序的返回值。

生成器没有办法使用 return 来返回值。

  1. >>> def g3():
  2. ... yield 'hello'
  3. ... return 'world'
  4. ...
  5. >>> g=g3()
  6. >>> next(g)
  7. 'hello'
  8. >>> next(g)
  9. Traceback (most recent call last):
  10. File "<stdin>", line 1, in <module>
  11. StopIteration: world

第一部分

生成器有这么几个方法:

  • __next__ 不用说了,每次 for 还有 next 都是调用这个方法。
  • send(value) 用 value 对 yield 语句赋值,再执行接下来的代码直到下个 yield。
  • throw(type[, value[, traceback]]) 抛出错误,类似于 raise 吧。
  • close() 告诉生成器,你已经死了。再调用会抛出 StopIteration。
  • gi_running 查看生成器是否再运行。

实例1

  1. def gen():
  2. yield 1
  3. yield 2
  4. g = gen()
  5. for i in g:
  6. print(i)
  7. 打印:
  8. 1
  9. 2

实例 2

在调用send方法前,必须先调用一次__next__,让生成器执行到 yield 语句处, 才能进行赋值。外面加上 while 循环是为了避免出现send之后, 生成器没有 yield 语句了,抛出 StopIteration 的情况。

  1. def gen():
  2. while 1:
  3. h = yield
  4. print(h)
  5. g = gen()
  1. g.send(None) #表示开始
  1. g.send('aaa')
  1. aaa
  1. g.send('bbb')
  1. bbb
  1. g.close()
  1. g.send('ccc')
  1. ---------------------------------------------------------------------------
  2. StopIteration Traceback (most recent call last)
  3. <ipython-input-11-b18ebbba6fe9> in <module>()
  4. ----> 1 g.send('ccc')
  5. StopIteration:
  1. g.gi_running
  1. False

第二部分

yield

实例1:

  1. def add(s, x):
  2. return s + x
  3. def gen():
  4. for i in range(4):
  5. yield i
  6. base = gen()
  7. for n in [1, 10]:
  8. base = (add(i, n) for i in base)
  9. print(list(base))
  10. 结果:
  11. [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:

  1. #coding: U8
  2. def h():
  3. print('Wen Chuan')
  4. m = yield 5 #m是下一个yield传进来的参数,即send()的内容
  5. print(m)
  6. d = yield 12
  7. # print(d) #这里d没有值,因为下面没有send了,d是下一个yield传进来的参数
  8. print('We are together!')
  9. c = h()
  10. x = c.send(None) #send方法 给yield传参
  11. #x 获取了 yield 5 的参数值 5
  12. #相当于c.__next__(),第一次使用 send参数必须为None,表示启动生成器,直到下一个 yield 表达式处。
  13. #m = Fighting! 是下一个send的参数
  14. y = c.send('Fighting!') #y 获取了 yield 12 的参数值 12
  15. print('We will never forget the date', x, '.', y)
  16. 打印:
  17. Wen Chuan
  18. Fighting!
  19. ('We will never forget the date', 5, '.', 12)

实例3: 生产-消费者模型

  1. def consumer():
  2. r = ''
  3. while True:
  4. n = yield r
  5. if not n:
  6. return
  7. print('[CONSUMER] Consuming %s...' % n)
  8. r = '200 OK'
  9. def produce(c):
  10. c.send(None)
  11. n = 0
  12. while n < 5:
  13. n = n + 1
  14. print('[PRODUCER] Producing %s...' % n)
  15. r = c.send(n)
  16. print('[PRODUCER] Consumer return: %s' % r)
  17. c.close()
  18. c = consumer()
  19. produce(c)
  20. 打印:
  21. [PRODUCER] Producing 1...
  22. [CONSUMER] Consuming 1...
  23. [PRODUCER] Consumer return: 200 OK
  24. [PRODUCER] Producing 2...
  25. [CONSUMER] Consuming 2...
  26. [PRODUCER] Consumer return: 200 OK
  27. [PRODUCER] Producing 3...
  28. [CONSUMER] Consuming 3...
  29. [PRODUCER] Consumer return: 200 OK
  30. [PRODUCER] Producing 4...
  31. [CONSUMER] Consuming 4...
  32. [PRODUCER] Consumer return: 200 OK
  33. [PRODUCER] Producing 5...
  34. [CONSUMER] Consuming 5...
  35. [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:

发送值到生成器函数在中

  1. def mygen():
  2. """Yield 5 until something else is passed back via send()"""
  3. a = 5
  4. while True:
  5. f = (yield a) #yield a and possibly get f in return
  6. if f is not None:
  7. a = f #store the new value
  8. >>> g = mygen()
  9. >>> g.next()
  10. 5
  11. >>> g.next()
  12. 5
  13. >>> g.send(7) #we send this back to the generator
  14. 7
  15. >>> g.next() #now it will yield 7 until we send something else
  16. 7

yield 处理异常

  1. import random
  2. def get_record(line):
  3. num = random.randint(0, 3)
  4. if num == 3:
  5. raise Exception("3 means danger")
  6. return line
  7. def parsefunc(stream):
  8. for line in stream:
  9. try:
  10. rec = get_record(line)
  11. except Exception as e:
  12. yield (None, e)
  13. else:
  14. yield (rec, None)
  15. if __name__ == '__main__':
  16. with open('data/test.txt') as f:
  17. for rec, e in parsefunc(f):
  18. if e:
  19. print("Got an exception %s" % e)
  20. else:
  21. 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. linux内核内存管理(zone_dma zone_normal zone_highmem)

    Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当内核空间使用用户空间指针时,对应的数 ...

  2. java基础(一)对象

    对象的创建 Test test = new Test(); Test test = new Test("a"); 其实,对象被创建出来时,对象就是一个对象的引用,这个引用在内存中为 ...

  3. 使用gulp构建自动化工作流

    简单易用 高效构建 高质量的生态圈 可能很多人会说现在提gulp也太落后了吧,但我想说写点东西并不是为了讨论它是否过时,而是来帮助我们自己来记忆.整理和学习.任何工具,我需要,我才去使用它,正如此时我 ...

  4. AI(三):微信与luis结合(上)

    目录 基本原理 公众号申请及配置 验证服务器有效性 微信请求消息类型 基本原理 基本原理如上图:腾讯微信服务器就相当于一个转发服务器,终端(手机.Pad等)发起请求至微信服务器,微信服务器然后将请求转 ...

  5. C# 动态对象(dynamic)的用法

    说到正确用法,那么首先应该指出一个错误用法: 常有人会拿var这个关键字来和dynamic做比较.实际上,var和dynamic完全是两个概念,根本不应该放在一起做比较.var实际上是编译期抛给我们的 ...

  6. java静态方法之线程安全问题

    静态方法和实例方法的区别是静态方法只能引用静态变量,静态方法通过类名来调用,实例方法通过对象实例来调用 每个线程都有自己的线程栈,栈与线程同时创建,每一个虚拟机线程都有自己的程序计数器PC,在任何时刻 ...

  7. apk反编译查看源码

    1.将apk解压

  8. mac 下mysql

    We've installed your MySQL database without a root password. To secure it run: mysql_secure_installa ...

  9. 第一百二十四节,JavaScriptCookie与存储

    JavaScriptCookie与存储 学习要点: 1.cookie 2.cookie局限性 3.其他存储 随着Web越来越复杂,开发者急切的需要能够本地化存储的脚本功能.这个时候,第一个出现的方案: ...

  10. 从excel读数据到informix的Found a quote for which there is no matching quote错误

    我从excel读取数据,然后存储到Informix数据库里.偶尔会发现出现Found a quote for which there is no matching quote这个错误.调试后发现,是因 ...