Shu*_*man 13 python yield generator contextmanager
import contextlib
import time
@contextlib.contextmanager
def time_print(task_name):
t = time.time()
try:
yield
finally:
print task_name, "took", time.time() - t, "seconds."
def doproc():
x=1+1
with time_print("processes"):
[doproc() for _ in range(500)]
# processes took 15.236166954 seconds.
Run Code Online (Sandbox Code Playgroud)
什么时候doproc在使用这个装饰器时被执行?
Mar*_*ers 16
yield表达式将控制权返回给使用生成器的任何内容.生成器此时暂停,这意味着@contextmanager装饰器知道代码是通过设置部分完成的.
换句话说,您在上下文管理器__enter__阶段要做的所有事情都必须在之前进行yield.
一旦你的上下文退出(所以with语句下的块完成),@contextmanager就会调用装饰器来完成__exit__上下文管理器协议的一部分,并且会做以下两件事之一:
如果没有例外,它将恢复你的发电机.因此,您的发电机在该yield线路上取消暂停,您进入清理阶段,该部分
如果存在异常,则装饰器用于generator.throw()在生成器中引发该异常.这就好像这yield条线引起了那个例外.因为你有一个finally子句,它会在你的生成器退出之前执行,因为异常.
因此,在您的具体示例中,序列如下:
with time_print("processes"):
这将创建上下文管理器并对其进行调用__enter__.
生成器开始执行,t = time.time()运行.
该yield表达式暂停发电机,控制返回到装饰.这取得了所产生的一切,并将其返回到with声明中,以防有as target一部分.这None是屈服的(只有一个简单的yield表达).
[doproc() for _ in range(500)] 运行并完成.
__exit__运行上下文管理器方法,不传递任何异常.
装饰者恢复发电机,它从它停止的地方继续.
finally:输入并print task_name, "took", time.time() - t, "seconds."执行该块.
生成器退出,装饰器__exit__方法退出,一切都完成了.
@Martijn Pieters 的精彩解释。由于在您的情况下yield是多余的,您可以通过创建自己的上下文管理器(没有yield和contextlib.contextmanager)来实现相同的目的。这更简单,更易读。因此,在您的情况下,您可以实现以下内容。
import time
class time_print(object):
def __init__(self, task_name):
self.task_name = task_name
def __enter__(self):
self.t = time.time()
def __exit__(self):
print self.task_name, "took", time.time() - self.t, "seconds."
def doproc():
x = 1 + 1
with time_print("processes"):
# __enter__ is called
[doproc() for _ in range(500)]
# __exit__ is called
Run Code Online (Sandbox Code Playgroud)
正如@Martijun Pieters 所解释的,在内部contextlib.contextmanager调用这些进入和退出魔术函数。希望这可以帮助!