文章目录
在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表达式将使得该函数成为一个异步生成器。