Функция, выполняющая функции декоратора и контекстного менеджера в Python - PullRequest
47 голосов
/ 09 февраля 2012

Это может быть слишком далеко, но в основном из любопытства.

Возможно ли иметь вызываемый объект (функцию / класс), который действует как и Менеджер контекста и декоратор одновременно:

def xxx(*args, **kw):
    # or as a class

@xxx(foo, bar)
def im_decorated(a, b):
    print('do the stuff')

with xxx(foo, bar):
    print('do the stuff')

Ответы [ 4 ]

45 голосов
/ 09 февраля 2012

Начиная с Python 3.2, поддержка этого даже включена в стандартную библиотеку.Извлечение из класса contextlib.ContextDecorator облегчает написание классов, которые могут использоваться как в качестве декоратора, так и в качестве менеджера контекста.Эту функциональность можно легко перенести в Python 2.x - вот базовая реализация:

class ContextDecorator(object):
    def __call__(self, f):
        @functools.wraps(f)
        def decorated(*args, **kwds):
            with self:
                return f(*args, **kwds)
        return decorated

Извлеките свой менеджер контекста из этого класса и определите методы __enter__() и __exit__() как обычно.

24 голосов
/ 08 августа 2016

В Python 3.2+ вы можете определить менеджер контекста, который также является декоратором, используя @contextlib.contextmanager.

Из документов:

contextmanager() использует ContextDecorator, поэтому создаваемые им менеджеры контекста могут использоваться как декораторы, а также в with инструкциях

Пример использования:

<code>>>> <b><i>from contextlib import contextmanager</b></i>
>>> <b><i>@contextmanager</i></b>
... <b><i>def example_manager(message):</i></b>
... <b><i>    print('Starting', message)</i></b>
... <b><i>    try:</i></b>
... <b><i>        yield</i></b>
... <b><i>    finally:</i></b>
... <b><i>        print('Done', message)</i></b>
... 
>>> <b><i>with example_manager('printing Hello World'):</b></i>
... <b><i>    print('Hello, World!')</b></i>
... 
Starting printing Hello World
Hello, World!
Done printing Hello World
>>> 
>>> <b><i>@example_manager('running my function')</b></i>
... <b><i>def some_function():</b></i>
... <b><i>    print('Inside my function')</b></i>
... 
>>> <b><i>some_function()</i></b>
Starting running my function
Inside my function
Done running my function
12 голосов
/ 09 февраля 2012
class Decontext(object):
    """
    makes a context manager also act as decorator
    """
    def __init__(self, context_manager):
        self._cm = context_manager
    def __enter__(self):
        return self._cm.__enter__()
    def __exit__(self, *args, **kwds):
        return self._cm.__exit__(*args, **kwds)
    def __call__(self, func):
        def wrapper(*args, **kwds):
            with self:
                return func(*args, **kwds)
        return wrapper

теперь вы можете сделать:

mydeco = Decontext(some_context_manager)

, что позволяет как

@mydeco
def foo(...):
    do_bar()

foo(...)

, так и

with mydeco:
    do_bar()
4 голосов
/ 09 февраля 2012

Вот пример:

class ContextDecorator(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
        print "init", foo, bar

    def __call__(self, f):
        print "call"
        def wrapped_f():
            print "about to call"
            f()
            print "done calling"
        return wrapped_f

    def __enter__(self):
        print "enter"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print "exit"

with ContextDecorator(1, 2):
    print "with"

@ContextDecorator(3, 4)
def sample():
    print "sample"

sample()

Это печатает:

init 1 2
enter
with
exit
init 3 4
call
about to call
sample
done calling
...