with - Quelles sont les utilisations courantes pour les décorateurs Python?




wrapper python (10)

Decorator peut être utilisé pour créer facilement des variables de méthode de fonction.

def static_var(varname, value):
    '''
    Decorator to create a static variable for the specified function
    @param varname: static variable name
    @param value: initial value for the variable
    '''
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

@static_var("count", 0)
def mainCallCount():
    mainCallCount.count += 1

Bien que j'aime à me considérer comme un codeur Python raisonnablement compétent, un aspect de la langue que je n'ai jamais été capable de grok est celui des décorateurs.

Je sais ce qu'ils sont (superficiellement), j'ai lu des tutoriels, des exemples, des questions sur Stack Overflow, et je comprends la syntaxe, je peux écrire le mien, parfois utiliser @classmethod et @staticmethod, mais il ne me vient jamais à l'esprit d'utiliser décorateur pour résoudre un problème dans mon propre code Python. Je ne rencontre jamais un problème où je pense, "Hmm ... cela ressemble à un travail pour un décorateur!"

Donc, je me demande si vous pourriez donner des exemples d'endroits où vous avez utilisé des décorateurs dans vos propres programmes, et j'espère que je vais avoir un "A-ha!" moment et les obtenir .


Il y a un certain nombre d'utilisations suggérées et d'extraits sur le wiki de Python .


J'utilise des décorateurs pour les paramètres de contrôle de type qui sont transmis à mes méthodes Python via certains RMI. Donc, au lieu de répéter le même paramètre de comptage, exception-soulevant mumbo-jumbo encore et encore

def myMethod(ID, name):
    if not (myIsType(ID, 'uint') and myIsType(name, 'utf8string')):
        raise BlaBlaException() ...

Je viens de déclarer

@accepts(uint, utf8string)
def myMethod(ID, name):
    ...

et accepte () fait tout le travail pour moi.


J'utilise le décorateur suivant pour faire une fonction threadsafe. Cela rend le code plus lisible. Il est presque similaire à celui proposé par John Fouhy mais la différence est que l'on travaille sur une seule fonction et qu'il n'est pas nécessaire de créer explicitement un objet lock.

def threadsafe_function(fn):
    """decorator making sure that the decorated function is thread safe"""
    lock = threading.Lock()
    def new(*args, **kwargs):
        lock.acquire()
        try:
            r = fn(*args, **kwargs)
        except Exception as e:
            raise e
        finally:
            lock.release()
        return r
    return new

class X:
    var = 0

    @threadsafe_function     
    def inc_var(self):
        X.var += 1    
        return X.var

Je les ai utilisés pour la synchronisation.

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        def newFunction(*args, **kw):
            lock.acquire()
            try:
                return f(*args, **kw)
            finally:
                lock.release()
        return newFunction
    return wrap

Comme indiqué dans les commentaires, depuis Python 2.5, vous pouvez utiliser une instruction with en conjonction avec un objet threading.Lock (ou multiprocessing.Lock depuis la version 2.6) pour simplifier l'implémentation du décorateur:

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        def newFunction(*args, **kw):
            with lock:
                return f(*args, **kw)
        return newFunction
    return wrap

Peu importe, vous l'utilisez ensuite comme ceci:

import threading
lock = threading.Lock()

@synchronized(lock)
def do_something():
  # etc

@synchronzied(lock)
def do_something_else():
  # etc

Fondamentalement, il met juste lock.acquire() / lock.release() de chaque côté de l'appel de fonction.


Je les ai utilisés récemment, tout en travaillant sur l'application web de réseautage social. Pour les communautés / groupes, j'étais censé donner l'autorisation d'adhésion pour créer une nouvelle discussion et répondre à un message que vous devez être le membre de ce groupe particulier. J'ai donc écrit un décorateur @membership_required et l' @membership_required mis là où je le @membership_required .


La bibliothèque Twisted utilise des décorateurs combinés avec des générateurs pour donner l'illusion qu'une fonction asynchrone est synchrone. Par exemple:

@inlineCallbacks
def asyncf():
    doStuff()
    yield someAsynchronousCall()
    doStuff()
    yield someAsynchronousCall()
    doStuff()

Grâce à cela, le code qui aurait été décomposé en une tonne de fonctions de rappel peut être écrit tout naturellement en un seul bloc, ce qui le rend beaucoup plus facile à comprendre et à maintenir.


Les décorateurs sont utilisés pour tout ce que vous voulez "envelopper" de manière transparente avec des fonctionnalités supplémentaires.

Django les utilise pour encapsuler la fonctionnalité "login required" sur les fonctions de vue , ainsi que pour enregistrer les fonctions de filtrage .

Vous pouvez utiliser des décorateurs de classe pour ajouter des journaux nommés aux classes .

Toute fonctionnalité suffisamment générique que vous pouvez "coller" à un comportement de classe ou de fonction existant est un jeu équitable pour la décoration.

Il y a aussi une discussion des cas d'utilisation sur le groupe de discussion Python-Dev désigné par PEP 318 - Décorateurs pour les fonctions et les méthodes .


Pour nosetests, vous pouvez écrire un décorateur qui fournit une fonction ou une méthode de test unitaire avec plusieurs ensembles de paramètres:

@parameters(
   (2, 4, 6),
   (5, 6, 11),
)
def test_add(a, b, expected):
    assert a + b == expected

Une utilisation évidente est pour la journalisation, bien sûr:

import functools

def log(logger, level='info'):
    def log_decorator(fn):
        @functools.wraps(fn)
        def wrapper(*a, **kwa):
            getattr(logger, level)(fn.__name__)
            return fn(*a, **kwa)
        return wrapper
    return log_decorator

# later that day ...
@log(logging.getLogger('main'), level='warning')
def potentially_dangerous_function(times):
    for _ in xrange(times): rockets.get_rocket(NUCLEAR=True).fire()




decorator