13

I'm trying to write code that supports the following semantics:

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

The scope, among other things (setup, cleanup) should decide if this section should run.
For instance, if the user configured the program to bypass 'action_name' than, after Scope() is evaluated do_some_other_stuff() will be executed without calling do_something() first.
I tried to do it using this context manager:

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

but got RuntimeError: generator didn't yield exception (when action is 'bypass').
I am looking for a way to support this without falling back to the more verbose optional implementation:

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

Does anyone know how I can achieve this?
Thanks!

P.S. I am using python2.7

EDIT:
The solution doesn't necessarily have to rely on with statements. I just didn't know exactly how to express it without it. In essence, I want something in the form of a context (supporting setup and automatic cleanup, unrelated to the contained logic) and allowing for conditional execution based on parameters passed to the setup method and selected in the configuration.
I also thought about a possible solution using decorators. Example:

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

but I don't want to enforce too much of the internal structure (i.e., how the code is divided to functions) based on these scopes.
Does anybody have some creative ideas?

3
  • 4
    with is not if ... you can write s.do_something() and have the contextmanager return a object that does nothing. Commented Nov 30, 2010 at 14:18
  • Thought about it too, but I still hope to find an elegant solution... Commented Nov 30, 2010 at 21:55
  • Also, I don't know anything about the code inside this scope (e.g., do_something()) in advance. Commented Nov 30, 2010 at 22:02

4 Answers 4

7

You're trying to modify the expected behaviour of a basic language construct. That's never a good idea, it will just lead to confusion.

There's nothing wrong with your work-around, but you can simplify it just a bit.

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

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

Your scope could instead be a class whose __enter__ method returns either a useful object or None and it would be used in the same fashion.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. (Not trying to start a debate about python context semantics here, ) I disagree with the "expected behavior of a basic language construct" part. For me, it was very clear that the 'yield' part of a contextmanager ma be skipped. Until I caught the "didn't yield" exception. I think the language should have taken care of it, maybe by throwing a predefined exception type from enter, similarly to the StopIteration exception for the next() method.
5

The following seems to work:

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"

Considerations:

  • needs someone to come up with better names
  • context_if_condition can't be used without skippable but there's no way to enforce that/remove the redundancy
  • it could catch and suppress the RuntimeError from a deeper function than intended (a custom exception could help there, but that makes the whole construct messier still)
  • it's not any clearer than just using @Mark Ransom's version

Comments

1

I don't think this can be done. I tried implementing a context manager as a class and there's just no way to force the block to raise an exception which would subsequently be squelched by the __exit__() method.

1 Comment

I had that feeling. I hoped it can be solved using some of that python magic I usually find on those cases. Seems like a nice functionality, though. Maybe I can request it as an enhancement... :)
-2

I have the same use case as you, and came across the conditional library that someone has helpfully developed in the time since you posted your question.

From the site, its use is as:

with conditional(CONDITION, CONTEXTMANAGER()):
    BODY()

1 Comment

This is not a solution. the conditional library applies the condition to whether or not the given context manager is entered. The body is always executed regardless.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.