从上下文管理器中止 with 语句的执行

abort execution of with statement from context manager

我对 python 中的装饰器、上下文管理器等比较陌生。 基本上我想做如下的事情:

@contextmanager
def cd(to_dir):
    from_dir = os.getcwd()
    try:
        os.chdir(os.path.expanduser(to_dir))
        yield
    except Exception:
        log.error(traceback.format_exc())
        log.error(f"Failed to cd into {to_dir} from {from_dir}")
    finally:
        os.chdir(from_dir)

我想像这样使用它:

with cd('here'):
  ...
  ...
  ...

我特别想确保如果 cdhere 失败时不会执行 with 语句的主体,这就是为什么我试图在上下文管理器中捕获异常并防止它来自 yielding。 然而,似乎 python 生成器必须 yield 否则我会收到如下错误:

    raise RuntimeError("generator didn't yield") from None                                                                                         |
RuntimeError: generator didn't yield  

如果 chdir 失败,我如何禁止执行 with 的主体?

我能想到的一个解决方案是:

@contextmanager
def cd(to_dir):
   from_dir = os.getcwd()
   try:
       os.chdir(os.path.expanduser(to_dir))
       yield True
   except Exception:
       log.error(traceback.format_exc())
       log.error(f"Failed to cd into {to_dir} from {from_dir}")
       yield False
   finally:
       os.chdir(from_dir)

然后使用如下:

with cd('here') as success:
  if success:
    ....
    ....

然而,这确实需要在使用 with 语句的任何地方附加条件,因此在现有程序中的任何地方更改都不方便。另一个问题是,如果在 with 语句的主体内发生异常,它也会被 cd contextmanager 函数内的 except 子句捕获,从而导致有关 cd.[=21 失败的虚假错误=]

上下文管理器无法跳过上下文主体,除非引发错误。 context manager protocol 只允许 return 一个值绑定在正文中(as 的目标),但不表示跳过正文。因此,如果进入上下文可能会失败,要么 a) 不要抑制失败异常,要么 b) 用新异常替换失败异常。

@contextmanager
def cd(to_dir):
    from_dir = os.getcwd()
    # if this fails, the exception bubbles out of the context manager
    os.chdir(os.path.expanduser(to_dir))
    try:
        yield
    finally:
        os.chdir(from_dir)

如果初始 os.chdir 失败,异常将“退出”with 语句处的上下文,而不进入正文。