Conditional with statement in Python
As of Python 3.7 you can use contextlib.nullcontext
:
from contextlib import nullcontext
if needs_with():
cm = get_stuff()
else:
cm = nullcontext()
with cm as gs:
# Do stuff
contextlib.nullcontext
is pretty much just a no-op context manager. You can pass it an argument that it will yield, if you depend on something existing after the as
:
>>> with nullcontext(5) as value:
... print(value)
...
5
Otherwise it'll just return None
:
>>> with nullcontext() as value:
... print(value)
...
None
It's super neat, check out the docs for it here: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext
If you want to avoid duplicating code and are using a version of Python prior to 3.7 (when contextlib.nullcontext
was introduced) or even 3.3 (when contextlib.ExitStack
was introduced), you could do something like:
class dummy_context_mgr():
def __enter__(self):
return None
def __exit__(self, exc_type, exc_value, traceback):
return False
or:
import contextlib
@contextlib.contextmanager
def dummy_context_mgr():
yield None
and then use it as:
with get_stuff() if needs_with() else dummy_context_mgr() as gs:
# do stuff involving gs or not
You alternatively could make get_stuff()
return different things based on needs_with()
.
(See Mike's answer or Daniel's answer for what you can do in later versions.)
Python 3.3 and above
Python 3.3 introduced contextlib.ExitStack
for just this kind of situation. It gives you a "stack", to which you add context managers as necessary. In your case, you would do this:
from contextlib import ExitStack
with ExitStack() as stack:
if needs_with():
gs = stack.enter_context(get_stuff())
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
Anything that is entered to stack
is automatically exit
ed at the end of the with
statement as usual. (If nothing is entered, that's not a problem.) In this example, whatever is returned by get_stuff()
is exit
ed automatically.
If you have to use an earlier version of python, you might be able to use the contextlib2
module, although this is not standard. It backports this and other features to earlier versions of python. You could even do a conditional import, if you like this approach.
Python 3.7 and above
Python 3.7 further introduced contextlib.nullcontext
(a couple years after this answer was originally posted, and since that time mentioned in several other answers). In the comments, @Kache points out the most elegant usage of this option:
from contextlib import nullcontext
with get_stuff() if needs_with() else nullcontext() as gs:
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
Note that if needs_with()
is False
, then gs
will be None
inside the context block. If you want gs
to be something_else
in that case, you just replace nullcontext()
with nullcontext(something_else)
.
This approach is obviously not as flexible as ExitStack
, because this is just a binary choice, whereas ExitStack
allows you to add as many exit
ing things as you want, with complicated logic and so on. But this certainly answers the OP's simple requirements.