简体   繁体   中英

Python context manager to decorator (and inversely)

I would like to have:

# Simple example, one could replace try/except by any other nested construct
def mycontextmanager_generator(foo):
    try:
        yield
    except:
        print 'bar'
        raise

mycontextmanager = build_contextmanager(mycontextmanager_generator)
mydecorator = build_decorator(mycontextmanager_generator)


>>> with mycontextmanager():
>>>     raise Exception('baz gone bar in context manager')
... bar


>>> @mydecorator()
>>> def bazzer():
>>>     raise Exception('baz gone bar in decorator')
>>> bazzer()
... bar

In this example, I build a context manager from a generator function, and a decorator from the same function. This is what I tried to achieve in an unsuccessful way.

More generally, what I want is to be DRY: write once the try/except block, and re-use it through both a decorator and a context manager Again: by writing the try/except bloc only once , whether in a generator function or any other wrapper.

The ContextDecorator thing (in contextlib in py3 / contextlib2 in py2) is only usable with classes, but it seems to be useless in that case... Am I missing something? Is there a means to implement my try/except block with a class-based ContextManager using __enter__ and __exit__ ?

Or is there a possibility to transform a contextmanager built with yield syntax into a decorator?

Or the contrary (decorator to contextmanager)?

If no, would be happy to know what is the limitation of Python for that matter.

To my understanding, the yield syntax is very tightly bound to the Python interpreter and context switching, and I don't know if it is possible to change its behavior on that point.

You can easily achieve what you need by combining the class that contextmanager uses to manage its contexts ( _GeneratorContextManager ) and the ContextDecorator class. eg.

from contextlib import ContextDecorator, _GeneratorContextManager
from functools import wraps

class MyContextManager(_GeneratorContextManager, ContextDecorator):
    pass

def contextmanager(func):
    @wraps(func)
    def helper(*args, **kwds):
        return MyContextManager(func, args, kwds)
    return helper

@contextmanager
def print_bar_on_error():
    try:
        yield
    except:
        print('bar')
        raise

with print_bar_on_error():
    raise Exception('baz gone bar in context manager')

produces:

bar
Traceback (most recent call last):
  File "run.py", line 28, in <module>
    raise Exception('baz gone bar in context manager')
Exception: baz gone bar in context manager

And when used as a decorator

@print_bar_on_error()
def bazzer():
    raise Exception('baz gone bar in decorator')
bazzer()

produces:

bar
Traceback (most recent call last):
  File "run.py", line 32, in <module>
    bazzer()
  File "c:\Users\User\AppData\Local\Programs\Python\Python35-32\lib\contextlib.py", line 30, in inner
    return func(*args, **kwds)
  File "run.py", line 31, in bazzer
    raise Exception('baz gone bar in decorator')
Exception: baz gone bar in decorator
    return func(*args, **kwds)
Exception: baz gone bar in decorator

A simpler to understand solution than Dunes' one, albeit not taking advantage of ContextDecorator double-syntax.

import contextlib
import functools

def handler():
    try:
        yield
    except:
        print 'bar'


my_contextmanager = contextlib.contextmanager(handler)


def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        with my_contextmanager():
            func(*args, **kwargs)
    return wrapper


with my_contextmanager():
    raise Exception('baz')

@my_decorator
def f():
    raise Exception('baz')

f()

gives:

bar
bar

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM