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.
使用这个装饰器时doproc是什么时候执行的?
答案 0 :(得分:12)
yield
表达式将控制权返回给使用生成器的任何内容。此时生成器暂停,这意味着@contextmanager
装饰器知道代码是使用 setup 部分完成的。
换句话说,您要在上下文管理器__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__
方法退出,一切都已完成。
答案 1 :(得分:1)
@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
内部 contextlib.contextmanager 调用这些 __ enter __ 和 __ exit __ 魔术功能,如@Martijun Pieters所述。希望这会有所帮助!