web-dev-qa-db-ja.com

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')
52
Jacob Oscarson

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__()メソッドを通常どおり定義します。

45
Sven Marnach

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_
25
Mark Amery
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()
12
nosklo

次に例を示します。

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

ここでは@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
1
openwonk