Quali sono alcuni usi comuni per i decoratori di Python? [chiuso]


337

Mentre mi piace pensare a me stesso come un programmatore Python ragionevolmente competente, un aspetto del linguaggio che non sono mai stato in grado di comprendere è il decoratore.

So cosa sono (superficialmente), ho letto tutorial, esempi, domande su Stack Overflow e capisco la sintassi, posso scrivere il mio, ogni tanto uso @classmethod e @staticmethod, ma non mi viene mai in mente di usare un decoratore per risolvere un problema nel mio codice Python. Non ho mai riscontrato un problema in cui penso "Hmm ... questo sembra un lavoro per un decoratore!"

Quindi, mi chiedo se voi ragazzi potreste offrire alcuni esempi di dove avete usato decoratori nei vostri programmi, e spero che avrò un "A-ha!" momento e ottenerli .


5
Inoltre, i decoratori sono utili per Memoizing, ovvero memorizzare nella cache un risultato di calcolo lento di una funzione. Il decoratore può restituire una funzione che controlla gli input e, se sono già stati presentati, restituisce un risultato memorizzato nella cache.
Peter,

1
Si noti che Python ha un decoratore incorporato functools.lru_cache, che fa esattamente quello che ha detto Peter, dal Python 3.2, rilasciato nel febbraio 2011.
Taegyung,

Il contenuto della libreria Python Decorator dovrebbe darti una buona idea di altri usi per loro.
martineau,

Risposte:


126

Uso i decoratori principalmente per scopi di cronometraggio

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
Sotto Unix, time.clock()misura il tempo della CPU. È possibile utilizzare time.time()invece se si desidera misurare il tempo dell'orologio da parete.
Jabba,

20
Ottimo esempio! Non ho idea di cosa faccia però. Una spiegazione di cosa stai facendo lì e di come il decoratore risolva il problema sarebbe molto bella.
MeLight,

7
Bene, misura il tempo necessario per myFunctionl'esecuzione ...
RSabet,

98

Li ho usati per la sincronizzazione.

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

Come sottolineato nei commenti, da Python 2.5 è possibile utilizzare withun'istruzione in combinazione con un oggetto threading.Lock(o multiprocessing.Lockdalla versione 2.6) per semplificare l'implementazione del decoratore solo per:

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

Indipendentemente da ciò, lo usi in questo modo:

import threading
lock = threading.Lock()

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

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

Fondamentalmente mette lock.acquire()/ lock.release()su entrambi i lati della chiamata di funzione.


18
Forse giustificato, ma i decoratori sono intrinsecamente confusi, esp. ai novizi del primo anno che vengono dietro di te e cercano di modificare il tuo codice. Evitatelo con semplicità: basta che do_something () racchiuda il suo codice in un blocco in "with lock:" e tutti possono vedere chiaramente il vostro scopo. I decoratori sono ampiamente abusati da persone che vogliono sembrare intelligenti (e molti in realtà lo sono), ma poi il codice arriva ai semplici mortali e si esaurisce.
Kevin J. Rice,

18
@ KevinJ.Rice Vincolare il tuo codice in modo che i "primi anni" possano capire meglio che è una pratica terribile. La sintassi di Decorator è molto più facile da leggere e disaccoppia notevolmente il codice.
TaylerJones,

18
@TaylerJones, la leggibilità del codice è quasi la mia massima priorità durante la scrittura. Il codice viene letto più di 7 volte ogni volta che viene modificato. Il codice difficile da capire (per i sostenitori o per gli esperti che lavorano sotto la pressione del tempo) è un debito tecnico che deve essere pagato ogni volta che qualcuno visita l'albero delle fonti.
Kevin J. Rice,

@TaylerJones Uno dei compiti più importanti per un programmatore è fornire chiarezza.
JDOaktown,

71

Uso i decoratori per i parametri di controllo del tipo che vengono passati ai miei metodi Python tramite alcuni RMI. Quindi, invece di ripetere lo stesso conteggio dei parametri, aumentare le eccezioni mumbo-jumbo ancora e ancora.

Ad esempio, anziché:

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

Dichiaro solo:

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

e accepts()fa tutto il lavoro per me.


15
Per chiunque sia interessato, c'è un'implementazione di @acceptsin PEP 318.
martineau,

2
Penso che ci sia un refuso .. il primo metodo dovrebbe essere accettato .. hai dichiarato entrambi come "myMethod"
DevC

1
@DevC No, non sembra un errore di battitura. Dal momento che chiaramente non è un'implementazione di "accetta (..)", e qui "accetta (..)" fa il lavoro che altrimenti verrebbe svolto dalle due righe all'inizio di "myMethod (..)" - questo è il unica interpretazione che si adatta.
Evgeni Sergeev,

1
Ci scusiamo per il bump, volevo solo sottolineare che il controllo del tipo di argomenti passati e sollevare un TypeError altrimenti è considerato una cattiva pratica perché non accetterà ad esempio un int se controlla solo i float e perché normalmente il il codice stesso dovrebbe adattarsi a diversi tipi di valori passati per la massima flessibilità.
Gustavo6046,

2
Il modo consigliato di fare il controllo dei tipi in Python è attraverso il built-in isinstance()funzione, come si fa nel PEP 318 implementazione del decoratore. Dato che il suo classinfoargomento può essere uno o più tipi, usarlo mitigherebbe anche le obiezioni (valide) di @ Gustavo6046. Python ha anche una Numberclasse base astratta, quindi isinstance(42, numbers.Number)sono possibili test molto generici .
martineau,

48

I decoratori vengono utilizzati per tutto ciò che si desidera "avvolgere" in modo trasparente con funzionalità aggiuntive.

