80 votes

Fonction agissant à la fois comme décorateur et gestionnaire de contexte en Python ?

C'est peut-être pousser les choses un peu trop loin, mais surtout par curiosité

Serait-il possible d'avoir un objet appelable (fonction/classe) qui agirait en tant que les deux un gestionnaire de contexte et un décorateur en même temps :

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')

0 votes

Pouvez-vous donner un exemple de ce qu'il ferait ? les deux fonctionnalités sont-elles différentes ?

0 votes

Il s'agirait en fait de deux manières différentes de mettre en place les fixtures dans les suites de tests.

74voto

Sven Marnach Points 133943

À partir de Python 3.2, cette prise en charge est même incluse dans la bibliothèque standard. En dérivant de la classe contextlib.ContextDecorator permet d'écrire facilement des classes qui peuvent être utilisées à la fois comme décorateur et comme gestionnaire de contexte. Cette fonctionnalité pourrait être facilement rétroportée vers Python 2.x -- voici une implémentation de base :

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

Faites dériver votre gestionnaire de contexte de cette classe et définissez l'élément __enter__() y __exit__() méthodes comme d'habitude.

57voto

Mark Amery Points 4705

En Python 3.2+, vous pouvez définir un gestionnaire de contexte qui est aussi un décorateur en utilisant @contextlib.contextmanager .

Dans la documentation :

contextmanager() utilise ContextDecorator de sorte que les gestionnaires de contexte qu'il crée puissent être utilisés comme décorateurs ainsi que dans des with déclarations

Exemple d'utilisation :

>>> 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

17voto

nosklo Points 75862
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

maintenant vous pouvez le faire :

mydeco = Decontext(some_context_manager)

et qui permet à la fois

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

foo(...)

et

with mydeco:
    do_bar()

3 votes

Comment passer des arguments au décorateur si le gestionnaire de contexte prend des arguments ?

0 votes

Le gestionnaire de contexte @Warz est déjà créé pendant Decontext(some_context_manager) . __enter__ y __exit__ sont prédéfinies.

0 votes

Question connexe utilisant Decontext avec des arguments : Passage d'arguments au décorateur de décontexte

9voto

jterrace Points 21939

Voici un exemple :

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()

Cette empreinte :

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

4 votes

Lorsqu'il est utilisé comme décorateur, il n'agit pas de la même manière qu'un gestionnaire de contexte, ce qui semble être l'intention de l'OP. (Voir le commentaire de l'OP -- "Il s'agirait essentiellement de deux façons alternatives de mettre en place les fixtures dans les suites de test").

1 votes

Bien sûr, le but était de montrer comment une seule classe peut faire les deux. Je laisserais au PO le soin de la personnaliser pour la fonctionnalité voulue.

6voto

openwonk Points 7520

Bien que je sois d'accord avec @jterrace ici (et que j'ai upvoted), j'ajoute une très légère variation qui retourne la fonction décorée, et inclut des arguments à la fois pour le décorateur et la fonction décorée.

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

0 votes

Ça a l'air bien ! Quand le monde commencera à être correctement mis à jour vers la 3.6+, je m'en tiendrai à docs.python.org/3/library/ mais si le 2 est un risque, votre solution semble très tentante !

0 votes

Ça a marché comme sur des roulettes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X