6

How can I use a context manager in a lambda? Hacks accepted. Defer opinions about this being a bad usage of lambdas.

I know I can do this:

def f():
    with context():
        return "Foo"

But I would like to do something like this:

lambda: with context(): "Foo"
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Carl Thomé
  • 2,703
  • 3
  • 19
  • 41

2 Answers2

4

You can't replace the work with does with an expression, no. There are no hacks to get you there either, because there is no way to handle exceptions and finalisation within an expression.

That's because you can only use one expression in a lambda. with is a statement, not an expression. You'd have to replace that with exception handling (try..except..finally) and calls to the __enter__ and __exit__ methods (storing the __exit__ method first). However, exception handling can only be done with statements, because an exception ends the current expression immediately. See Python Try Catch Block inside lambda.

Your only option is to stick to using a proper function instead.

Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    What about ContextDecorator? – Carl Thomé Jan 21 '17 at 18:04
  • Like [this](https://gist.github.com/carlthome/31ba4093a6f7029053de02d70fc48ef1) for example. Is there a problem with that in case of raised exceptions or something? – Carl Thomé Jan 21 '17 at 18:18
  • Ah, I forgot about that one. `ContextDecorator` is not part of the lambda expression. It produces a new function object that contains the `with` statement, then calls the original function, and does all the exception handling. That's something different. And the context manager explicitly needs to support that use. – Martijn Pieters Jan 21 '17 at 18:20
  • 1
    I'd say making a context manager extend `ContextDecorator` instead of calling `contextmanager` is pretty trivial refactoring for getting lambdas to work with context managers though. In other words, there is an option. Are you aware of any downsides to this? Thanks for answering! – Carl Thomé Jan 21 '17 at 18:25
  • @CarlThomé: yes, you can't apply `ContextDecorator` to existing context manager objects, and you can't access whatever `__enter__` returns (what is assigned to `` in `with as `). At that point you can either write a dedicated decorator function (and pass in the `as` result), or just write a function and use `with`. – Martijn Pieters Jan 21 '17 at 18:44
3

One possible workaround for getting lambdas working with a context manager is to make the context manager a ContextDecorator, then both with statements and lambda expressions will work because a lambda can use the decorator pattern instead.

Example

from contextlib import ContextDecorator


def f(x):
     """Just prints the input, but this could be any arbitrary function."""
     print(x)


class mycontext(ContextDecorator):
    def __enter__(self):
        f('Starting')
        return self

    def __exit__(self, *exc):
        f('Finishing')
        return False

with mycontext():
    f('The bit in the middle')

mycontext()(lambda: f('The bit in the middle'))()
Carl Thomé
  • 2,703
  • 3
  • 19
  • 41