Was sind einige häufige Verwendungszwecke für Python-Dekoratoren? [geschlossen]


335

Während ich mich gerne als einigermaßen kompetenten Python-Codierer betrachte, ist ein Aspekt der Sprache, die ich nie beherrschen konnte, Dekorateure.

Ich weiß, was sie sind (oberflächlich), ich habe Tutorials, Beispiele und Fragen zum Stapelüberlauf gelesen und ich verstehe die Syntax, kann meine eigene schreiben, gelegentlich @classmethod und @staticmethod verwenden, aber mir fällt nie ein, a zu verwenden Dekorateur, um ein Problem in meinem eigenen Python-Code zu lösen. Ich stoße nie auf ein Problem, bei dem ich denke: "Hmm ... das sieht nach einem Job für einen Dekorateur aus!"

Ich frage mich also, ob ihr vielleicht einige Beispiele dafür anbietet, wo ihr Dekorateure in euren eigenen Programmen verwendet habt, und hoffentlich habe ich ein "A-ha!" Moment und hol sie dir .


5
Außerdem sind Dekorateure zum Auswendiglernen nützlich - das heißt, es wird ein langsam zu berechnendes Ergebnis einer Funktion zwischengespeichert. Der Dekorateur kann eine Funktion zurückgeben, die die Eingaben überprüft, und wenn sie bereits präsentiert wurden, ein zwischengespeichertes Ergebnis zurückgeben.
Peter

1
Beachten Sie, dass Python einen integrierten Dekorator hat functools.lru_cache, der genau das tut, was Peter seit Python 3.2 im Februar 2011
gesagt hat

Der Inhalt der Python Decorator-Bibliothek sollte Ihnen eine gute Vorstellung von anderen Verwendungsmöglichkeiten geben.
Martineau

Antworten:


126

Ich benutze Dekorateure hauptsächlich für Timing-Zwecke

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
Misst unter Unix time.clock()die CPU-Zeit. Möglicherweise möchten time.time()Sie stattdessen verwenden, wenn Sie die Wanduhrzeit messen möchten.
Jabba

20
Tolles Beispiel! Keine Ahnung was es macht. Eine Erklärung, was Sie dort tun und wie der Dekorateur das Problem löst, wäre sehr schön.
MeLight

7
Nun, es misst die Zeit, die es braucht, um myFunctionzu laufen ...
RSabet

98

Ich habe sie für die Synchronisation verwendet.

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

Wie in den Kommentaren erwähnt, können Sie seit Python 2.5 eine withAnweisung in Verbindung mit einem Objekt threading.Lock(oder multiprocessing.Lockseit Version 2.6) verwenden, um die Implementierung des Dekorateurs auf Folgendes zu vereinfachen:

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

Unabhängig davon verwenden Sie es dann wie folgt:

import threading
lock = threading.Lock()

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

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

Grundsätzlich wird nur lock.acquire()/ lock.release()auf beiden Seiten des Funktionsaufrufs platziert.


17
Möglicherweise gerechtfertigt, aber Dekorateure sind von Natur aus verwirrend, insb. an Noobs im ersten Jahr, die hinter dir stehen und versuchen, deinen Code zu modifizieren. Vermeiden Sie dies einfach: Lassen Sie do_something () seinen Code in einen Block unter 'with lock:' einschließen, und jeder kann Ihren Zweck klar erkennen. Dekorateure werden von Leuten, die klug erscheinen wollen (und viele sind es tatsächlich), stark überbeansprucht, aber dann kommt der Code zu bloßen Sterblichen und wird verwüstet.
Kevin J. Rice

18
@ KevinJ.Rice Den Code so einzuschränken, dass "Noobs im ersten Jahr" besser verstehen können, dass es eine schreckliche Praxis ist. Die Decorator-Syntax ist viel einfacher zu lesen und entkoppelt den Code erheblich.
TaylerJones

18
@TaylerJones, die Lesbarkeit von Code hat beim Schreiben fast meine höchste Priorität. Der Code wird bei jeder Änderung mehr als 7 Mal gelesen. Schwer zu verstehender Code (für Noobs oder für Experten, die unter Zeitdruck arbeiten) ist eine technische Schuld, die jedes Mal bezahlt werden muss, wenn jemand den Quellbaum besucht.
Kevin J. Rice

@TaylerJones Eine der wichtigsten Aufgaben eines Programmierers ist es, Klarheit zu schaffen.
JDOaktown

70