Django li usa per racchiudere la funzionalità "login richiesto" nelle funzioni di visualizzazione , nonché per registrare le funzioni di filtro .

È possibile utilizzare i decoratori di classi per aggiungere registri con nome alle classi .

Qualsiasi funzionalità sufficientemente generica da "attaccare" al comportamento di una classe o di una funzione esistente è un gioco equo per la decorazione.

C'è anche una discussione sui casi d'uso nel newsgroup Python-Dev indicato da PEP 318 - Decoratori per funzioni e metodi .


Cherrypy usa @ cherrypy.expose per mantenere le funzioni pubbliche e quelle nascoste. Questa è stata la mia prima introduzione e mi ci sono abituato lì.
Marc Maxmeister,

26

Per i nosetest, puoi scrivere un decoratore che fornisce una funzione o un metodo di unit test con diversi set di parametri:

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

23

La libreria Twisted utilizza decoratori combinati con generatori per dare l'illusione che una funzione asincrona sia sincrona. Per esempio:

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

Usando questo, il codice che sarebbe stato suddiviso in tonnellate di piccole funzioni di callback può essere scritto in modo abbastanza naturale come un singolo blocco, rendendo molto più facile da capire e mantenere.


14

Un uso ovvio è per la registrazione, ovviamente:

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

Li uso principalmente per il debug (wrapper attorno a una funzione che stampa i suoi argomenti e risultati) e la verifica (ad esempio per verificare se un argomento è di tipo corretto o, nel caso di un'applicazione Web, se l'utente ha i privilegi sufficienti per chiamare un particolare metodo).


6

Sto usando il seguente decoratore per creare una funzione thread-safe. Rende il codice più leggibile. È quasi simile a quello proposto da John Fouhy, ma la differenza è che si lavora su una singola funzione e che non è necessario creare esplicitamente un oggetto 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

1
Questo significa che ogni funzione, così decorata, ha il suo lucchetto?
addolorarsi il

1
@grieve sì, ogni volta che viene utilizzato (chiamato) il decoratore crea un nuovo oggetto di blocco per la funzione / metodo che viene decorato.
martineau,

5
È davvero pericoloso. Il metodo inc_var () è "threadsafe" in quanto solo una persona alla volta può chiamarlo. Detto questo, poiché il metodo opera sulla variabile membro "var" e presumibilmente anche altri metodi possono operare sulla variabile membro "var" e tali accessi non sono sicuri poiché il blocco non è condiviso. In questo modo, l'utente della classe X ha un falso senso di sicurezza.
Bob Van Zant,

Non è sicuro per i thread fino a quando non viene utilizzato il blocco singolo.
Chandu

5

I decoratori vengono utilizzati per definire le proprietà di una funzione o come boilerplate che la altera; è possibile ma controintuitivo per loro restituire funzioni completamente diverse. Osservando le altre risposte qui, sembra che uno degli usi più comuni sia quello di limitare l'ambito di qualche altro processo: registrazione, profilazione, controlli di sicurezza, ecc.

CherryPy utilizza il dispacciamento degli oggetti per abbinare gli URL agli oggetti e, eventualmente, ai metodi. I decoratori di questi metodi segnalano se CherryPy è autorizzato o meno a utilizzare tali metodi. Ad esempio, adattato dal tutorial :

class HelloWorld:

    ...

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

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

cherrypy.quickstart(HelloWorld())

Questo non è vero. Un decoratore può cambiare completamente il comportamento di una funzione.
ricorsivo il

Va bene. Ma quanto spesso un decoratore "modifica completamente il comportamento di una funzione?" Da quello che ho visto, quando non vengono utilizzati per specificare le proprietà, vengono utilizzati solo per il codice della caldaia. Ho modificato la mia risposta.
Nikhil Chelliah,

5

Li ho usati di recente, mentre lavoravo su un'applicazione web di social network. Per la comunità / i gruppi, avrei dovuto autorizzare l'adesione a creare nuove discussioni e rispondere a un messaggio che devi essere membro di quel particolare gruppo. Quindi, ho scritto un decoratore @membership_requirede l'ho messo dove avevo richiesto dal mio punto di vista.


1

Uso questo decoratore per correggere i parametri

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

questo scritto quando refactoring alcune funzioni devono passare l'argomento "wanN" ma nei miei vecchi codici, ho passato solo N o "N"


1

Decorator può essere utilizzato per creare facilmente variabili del metodo di funzione.

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
Grazie per il tuo esempio, ma (apolgie) devo dire WTF: perché dovresti usare questo? Ha un enorme potenziale per confondere le persone. Ovviamente, rispetto le esigenze per gli usi del caso limite, ma stai affrontando un problema comune che molti sviluppatori Python inesperti hanno - non usare abbastanza le classi. Cioè, basta avere una semplice var di classe di conteggio, inizializzarla e usarla. Noobs tende a scrivere drop-thru (codice non di classe) e cerca di far fronte alla mancanza di funzionalità di classe con soluzioni elaborate. Per favore no? Per favore? mi dispiace arpa, grazie per la tua risposta, ma hai premuto un tasto di scelta rapida per me.
Kevin J. Rice,

Sarei -1 su questo se si presentasse come una richiesta pull per me di rivedere il codice, e quindi sono anche -1 su questo come buon pitone.
Techdragon,

Carina. Stupido, ma carino. :) Non mi dispiace l'attributo di funzione occasionale, ma sono una cosa così rara nel tipico codice Python che se ne userò uno, preferirei farlo esplicitamente, piuttosto che nasconderlo sotto un decoratore.
PM 2Ring
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.