Python生成器函数的定义,使用,方法,实例,(yield、yield from)

本位目标人群是拥有Python基础的开发者,主要目的是用通俗的语言讲Python生成器函数的特性,方法,和使用。

文章目录

  • 前言
  • 一、生成器是什么?
  • 二、生成器的各种方法和关键字
    • 1.yield, yield from, next
    • 2.send, close, throw
  • 三、使用生成器及实例
  • 总结


前言

生成器是Python高级编程中重要的知识点,我们平常调用的库其中有不少生成器的使用,但我们自己很少制作他,今天我们来自己写一个生成器并了解其工作原理。


以下是本篇文章正文内容,下面案例可供参考

一、生成器是什么?

定义:1,生成器也是一种迭代器,但是你只能对其迭代一次   。

            2,这是因为它们并没有把所有的值存在内存中,而是在运行时生成值。你通过遍历        来使用它们,要么用一个“for”循环,要么将它们传递给任意可以进行迭代的函数和结构。大多数时候生成器是以函数来实现的。然而,它们并不返回一个值,而是yield(暂且译作“生出”)一个值。

我们来用人话解释一下这两条:

1,迭代器大家都非常熟悉了,在这里不过多赘述了,生成器是迭代器的一种,是一个可迭代的对象,但是我们只能迭代一次,也就是我们在for循环迭代的时候,每次只能够得到一个值。

2,因为生成器没有把所有值存在内存中,而是不断生成,所以它相比普通的迭代器和循环存储在列表更省内存。

通过解释我们知道,生成器是迭代器的细分,它跟迭代器有一样的迭代方法,也更省内存。我们来通过一个简单的对比来理解一下;

首先看这个例子:

# 如果我们要打印1-10这样的数据,用最常用的方法是使用range函数,range函数相当于生成了一个内容为1-10的列表
# 但是如果1-1000000呢?随着极限的增大,range生成的列表占用内存越来越大,而且这个列表的长度是不确定的
for i in range(1, 11):
    print(i)

这样来打印1-10实际上就是创建了一个元素为1-10的列表,所以如果我们要取的极限太大,那列表就会很长很长,就非常的占内存。

而通过生成器就可以解决内存的问题:

# 只有当我们调用next()方法时,才会真正的从内存中取出数据,并且这个数据只能被取出一次
def get_num():
    num = 0
    while num < 10:
        num += 1
        yield num

for g in get_num():
    print(g)

 因为生成器每次遇到yield只产生一个值,所以它一边运算一边产生一个值,每次都覆盖,这样内存占用是不是大大减少了呢?

大家看到了关键字yield和我们用for循环迭代这个生成器函数,以及注释里的next方法,那这是啥东西呢,咱们在下个板块看。

二、生成器的各种方法

1.yield, next()

return我们知道是返回一个值然后这个函数的运行就结束了,我们可以吧yield想象成一个return,但是不是让程序结束,而是返回一个值之后暂停。

首先咱们连着上文的例子,:

 def get_num():
    num = 0
    while num < 10:
        num += 1
        return num

print(get_num())

输出:1 

没错,这个函数我们在进入循环后:一,num加上了1,此时num=1 。二,return出来了num,函数结束。所以我们打印函数的返回值就是一个1

如果我们稍微改一下:

# 只有当我们调用next()方法时,才会真正的从内存中取出数据,并且这个数据只能被取出一次
def get_num():
    num = 0
    while num < 10:
        print('循环')
        num += 1
        yield num

g = get_num()
print(next(g))

输出:循环

            1

我们发现,这个改成yield出来值之后就是一个生成器函数,这个生成器我们把它给了g这个变量,并且调用了next方法,输出了next方法的返回值,而这个返回值刚好就是第一次循环里的num。之后程序结束

由此我们可以得知,生成器遇到yield时就会返回出一个值并且暂停函数运行,如果我们调用了next则yield后面的值就传递给了next方法,变成了next方法的返回值。之后yield这一句话就可以跳过了,回到了循环顶部,但暂停状态没有解除。

如果我们调用两次next方法:

def get_num():
    num = 0
    while num < 10:
        print('循环')
        num += 1
        yield num

g = get_num()
print(next(g))
print(next(g))

输出: 循环
             1
             循环
             2

我们发现在第二次next调用后,第二次循环才执行,同样遇到了yield,暂停函数,返回值,回到循环顶部........由此可见,next的作用就是先解除暂停,把yield这个值返回出来之后跳出,next的工作已经结束了,此时函数在yield的后一句也就是循环顶部暂停着,当下次next调用时就会解除暂停,然后返回yield,然后next工作结束程序跳过yield......

这就是next的作用,在刚刚暂停的地方开始执行,并返回yield的值

这里引用一句别的大佬「冯爽朗」的解释:

到这里你可能就明白yield和return的关系和区别了,带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,所以调用next的时候,生成器并不会从函数的开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。

接下来还有一个知识点,就是yield from,这个一般就是在生成器里调用别的生成器或自己(递归),并且yield出别的生成器里yield的值。是有点绕哦,换个理解的办法,你就吧yield from当成:

for item in get_num():
    yield item

这个知识点的应用我们放在最后一个板块来看。

