在Python中for循环运行机制探究以及可迭代对象、迭代器详解中,我们结合对for循环机制的探究,深入分析了Python中可迭代对象、迭代器等概念。实际上,除此之外,Python中还有另一个很重要的概念生成器。
在了解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()
对于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表达式只能在定义的函数体中使用。
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()函数一次获取一个。
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.
- 在字面含义不明确的地方,推荐使用全名以避免歧义。
- 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
- 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
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]: 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表达式将使得该函数成为一个异步生成器。