我可以使用Python的with语句进行条件执行吗?

13 投票
4 回答
4366 浏览
提问于 2025-04-16 07:49

我正在尝试编写代码,支持以下功能:

with scope('action_name') as s:
  do_something()
  ...
do_some_other_stuff()

这个范围(除了其他事情,比如设置和清理)应该决定这一部分代码是否应该运行。
举个例子,如果用户设置程序跳过某个'动作名称',那么在评估完Scope()后,do_some_other_stuff()会被执行,而不需要先调用do_something()。
我试着用这个上下文管理器来实现:

@contextmanager
def scope(action):
  if action != 'bypass':
    yield

但是遇到了RuntimeError: generator didn't yield的错误(当action'bypass'时)。
我希望能找到一种方法来支持这个功能,而不需要使用更冗长的可选实现:

with scope('action_name') as s:
  if s.should_run():
    do_something()
    ...
do_some_other_stuff()

有没有人知道我该怎么做?
谢谢!

附言:我使用的是python2.7

编辑:
解决方案不一定要依赖于with语句。我只是不知道如何在没有它的情况下准确表达。实际上,我想要的是一种上下文的形式(支持设置和自动清理,这与内部逻辑无关),并允许根据传递给设置方法的参数和在配置中选择的内容进行条件执行。
我也考虑过使用装饰器的可能解决方案。举个例子:

@scope('action_name') # if 'action_name' in allowed actions, do:
                      #   setup()
                      #   do_action_name()
                      #   cleanup()
                      # otherwise return
def do_action_name()
  do_something()

但我不想过多地强制内部结构(也就是代码如何分成函数)基于这些范围。
有没有人有一些创造性的想法?

4 个回答

1

我觉得这件事做不到。我试着把上下文管理器做成一个类,但没有办法强制让代码块抛出一个异常,然后这个异常又被__exit__()方法给压制掉。

4

以下内容似乎可以正常工作:

from contextlib import contextmanager

@contextmanager
def skippable():
    try:
        yield
    except RuntimeError as e:
        if e.message != "generator didn't yield":
            raise

@contextmanager
def context_if_condition():
    if False:
        yield True

with skippable(), context_if_condition() as ctx:
    print "won't run"

需要注意的事项:

  • 需要有人想出更好的名字
  • context_if_condition 不能在没有 skippable 的情况下使用,但没有办法强制执行这一点,也无法去掉重复的部分
  • 它可能会捕捉并抑制比预期更深层函数的运行时错误(自定义异常可以在这方面提供帮助,但这样会让整个结构变得更复杂)
  • 它并没有比直接使用 @Mark Ransom 的版本更清晰
7

你正在尝试改变一种基本语言结构的预期行为。这通常不是个好主意,因为这样会让人感到困惑。

你的解决方法没有问题,但其实可以稍微简化一下。

@contextmanager 
def scope(action): 
  yield action != 'bypass'

with scope('action_name') as s: 
  if s: 
    do_something() 
    ... 
do_some_other_stuff() 

你的 scope 可以改成一个类,这个类的 __enter__ 方法可以返回一个有用的对象或者 None,这样用法就和之前一样了。

撰写回答