contextlib.contextmanager in Python 3 added the ability to define functions which work both as decorators and context managers, which is great and one of the features I appreciate the most about Python 3 (seriously). 
I am facing a use case where I find contextlib.contextmanager has some limitations though. I have a decorator which looks like this:


def wrap_exceptions(fun):
    """Decorator which translates bare OSError and IOError exceptions
    into NoSuchProcess and AccessDenied.
    """
    @functools.wraps(fun)
    def wrapper(self, *args, **kwargs):
        # here "self" is the Process class instance
        try:
            return fun(self, *args, **kwargs)
        except EnvironmentError as err:
            if err.errno in (errno.ENOENT, errno.ESRCH):
                raise NoSuchProcess(self.pid, self._name)
            if err.errno in (errno.EPERM, errno.EACCES):
                raise AccessDenied(self.pid, self._name)
            raise
    return wrapper


...and I use it like this:


class Process:

    @wrap_exceptions
    def exe():
        ...


The two key things about this decorator are:
- it's designed to be used with class methods
- it has a reference to the method's class instance (self)

I would like to push this a bit further and make wrap_exceptions() work also a contextmanager, which is what contextlib.contextmanager should allow me to do in an easy way. As contextlib.contextmanager stands right it won't allow my use case though as there's no way to pass a reference of the class instance (Process) to wrap_exceptions. So here is my proposal: what if we add a new "with_context" argument to contextlib.contextmanager?
The resulting code would look like this:


@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
 
 
class Process:
 
    @wrap_exceptions()
    def exe():
        ...
 
 
class Process:
 
    def exe(self):
        with wrap_exceptions(self):
            ...


It must be noted that:

- when with_context=True and wrap_exceptions is used as a decorator it can only be used to decorate class methods and not regular functions
- when used as a decorator "self" is automatically passed as the first argument for wrap_exceptions

I'm not sure if this is actually possible as I haven't gone through contextlib.contextmanager in details (it's quite magical).

Thoughts?

--
Giampaolo - http://grodola.blogspot.com