Ich verwende Dekoratoren zur Typprüfung von Parametern, die über ein RMI an meine Python-Methoden übergeben werden. Anstatt die gleiche Parameterzählung zu wiederholen, wird immer wieder Mumbo-Jumbo ausgelöst.

Zum Beispiel anstelle von:

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

Ich erkläre nur:

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

und accepts()erledigt die ganze Arbeit für mich.


15
Für alle Interessierten gibt es eine Implementierung von @acceptsin PEP 318.
Martineau

2
Ich denke, es gibt Tippfehler. Die erste Methode sollte akzeptiert werden. Sie haben beide als "myMethod"
deklariert

1
@ DevC Nein, es sieht nicht nach einem Tippfehler aus. Da dies eindeutig keine Implementierung von "accept (..)" ist und hier "accept (..)" die Arbeit erledigt, die sonst von den beiden Zeilen zu Beginn von "myMethod (..)" ausgeführt würde - das ist die nur Interpretation, die passt.
Evgeni Sergeev

1
Entschuldigung für die Beule, ich wollte nur darauf hinweisen, dass das Überprüfen des Typs der übergebenen Argumente und das Auslösen eines TypeError ansonsten als schlechte Praxis angesehen wird, da es zB ein int nicht akzeptiert, wenn es nur nach Floats sucht, und weil normalerweise das Der Code selbst sollte sich an verschiedene Arten von Werten anpassen, die für maximale Flexibilität übergeben werden.
Gustavo6046

2
Die empfohlene Methode zur Typprüfung in Python erfolgt über die integrierte isinstance()Funktion, wie sie in der PEP 318- Implementierung des Decorators durchgeführt wird. Da es sich bei dem classinfoArgument um einen oder mehrere Typen handeln kann, würde die Verwendung auch die (gültigen) Einwände von @ Gustavo6046 abschwächen. Python hat auch eine Numberabstrakte Basisklasse, so dass sehr generische Tests wie isinstance(42, numbers.Number)möglich möglich sind.
Martineau

48

Dekorateure werden für alles verwendet, was Sie transparent mit zusätzlichen Funktionen "umhüllen" möchten.

Django verwendet sie zum Umschließen der Funktionen "Anmeldung erforderlich" in Ansichtsfunktionen sowie zum Registrieren von Filterfunktionen .

Sie können Klassendekoratoren verwenden, um benannte Protokolle zu Klassen hinzuzufügen .

Jede ausreichend allgemeine Funktionalität, die Sie an das Verhalten einer vorhandenen Klasse oder Funktion "anheften" können, ist ein faires Spiel für die Dekoration.

Es gibt auch eine Diskussion über Anwendungsfälle in der Python-Dev-Newsgroup, auf die PEP 318 - Decorators for Functions and Methods hinweist .


Cherrypy verwendet @ cherrypy.expose, um genau zu bestimmen, welche Funktionen öffentlich und welche verborgen sind. Das war meine erste Einführung und ich habe mich dort daran gewöhnt.
Marc Maxmeister

26

Für Nasentests können Sie einen Dekorateur schreiben, der eine Unit-Test-Funktion oder -Methode mit mehreren Parametersätzen bereitstellt:

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

23

Die Twisted-Bibliothek verwendet Dekoratoren in Kombination mit Generatoren, um die Illusion zu erzeugen, dass eine asynchrone Funktion synchron ist. Zum Beispiel:

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

Auf diese Weise kann Code, der in eine Menge kleiner Rückruffunktionen aufgeteilt worden wäre, ganz natürlich als einzelner Block geschrieben werden, was das Verständnis und die Wartung erheblich vereinfacht.


14

Eine offensichtliche Verwendung ist natürlich die Protokollierung:

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

Ich verwende sie hauptsächlich zum Debuggen (Wrapper um eine Funktion, die ihre Argumente und Ergebnisse druckt) und zur Überprüfung (z. B. um zu überprüfen, ob ein Argument vom richtigen Typ ist oder ob der Benutzer im Fall einer Webanwendung über ausreichende Berechtigungen zum Aufrufen einer bestimmten Funktion verfügt Methode).


6

Ich verwende den folgenden Dekorator, um eine Funktion threadsicher zu machen. Dadurch wird der Code besser lesbar. Es ist fast ähnlich wie das von John Fouhy vorgeschlagene, aber der Unterschied besteht darin, dass man an einer einzelnen Funktion arbeitet und dass es nicht notwendig ist, ein Sperrobjekt explizit zu erstellen.

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
Bedeutet dies, dass jede so dekorierte Funktion ein eigenes Schloss hat?
Trauer

1
@grieve yes, jedes Mal, wenn der Dekorator verwendet (aufgerufen) wird, wird ein neues Sperrobjekt für die zu dekorierende Funktion / Methode erstellt.
Martineau

5
Das ist wirklich gefährlich. Die Methode inc_var () ist "threadsicher", da jeweils nur eine Person sie aufrufen kann. Das heißt, da die Methode mit der Mitgliedsvariablen "var" arbeitet und vermutlich auch andere Methoden mit der Mitgliedsvariablen "var" arbeiten können und diese Zugriffe nicht threadsicher sind, da die Sperre nicht gemeinsam genutzt wird. Wenn Sie dies auf diese Weise tun, erhält der Benutzer der Klasse X ein falsches Sicherheitsgefühl.
Bob Van Zant

Das ist nicht threadsicher, bis Single Lock verwendet wird.
Chandu

5

Dekorateure werden entweder verwendet, um die Eigenschaften einer Funktion zu definieren, oder als Boilerplate, das sie ändert. Es ist möglich, aber nicht intuitiv, völlig unterschiedliche Funktionen zurückzugeben. Wenn man sich die anderen Antworten hier ansieht, scheint es eine der häufigsten Anwendungen zu sein, den Umfang eines anderen Prozesses einzuschränken - sei es Protokollierung, Profilerstellung, Sicherheitsüberprüfungen usw.

CherryPy verwendet das Versenden von Objekten, um URLs mit Objekten und schließlich Methoden abzugleichen. Dekorateure dieser Methoden signalisieren, ob CherryPy diese Methoden überhaupt verwenden darf oder nicht . Zum Beispiel aus dem Tutorial angepasst :

class HelloWorld:

    ...

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

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

cherrypy.quickstart(HelloWorld())

Das ist nicht wahr. Ein Dekorateur kann das Verhalten einer Funktion vollständig ändern.
rekursiv

Okay. Aber wie oft ändert ein Dekorateur "das Verhalten einer Funktion vollständig"? Soweit ich gesehen habe, werden sie, wenn sie nicht zum Angeben von Eigenschaften verwendet werden, nur für Boilerplate-Code verwendet. Ich habe meine Antwort bearbeitet.
Nikhil Chelliah

5

Ich habe sie kürzlich verwendet, als ich an einer Webanwendung für soziale Netzwerke gearbeitet habe. Für Community / Gruppen sollte ich die Berechtigung zur Mitgliedschaft erteilen, um eine neue Diskussion zu erstellen und auf eine Nachricht zu antworten, dass Sie Mitglied dieser bestimmten Gruppe sein müssen. Also schrieb ich einen Dekorateur @membership_requiredund stellte das, was ich brauchte, in meine Sicht.


1

Ich benutze diesen Dekorator, um Parameter zu korrigieren

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

Dies wurde geschrieben, wenn ich einige Funktionen umgestalte, die das Argument "wanN" übergeben müssen, aber in meinen alten Codes habe ich nur N oder 'N' übergeben


1

Mit Decorator können auf einfache Weise Funktionsmethodenvariablen erstellt werden.

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
Vielen Dank für Ihr Beispiel, aber (apolgies) Ich muss WTF sagen - Warum sollten Sie dies verwenden? Es hat ein RIESIGES Potenzial, Menschen zu verwirren. Natürlich respektiere ich die Anforderungen für Edge-Case-Anwendungen, aber Sie stoßen auf ein häufiges Problem, das viele unerfahrene Python-Entwickler haben - nicht genügend Klassen zu verwenden. Das heißt, haben Sie einfach eine einfache Klasse var of count, initialisieren Sie sie und verwenden Sie sie. Noobs neigen dazu, Drop-Thru (nicht klassenbasierten Code) zu schreiben und versuchen, den Mangel an Klassenfunktionalität mit aufwändigen Problemumgehungen zu bewältigen. Bitte nicht? Bitte? Entschuldigung für die Harfe, danke für Ihre Antwort, aber Sie haben einen Hot-Button für mich gedrückt.
Kevin J. Rice

Ich würde diesbezüglich -1 sein, wenn es sich als Pull-Anfrage für mich zur Codeüberprüfung herausstellen würde, und daher bin ich auch -1 als gute Python.
Techdragon

Niedlich. Dumm, aber süß. :) Ich habe nichts gegen das gelegentliche Funktionsattribut, aber sie sind im typischen Python-Code so selten, dass ich es lieber explizit tun würde, wenn ich eines verwenden würde, als es unter einem Dekorateur zu verstecken.
PM 2Ring
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.