python生成器详解
2017-08-16 22:39
399 查看
python生成器和python装饰器几乎平起平坐,在python世界中占有重要的地位。如果说python装饰器是为了让程序员悄无声息拓展函数功能,那么python生成器就是为了让你的代码更省资源,更高效!说实话python生成器的抽象程度和灵活程度都要比python装饰器高,功能也更加强大,所以我也只能暂时比较粗略地说句“python生成器就像一把瑞士军刀,让你的代码更加短小(简洁)精悍(有力)”。
首先我们来看一段python2环境下运行的一段代码:
打印结果如下:
看了这段代码,你可能会说:python真麻烦,有range又有xrange,有[]又有(),运行结果还一样。这不是白费事嘛?
那我们现在稍微改动下代码:
打印结果如下:
这里需要说明:range()返回一个列表list,xrange()返回一个生成器;也即[]返回一个列表list而()返回一个生成器。
* 注意:用[]推导出来的是迭代器(Iterables)。用()推导出来的是生成器(Generators)。*
可迭代对象(iterable) 与 迭代器(iterator)
在python中,迭代通常是通过for … in …来完成的,而且只要是可迭代对象(iterable),都能进行迭代。
例如,在下面的遍历中
这个something就是一个可迭代对象(iterable)。比如list,string,file。
对于iterable,我们该关注的是,它是一个能一次返回一个成员的对象(iterable is an object capable of returning its members one at a time),一些iterable将所有值都存储在内存中,比如list,而另一些并不是这样,比如我们下面将讲到的iterator。
iterator.next()是iterator区别于iterable的关键了,它允许我们显式地获取一个元素.当调用next()方法时,实际上产生了2个操作:
更新iterator状态,令其指向后一项,以便下一次调用;
返回当前结果;
二者的关系如下 :
iter(可迭代对象) 生成迭代器
我们现在不用for循环遍历这个迭代器,我们尝试调用next()方法。
除了最后一行抛出异常外,其他结果和for循环一样。也就是说,用for循环遍历迭代器(iterator)X时,会循环地调用X的next()方法取得每一次的值,直到iterator为空,返回的StopIteration作为循环结束的标志。for … in … 会自动处理StopIteration异常,从而避免了抛出异常而使程序中断。
我们对一个可迭代对象(iterable)用for … in …进行迭代时,实际是先通过调用iter()方法得到一个iterator,假设叫做X.然后循环地调用X的next()方法取得每一次的值,直到iterator为空,返回的StopIteration作为循环结束的标志.for … in … 会自动处理StopIteration异常,从而避免了抛出异常而使程序中断.
需要格外注意的是,iterator是消耗型的,即每一个值被使用过后,就消失了.因此,你可以将以上的操作2理解成pop.对iterator进行遍历之后,其就变成了一个空的容器了,但不等于None哦.因此,若要重复使用iterator,利用list()方法将其结果保存起来是一个不错的选择。
代码如下:
生成器概念
只要 Python 函数的定义体中有 yield 关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。
如何“生成”生成器
如何产生一个生成器呢,方法有二:
1.生成器函数(generator function):带有yield关键字的函数就是生成器函数,它的返回值是个生成器。
2.生成器表达式(generator expression):而形如(elem for elem in [1, 2, 3])的表达式,称为generator expression,也能产生一个生成器。
生成器表达式生成生成器:
我们调用next()方法,也是可以的:
这和我们上面迭代器(iterator)的使用一毛一样!
其实,生成器(generator)就是迭代器(iterator)的一种,以更优雅的方式实现的iterator,而且完全可以像使用iterator一样使用generator。当然除了定义,定义一个iterator,你需要分别实现_ _ iter _ _ ()方法和 _ _ next_ _()方法,但generator只需要一个yield关键字就可以。
生成器函数产生生成器
这里你也能猜出来,我们调用next()方法除了会抛出异常,效果也一样:
其实 ,for循环会每次隐时调用next()方法,前进到函数中的下一个yield语句处。
生成器函数执行过程
生成器另外一个黑科技是生成器中的yield语句会挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行。
我们来看代码:
打印结果:
❶ 定义生成器函数的方式与普通的函数无异,只不过要使用 yield 关键字。
❷ 在 for 循环中第一次隐式调用 next() 函数时(序号➎),会打印
‘start’,然后停在第一个 yield 语句,生成值 ‘A’。
❸ 在 for 循环中第二次隐式调用 next() 函数时,会打印
‘continue’,然后停在第二个 yield 语句,生成值 ‘B’。
❹ 第三次调用 next() 函数时,会打印 ‘end.’,然后到达函数定义体
的末尾,导致生成器对象抛出 StopIteration 异常。
❺ 迭代时,for 机制的作用与 g = iter(gen_AB()) 一样,用于获取
生成器对象,然后每次迭代时调用 next(g)。
❻ 循环块打印 –> 和 next(g) 返回的值。但是,生成器函数中的
print 函数输出结果之后才会看到这个输出。
❼ ‘start’ 是生成器函数定义体中 print(‘start’) 输出的结果。
❽ 生成器函数定义体中的 yield ‘A’ 语句会生成值 A,提供给 for 循
环使用,而 A 会赋值给变量 i,最终输出 –> A。
❾ 第二次调用 next(g),继续迭代,生成器函数定义体中的代码由
yield ‘A’ 前进到 yield ‘B’。文本 continue 是由生成器函数定义
体中的第二个 print 函数输出的。
❿ yield ‘B’ 语句生成值 B,提供给 for 循环使用,而 B 会赋值给变
量 c,所以循环打印出 –> B。
⓫ 第三次调用 next(it),继续迭代,前进到生成器函数的末尾。文本
end. 是由生成器函数定义体中的第三个 print 函数输出的。到达生成
器函数定义体的末尾时,生成器对象抛出 StopIteration 异常。for
机制会捕获异常,因此循环终止时没有报错。
首先我们来看一段python2环境下运行的一段代码:
#注意,在python3.x中range和xrange已被合并,这段代码运行必须在py2下! for i in range(3): print(i) print('----------') for i in xrange(3): print(i) print('----------') lst1 = [x*x for x in range(3)] lst2 = (x*x for x in range(3)) for i in lst1: print(i) print('----------') for i in lst2: print(i)
打印结果如下:
0 1 2 ---------- 0 1 2 ---------- 0 1 4 ---------- 0 1 4
看了这段代码,你可能会说:python真麻烦,有range又有xrange,有[]又有(),运行结果还一样。这不是白费事嘛?
那我们现在稍微改动下代码:
a = range(3) b = xrange(3) print(type(a)) print(a) print(a[0],a[1]) print('---------') print(type(b)) print(b) print(b[0],b[1]) print('---------') lst1 = [x*x for x in range(3)] lst2 = (x*x for x in range(3)) print(type(lst1)) print(type(lst2))
打印结果如下:
<type 'list'> [0, 1, 2] (0, 1) --------- <type 'xrange'> xrange(3) (0, 1) --------- <type 'list'> <type 'generator'>
这里需要说明:range()返回一个列表list,xrange()返回一个生成器;也即[]返回一个列表list而()返回一个生成器。
* 注意:用[]推导出来的是迭代器(Iterables)。用()推导出来的是生成器(Generators)。*
可迭代对象(iterable) 与 迭代器(iterator)
iterable可迭代对象是实现了__iter__()方法的对象。更确切的说,是container.__iter__()方法,该方法返回的是的一个iterator对象,因此你可以从iterable可迭代对象获得iterator迭代器。
在python中,迭代通常是通过for … in …来完成的,而且只要是可迭代对象(iterable),都能进行迭代。
例如,在下面的遍历中
for value in something
这个something就是一个可迭代对象(iterable)。比如list,string,file。
对于iterable,我们该关注的是,它是一个能一次返回一个成员的对象(iterable is an object capable of returning its members one at a time),一些iterable将所有值都存储在内存中,比如list,而另一些并不是这样,比如我们下面将讲到的iterator。
iterator(迭代器)是这样的对象:实现了无参的__next__() 方法,返回序列中的下一个元素;如果没有元素,将会抛出StopIteration异常。Python的迭代器也实现了__iter__()方法,所以一个迭代器(iterator)也是可迭代的(iterable)。
iterator.next()是iterator区别于iterable的关键了,它允许我们显式地获取一个元素.当调用next()方法时,实际上产生了2个操作:
更新iterator状态,令其指向后一项,以便下一次调用;
返回当前结果;
二者的关系如下 :
iter(可迭代对象) 生成迭代器
>>> s = 'abc' #s是一个可迭代对象 >>> it = iter(s) #it是一个迭代器 >>> for i in it: ... print(i)
我们现在不用for循环遍历这个迭代器,我们尝试调用next()方法。
>>> s = 'abc' >>> it = iter(s) >>> next(it) 'a' >>> s = 'abc' >>> it = iter(s) >>> next(it) 'a' >>> next(it) 'b' >>> next(it) 'c' >>> next(it) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>>
除了最后一行抛出异常外,其他结果和for循环一样。也就是说,用for循环遍历迭代器(iterator)X时,会循环地调用X的next()方法取得每一次的值,直到iterator为空,返回的StopIteration作为循环结束的标志。for … in … 会自动处理StopIteration异常,从而避免了抛出异常而使程序中断。
我们对一个可迭代对象(iterable)用for … in …进行迭代时,实际是先通过调用iter()方法得到一个iterator,假设叫做X.然后循环地调用X的next()方法取得每一次的值,直到iterator为空,返回的StopIteration作为循环结束的标志.for … in … 会自动处理StopIteration异常,从而避免了抛出异常而使程序中断.
需要格外注意的是,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 # 可见,itertor一定是iterable,但iterable不一定是itertor # iterator是消耗型的,用一次少一次.对iterator进行变量,iterator就空了! >>> c = list(b) >>> c [1, 2, 3] >>> d = list(b) >>> d [] # 空的iterator并不等于None. >>> if b: ... print(1) ... 1 >>> if b == None: ... print(1) ... # 再来感受一下next() >>> e = iter(a) >>> next(e) #next()实际调用了__next__()方法,此后不再多说 1 >>> next(e) 2
生成器概念
只要 Python 函数的定义体中有 yield 关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。
函数返回一个值,而生成器函数生成一个值。不仅如此,函数返回值后就退出了,而生成器可以生产多次值,生成器函数中还能出现多个yield。
如何“生成”生成器
如何产生一个生成器呢,方法有二:
1.生成器函数(generator function):带有yield关键字的函数就是生成器函数,它的返回值是个生成器。
2.生成器表达式(generator expression):而形如(elem for elem in [1, 2, 3])的表达式,称为generator expression,也能产生一个生成器。
生成器表达式生成生成器:
>>> gen = ( i for i in [2,3,4,5,6]) >>> gen <generator object <genexpr> at 0x00000178D648D1A8> >>> for i in gen: ... print(i) ... 2 3 4 5 6
我们调用next()方法,也是可以的:
>>> gen = ( i for i in [2,3,4,5,6]) >>> next(gen) 2 >>> next(gen) 3 >>> next(gen) 4 >>> next(gen) 5 >>> next(gen) 6 >>> next(gen) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
这和我们上面迭代器(iterator)的使用一毛一样!
其实,生成器(generator)就是迭代器(iterator)的一种,以更优雅的方式实现的iterator,而且完全可以像使用iterator一样使用generator。当然除了定义,定义一个iterator,你需要分别实现_ _ iter _ _ ()方法和 _ _ next_ _()方法,但generator只需要一个yield关键字就可以。
生成器函数产生生成器
>>> def gen123(): ... yield 1 ... yield 2 ... yield 3 ... >>> gen123 <function gen123 at 0x00000178D6614D08> >>> gen123() <generator object gen123 at 0x00000178D648D1A8> >>> for i in gen123(): ... print(i) ... 1 2 3
这里你也能猜出来,我们调用next()方法除了会抛出异常,效果也一样:
>>> def gen123(): ... yield 1 ... yield 2 ... yield 3 ... >>> gen = gen123() >>> next(gen) 1 >>> next(gen) 2 >>> next(gen) 3 >>> next(gen) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
其实 ,for循环会每次隐时调用next()方法,前进到函数中的下一个yield语句处。
生成器函数执行过程
生成器另外一个黑科技是生成器中的yield语句会挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行。
我们来看代码:
>>> def genAB(): #❶ ... print('start') ... yield 'A' #❷ ... print('continue') ... yield 'B' #❸ ... print('end') #❹ ... >>> for i in genAB(): #❺ ... print('--->',i) #❻ ...
打印结果:
start ---> A continue ---> B end
❶ 定义生成器函数的方式与普通的函数无异,只不过要使用 yield 关键字。
❷ 在 for 循环中第一次隐式调用 next() 函数时(序号➎),会打印
‘start’,然后停在第一个 yield 语句,生成值 ‘A’。
❸ 在 for 循环中第二次隐式调用 next() 函数时,会打印
‘continue’,然后停在第二个 yield 语句,生成值 ‘B’。
❹ 第三次调用 next() 函数时,会打印 ‘end.’,然后到达函数定义体
的末尾,导致生成器对象抛出 StopIteration 异常。
❺ 迭代时,for 机制的作用与 g = iter(gen_AB()) 一样,用于获取
生成器对象,然后每次迭代时调用 next(g)。
❻ 循环块打印 –> 和 next(g) 返回的值。但是,生成器函数中的
print 函数输出结果之后才会看到这个输出。
❼ ‘start’ 是生成器函数定义体中 print(‘start’) 输出的结果。
❽ 生成器函数定义体中的 yield ‘A’ 语句会生成值 A,提供给 for 循
环使用,而 A 会赋值给变量 i,最终输出 –> A。
❾ 第二次调用 next(g),继续迭代,生成器函数定义体中的代码由
yield ‘A’ 前进到 yield ‘B’。文本 continue 是由生成器函数定义
体中的第二个 print 函数输出的。
❿ yield ‘B’ 语句生成值 B,提供给 for 循环使用,而 B 会赋值给变
量 c,所以循环打印出 –> B。
⓫ 第三次调用 next(it),继续迭代,前进到生成器函数的末尾。文本
end. 是由生成器函数定义体中的第三个 print 函数输出的。到达生成
器函数定义体的末尾时,生成器对象抛出 StopIteration 异常。for
机制会捕获异常,因此循环终止时没有报错。
相关文章推荐
- Python生成器(Generator)详解
- python中的yield生成器详解
- 举例详解Python中yield生成器的用法
- Python 中迭代器与生成器实例详解
- 详解Python3中yield生成器的用法
- Python生成器generator之next和send的运行流程(详解)
- Python的迭代器和生成器详解
- Python中生成器和迭代器的区别详解
- 详解Python中的生成器表达式(generator expression)
- Python生成器(Generator)详解
- 详解Python中yield生成器的用法
- python 迭代器与生成器 详解
- Python 生成器详解 generator
- 举例详解Python中yield生成器的用法
- Python 生成器原理详解
- python 迭代器与生成器 详解
- Python 生成器原理详解
- Python 迭代器与生成器实例详解
- python迭代器与生成器详解
- Python生成器(Generator)详解