深度实践OpenStack:基于Python的OpenStack组件开发—3.5.5 生成器
3.5.5 生成器
下面首先给出生成器的定义:生成器是一次生成一个值的特殊类型函数,可以将其视为可恢复函数。调用该函数将返回一个可用于生成连续 x 值的生成器。
这个定义比较绕口,下面来做一个形象的比较。
int get_next()
{
static int number = 1; return number++;
}
上边是一段简单的c语言代码,它的功能是只要程序不退出,就一直返回静态变量 number+1的值。由于number是有static修饰的,因此,每一次调用get_next函数返回的值都不一样。Python生成器的功能类似以上的这个函数。下面介绍如何构造一个生成器:
方法1:
def first_generator():
yield "hello"
方法2:
def generator_fib(n):
first = 0
second = 1
for x in xrange(n):
first,second = second,first+second
yield first
以上的代码中用到一个新的关键字:yield。在Python中使用了yield关键字的函数一定是一个生成器。yield关键字表示返回当前的值,但是保存当前所有变量及其内存状态,当下一次代码被调用时,返回原有的内存状态。
方式1中的first_generator函数比较简单,每次调用时,都只是返回“hello”字符串。但是,这个函数和def hello():return “hello”有本质区别,例如:
type(hello()) ====><type 'str'>
type(first_generator()) ====><type 'generator'>
调用hello这个方法之后,返回的是一个计算之后的值;而调用first_generator之后,返回的是一个计算表达式,真正获取它的值需要调用next方法,或者在for循环中直接进行迭代。
方式2中的generator_fib函数返回的是一个生成器,这个生成器的作用是返回0~n之间的斐波那契数列。
前面讲了如何构造一个生成器,那这个生成器怎么使用?
result = first_generator()
for res in result:
print res
result = generator_fib(10)
for res in result:
print res
result = first_generator()
result.next()
result = generator_fib(10)
result.next()
说
明 当使用next方法的时候,执行到生成器的末尾会产生StopIteration异常。
下面学习如何使用其他方式创建生成器。我们把前面所学的列表推导:
list_test = [x for x in xrange(10)]
修改一下,就成为了生成器:
list_test = (x for x in xrange(10))
同样地,也可以把文件对象改造成生成器:
file_obj = open(file_path,mode)
file_generator = (line for line in file_obj)
简单的生成器就讲到这里,而生成器的作用也不仅仅是实现循环这么简单。例如,可以使用生成器来实现cat file | grep keyword这样一个Linux命令的功能,有兴趣的可自行练习一下相关的内容。
总结:使用生成器和使用其他可迭代的数据类型非常相似,不同的地方在于内存的处理方式。生成器的执行结果并没有在内存中立即展开,而是在执行结果被调用时才进行展开,因此,从内存消耗角度上看,生成器更加节省。
为此,提出Python性能优化的第八条原则:在不影响可读性的原则上,建议使用生成器以节省内存,尤其是在处理需要大量内存的操作时。
同时需要注意的是()的用法,我们知道tuple也是使用()定义的,并且,函数和方法也都采用了(),因此,()到底表示的是什么,需要根据上下文的语境来考虑:出现在def关键字同一行,表示定义方法或者函数;和“,”一起使用,表示构造tuple;和列表推导式一起使用,表示构造生成器。
- 点赞
- 收藏
- 关注作者
评论(0)