【Python笔记】如何理解python中的generator functions和yield表达式

本篇笔记记录自己对Python的generator functions和yield表达式的理解。

1. Generator Functions

Python支持的generator functions语法允许我们定义一个行为与iterator类似的函数,它可以被用在需要循环调用的场合。与普通函数相比,generator functions只是在函数定义中多了1个yield表达式,除此之外,没有其它特别之处。

当generator函数被创建时,python解释器会自动为它实现iteration protocol(实现__iter__和next方法,具体可参见python官网文档Iterator Types部分)以支持其被用于需要迭代的场合。

调用普通函数时,被调用函数通常会从函数体第1行开始执行并在遇到return或抛异常时退出,其内部变量也会随之销毁。而generator functions被调用时,它会返回一个generator object,这个object被循环调用时,每次都会yield一个值,而非return一个值(由此带来的有异于普通函数的行为,会在下一小节介绍yield表达式时做详细说明)。

generator functions具有2个典型特性:

1) 懒惰求值,故节省内存,详见这篇文档Improved Performance部分的说明

2) 具有iterator的行为特性,故它可以被用在for循环中

例如在Python2.x中,若for循环的循环次数非常大,为节省内存,我们通常会写出如下代码:

for idx in xrange(0, 100000000):

do something

这里的xrange()函数返回的是一个xrange对象(而range()则返回一个list对象),从概念上来看,该xrange对象是一个generator object,由于generator的上述2个特性,xrange object可用于for循环,且不管循环次数多大,它都只占用极少量内存(因为它不会一次性生成包含n个元素的list)

需要注意的是:只有在不需要频繁生成同一份数据集合的情况下(大部分应用都是这类case),generator function才能提供预期的性能优势;否则,其优势也可能转变为劣势。

通过下面的场景,相信可以帮助我们容易地理解这句话。

假设python进程生成1个整数的过程很耗时,下面是一种调用sum和product的方式:

      >>> s = sum(xrange(1000000))
      >>> p = product(xrange(1000000))

再对比另一种实现方式:

      >>> nums = list(xrange(1000000))
      >>> s = sum(nums)
      >>> p = product(nums)

很显然,第1种方式调用sum和product时会分别生成1次元素为integer的大集合,而第2种方式只会生成1次并存放在内存供后续使用中。在我们的假设场景下,第2种方式会更节省时间(典型的空间换时间)。不过,如果机器内存受限以至于无法hold住大集合,则只能采用第1种实现方式(时间换空间的折衷方式),即使其性能有损耗。

除支持generator functions外,python还支持generator expressions语法。关于generator的详细说明,强烈建议精读PYTHON-
GENERATOR FUNCTIONS AND EXPRESSIONS
这篇文章 。

2. yield表达式 

官网文档可知,yield表达式只能用于generator function的定义体中,也即,只要某函数定义中出现了yield表达式,该函数就成了generator函数,而不再是个普通函数。

我们可以从"yield"的字面含义来理解yield表达式的行为:程序运行至该表达式时,会暂时"放弃"继续向下执行的权利,程序控制权会返回给其调用者且由yield表达式"产生"的值也会返回给caller,此外,函数执行yield表达式后挂起时的context(包括那个时刻函数的local scope和local variables等信息)会被保存下来以便后续恢复执行现场(与CPU处理中断信号的过程非常类似)。

当然,仅从字面意思推断yield表达式行为的思路并不严谨,这样解释只是为了辅助理解。下面是PYTHON-
GENERATOR FUNCTIONS AND EXPRESSIONS
一文中出现的关于generator functions和yield表达式行为的更严谨解释:

Normal functions return a value and then exit. But generator functions automatically suspend and resume their execution. Because of that, they are often a useful alternative to both computing an entire series
of values up front and manually saving and restoring state in classes. Because the state that generator functions retain when they are suspended includes their local scope, their local variables retain information and make it available when the functions are
resumed.

The primary difference between generator and normal functions is that a generator yields a value, rather than returns a value. The yield suspends the function and sends a value back to the caller while retains
enough state to enable the function immediately after the last yield run. This allows the generator function to produce a series of values over time rather than computing them all at once and sending them back in a list.

为更好地理解上面这段话,可以分析下面这段代码:

>>> def create_counter(n):
	print('create_counter()')
	while True:
		yield n
		print('increment n')
		n += 1

>>> c = create_counter(2)
>>> c
<generator object create_counter at 0x03004B48>
>>> next(c)
create_counter()
2
>>> next(c)
increment n
3
>>> next(c)
increment n
4
>>> 

上面的代码用def定义了名为create_counter的generator function来实现计数器功能。

对象c是由create_counter创建出来的generator object,当调用python built-in函数next()时,它会调用其参数c的__next__方法。

备注:由第1小节的介绍可知,python解释器在创建generator object时会为其自动生成支持__next__的代码。

第1次调用next()时,c对象会执行yield n,其行为是函数体被挂起并将n返回给调用者,故可以看到第1次调用next()的输出并未包含"increment n"这个字符串。其实,函数体挂起并返回时,其执行上下文会被记录下来,只不过这一步我们无法显式感受到而已。

第2次调用next()时,generator function在上次挂起的地方恢复执行环境(本例中为n的值)后继续向下执行,故先输出"increment n"字符串,然后n+1,由于while loop的存在,因此程序又执行到yield n,它又会挂起并返回,故终端输出3

第n次调用next()时,函数行为与第2次调用完全一致,此处不赘述。

真正理解了上面代码的输出后,相信我们也会真正理解yield表达式和generator function的语法行为。

