TOC

生成器函数

    生成器指的就是生成器对象,可以使用生成器表达式得到,也可以使用yield关键字得到一个生成器函数,调用这个函数得到一个生成器对象,同样的也是一个可迭代对象,也是一个迭代器,其一样有惰性求职、延迟计算的特性,同样也只能往前不能后退;
    生成器函数,即在函数中存在yield语句,只要有了yield语句,这个函数将不再是一个普通函数,一旦调用将立即返回一个生成器对象,这个函数内部的函数体不会立即执行,它等着调用者对他进行迭代,使用next语句可以驱动这个生成器对象的执行,每次执行当碰到yield语句为止,碰到yield语句之后会返回yield语句后面的值,并暂停当前函数执行,如果再次next这个生成器对象,将会继续上一次往下执行,碰到下一条yield为止,如果碰到return语句会终止当前函数执行,并且抛出StopIteration的异常;
    在之前,使用生成器表达式来创建一个生成器对象,但是它无法实现过于复杂的生成器,所以我们就可以利用yield来自己去创建一个生成器函数,它他们之间其实出了语法不一样,一个是生成器表达式,一个生成器函数,其他的任何特性都是一样的,因为他们都会得到一个生成器对象,当生成器迭代完成之后一样,也会抛出StopIteration;
    函数体当中,只要包含了yield语句,那么它就是生成器函数,调用后就会返回一个生成器对象,如下示例;
def inc():
    for i in range(2):
        yield i
i = inc()
print(type(i)) # <class 'generator'>
print(next(i)) # 0
print(next(i)) # 1
print(next(i)) # StopIteration
    可以看到我们上述到函数,在执行一个next(i)的时候,抛出一个0,但是这个函数没有使用return语句,这个0是怎么出来的呢,这就是yield语句的作用,yield语句并不能结束函数,但是yield语句有一个非常巨大的作用,可以在执行完成yield语句之后暂停当前函数执行;
    一般来讲,函数是有遇到return语句才会返回,也就是说没有return语句这个函数的执行是不会结束的,这是普通函数的执行,但是到了生成器这儿会发现,我们每次调用它,它返回的不是立即执行的结果,而是返回一个生成器对象,我们还需要遍历才能立即拿到这个里面的数据,所以我们往往得需要使用next函数来去驱动生成器函数,在执行的过程中一碰到yield语句就将yield后面的值抛出,然后就会停下来,等待下一次使用next语句驱动它,这就是它的好处;
生成器函数的执行
    通过下面的代码,我们可以清晰的证明上述说明的,每当遇到yield语句,将yield后面的值抛出之后,就会立即暂停生成器函数的继续执行;
    同时也可以通过下面的例子可以看出一个问题,生成器函数一旦遇到return语句就说明这个生成器函数就执行结束了,生成器走到头了,然后就会抛出StopIteration;
    因为当一个函数没有显示给定return语句的时候,其实Python默认就隐式给了一个return None,所以从这一点来看上面到代码,当i为1的时候,我们在此next(i),其实就会碰到一个return None,所以就会抛出StopIteration,这也就是抛出StopIteration的原因;
def gen():
    print("第一次")
    yield 1
    print("第二次")
    yield 2
    return 3
    print("第三次")
    yield 4
g=gen()
print(next(g))
# 第一次
# 1
print(next(g))
# 第二次
# 2
print(next(g))
# StopIteration
  • 重点总结:一个生成器函数,可以多次yield,但是生成器函数一旦碰到return语句相当于此生成器对象结束,就会立即抛出StopIteration异常,如果在return语句后面还有yield语句,也不会继续执行,在函数没有显示给定return语句的时候,默认会有一个return None,所以一般情况下在一个生成器函数不会显示写一个return语句;

生成器的应用

    无限循环和yield是一个非常好的结合,因为生成器会暂停函数的执行;
def gen():
    count = 0
    while True:
        count += 1
        yield count
g=gen()
print([next(g) for x in range(10)]) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

生成器传参

    yield生成器中提供了send方法,可以给生成器传递参数,在传递参数的同时也可以从生成器获取一条数据,也就是说它能和生成器交互的同时也具有了next方法,同时,当send方法传递一个None时,等价于next()方法;
    但是需要注意,在一个生成器对象没有执行next方法之前,由于没有yield语句被挂起,所以执行send方法会报错,所以,在调用send方法之前,还是先调用一次next方法为好;
def gen():
    count = 100
    while True:
        count += 1
        result = yield count
        if result is not None:
            print("收到send的值为:%s" % result)
            count = result # 如果收到参数,就重置count

g = gen()
print(next(g)) # 100
# 像生成器传参,同时从生成器获取一个值
print(g.send(0)) # 1
print(next(g)) # 2
print(next(g)) # 3

# 变种
def counter():
    def gen():
        count = 100
        while True:
            count += 1
            result = yield count
            if result is not None:
                print("收到send的值为:%s" % result)
                count = result

    g = gen()
    return lambda x=False: g.send(0) if x else next(g)
c=counter()
print(c()) # 101
print(c(True))
# 收到send的值为:0
# 1
print(c()) # 2

协程(coroutine)

    协程是生成器的一种高级实现,它比进程、线程轻量级,是在用户空间调度函数的一种实现,python3的asyncio就是协程的实现,已经加入到标准库当中,使用async、awiait关键字直接原生支持协程,协程的调度思路是,有两个生成器A、B,next(A),后A执行到来yield语句暂停 ,然后去执行next(B),B执行到yield语句也暂停,然后再次调用next(A),再调用next(B)周而复始,就实现来调度效果;
    yield语句还可以实现协程,在Python领域被广泛应用,协程类似进程线程一样概念的东西,但是它又不属于操作系统的内容,而是后来提出的一个内容,它实际上还是依赖于进程和线程的, 但是协程的概念提出来之后,就可以不使用多线程了,因为多线程很多问题,这就是线程带来的很多问题,我们没法解决了,或者解决的时候代价很高这个时候就发展出了协程,协程就是使用yield实现的;
    yield的特性就是暂停当前函数执行,所以这种特性,就可以很好的利用在网络编程上了,因为线程进程本身的原理就是交替的使用CPU,没有什么真实的并行之说,正因为yield语句可以暂停当前函数的特性,所以,我们可以首先执行一个生成函数,然后执行第二个生成器函数,交替执行,从而达到多线程的效果;

yield from

    在python3.x之后,提供了一种新的语法,yield from,可以从一个可迭代对象里返回一个以yield生成器的形式一个一个的返回可迭代对象内部的元素;
def gen():
    yield from range(5)
g=gen()
print(next(g)) # 0
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注