How to stop statements within a Python with context from executing?

Question:

I’ve some code like this:

with SomeContext(args):
  <statement1>
  .
  .
  <statementN>

I want this code to behave like this:

if some_condition(args):
  f1()
else:
  <statement1>
  .
  .
  <statementN>

Statements in else block will also need an access to args.

But I want to hide f1 and some_condition from user of this abstraction, so using an if-else block is out of option. Also I don’t want to force a constraint on the user to wrap all statements in a function. Is it possible to do some python magic in the with context that allows this?

Asked By: Nikhil Garg

||

Answers:

Yes, you can easily create such a context manager as follows.

import contextlib

@contextlib.contextmanager
def SomeContext(args):
    if some_condition(args):
        f1()
    else:
        yield

The user’s wrapped code gets executed at the point of the yield. I don’t think it’s a problem that the context manager sometimes does not execute the user’s code, but I haven’t checked.

Answered By: DaveP

The closest I can get is to use two nested context managers, something like the following:

class SkippedException(Exception):
    pass

class SkipContext:
    def __enter__(self):
        pass
    def __exit__(self, type, value, tb):
        return type is SkippedException

class SomeContext:
    def __init__(self, arg):
        self.arg = arg
    def __enter__(self):
        if self.arg == 1:
            print "arg", self.arg
            raise SkippedException()
    def __exit__(self, type, value, tb):
        pass

with SkipContext(), SomeContext(1):
    print "body"

The SkipContext manager essentially catches the SkippedException raised by the inner SomeContext manager in the case where arg == 1.

Note that the syntax of multiple context expressions is only supported in Python 2.7 or later. In earlier versions, you would have to write:

with SkipContext():
    with SomeContext(1):
        print "body"

The contextlib.nested context manager, despite claims in the documentation, doesn’t exactly match the semantics of the above nested with statement when exceptions are thrown from within __enter__, so it doesn’t work in this case.

It should be noted that PEP 343 mentions that macros (like context managers) that hide flow control should be discouraged, and references Raymond Chen’s rant against hidden flow control.

Answered By: Greg Hewgill
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.