Python-Dekorateure im Unterricht


140

Kann man so etwas schreiben wie:

class Test(object):
    def _decorator(self, foo):
        foo()

    @self._decorator
    def bar(self):
        pass

Dies schlägt fehl: self in @self ist unbekannt

Ich habe auch versucht:

@Test._decorator(self)

was auch fehlschlägt: Test unbekannt

Ich möchte einige Instanzvariablen im Dekorator vorübergehend ändern und dann die dekorierte Methode ausführen, bevor ich sie wieder ändere.

Antworten:


267

Würde so etwas tun, was Sie brauchen?

class Test(object):
    def _decorator(foo):
        def magic( self ) :
            print "start magic"
            foo( self )
            print "end magic"
        return magic

    @_decorator
    def bar( self ) :
        print "normal call"

test = Test()

test.bar()

Dies vermeidet den Aufruf von self, auf den Dekorator zuzugreifen, und lässt ihn als reguläre Methode im Klassennamensraum verborgen.

>>> import stackoverflow
>>> test = stackoverflow.Test()
>>> test.bar()
start magic
normal call
end magic
>>> 

bearbeitet, um Frage in Kommentaren zu beantworten:

Wie man den versteckten Dekorateur in einer anderen Klasse benutzt

class Test(object):
    def _decorator(foo):
        def magic( self ) :
            print "start magic"
            foo( self )
            print "end magic"
        return magic

    @_decorator
    def bar( self ) :
        print "normal call"

    _decorator = staticmethod( _decorator )

class TestB( Test ):
    @Test._decorator
    def bar( self ):
        print "override bar in"
        super( TestB, self ).bar()
        print "override bar out"

print "Normal:"
test = Test()
test.bar()
print

print "Inherited:"
b = TestB()
b.bar()
print

Ausgabe:

Normal:
start magic
normal call
end magic

Inherited:
start magic
override bar in
start magic
normal call
end magic
override bar out
end magic

