[Python-ideas] @contextlib.contextmanager(with_context=True) - passing a context to contextmanager

Nick Coghlan ncoghlan at gmail.com
Fri Jul 10 09:09:03 CEST 2015

On 10 July 2015 at 07:34, Giampaolo Rodola' <g.rodola at gmail.com> wrote:
> @contextlib.contextmanager(with_context=True)
> def wrap_exceptions(ctx):
>     # ctx is the Process class instance
>     try:
>         yield
>     except EnvironmentError as err:
>         pid = ctx.pid
>         name = ctx.name
>         if err.errno in (errno.ENOENT, errno.ESRCH):
>             raise NoSuchProcess(pid, name)
>         if err.errno in (errno.EPERM, errno.EACCES):
>             raise AccessDenied(pid, name)
>         raise

There isn't anything in this syntax which says to me "designed to be
used as a class method decorator", nor in the invocation that says
"this CM gets access to the class or instance object" :)

The mechanism underlying the context-manager-or-decorator behaviour is
actually https://docs.python.org/3/library/contextlib.html#contextlib.ContextDecorator,
and that's entirely unaware of both the function being decorated *and*
its runtime arguments. This means it is unaware of the details of
method invocations as well.

If you want a method aware context manager, you'll likely want to
write a decorator factory that accepts the relevant CM as a parameter.
For example (untested):

    def with_cm(cm):
        def decorator(f):
            def wrapper(self, *args, **kwargs):
                with cm(self):
                    return f(self, *args, **kwargs)

Used as:

    class Process:

        def exe():

Regardless, I'd advise against trying to hide the fact that there's an
extra step going on in order to make the function being wrapped and/or
one or more of its arguments available to the context manager, as that
doesn't happen by default.


Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia

More information about the Python-ideas mailing list