50 votes

Comment construire un décorateur avec des paramètres facultatifs?

Je voudrais créer un décorateur qui pourrait être utilisé avec ou sans paramètre : Quelque chose comme ceci :

class d(object):
    def __init__(self,msg='mon message par défaut'):
        self.msg = msg
    def __call__(self,fn):
        def newfn():
            print self.msg
            return fn()
        return newfn

@d('Cela fonctionne')
def hello():
    print 'Bonjour le monde !'

@d
def too_bad():
    print 'ne fonctionne pas'

Dans mon code, seule l'utilisation du décorateur avec un paramètre fonctionne : Comment faire pour que les deux fonctionnent (avec et sans paramètre) ?

53voto

Eric Points 1288

J'ai trouvé un exemple, vous pouvez utiliser @trace ou @trace('msg1','msg2'): bien!

def trace(*args):
    def _trace(func):
        def wrapper(*args, **kwargs):
            print enter_string
            func(*args, **kwargs)
            print exit_string
        return wrapper
    if len(args) == 1 and callable(args[0]):
        # Aucun argument, c'est le décorateur
        # Définir des valeurs par défaut pour les arguments
        enter_string = 'entrer'
        exit_string = 'sortir'
        return _trace(args[0])
    else:
        # Cela ne fait que renvoyer le décorateur
        enter_string, exit_string = args
        return _trace

1 votes

Je suppose que cela fonctionne pour votre cas et le serait pour des cas similaires. Mais que se passe-t-il si l'argument du décorateur est en fait un seul appelable ? Comment distinguerez-vous entre la fonction décorée et l'argument ?

2 votes

@ksrini: Ignacio l'a souligné dans sa réponse il y a des années.

3 votes

Vous pouvez également contourner cela en utilisant des arguments de mots-clés (comme @trace(default=...)).

29voto

Glenn Maynard Points 24451

Si vous souhaitez passer des paramètres à votre décorateur, vous devez toujours l'appeler en tant que fonction :

@d()
def func():
    pass

Sinon, vous devez essayer de détecter la différence de paramètres - en d'autres termes, vous devez deviner ce que l'appelant signifie. Ne créez pas une API qui doit deviner ; dites toujours clairement ce que vous voulez dès le départ.

En d'autres termes, une fonction devrait soit être un décorateur, soit une fabrique de décorateurs ; elle ne devrait pas être les deux à la fois.

Remarquez que si tout ce que vous voulez faire est de stocker une valeur, vous n'avez pas besoin d'écrire une classe.

def d(msg='mon message par défaut'):
    def decorator(func):
        def newfn():
            print msg
            return func()
        return newfn
    return decorator

@d('Cela fonctionne')
def hello():
    print 'bonjour le monde !'

@d()
def hello2():
    print 'également bonjour le monde'

19voto

martineau Points 21665

Cela fonctionnerait.

def d(arg):
    if callable(arg):  # Suppose que l'argument optionnel n'est pas.
        def newfn():
            print('mon message par défaut')
            return arg()
        return newfn
    else:
        def d2(fn):
            def newfn():
                print(arg)
                return fn()
            return newfn
        return d2

@d('Cela fonctionne')
def hello():
    print('Bonjour le monde !')

@d  # Aucun argument explicite ne résultera en un message par défaut.
def hello2():
    print('Bonjour2 le monde !')

@d('En l'appliquant deux fois')
@d('Cela fonctionnerait également')
def hello3():
    print('Bonjour3 le monde !')

hello()
hello2()
hello3()

Résultat:

Cela fonctionne
Bonjour le monde !
mon message par défaut
Bonjour2 le monde !
En l'appliquant deux fois
Cela fonctionnerait également
Bonjour3 le monde !

Si une fonction de décoration @ n'est pas passée d'arguments explicites, elle est appelée avec la fonction définie dans le def suivant. Si des arguments sont passés, alors elle est d'abord appelée avec eux et ensuite le résultat de cet appel préliminaire (qui doit lui-même être un callable) est appelé avec la fonction en cours de définition. De toute façon, la valeur de retour de l'appel final ou unique est liée au nom de la fonction définie.

16voto

ricardo Points 131

Si vous n'avez pas de problème à utiliser des arguments nommés, j'ai créé quelque chose de similaire à ce dont vous avez besoin :

def cached_property(method=None, get_attribute=lambda a: '_%s_cached' % (a,)):
    """
    Met en cache l'attribut d'un objet.

    Peut être utilisé sous les formes suivantes :
    @cached_property
    @cached_property()
    @cached_property(get_attribute=lambda x: 'bla')

    @param method: la méthode à mettre en mémoire
    @param get_attribute: une fonction appelable qui devrait retourner l'attribut mis en cache
    @return une méthode mise en mémoire
    """
    def decorator(method):
        def wrap(self):
            private_attribute = get_attribute(method.__name__)
            try:
                return getattr(self, private_attribute)
            except AttributeError:
                setattr(self, private_attribute, method(self))
                return getattr(self, private_attribute)
        return property(wrap)
    if method:
        # Il s'agissait d'un appel de décorateur réel, par exemple : @cached_property
        return decorator(method)
    else:
        # Il s'agit d'un appel d'usine, par exemple : @cached_property()
        return decorator

Cela fonctionne parce qu'un seul argument sans mot-clé, la fonction décorée, est passé au décorateur.

Remarquez que j'ai également utilisé les arguments passés à la fonction décorée, dans ce cas 'self'.

0 votes

TypeError: L'objet 'property' n'est pas appelable

5voto

Vous devez détecter si l'argument du décorateur est une fonction, et utiliser un décorateur simple dans ce cas. Ensuite, vous devez espérer de ne jamais avoir besoin de passer uniquement une fonction au décorateur paramétré.

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