Python中的yield关键字及表达式、生成器、生成器迭代器、生成器表达式详解

Python中for循环运行机制探究以及可迭代对象、迭代器详解中,我们结合对for循环机制的探究,深入分析了Python中可迭代对象、迭代器等概念。实际上,除此之外,Python中还有另一个很重要的概念生成器

1. yield关键字及表达式、生成器、生成器迭代器、生成器表达式

1.1 yield关键字及表达式(yield expression)

1.1.1 yield关键字

在了解yield表达式之前,先了解一下这个关键字。Python官方文档中对其定义为:

  • Each yield temporarily suspends processing, remembering the location execution state (including local variables and pending try-statements).
  • 每一个yield会暂时挂起当前处理,记录当前位置的执行状态(包括局部变量等)。
  • When the generator iterator resumes, it picks up where it left off (in contrast to functions which start fresh on every invocation).
  • 当生成器迭代器重新执行,yield将会从其上次挂起之处继续执行,而不是像普通函数一样每调用一次都重新开始执行。

下面代码演示了yield关键字的作用:

def create_fibonacci(num_of_sequence):
    current_num, next_num = 0, 1
    current_index = 0
    while current_index < num_of_sequence:
        # 程序执行到此处时,会:
        # 1.返回current_num的值;
        # 2.保存当前的状态,包括所有局部变量的状态
        # 3.下次执行时,从yield current_num的下一句开始执行
        yield current_num
        current_num, next_num = next_num, current_num + next_num
        current_index += 1


def main():
    fib_generator_iterator = create_fibonacci(8)
    # 通过next()函数一次获取一个生成器迭代器中的值
    print(next(fib_generator_iterator))
    print(next(fib_generator_iterator))
    print(next(fib_generator_iterator))
    print(next(fib_generator_iterator))
    print(next(fib_generator_iterator))
    print(next(fib_generator_iterator))


if __name__ == '__main__':
    main()


1.1.2 yield表达式

对于yield表达式,实际上,因为yield作为关键字并不能单独使用,所以该关键字总是以表达式的形式出现在代码中,如上述代码第9行的yield current_num就是一个yield表达式。

具体地,在Python官方文档中,关于yield表达式,有:

  • The yield expression is used when defining a generator function or an asynchronous generator function and thus can only be used in the body of a function definition.
  • yield表达式在定义生成器函数或异步生成器函数时使用,因此,yield表达式只能在定义的函数体中使用。

1.2 生成器(generator)

Python官方文档中,对于生成器的定义为:

  • A function which returns a generator iterator.
  • 生成器是一个可以返回生成器迭代器的函数
  • It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.
  • 生成器看来像一个普通函数,但生成器和普通函数的区别在于,生成器中包含yield表达式,该yield表达式可以生成一系列可用于for循环的值,这些值还可以通过next()函数一次获取一个。

1.3 生成器迭代器(generator iterator)

Python官方文档中,对于生成器迭代器的定义为:

  • An object created by a generator function.
  • 生成器迭代器是一个由生成器函数创建的对象

实际上,生成器迭代器是一种特殊的迭代器,这一点从其名字中即可以印证:即用生成器来修饰迭代器得到生成器迭代器的名字。故所有接受迭代器的地方也都支持生成器迭代器,如for循环,list()tuple()等处。

为了避免名词混淆,Python中对于生成器一词generator还做了如下解释说明:

  • Usually refers to a generator function, but may refer to a generator iterator in some contexts.
  • 生成器一词generator一般指的是生成器函数,但是在一些上下文中也可能指生成器迭代器。
  • In cases where the intended meaning isn’t clear, using the full terms avoids ambiguity.
  • 在字面含义不明确的地方,推荐使用全名以避免歧义。

