Quelles sont les utilisations courantes des décorateurs Python? [fermé]


337

Bien que j'aime à me considérer comme un codeur Python raisonnablement compétent, un des aspects du langage que je n'ai jamais réussi à comprendre est celui des décorateurs.

Je sais ce que c'est (superficiellement), j'ai lu des tutoriels, des exemples, des questions sur Stack Overflow, et je comprends la syntaxe, je peux écrire la mienne, parfois utiliser @classmethod et @staticmethod, mais il ne m'est jamais venu à l'esprit d'utiliser un 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 offrir des exemples où vous avez utilisé des décorateurs dans vos propres programmes, et j'espère que j'aurai un "A-ha!" moment et les obtenir .


5
En outre, les décorateurs sont utiles pour la mémorisation, c'est-à-dire la mise en cache d'un résultat lent à calculer d'une fonction. Le décorateur peut renvoyer une fonction qui vérifie les entrées et, si elles ont déjà été présentées, retourner un résultat mis en cache.
Peter

1
Notez que Python a un décorateur intégré functools.lru_cache, qui fait exactement ce que Peter a dit, depuis Python 3.2, sorti en février 2011.
Taegyung

Le contenu de la bibliothèque Python Decorator devrait vous donner une bonne idée de leurs autres utilisations.
martineau

Réponses:


126

J'utilise des décorateurs principalement à des fins de synchronisation

def time_dec(func):

  def wrapper(*arg):
      t = time.clock()
      res = func(*arg)
      print func.func_name, time.clock()-t
      return res

  return wrapper


@time_dec
def myFunction(n):
    ...

13
Sous Unix, time.clock()mesure le temps CPU. Vous voudrez peut-être utiliser à la time.time()place si vous souhaitez mesurer l'heure de l'horloge murale.
Jabba

20
Excellent exemple! Aucune idée de ce qu'il fait cependant. Une explication de ce que vous faites là-bas et de la façon dont le décorateur résout le problème serait très agréable.
MeLight

7
Eh bien, il mesure le temps qu'il faut pour myFunctioncourir ...
RSabet

98

