bvac

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

第三部分(先看)

先讲 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)
posted on 2016-05-26 08:30  bvac  阅读(607)  评论(0编辑  收藏  举报