1.3.1 __next__魔法方法

  • This method is normally called implicitly, e.g. by a for loop, or by the built-in next() function.
  • 该方法通常被隐式调用,如:被for循环调用,或被內置函数next()调用。
  • Starts the execution of a generator function or resumes it at the last executed yield expression.
  • 调用该函数将启动生成器函数开始执行或者接着从上一次执行的yield表达式处开始执行。
  • When a generator function is resumed with a __next__ method, the current yield expression always evaluates to None.
  • 当一个生成器函数由__next__方法重启后,当前的yield表达式返回的值总是None
  • The execution then continues to the next yield expression, where the generator is suspended again, and the value of the expression_list is returned to __next__’s caller.
  • 然后程序继续执行至下一个yield表达式,程序在此处又一次被暂停,且expression_list的值被返回至__next__的调用者。
  • If the generator exits without yielding another value, a StopIteration exception is raised.
  • 如果生成器未能返回另一个值而退出,则程序抛出StopIteration异常。

关于上述说明,如下列代码验证:

def create_fibonacci(num_of_sequence):
    current_num, next_num = 0, 1
    current_index = 0
    while current_index < num_of_sequence:

        ret = yield current_num
        print(">>>>>ret>>>>>", ret)
        current_num, next_num = next_num, current_num + next_num
        current_index += 1


def main():
    fib_generator_iterator = create_fibonacci(8)
    print(next(fib_generator_iterator))
    print(next(fib_generator_iterator))


if __name__ == '__main__':
    main()

其运行结果为:

0
>>>>>ret>>>>> None
1

1.3.2 send(value)方法

  • Resumes the execution and “sends” a value into the generator function.
  • 该方法恢复程序执行并且“发送”一个值进入生成器函数中。
  • The value argument becomes the result of the current yield expression.
  • value形参会成为当前yield表达式的结果。
  • The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value.
  • send()方法返回生成器产生的下一个值,或者当生成器因无法产生新值而退出时抛出StopIteration异常。
  • When send() is called to start the generator, it must be called with None as the argument, because there is no yield expression that could receive the value.
  • 当调用send()方法启动生成器时,value参数必须传入None,因为可接收该值的yield表达式。

关于上述说明,如下列代码验证:

def create_fibonacci(num_of_sequence):
    current_num, next_num = 0, 1
    current_index = 0
    while current_index < num_of_sequence:
        ret = yield current_num
        print(">>>>>>>>ret>>>>>>>>", ret)
        current_num, next_num = next_num, current_num + next_num
        current_index += 1


def generator_send():
    generator = create_fibonacci(10)
    print(generator.send(None))
    print(generator.send("CodingGuru"))



def main():
    generator_send()


if __name__ == '__main__':
    main()

其运行结果为:

0
>>>>>>>>ret>>>>>>>> CodingGuru
1

1.4 生成器表达式(generator expression)

Python官方文档中,对于生成器表达式的定义为:

  • An expression that returns an iterator.
  • 生成器表达式是返回一个迭代器的表达式。

实际上,生成器表达式和列表生成式的格式类似,如下所示:

In [1]: [i * i for i in range(10)]
Out[1]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

In [2]: (i * i for i in range(10))
Out[2]: <generator object <genexpr> at 0x7f9916b04fc0>

即,生成器表达式和列表推导式看起来的区别仅在于前者将后者的[expression]换成了(expression)。但实际上,二者更大的区别类似于在Python中for循环运行机制探究以及可迭代对象、迭代器详解中提及的两种数据生成策略上,也就是说:

  • 列表推导式一次性产生所有需要的数据,并用一个列表存储,内存的开销较大;
  • 生成器推导式只是给出了数据生成的方式,仅在需要时逐个生成,使用结束后会很快被回收,内存开销小得多。

例如,如果想要求1到10平方的和,使用生成器表达式和列表推导式都可以,如:

In [3]: sum(i * i for i in range(10))
Out[3]: 285

In [4]: sum([i * i for i in range(10)])
Out[4]: 285

但是当增加range的上界(如在我的8GB内存8核CPU的设备上增加至100,000,000),列表推导式方法将很快因内存占用过大导致计算机无法运行出结果,而生成器表达式却可以承受更大的上界。

  • Using a yield expression in a function’s body causes that function to be a generator, and using it in an async def function’s body causes that coroutine function to be an asynchronous generator.
  • 在函数体中使用yield表达式将使得该函数成为一个生成器,而在用关键字async定义的异步函数体重使用yield表达式将使得该函数成为一个异步生成器。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值