Je les ai utilisés pour la synchronisation.

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(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 withinstruction en conjonction avec un objet threading.Lock(ou multiprocessing.Lockdepuis la version 2.6) pour simplifier l'implémentation du décorateur pour simplement:

import functools

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

Quoi qu'il en soit, 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 place simplement lock.acquire()/ lock.release()de chaque côté de l'appel de fonction.


18
Peut-être justifié, mais les décorateurs sont intrinsèquement déroutants, en particulier. aux noobs de première année qui viennent derrière vous et tentent de modifier votre code. Évitez cela avec simplicité: faites simplement do_something () enfermer son code dans un bloc sous 'with lock:' et tout le monde peut clairement voir votre objectif. Les décorateurs sont largement surutilisés par les gens qui veulent paraître intelligents (et beaucoup le sont en fait), mais le code revient à de simples mortels et devient effacé.
Kevin J. Rice,

18
@ KevinJ.Rice Contraindre votre code pour que les «noobs de première année» puissent mieux comprendre que c'est une pratique terrible. La syntaxe de Decorator est beaucoup plus facile à lire et dissocie considérablement le code.
TaylerJones

18
@TaylerJones, la lisibilité du code est à peu près ma plus haute priorité lors de l'écriture. Le code est lu plus de 7 fois pour chaque modification. Le code difficile à comprendre (pour les noobs ou pour les experts qui travaillent sous la pression du temps) est une dette technique qui doit être payée chaque fois que quelqu'un visite l'arborescence source.
Kevin J. Rice,

@TaylerJones L'une des tâches les plus importantes pour un programmeur est de fournir de la clarté.
JDOaktown

71

J'utilise des décorateurs pour les paramètres de vérification de type qui sont passés à mes méthodes Python via certains RMI. Donc, au lieu de répéter le même comptage de paramètres, le mumbo-jumbo qui soulève des exceptions encore et encore.

Par exemple, au lieu de:

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

Je déclare simplement:

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

et accepts()fait tout le travail pour moi.


15
Pour toute personne intéressée, il y a une implémentation de @acceptsPEP 318.
martineau

2
Je pense qu'il y a une faute de frappe .. la première méthode devrait être acceptée .. vous avez déclaré les deux comme "myMethod"
DevC

1
@DevC Non, cela ne ressemble pas à une faute de frappe. Puisque ce n'est clairement pas une implémentation de "accepte (..)", et ici "accepte (..)" fait le travail qui serait autrement effectué par les deux lignes au début de "myMethod (..)" - c'est le seule interprétation qui convient.
Evgeni Sergeev

1
Désolé pour le bump, je voulais juste souligner que vérifier le type des arguments passés et déclencher une TypeError est sinon considéré comme une mauvaise pratique car il ne va pas accepter par exemple un int s'il ne vérifie que les flottants, et parce que normalement le le code lui-même doit s'adapter aux différents types de valeurs transmises pour une flexibilité maximale.
Gustavo6046

2
La méthode recommandée pour effectuer la vérification de type en Python est via la isinstance()fonction intégrée, comme cela se fait dans l' implémentation PEP 318 du décorateur. Puisque son classinfoargument peut être d'un ou plusieurs types, son utilisation atténuerait également les objections (valides) de @ Gustavo6046. Python a également une Numberclasse de base abstraite, donc des tests très génériques comme isinstance(42, numbers.Number)sont possibles.
martineau

48

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

Django les utilise pour encapsuler la fonctionnalité "connexion requise" sur les fonctions d'affichage , ainsi que pour enregistrer les fonctions de filtre .

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

Toute fonctionnalité suffisamment générique sur laquelle vous pouvez vous "accrocher" au comportement d'une classe ou d'une fonction existante est un jeu équitable pour la décoration.

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


Cherrypy utilise @ cherrypy.expose pour garder directement les fonctions publiques et les fonctions cachées. Ce fut ma première introduction et je m'y suis habitué.
Marc Maxmeister

26

Pour les tests noset, 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

23

La bibliothèque Twisted utilise des décorateurs combinés à 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()

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


14

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

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

10

Je les utilise principalement pour le débogage (wrapper autour d'une fonction qui affiche ses arguments et ses résultats) et la vérification (par exemple pour vérifier si un argument est de type correct ou, dans le cas d'une application Web, si l'utilisateur a les privilèges suffisants pour appeler un particulier méthode).


6

J'utilise le décorateur suivant pour créer une fonction threadsafe. Cela rend le code plus lisible. Elle est presque similaire à celle proposée 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 verrou.

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

1
Est-ce à dire que chaque fonction, ainsi décorée, a son propre cadenas?
pleurer le

1
@grieve yes, chaque fois que le décorateur est utilisé (appelé), il crée un nouvel objet de verrouillage pour la fonction / méthode en cours de décoration.
martineau

5
C'est vraiment dangereux. La méthode inc_var () est "threadsafe" en ce sens qu'une seule personne peut l'appeler à la fois. Cela dit, étant donné que la méthode fonctionne sur la variable membre "var" et que d'autres méthodes peuvent également fonctionner sur la variable membre "var" et que ces accès ne sont pas threadsafe car le verrou n'est pas partagé. Faire les choses de cette façon donne à l'utilisateur de la classe X un faux sentiment de sécurité.
Bob Van Zant

Ce n'est pas sûr pour le fil jusqu'à ce qu'un seul verrou soit utilisé.
Chandu

5

Les décorateurs sont utilisés soit pour définir les propriétés d'une fonction, soit comme passe-partout qui la modifie; il est possible mais contre-intuitif pour eux de retourner des fonctions complètement différentes. En regardant les autres réponses ici, il semble que l'une des utilisations les plus courantes consiste à limiter la portée d'un autre processus - que ce soit la journalisation, le profilage, les contrôles de sécurité, etc.

CherryPy utilise la répartition des objets pour faire correspondre les URL aux objets et, éventuellement, aux méthodes. Les décorateurs de ces méthodes indiquent si CherryPy est même autorisé à utiliser ces méthodes. Par exemple, adapté du tutoriel :

class HelloWorld:

    ...

    def secret(self):
        return "You shouldn't be here."

    @cherrypy.expose
    def index(self):
        return "Hello world!"

cherrypy.quickstart(HelloWorld())

Ce n'est pas vrai. Un décorateur peut changer complètement le comportement d'une fonction.
récursif le

D'accord. Mais à quelle fréquence un décorateur "change-t-il complètement le comportement d'une fonction?" D'après ce que j'ai vu, lorsqu'ils ne sont pas utilisés pour spécifier des propriétés, ils sont simplement utilisés pour le code passe-partout. J'ai modifié ma réponse.
Nikhil Chelliah

5

Je les ai utilisés récemment, tout en travaillant sur une application Web de réseautage social. Pour la communauté / les 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 membre de ce groupe particulier. J'ai donc écrit un décorateur @membership_requiredet je l'ai mis là où je le souhaitais à mon avis.


1

J'utilise ce décorateur pour fixer le paramètre

def fill_it(arg):
    if isinstance(arg, int):
        return "wan" + str(arg)
    else:
        try:
            # number present as string
            if str(int(arg)) == arg:
                return "wan" + arg
            else:
                # This should never happened
                raise Exception("I dont know this " + arg)
                print "What arg?"
        except ValueError, e:
            return arg

def fill_wanname(func):
    def wrapper(arg):
        filled = fill_it(arg)
        return func(filled)
    return wrapper

@fill_wanname
def get_iface_of(wanname):
    global __iface_config__
    return __iface_config__[wanname]['iface']

ceci écrit quand je refactorise certaines fonctions qui doivent passer l'argument "wanN" mais dans mes anciens codes, je ne passe que N ou 'N'


1

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

6
Merci pour votre exemple, mais (excuses) je dois dire WTF - Pourquoi utiliseriez-vous cela? Il a un énorme potentiel de confusion pour les gens. Bien sûr, je respecte les besoins d'utilisation des cas limites, mais vous rencontrez un problème commun à de nombreux développeurs Python inexpérimentés - n'utilisant pas suffisamment les classes. Autrement dit, il suffit d'avoir une simple classe var de count, de l'initialiser et de l'utiliser. Les noobs ont tendance à écrire drop-thru (code non basé sur une classe) et à essayer de faire face au manque de fonctionnalité de classe avec des solutions de contournement élaborées. Je vous en prie, non? S'il vous plaît? désolé de harpe, merci pour votre réponse, mais vous avez appuyé sur un hot-button pour moi.
Kevin J. Rice,

Je serais -1 à ce sujet si cela apparaissait comme une demande d'extraction pour que je révise le code, et donc je suis aussi -1 à ce sujet en bon python.
Techdragon

Mignonne. Idiot, mais mignon. :) Cela ne me dérange pas l'attribut de fonction occasionnelle, mais ils sont une chose si rare dans le code Python typique que si je vais en utiliser un, je préfère le faire explicitement, plutôt que de le cacher sous un décorateur.
PM 2Ring
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.