一、背景介绍
生成器是什么呢?是生成器对象,前面所说的生成器就是生成器对象。英文generator。它可以由两种方式得到,一种方式是生成器表达式,还有一种方式是生成器函数。
那什么是生成器函数呢?生成器函数就是一个函数体内包含了yield语句,就算生成器函数。判断条件其实很简单,只要有yield语句出现,你就认为它是生成器函数就可以了。
生成器函数在调用的时候,不在直接返回调用结果,它返回的是生成器对象。
生成器对象是一个可迭代对象,是一个特殊的迭代器。生成器对象最大的特点在于,延迟计算、惰性求值。
二、原理介绍
首先看代码
m=(i for i in range(5))
怎么理解这句代码?
首先这是一个生成器表达式,因为它是表达式而且是赋值语句,右侧的表达式会
立即执行,执行完的结果是什么呢?
m这个标识符记住了这个结果,m就是指代的是生成器表达式所生成的生成器
对象。生成器又是一个特殊的可迭代对象,是特殊的迭代器,既然是跌打器
就可以用next访问了。这是第一种方式
我们继续来看第二种方式来完成生成器对象的创建
def foo():
for i in range(5):
yield i
foo()
这个函数执行完,不会立即返回一个结果,而是返回一个生成器对象,在 内存中
的位置,这个就是生成器函数,看见yield 就算生成器函数
注意:
next(foo())
next(foo())
这两次都输出0,因为这是两次函数调用,返回了两个生成器对象如果想输出1 ,
就需要一个变量去接收这个foo函数的地址,在用next去驱动这个变量就可以
达到拨动这个值的效果了。如下
n=foo()
n变为生成器对象
next(n)
next(n)
这样会输出 0 1
生成器对象走完就结束,在访问就报错
python提供了两种方案,一种是使用生成器表达式来简单构造生成器对象。一种是使用函数来构造一个非常复杂的生成器对象,我们根据需要去选择那个方式。
三、生成器函数的执行
我们要搞明白生成器函数是怎么执行的,我们直接来看下面的代码
def foo2():
print(1)
yield 2
print(3)
yield 4
print(5)
yield 6
print(7)
yield 8
g=foo()
print(next(g)) 这个第一次执行打印什么返回什么?
打印1 返回2。
print(next(g))
打印3 返回4
其实执行过程就是碰到yield就返回,然后下次执行的时候,继续从上次yield退出
的位置继续执行。一直到这个生成器都访问完成
执行到return语句会报异常,return 的7 是拿不到的,只要next语句碰到return
语句就是这样的。
def foo():
for i in range(5):
yield i
return None
这个没报异常是因为,最后有return None,这个是隐藏返回的
碰到return这个迭代器就结束了
next(g,'end') 加了end以后就不会报这个stopiteration这个异常了
在继续看一段代码
def foo1():
print(111)
yield 'abc'
print(222)
yield
g=foo1()
next(g)
输出 111
‘abc’
next(g) 问题在于,第二次使用next,会输出什么?
自己认为只会打印 222
这是对的,但是yield要注意,它本质上是 yield None 返回了一个None.
yield None和return None是两回事,
执行到yield的时候,只是暂停函数的执行,并不是结束函数执行。
return是结束函数执行,要注意区分
四、生成器的应用
1.计数器的实现
def counter():
i=0
while True:
i+=1
yield i
c=counter()
next(c)
这样就可以完成一个计数器了
能不能在把这段代码变得更复杂一点呢?让你调用一个函数,调用一次就加1一次
c=counter1()
print(c())
print(c())
要求这两次输出一次是1 第二次输出2,这是进行了两次函数调用。
要注意生成器对象,不是可调用对象,生成器对象后面是不加括号的
那就说明,我们要实现这个功能,说明返回的是一个函数,然后我现
在又想要这个计数的能力。
def counter1():
def inc():
i=0
while True:
i+=1
yield i
return inc
问题:下面这样写会输出什么?
c=counter1()
print(c())
print(c())
解释:
首先 c=counter1() ,这个c就拿到了inc的函数地址,所以c就是inc了,
print(c())
print(c())
后面的这两次是,进行了两次函数调用,生成了两个生成器对象。
代码写成这样仍然,没用完成功能,下面该怎么修改代码?
def counter2():
def inc():
i=0
while True:
i+=1
yield i
c=inc()
return next(c)
接下来,我在函数内部加一个 c=inc() ,return 返回的是next(c),这样就能
完成通过函数调用进行计数了
g=counter2()
print(g)
print(g)
这两个语句输出什么?
自己认为第一次print(g)会输出 1 第二次print(g)会输出 2
自己的分析是不对的,这两次输出的都是1,为什么?因为这里函数调用
值进行了一次,g=counter2(),只进行了一次函数调用,所以两次返回
的都是return next(c),这个只调用了一次。
现在依然没用增加,该怎么办呢?
继续看下面代码
def counter3():
def inc():
i=0
while True:
i+=1
yield i
c=inc()
def fn():
return next(c)
return fn
foo2=counter3()
print(foo2())
print(foo2())
这次输出什么?foo2接收的是什么?
foo2接收了fn函数的返回值是fn函数的地址,所以foo()相当于在对fn函数进行
调用,两次调用相当于
进行了两次next(c)
输出 1
2
这里就是用到了闭包技术,fn内部用到了外部函数变量 就是这个next(c)
执行过程:
c=inc(),生成器对象给了c,定义一个新函数,新函数返回的是fn函数地址。
foo2=counter3(),这个执行完,按道理c和inc都应该消亡,但是由于闭包
技术的存在,这连个标识符指向的空间被保存下来了。
我们发现这里有单行函数,我们可以改造成匿名函数
def fn():
return next(c)
return fn
return lambda :next(c)
进行修改---------------------------
def counter4():
def inc():
i=0
while True:
i+=1
yield i
c=inc()
return lambda :next(c)
f=counter4()
print(f())
print(f())
print(f())
输出 1 2 3 这样就能想调用函数一样,使用计数器
原理:
对于同一个生成器对象,next第二次,就依次执行输出2.因为第一次运行的结果
还保存在内存中因为闭包的存在,第一次print(f())执行完,c=inc()这里指向
的内存空间已经保存下来了,所以第二次print(f()),执行到next的时候,
依然会在上次执行结束的地方继续执行,所以才会输出2
2.斐波拉契数列生成器版
def fib():
a=0
b=1
yield b
while True:
a,b=b,a+b
yield b
f=fib()
for i in range(5):
print(i+1,next(f))
3.生成器的交互
def counter():
def inc():
i=0
while True:
i+=1
yield i
c=inc()
def fn():
return next(c)
return fn
f3=counter
g=f3()
这里我们想做一个什么事情呢,想让我们在做的过程这,能做到重启.
我们想让他有一个交互过程,我们先举一个例子:
bar 函数中有yield说明是生成器函数,此生成器函数运行后,是拿到了生成器对象
def bar():
for i in range(5):
x=yield 100
print(x,'+++')
g=bar()
输出结果:
print(next(g))
100
执行1次以后都是 100 看不到后面的字符'+++'
这个函数执行过程是,for 进入后,执行到yield 100 就暂停了,前面的x=
并未执行。所以说这个赋值语句只执行了一半就暂停了x=这里没运行
print(next(g))
然后这个第二次执行next(g)输出 None +++ ,x为什么输出None,因为上次在
执行的时候x赋值语言没执行完就碰到 yield了,然后这次执行的时候,在
yield后继续执行,说白了就是把x= 这个赋值语句跳过了。
next函数只能驱动迭代器
g.send(1) 这个是什么意思呢?
send()函数两个作用,驱动,send的实参作为yield语句的表达式的返回值
send里的参数,可以做作为yield的返回值
什么意思呢?
就是说这个g.send(1)里面的参数1 可以给 x=yield 100。给yield前面的
x赋值,让他不会没用值这种情况。这样就形成了一种交互。
yield可以给我返回值,同样的我也可以通过send(),给这个迭代器中的
yield返回一个值