python-生成器

生成器是用来创建 Python 序列的一个对象。使用它可以迭代庞大的序列,且不需要在内存中创建和存储整个序列。通常,生成器是为迭代器产生数据的。
生成器函数和普通函数类似,但是它的返回值使用 yield 语句声明而不是 return。

Demo1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def my_range(first=0, last=10, step=1):
number = first
while number < last:
yield number
number += step
# my_range
<function my_range at 0x7f4ee1c9f578> #就是普通函数
# ranger = my_range(1, 5)
# ranger
<generator object my_range at 0x7f4eed60bfa0> # 返回的是生成器对象
for i in ranger: # 迭代
print i
  • 生成器与迭代器区别:
    每次执行迭代器的next()方法并返回后,该方法的上下文环境即消失了,也就是所有在next()方法中定义的局部变量就无法被访问了。
    而对于生成器,每次执行next()方法后,代码会执行到yield关键字处,并将yield后的参数值返回,同时当前生成器函数的上下文会被保留下来。也就是函数内所有变量的状态会被保留,同时函数代码执行到的位置会被保留,就像函数被暂停了一样。当再一次调用next()方法时,代码会从yield关键字的下一行开始执行。
    如果执行next()时没有遇到yield关键字即退出(或返回),则抛出StopIteration异常。

close() 方法

顾名思义,close()方法就是关闭生成器。生成器被关闭后,再次调用next()方法,不管能否遇到yield关键字,都会立即抛出StopIteration异常。

1
2
3
gen = (x for x in range(5))
gen.close()
gen.next() # StopIteration

send() 方法

可以通过send()方法,向生成器内部传递参数。我们来看个例子:

1
2
3
4
5
6
7
def count(n):
x = 0
while x < n:
value = yield x
if value is not None:
print 'Received value: %s' %value
x += 1

将”yield x”的值赋给了变量value,并将其打印出来。如何给value传值呢?

1
2
3
gen = count(5)
print gen.next() # print 0
print gen.send('Hello') # Received value: Hello, then print 1

我们先调用next()方法,让代码执行到yield关键字(这步必须要),当前打印出0。然后当我们调用”gen.send(‘Hello’)”时,字符串’Hello’就被传入生成器中,并作为yield关键字的执行结果赋给变量”value”,所以控制台会打印出”Received value: Hello”。然后代码继续执行,直到下一次遇到yield关键字后暂定,此时生成器返回的是1。

简单的说,send()就是next()的功能,加上传值给yield。如果你有兴趣看下Python的源码,你会发现,其实next()的实现,就是send(None)。

throw() 方法

除了向生成器函数内部传递参数,我们还可以传递异常。还是先看例子:

1
2
3
4
5
6
7
8
9
10
11
def throw_gen():
try:
yield 'Normal'
except ValueError:
yield 'Error'
finally:
print 'Finally'
gen = throw_gen()
print gen.next() # Normal
print gen.next() # Finally, then StopIteration

如果像往常一样调用next()方法,会返回’Normal’。再次调用next(),会进入finally语句,打印’Finally’,同时由于函数退出,生成器会抛出StopIteration异常。我们换个方式,在第一次调用next()方法后,调用throw()方法,情况会怎样?

1
2
3
4
gen = throw_gen()
print gen.next() # Normal
print gen.throw(ValueError) # Error
print gen.next() # Finally, then StopIteration

我们会看到,throw()方法向生成器函数内部传递了”ValueError”异常,代码进入”except ValueError”语句,当遇到下一个yield时才暂停并退出,此时生成器返回的是’Error’字符串。简单的说,throw()就是next()的功能,加上传异常给yield。

Demo2

使用协程写一个生产者消费者的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def consumer():
last = ''
while True:
receival = yield last
if receival is not None:
print 'Consume %s' % receival
last = receival
def producer(gen, n):
gen.next() # 必须先执行next,否则send会报错
x = 0
while x < n:
x += 1
print 'Produce %s' % x
last = gen.send(x)
gen.close()
gen = consumer()
producer(gen, 5)

执行下例子,你会看到控制台交替打印出生产和消费的结果。消费者consumer()函数是一个生成器函数,每次执行到yield时即挂起,并返回上一次的结果给生产者。生产者producer()接收到生成器的返回,并生成一个新的值,通过send()方法发送给消费者。至此,我们成功实现了一个(伪)并发。

参考:谈谈Python的生成器