with 语句中的条件或可选上下文管理器

Conditional or optional context managers in with statement

假设我正在使用某种上下文管理器(来自第三方库):

with freeze_time(test_dt):
    lines_of_code_1
    lines_of_code_2
    lines_of_code_3

但是,假设如果 test_dt 没有值,上下文管理器不应该 运行,但所有剩余的代码应该 运行,像这样:

if test_dt:
    with freeze_time(test_dt):
        lines_of_code_1
        lines_of_code_2
        lines_of_code_3
else:
    lines_of_code_1
    lines_of_code_2
    lines_of_code_3

假设lines_of_code这里有2-3行完全相同的代码,有没有更简洁的写法?我知道我可以这样写:

def do_thing():
    lines_of_code_1
    lines_of_code_2
    lines_of_code_3

if test_dt:
    with freeze_time(test_dt):
        do_thing()
else:
    do_thing()

但我并不喜欢这种格式。另外,我不想在我的代码中到处乱扔这种模式。

还有最后一种可能性,但我不确定它是否会起作用:子class上下文管理器并跳过__enter____exit__函数29=] 给定的是空的,像这样:

class optional_freeze_time(object):
    def __init__(self, test_dt=None):
        if test_dt:
            self.ctx_manager = freeze_time(test_dt)
        else:
            self.ctx_manager = None
    def __enter__(self, *args, **kwargs):
        if self.ctx_manager:
            self.ctx_manager.__enter__(*args, **kwargs)
    def __exit__(self, *args, **kwargs):
        if self.ctx_manager:
            self.ctx_manager.__exit__(*args, **kwargs)

我用空白上下文管理器 class 对其进行了测试,它似乎运行正常。但是,我担心如果我这样做,真正的上下文管理器是否会正确运行(我不太熟悉它的工作原理)。

这里有一个简单的方法来环绕现有的上下文管理器,甚至不使用任何 类:

from contextlib import contextmanager

@contextmanager
def example_context_manager():
    print('before')
    yield
    print('after')

@contextmanager
def optional(condition, context_manager):
    if condition:
        with context_manager:
            yield
    else:
        yield

with example_context_manager():
    print(1)

with optional(True, example_context_manager()):
    print(2)

with optional(False, example_context_manager()):
    print(3)

输出:

before
1
after
before
2
after
3

我可能会继承父上下文管理器并编写如下内容:

class BaseContextManager:
    def __enter__(self):
        print('using Base')
    def __exit__(self, *args, **kwargs):
        print('exiting Base')


class MineContextManager(BaseContextManager):
    def __init__(self, input=None):
        self.input = input

    def __enter__(self):
        if self.input:
            super().__enter__()

    def __exit__(self, *args, **kwargs):
        if self.input:
            super().__exit__()

if __name__ == '__main__':

    with BaseContextManager():
        print('code with base')

    with MineContextManager():
        print('code without base')

    with MineContextManager(input=True):
        print('code again with base')

这给出:

using Base
code with base
exiting Base
code without base
using Base
code again with base
exiting Base

只需使用

(freeze_time if test_dt else (lambda func: contextmanager(func))(lambda dt: (yield)))(test_dt)

示例:

from contextlib import contextmanager

test_dt = None

@contextmanager
def freeze_time(test_dt):
    print("frozen")
    yield
    print("unfrozen")

with (freeze_time if test_dt else (lambda func: contextmanager(func))(lambda dt: (yield)))(test_dt):
    print("The cold impairs your judgment.")

新访客可能对 contextlib.ExitStack:

感兴趣
with ExitStack() as stack:
  if condition:
    stack.enter_context(freeze_time(...))
  lines_of_code_1
  lines_of_code_2
  lines_of_code_3

在这个 with 语句之后,freeze_time 仅在条件为真时相关。