2.send,close,throw

send方法包含了next方法,向生成器发送一个值之后就恢复运行,返回值就是yield的值。

def get_num():
    num = 0
    while num < 10:
        num += 1
        var = yield num
        print(var)

g = get_num()
print(next(g))
print(g.send(1))

输出: 1
             1
             2

此时第一个1是next函数获得的yield返回值,但是yield给变量啥都没啊,所以现在var是None,之后跳过yield这行到了下面一行前暂停着,这个时候send来了,发了个1给var,然后执行了next,这就是第二个1,这个1是send发的,在生成器内部执行了,这个2就是send包含的next的返回结果,也是send的返回值。

接下来是close,next在遇到StopIteration异常就会停止迭代,StopIteration异常会在生成器函数体运行完毕后或者close之后抛出,close会在函数暂停的地方抛出GeneratorExit异常

GeneratorExit 异常的产生意味着生成器对象的生命周期已经结束,因此生成器方法后续语句中不能再有 yield,否则会产生 RuntimeError

def get_num():
    num = 0
    while num < 10:
        num += 1
        yield num

g = get_num()
print(next(g))
g.close()
print(next(g))

对已经关闭的生成器对象使用 next 会抛出 StopIteration 异常。

输出:1
Traceback (most recent call last):
  File "/Users/ruiyang/PycharmProjects/AnacondaProject_2/test2.py", line 11, in
    print(next(g))
StopIteration 

接下来是throw,throw是在生成器暂停的地方抛出一个异常,并且返回下一个yield的返回值 

def get_num():
    num = 0
    while num < 10:
        try:
            num += 1
            yield num
        except ZeroDivisionError:
            print("ZeroDivisionError")

g = get_num()
print(next(g))
print(g.throw(ZeroDivisionError))
print(next(g))

输出: 1
             ZeroDivisionError
             2
             3 

我们可以看到程序捕获到了一场,并且返回了下一个yield的值 

如果大家想看更详细的方法介绍移步此文:Python 生成器与它的 send,throw,close 方法_团子大圆帅的博客-CSDN博客

三、 使用生成器及示例

讲了这么多我们也该说一下构建生成器之后该如何使用了,首先生成器是一个可迭代对象,也是一种迭代器,所以我们用迭代的方式来使用生成器:

  1. for循环遍历,for循环在迭代的时候底层在不断的调用next函数
  2. 使用一些python内置的可以放入迭代对象的函数:例如list,enumerate..........

 这也就是文章开头是用for循环的原因:

# 只有当我们调用next()方法时,才会真正的从内存中取出数据,并且这个数据只能被取出一次
def get_num():
    num = 0
    while num < 10:
        num += 1
        yield num

for g in get_num():
    print(g)

接下来是一些实例,首先我们从简单的来说,我们可以用生成器来求斐波那契数列,非常pythonic且不会占很多内存:
 

def fibo():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

for f in fibo():
    print(f)
    if f > 100:
        f.close()
        break

你没看错函数内部就是while True,我们在外部close结束掉生成器的生命周期并且停止迭代就好了。如果不想外部这么多,我们在内部修改while循环条件即可。

接下来就是递归yield的用处了,这里面用到了上文说的yield from

加入我们有一个嵌套非常非常多的列表,我们用现在网上传的普通的递归会发现如果列表长度大于N(N>3)列表就不能正常解开了,因为return的递归只能返回一次值,其中逻辑非常复杂,自己改的话也非常难理解,这个时候,生成器就完美解决了这个问题:

# 多层嵌套的列表
num_list = [1, [2, [3, 'end'], 5], 6]


# 展开嵌套列表的生成器函数(递归)
def flatten(nested_list):
    for item in nested_list:
        if isinstance(item, list):
            # yield from flatten(item)就相当于for item in flatten(item): yield item
            yield from flatten(item)
        else:
            # 每次遇到yield函数就会返回一个值,并且暂停执行后面的代码,当函数外部调用next()函数时,就会从yield函数中恢复执行
            yield item

# 遍历生成器函数时for循环底层在不断的调用next()函数,直到遇到StopIteration异常,list构造函数可以将所有可迭代对象转换成list
print(list(flatten(num_list)))
# 如果当调用next方法时生成器函数结束(遇到空的return语句或是到达函数体末尾),则这次next方法的调用将抛出StopIteration异常(即for循环的终止条件)
# 如果调用next方法时生成器函数没有结束,则这次next方法的调用将返回遇到的yield的值,并且恢复生成器继续运行(即for循环的迭代条件)
for f in flatten(num_list):
    print(f)

输出: [1, 2, 3, 'end', 5, 6]
             1
             2
             3
             end
             5
             6

列表完美解开,各种类型都可以。

其中的逻辑就是如果不是列表就直接yield出这个值,如果是列表就调用自己,遍历列表,然后yield出列表里的值,以此类推,希望大家能够好好阅读一下代码并加以思考。 


总结

生成器python高级编程中重要的知识点,我们运用好生成器可以解决很多内存占用啊,等等的问题,希望如果阅读了感觉文章有用就请点个赞,有不足的地方也请指出。

你可能感兴趣的:(python,开发语言)