此外,Improve Your Python: ‘yield‘ and Generators Explained这篇文章最后部分对generator和yield表达式做了非常精炼且准确的总结,摘录如下:

1) generators are used to generate a series of values

2) yield is like the return of generator functions

3) The only other thing yield does is save the "state" of a generator function

4) A generator is just a special type of iterator

5) Like iterators, we can get the next value from a generator using next()

6) for gets values by calling next() implicitly

相信有了本篇笔记前面的介绍,这几点很容易理解。

下面是一段利用yield语法生成裴波纳契数列的代码,是不是很pythonic呢? ^_^

#!/bin/env python

def fib(threshold):
    a, b = 0, 1
    while a < threshold:
        yield a
        a, b = b, a + b 

def main():
    for n in fib(100):
        print n

if '__main__' == __name__:
    main()

【参考资料】

1. Python Docs: Yield expressions

2. PYTHON - GENERATOR FUNCTIONS AND EXPRESSIONS

3. Improve Your Python: ‘yield‘ and Generators Explained

4. Python PEP 255 -- Simple Generators

5. Python PEP 342 -- Coroutines via Enhanced Generators

========================= EOF =============================

时间: 2024-10-11 03:48:32

【Python笔记】如何理解python中的generator functions和yield表达式的相关文章

【Python笔记】剖析Python的切片(slicing)语法

相信即使是Python新手也很容易理解下面的切片行为: >>> s = 'this_is_a_test' >>> s[1 : 5] 'his_' 进一步,下面的语法及输出也很不难理解: >>> s = 'this_is_a_test' >>> s[ : : 2] 'ti_sats' 那么,下面的呢? >>> s = 'this_is_a_test' >>> s[ : : -1] 'tset_a_si

【python笔记 二 】python语句

1.for语句: 脚本实例: #!/usr/bin/python for i in range(1,15): print i else: print 'The loop is done !' 2.while/if语句: 脚本实例: #!/usr/bin/env python #FileName:while1.py running = True a = 20 b = int(raw_input('Enter A number b: ')) while running: if a == b: pri

【python笔记 三 】python脚本实战---数字密码小游戏

描述:A输入一个数值,输入时该数值已隐藏无法看到,让B猜该数字为多少,数值过小或过大都会提示,并会在最后猜对时统计出共猜了几次. 脚本内容: #!/usr/bin/env python #FileName:while1.py import getpass    #调用模块 a = int(getpass.getpass('Enter A Number a:'))    #模块赋值 running = True i = 0 while running: b = int(raw_input('Ent

Python笔记(五):异常处理和数据存储

注:和上一篇有关联 (一)  finally 和 输出异常信息 try:       the_man = open(r'C:\Users\123456\Desktop\test.txt')       print(the_man.readline(),end="") except IOError as err:     #输出异常信息     print("异常信息:"+ str(err)) #str()转换为字符串 finally:     #不管是否发生异常一定

笔记1:Python简单介绍

Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.自从20世纪90年代初Python语言诞生至今,它逐渐被广泛应用于处理系统管理任务和Web编程.Python已经成为最受欢迎的程序设计语言之一.2011年1月,它被TIOBE编程语言排行榜评为2010年度语言.Python语法简洁而清晰,具有丰富和强大的类库,常被称为胶水语言,它能够把用其他语言制作的各种模块(尤其是C/C++)很轻松地联结在一起. 人们为什么使用Python? 提高开发者效率 提高软件质量 程序的可移植性 内置

JavaScript中的Generator函数

1. 简介 Generator函数时ES6提供的一种异步编程解决方案.Generator语法行为和普通函数完全不同,我们可以把Generator理解为一个包含了多个内部状态的状态机. 执行Generator函数回返回一个遍历器对象,也就是说Generator函数除了提供状态机,还是一个遍历器对象生成函数.Generator可以以此返回多个遍历器对象,通过这个对象可以以此访问到Generator函数内部的多个状态. 形式上Generator函数和普通的函数有两点不同,一是function关键字后面

yield表达式 python语法

可以先看下这篇文章:http://www.cnblogs.com/jiangtu/articles/6662043.html 原篇是转载的:http://www.python-tab.com/html/2015/pythonhexinbiancheng_0415/946.html  (去掉连字符 - ,博客园显示违禁字..) 之前对 yield表达式 了解的也不清楚,只知道包含 yield表达式 的函数 会被编译成迭代器,如以下代码: def g(n): for i in range(n): y

深入理解Python中的生成器

生成器(generator)概念 生成器不会把结果保存在一个系列中,而是保存生成器的状态,在每次进行迭代时返回一个值,直到遇到StopIteration异常结束. 生成器语法 生成器表达式: 通列表解析语法,只不过把列表解析的[]换成()生成器表达式能做的事情列表解析基本都能处理,只不过在需要处理的序列比较大时,列表解析比较费内存. Python 1 2 3 4 5 6 7 8 9 10 11 >>> gen = (x**2 for x in range(5)) >>>

【Python笔记】从一段Bug代码来理解Python的Naming Rule

从Python文档关于Naming and binding的说明可知,变量名是绑定到具体对象的,从这点来看,可以把它理解为C++中的引用.考虑下面两行语句: a = 'test' a = 'test_ext' 第1行执行后,Python解释器会在内存中创建string类型的对象'test',这个对象一旦创建就不能再修改其值.赋值符号只是将变量名a绑定到这个对象上而已. 第2行执行后,同理,值为'test_ext'的string对象被创建出来,变量名a重新绑定到这个新对象上.此时,'test'对象