在上下文管理器中,没有值的收益是什么

时间:2016-02-18 18:43:31

标签: python generator yield 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.

使用这个装饰器时doproc是什么时候执行的?

2 个答案:

答案 0 :(得分:12)

yield表达式将控制权返回给使用生成器的任何内容。此时生成器暂停,这意味着@contextmanager装饰器知道代码是使用 setup 部分完成的。

换句话说,您要在上下文管理器__enter__阶段执行的所有操作都必须在yield之前完成。

一旦您的上下文退出(因此with语句下的块已完成),将为上下文管理器协议的@contextmanager部分调用__exit__装饰器,并执行以下操作之一两件事:

  • 如果没有异常,它将恢复您的发电机。因此,您的生成器在yield行取消暂停,您进入清理阶段,部分

  • 如果有异常,装饰器使用generator.throw()在生成器中引发该异常。这就好像yield行导致了这个异常。因为你有一个finally子句,所以它会在你的生成器退出之前执行,因为例外。

因此,在您的具体示例中,序列如下:

  1. with time_print("processes"):

    这将创建上下文管理器并在其上调用__enter__

  2. 生成器开始执行,t = time.time()运行。

  3. yield表达式暂停生成器,控件返回装饰器。如果存在with部分,则接受所产生的任何内容并将其返回到as target语句。这里None屈服了(只有一个普通的yield表达式。)

  4. [doproc() for _ in range(500)]已运行并完成。

  5. 运行上下文管理器__exit__方法,不会传递任何异常。

  6. 装饰者恢复生成器,它从中断处继续。

  7. 输入finally:块并执行print task_name, "took", time.time() - t, "seconds."

  8. 生成器退出,装饰器__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所述。希望这会有所帮助!