7
Der Dekorateur oder die dekorierte Funktion? Beachten Sie, dass die zurückgegebene "magische" Funktion, die die Leiste umschließt, oben eine Selbstvariable empfängt, wenn "Leiste" für eine Instanz aufgerufen wird, und die Instanzvariablen, die sie vor und nach (oder sogar ob sie "Bar" anruft oder nicht, mit den angeforderten Instanzvariablen ändern kann. . Beim Deklarieren der Klasse gibt es keine Instanzvariablen. Wollten Sie der Klasse innerhalb des Dekorateurs etwas antun? Ich denke nicht, dass dies eine idiomatische Verwendung ist.
Michael Speer

1
Danke Michael, erst jetzt habe ich gesehen, dass ich das wollte.
18.

2
Ich finde diese Lösung viel schöner als die akzeptierte Antwort, da sie die Verwendung der @ decorator-Syntax am Definitionspunkt ermöglicht. Wenn ich am Ende des Kurses Dekorationsaufrufe einfügen muss, ist nicht klar, dass die Funktionen dekoriert werden. Es ist ein bisschen seltsam, dass Sie @staticmethod nicht für den Dekorateur selbst verwenden können, aber es funktioniert zumindest.
mgiuca

1
Ich glaube nicht, dass es funktioniert, wenn ich eine geerbte Klasse von Test erstelle. Zum Beispiel: Klasse TestB (Test): @_decorator def foobar (self): print "adsf" Gibt es eine Problemumgehung?
Extraeee

1
@extraeee: Überprüfen Sie die von mir vorgenommene Bearbeitung. Sie müssen den angegebenen Dekorateur als statische Methode qualifizieren, aber erst, nachdem Sie ihn verwendet haben (oder die statische Methodenversion einem anderen Namen zugewiesen haben)
Michael Speer

58

Was Sie tun möchten, ist nicht möglich. Nehmen Sie zum Beispiel an, ob der folgende Code gültig aussieht oder nicht:

class Test(object):

    def _decorator(self, foo):
        foo()

    def bar(self):
        pass
    bar = self._decorator(bar)

Es ist natürlich nicht gültig, da selfes zu diesem Zeitpunkt noch nicht definiert ist. Das Gleiche gilt, Testda es erst definiert wird, wenn die Klasse selbst definiert ist (die gerade bearbeitet wird). Ich zeige Ihnen dieses Code-Snippet, weil sich Ihr Dekorator-Snippet in dieses verwandelt.

Wie Sie sehen, ist der Zugriff auf die Instanz in einem solchen Dekorator nicht wirklich möglich, da Dekoratoren während der Definition der Funktion / Methode angewendet werden, mit der sie verbunden sind, und nicht während der Instanziierung.

Wenn Sie Zugriff auf Klassenebene benötigen , versuchen Sie Folgendes:

class Test(object):

    @classmethod
    def _decorator(cls, foo):
        foo()

    def bar(self):
        pass
Test.bar = Test._decorator(Test.bar)

5
sollte wahrscheinlich aktualisiert werden, um auf die genauere Antwort unten zu verweisen
Nathan Buesgens

1
Nett. Ihre Prosa sagt unmöglich, aber Ihr Code zeigt ziemlich genau, wie es geht.
Mad Physicist

22
import functools


class Example:

    def wrapper(func):
        @functools.wraps(func)
        def wrap(self, *args, **kwargs):
            print("inside wrap")
            return func(self, *args, **kwargs)
        return wrap

    @wrapper
    def method(self):
        print("METHOD")

    wrapper = staticmethod(wrapper)


e = Example()
e.method()

1
TypeError: Das Objekt 'staticmethod' kann nicht
aufgerufen werden

@ Wyx ruf nicht den Dekorateur an. Zum Beispiel sollte es @foonicht sein@foo()
docyoda

Sollte nicht das erste Argument wrappersein self?
CpILL

7

Ich verwende diese Art von Dekorator in einigen Debugging-Situationen. Sie ermöglicht das Überschreiben von Klasseneigenschaften durch Dekorieren, ohne dass die aufrufende Funktion gefunden werden muss.

class myclass(object):
    def __init__(self):
        self.property = "HELLO"

    @adecorator(property="GOODBYE")
    def method(self):
        print self.property

Hier ist der Dekorationscode

class adecorator (object):
    def __init__ (self, *args, **kwargs):
        # store arguments passed to the decorator
        self.args = args
        self.kwargs = kwargs

    def __call__(self, func):
        def newf(*args, **kwargs):

            #the 'self' for a method function is passed as args[0]
            slf = args[0]

            # replace and store the attributes
            saved = {}
            for k,v in self.kwargs.items():
                if hasattr(slf, k):
                    saved[k] = getattr(slf,k)
                    setattr(slf, k, v)

            # call the method
            ret = func(*args, **kwargs)

            #put things back
            for k,v in saved.items():
                setattr(slf, k, v)

            return ret
        newf.__doc__ = func.__doc__
        return newf 

Hinweis: Da ich einen Klassendekorator verwendet habe, müssen Sie @adecorator () mit den Klammern verwenden , um Funktionen zu dekorieren, auch wenn Sie dem Klassenkonstruktor des Dekorators keine Argumente übergeben.


7

Dies ist eine Möglichkeit, selfvon innerhalb einer decoratordefinierten Klasse auf dieselbe Klasse zuzugreifen (und diese verwendet zu haben) :

class Thing(object):
    def __init__(self, name):
        self.name = name

    def debug_name(function):
        def debug_wrapper(*args):
            self = args[0]
            print 'self.name = ' + self.name
            print 'running function {}()'.format(function.__name__)
            function(*args)
            print 'self.name = ' + self.name
        return debug_wrapper

    @debug_name
    def set_name(self, new_name):
        self.name = new_name

Ausgabe (getestet am Python 2.7.10):

>>> a = Thing('A')
>>> a.name
'A'
>>> a.set_name('B')
self.name = A
running function set_name()
self.name = B
>>> a.name
'B'

Das obige Beispiel ist albern, aber es funktioniert.


4

Ich habe diese Frage gefunden, als ich ein sehr ähnliches Problem untersucht habe. Meine Lösung besteht darin, das Problem in zwei Teile aufzuteilen. Zunächst müssen Sie die Daten erfassen, die Sie den Klassenmethoden zuordnen möchten. In diesem Fall ordnet handler_for dem Handler einen Unix-Befehl für die Ausgabe dieses Befehls zu.

class OutputAnalysis(object):
    "analyze the output of diagnostic commands"
    def handler_for(name):
        "decorator to associate a function with a command"
        def wrapper(func):
            func.handler_for = name
            return func
        return wrapper
    # associate mount_p with 'mount_-p.txt'
    @handler_for('mount -p')
    def mount_p(self, slurped):
        pass

Nachdem wir jeder Klassenmethode einige Daten zugeordnet haben, müssen wir diese Daten sammeln und in einem Klassenattribut speichern.

OutputAnalysis.cmd_handler = {}
for value in OutputAnalysis.__dict__.itervalues():
    try:
        OutputAnalysis.cmd_handler[value.handler_for] = value
    except AttributeError:
        pass

4

Hier ist eine Erweiterung der Antwort von Michael Speer, um noch ein paar Schritte weiter zu gehen:

Ein Instanzmethoden-Dekorator, der Argumente akzeptiert und auf eine Funktion mit Argumenten und einem Rückgabewert einwirkt.

class Test(object):
    "Prints if x == y. Throws an error otherwise."
    def __init__(self, x):
        self.x = x

    def _outer_decorator(y):
        def _decorator(foo):
            def magic(self, *args, **kwargs) :
                print("start magic")
                if self.x == y:
                    return foo(self, *args, **kwargs)
                else:
                    raise ValueError("x ({}) != y ({})".format(self.x, y))
                print("end magic")
            return magic

        return _decorator

    @_outer_decorator(y=3)
    def bar(self, *args, **kwargs) :
        print("normal call")
        print("args: {}".format(args))
        print("kwargs: {}".format(kwargs))

        return 27

Und dann

In [2]:

    test = Test(3)
    test.bar(
        13,
        'Test',
        q=9,
        lollipop=[1,2,3]
    )
    
    start magic
    normal call
    args: (13, 'Test')
    kwargs: {'q': 9, 'lollipop': [1, 2, 3]}
Out[2]:
    27
In [3]:

    test = Test(4)
    test.bar(
        13,
        'Test',
        q=9,
        lollipop=[1,2,3]
    )
    
    start magic
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-3-576146b3d37e> in <module>()
          4     'Test',
          5     q=9,
    ----> 6     lollipop=[1,2,3]
          7 )

    <ipython-input-1-428f22ac6c9b> in magic(self, *args, **kwargs)
         11                     return foo(self, *args, **kwargs)
         12                 else:
    ---> 13                     raise ValueError("x ({}) != y ({})".format(self.x, y))
         14                 print("end magic")
         15             return magic

    ValueError: x (4) != y (3)

