一、背景介绍

生成器是什么呢?是生成器对象,前面所说的生成器就是生成器对象。英文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返回一个值