Python函数学习-生成器函数

python

Python3函数学习

    本篇是对 Python3函数中生成器函数的学习与小结,用于复习巩固使用,如有理解偏差的地方,还望各位大佬指正。


生成器


生成器定义

生成器:generator
  • 生成器一般指的是生成器对象generator,是一种特殊的迭代器。
  • 可以由生成器表达式得到。
  • 可以使用yield关键字得到一个生成器函数,再通过调用这个函数得到一个生成器对象
  • 生成器(对象)可以使用next方法

注:本篇只讨论生成器函数。


生成器函数

生成器函数定义
  • 函数体中只要包含yield语句的函数,就是生成器函数,调用后返回生成器对象。
  • 即使return语句执行在yield语句之前,也是同样是生成器函数,调用后返回的仍然是生成器对象,只不过可迭代深度为零。
  • 生成器对象,是一个可迭代对象,也是一个迭代器。
  • 生成器对象,是延迟计算、惰性求值的。
  • 生成器对象可以使用next()方法,逐次输出,不可回头。
  • yield语句中断函数的特性,可以用于实现协程。

生成器函数实现

  • 在函数中加入yield语句,该函数就转变为生成器函数,通过调用该生成器函数,可以得到一个生成器对象,这个对象类型为generator,即生成器。生成器对象可以通过for循环遍历,也可以通过next()方法逐个遍历,超限会报错。
def func_gen(n=1):
    for i in range(n):
        yield i 
gen = func_gen()
print(gen)
print(type(gen))
for i in gen:
    print(i)



0
1
2
3
4

    使用next()方法,逐次读取。

def func_gen(n=1):
    if n == 1:
        return 4096
    for i in range(n):
        yield i 
    return 5
gen = func_gen()
print(gen)
print(type(gen))
print(next(gen))
print(next(gen))
print(next(gen))


0
1
2

注意:

  • next()方法超限会直接报错,如果不是有特别需求,一般推荐使用for循环遍历。
  • 生成器对象只能逐次遍历一遍,不能再回头读取。

生成器函数执行逻辑

  • 同一个生成器函数中yield语句可以有多个。可以这么理解:for i in range(5): yield i ,这样的循环体实质是执行了5次yield语句。
  • 遍历生成器函数对象时,函数执行到yield语句会中断执行,并返回yield表达式的值。
  • 再次遍历该对象时,函数从上一个yield断点位置继续执行,并执行到下一个yield语句。
  • 重复上一步,直到函数正常结束,或者遇到return语句。
  • return语句依然可以终止函数运行,但return语句的返回值不能被获取到。
  • return会导致当前函数返回,无法继续执行。生成器对象至此已经没有可迭代深度。
  • 继续使用next()方法,抛出StopIteration异常。
  • 如果函数没有显式的return语句,如果生成器函数执行到结尾(相当于执行了return None),一样会抛出StopIteration异常。
def gen():
    print('line 1')
    yield 1
    print('line 2')
    yield 2
    print('line 3')
    return 3
    yield 4

  • 如果直接用next()方法调用生成器函数本身,那么每次都会重新生成一个生成器对象,也就无法对某一个生成器对象持续向下遍历。
next(gen()) # line 1
next(gen()) # line 1
line 1
1
line 1
1   

  • 因此需要使用一个变量接收gen()返回的生成器对象。
g = gen()
print(g)


  • 用next方法,逐次遍历该生成器对象,可以正常输出。
print(next(g)) # line 1
print(next(g)) # line 2
line 1
1
line 2
2

  • 但是如果再继续用next()遍历,解释器会报错,StopIteration ,说明遍历超限了,函数已经在 return 3 处返回,不会执行到之后的yield
print(next(g)) # StopIteration
line 3

---------------------------------------------------------------------------

    StopIteration                             Traceback (most recent call last)
     in 
    ----> 1 print(next(g)) # StopIteration
    
    StopIteration: 3
    
    StopIteration: 3

生成器函数小结

  1. 包含yield语句的生成器函数调用生成 生成器对象的时候,生成器函数的函数体不会立即执行
  2. next(generator) 会从函数的当前位置向后执行到之后碰到的第一个yield语句,会中断函数执行,并返回yield表达式的值。
  3. 再次调用next函数,从上一次的中断点恢复执行,在重复2,3过程。
  4. 继续调用next函数,生成器函数如果结束执行了(显式或隐式调用了return语句),会抛出StopIteration异常。

生成器函数应用

生成器函数特性:
包含yield语句的生成器函数调用生成 生成器对象的时候,生成器函数的函数体 不会立即执行
  • 根据以上特性,生成器函数可以作为中断循环体的执行的隔断,每次调用生成器对象时,函数循环体才会继续运行一次。

  1. 无限循环:
def counter():
	i = 0
	while True:
		i += 1
		yield i
def inc(c):
	return next(c)
c = counter()
print(inc(c))

  1. 计数器:
def inc():
	def counter():
		i = 0
		while True:
			i += 1
			yield i
	c = counter()
	return lambda : next(c)
foo = inc()
print(foo()) # 1
print(foo()) # 2
print(foo()) # 3
1
2
3

  1. 处理递归问题:
  • 原本的死循环递归,会引起解释器超过最大递归深度报错,但是插入yield语句之后,循环体被卡主了,被生成器对象调用一次才函数循环体会执行一次。
def fib():
	x = 0
	y = 1
	while True:
		yield y
		x, y = y, x+y
foo = fib()
for _ in range(5):
	print(next(foo))	
1
1
2
3
5

协程coroutine
  • 生成器的高级用法
  • 比进程、线程轻量级
  • 是在用户空间调度函数的一种实现
  • Python3 asyncio就是协程实现,已经加入到标准库
  • Python3.5 使用async、await关键字直接原生支持协程
  • 协程调度器实现思路
    • 有2个生成器A、B
    • next(A)后,A执行到了yield语句暂停,然后去执行next(B),B执行到yield语句也暂停,然后再次调用next(A),再调用next(B)在,周而复始,就实现了调度的效果
    • 可以引入调度的策略来实现切换的方式
  • 协程是一种非抢占式调度,而这一点就从根本上避免了多线程的致命问题(加锁,死锁等问题)。

生成器函数语法糖-yield from

yield from
  • yield from是Python 3.3出现的新的语法
  • yield from iterable 是 for item in iterable: yield item 形式的语法糖
def inc():
	for x in range(1000):
		yield x

def inc(): 
	yield from range(1000)  ## 解释器优化的语法糖


你可能感兴趣的:(Python学习)