【问题标题】:what does yield without value do in context manager在上下文管理器中没有价值的产量有什么作用
【发布时间】:2016-05-31 02:43:32
【问题描述】:
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什么时候执行?

【问题讨论】:

  • From the docs: "在生成器 yield 的地方,嵌套在 with 语句中的块被执行。然后在块退出后,生成器被恢复。如果块中出现未处理的异常,它在生成器内重新生成,在产生产生的点。”
  • yield 没有参数在语义上等价于yield None

标签: python generator yield contextmanager


【解决方案1】:

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表达式暂停生成器,控制返回到装饰器。如果有 as target 部分,这将获取所产生的任何内容并将其返回到 with 语句。这里产生了None(只有一个普通的yield 表达式)。

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

  5. 上下文管理器__exit__方法运行,没有传入异常。

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

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

  8. 生成器退出,装饰器__exit__方法退出,全部完成。

【讨论】:

  • 我不明白“这需要任何结果”这句话。但是yield 没有参数。它没有“产生”任何东西......
  • @Alex:你总是产生一些东西。那可以是None,就像return 退出一个函数然后返回值是None。但是你也可以显式地产生一些东西,所以yield some_object,此时可以使用with context_manager as <target> 语法将该值分配给目标。
【解决方案2】:

@Martijn Pieters 的出色解释。由于 yield 在您的情况下是多余的,您可以通过创建自己的上下文管理器来实现相同的目的(没有 yieldcontextlib.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 将这些 enterexit 魔术函数称为由@Martijn-Pieters 解释。希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-01-20
    • 2021-01-28
    • 2023-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-06
    • 2016-10-03
    相关资源
    最近更新 更多