Функция, действующая как декоратор, так и менеджер контекста в Python?
Это может быть слишком далеко, но в основном из любопытства.
Возможно ли иметь вызываемый объект (функцию/класс), который одновременно выполняет функции диспетчера контекста и декоратора:
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')
Ответы
Ответ 1
Начиная с 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__()
, как обычно.
Ответ 2
В Python 3.2+ вы можете определить менеджер контекста, который также является декоратором, используя @contextlib.contextmanager
.
Из документов:
contextmanager()
использует ContextDecorator
, поэтому создатели контекста, которые он создает, могут использоваться как декораторы, так и в with
утверждения
Пример использования:
>>> from contextlib import contextmanager
>>> @contextmanager
... def example_manager(message):
... print('Starting', message)
... try:
... yield
... finally:
... print('Done', message)
...
>>> with example_manager('printing Hello World'):
... print('Hello, World!')
...
Starting printing Hello World
Hello, World!
Done printing Hello World
>>>
>>> @example_manager('running my function')
... def some_function():
... print('Inside my function')
...
>>> some_function()
Starting running my function
Inside my function
Done running my function
Ответ 3
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
Вот пример:
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
Ответ 5
Хотя я согласен (и одобрил) @jterrace здесь, я добавляю очень небольшое изменение, которое возвращает декорированную функцию и включает аргументы как для декоратора, так и для декорированной функции.
class Decon:
def __init__(self, a=None, b=None, c=True):
self.a = a
self.b = b
self.c = c
def __enter__(self):
# only need to return self
# if you want access to it
# inside the context
return self
def __exit__(self, exit_type, exit_value, exit_traceback):
# clean up anything you need to
# otherwise, nothing much more here
pass
def __call__(self, func):
def decorator(*args, **kwargs):
with self:
return func(*args, **kwargs)
return decorator