How do I write a null (no-op) contextmanager in Python?
Python 3.7 and above: use contextlib.nullcontext
, specifically designed for this reason.
Before Python 3.7, the standard library does not offer a context manager specifically designed for these use cases, but there are some workarounds.
Since Python 3.4, contextlib.suppress
can be used for that purpose in the first case, i.e. when there is no as
clause:
ctx_mgr = <meaningfulContextManager> if <condition> else contextlib.suppress()
with ctx_mgr:
...
Since Python 3.3, a similar work-around is also available, contextlib.ExitStack
, albeit slower than suppress
(it takes twice as long in my tests).
Before Python 3.3, or in case you need an as
clause before Python 3.7, developers need to roll their own.
Here is one possible implementation (see note at the bottom, but all errors are mine):
class NullContextManager(object):
def __init__(self, dummy_resource=None):
self.dummy_resource = dummy_resource
def __enter__(self):
return self.dummy_resource
def __exit__(self, *args):
pass
One can then write:
ctx_mgr = <meaningfulContextManager> if <condition> else NullContextManager(dummy_resource)
with ctx_mgr as resource:
<operations on resource>
Of course, dummy_resource
will need to support all operations required of the "meaningful" resource. So for example, if the meaningful context manager, on __enter__()
, returns something that is made to quack()
inside the managed block, dummy_resource
will also need to support that, albeit possibly without doing anything at all.
class DummyDuck(object):
def quack()
# Ssssh...
pass
ctx_mgr = <meaningfulContextManager> if <condition> else NullContextManager(DummyDuck())
with ctx_mgr as someDuck:
someDuck.quack()
Source: A Python feature request. Many thanks to all those who contributed to that discussion. This is my attempt at summarising its outcome in a self-answered question, to save people time reading that long thread. Also see Python documentation's mention of this use of ExitStack
.
A simple solution for Python 3.6 and below, including 2.7:
from contextlib import contextmanager
@contextmanager
def nullcontext(enter_result=None):
yield enter_result
Since Python 3.7 you should use the provided contextlib.nullcontext
instead.