3

Dekorateure scheinen besser geeignet zu sein, die Funktionalität eines gesamten Objekts (einschließlich Funktionsobjekte) im Vergleich zur Funktionalität einer Objektmethode zu ändern , die im Allgemeinen von Instanzattributen abhängt. Beispielsweise:

def mod_bar(cls):
    # returns modified class

    def decorate(fcn):
        # returns decorated function

        def new_fcn(self):
            print self.start_str
            print fcn(self)
            print self.end_str

        return new_fcn

    cls.bar = decorate(cls.bar)
    return cls

@mod_bar
class Test(object):
    def __init__(self):
        self.start_str = "starting dec"
        self.end_str = "ending dec" 

    def bar(self):
        return "bar"

Die Ausgabe ist:

>>> import Test
>>> a = Test()
>>> a.bar()
starting dec
bar
ending dec

1

Sie können den Dekorateur dekorieren:

import decorator

class Test(object):
    @decorator.decorator
    def _decorator(foo, self):
        foo(self)

    @_decorator
    def bar(self):
        pass

1

Ich habe eine Implementierung von Dekorateuren, die helfen könnten

    import functools
    import datetime


    class Decorator(object):

        def __init__(self):
            pass


        def execution_time(func):

            @functools.wraps(func)
            def wrap(self, *args, **kwargs):

                """ Wrapper Function """

                start = datetime.datetime.now()
                Tem = func(self, *args, **kwargs)
                end = datetime.datetime.now()
                print("Exection Time:{}".format(end-start))
                return Tem

            return wrap


    class Test(Decorator):

        def __init__(self):
            self._MethodName = Test.funca.__name__

        @Decorator.execution_time
        def funca(self):
            print("Running Function : {}".format(self._MethodName))
            return True


    if __name__ == "__main__":
        obj = Test()
        data = obj.funca()
        print(data)

1

Erklären Sie in der inneren Klasse. Diese Lösung ist ziemlich solide und wird empfohlen.

class Test(object):
    class Decorators(object):
    @staticmethod
    def decorator(foo):
        def magic(self, *args, **kwargs) :
            print("start magic")
            foo(self, *args, **kwargs)
            print("end magic")
        return magic

    @Decorators.decorator
    def bar( self ) :
        print("normal call")

test = Test()

test.bar()

Das Ergebnis:

>>> test = Test()
>>> test.bar()
start magic
normal call
end magic
>>> 
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.