Wie funktionieren Decoratoren ?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Gast

Hallo,

ich habe einiges über Decoratoren gelesen, habe es aber nicht geschafft das in einem simplen Beispiel nachzubauen.

Meine Testversuch sollte das untenstehende mit -1 Decorieren.

Code: Alles auswählen

def adder_deco(a):
    return a -1

@adder
def adder(a,b)
    return a+b

Das funktioniert in dieser Art aber nicht.
Kann mir bitte jemand erklären wie man das korrekt einsetzt ?
Vielen Dank

Edit (BlackJack): Code in Python-Tags gesetzt.
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Erstmal: Dekoratoren bearbeiten eine Funktion. Das bedeutet: sie kriegen ein Funktionsobjekt, und geben auch eins zurück. In Deinem Dekorator probierst Du vom Funktionsobjekt 1 abzuziehen, das geht natürlich nicht, und liefert den TypeError den Du wahrscheinlich siehst.

Andere Frage: was willst Du genau machen? Bitte beschreibe einmal ein bisschen deutlicher was der Dekorator machen soll, damit man dann auch einen funktionierenden posten kann. Mit -1 dekorieren kann nämlich sehr viele Sachen bedeuten (die Formulierung ist also sehr schwammig), die nicht notwendigerweise etwas mit dekorieren, sondern vielleicht mit currying zu tun haben.

--- Heiko.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Da hat jemand meinen Keks gefressen ....


Also ich versuche, das Ergebnis, einer beliebigen Funktion nachträglich durch eine Andere Funktion Nachzubearbeiten.

In dem Beispiel, das Ergebnis der Funktion Adder mit 1 zu subtrahieren.

So wie ich Dekorieren verstanden habe, könnte ich den Decorator überall davorsetzen wenn ich das Ergebnis um 1 Verringern will.
Oder ist das falsch?

Was stellt "Currying" dar ?
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

Dekoratoren sind nur ein anderer Syntax für folgendes

Code: Alles auswählen

def Spam(eggs):
  ....

Spam = MeinDeco(Spam)
Spam wird also umdefiniert als das Ergebnis von MeinDeco, auf Spam angewendet. Dies kann man nun so schreiben:

Code: Alles auswählen

@MeinDeco
def Spam(eggs):
...
Grund für diese in meinen Augen misslungenen Syntax ist, das man in Python, um Class methods oder Static methods zu implementieren, man immer folgendes machen musste:

Code: Alles auswählen

class Foo:
  def meinestatic(x):
    return x
  meinestatic = staticmethod(x)
Dies sahen viele als unpytonisch an, weil man erst nach der Funktionsdefinition sieht, was sie wirklich ist.

Code: Alles auswählen

@blabla
def blubb()...
heisst nichts anderes: nachdem blubb definiert ist, ersetze blubb durch blabla(blubb)


Mit currying bezeichnet man die Sichtweise, dass man eine Funktion mit 2 Parametern die etwas zurückgibt als eine Funktion mit einem Parameter, die eine Funktion zurückgibt, auffassen kann.

Nehmen wir mal die polnische Notation für die Grundrechenarten an:

+ 3 4

Nun kann man sagen: + ist eine Funktion, die 2 Zahlen übernimmt, und einen Zahl zurückgibt, oder, Plus angewandt auf eine Zahl ergibt einen Operator, der, angewandt auf eine Zahl, die addition mit der ursprünglichen Zahl darstellt:

+ 3 wäre dann z.B. eine Funktion F , für die gilt:
F x = x+3

Daselbe gilt natürlich auch für 3 und mehr Parameter[/code]
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Ich glaube du willst das hier:

Code: Alles auswählen

>>> def adder_deco(f):
...     def wrapped(*args, **kwargs):
...             res = f(*args, **kwargs)
...             return res - 1
...     return wrapped
...
>>> @adder_deco
... def adder(a, b):
...     return a + b
...
>>> adder(1, 5)
5
Aber wie funktioniert das?
zunächst (wie bereits angemertk) macht @adder_deco hier nichts anderes als adder = adder_deco(adder)
also wird eine funktion übergeben. Dann wird in der decorator funktion wieder eine Funktion erstellt, die das neue Objekt ist, dass zurückgegen wird.
Da "f" weiterhin im Namensbereich der Funktion verfügbar ist (siehe [wiki=Closures_richtig_anwenden]Closures[/wiki]) kann man das dann wieder aufrufen, eins abziehen und dieses Ergebnis wieder zurückliefern.
TUFKAB – the user formerly known as blackbird
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Danke Joghurt ...


Ok, Currying ist praktisch lambda .... richtig ?


Aber ich fürchte das Dekorieren habe ich zwar im ansatz verstanden, bin mir aber unklar wie das in echt aussehen soll. - Oder ich habs dann doch nicht verstanden :shock:


EDIT : Mal mit dem Beispiel von BlackBird rumprobieren .... Danke :idea:
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Dein Dekorator hier wäre:

Code: Alles auswählen

def sub_deco(f):
    def sub_1(a,b):
        return f(a,b)-1
    return sub_1

@sub_deco
def adder(a,b):
    return a+b
Der Dekorator kriegt wie Dir auch schon ganz genau erklärt wurde die Funktion im Original (f), und gibt eine neue Funktion sub_1 zurück. Der Name adder wird dann an sub_1 gebunden. sub_1 ist aber eine Closure (da als innere Funktion definiert, das bedeutet die Funktion die im Programm adder heißt lebt als f in der Closure weiter (da das Argument von sub_deco()). Wenn jetzt adder() aufgerufen wird wird als erstes sub_1() mit den Argumenten a, b aufgerufen. Dieses ruft f(a,b) auf (also das ursprüngliche adder), und subtrahiert 1 von diesem Rückgabewert.

Currying würde bedeuten, dass ich eine Funktion adder_min1 erzeugen würde, die automatisch nur einen Parameter nimmt und -1 dazuaddiert.

Hoffe das erklärt Dekoratoren!

<edit>
PS: Ich hab nicht gesehen dass blackbird schon was gepostet hatte... ;-) Noja, doppelte Arbeit ist geteilte Arbeit. Oder so was.
</edit>

--- Heiko.
Mad-Marty
User
Beiträge: 317
Registriert: Mittwoch 18. Januar 2006, 19:46

Klasse, jetzt hab ichs verstanden.

Danke euch allen !! :D

Ein bisschen gewöhnungsbedürftig ist die Syntax aber schon ...
also ich finde es nicht offensichtlich, wie die syntax dazu aussieht.


Ich hätte von der Beschreibung eher die syntax erwartet, das man im dekoratur nur beschreibt was auf den return von der original funktion passieren soll. Bei weiteren nachdenken wäre das ganze dann aber nicht OO tauglich ....


EDIT : Modelnine, ich fand das schon gut, 2 beispiele sehen zu können. :lol:
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

Nein, Currying ist nicht lambda.

Python hat kein Currying eingebaut. Wenn du bei Haskell einer Funktion, die 4 Parameter nimmt, nur 3 übergibst, bekommst du als Rückgabewert eine Funktion, die einen Parameter nimmt und dann das Ergebnis der Ursprungsfunktion mit allen 4 Paramtern zurückgibt.

Python würde eine TypeError melden.

(Deswegen definiert man in Haskell add z.B. auch so:
Add :: Int->Int->Int

Add ist eine Funktion, die einen Int nimmt und eine Funktion F zurückgibt.
Wobei F eine Funktion ist die einen Int nimmt und einen Int zurückgibt.)

http://de.wikipedia.org/wiki/Currying (PS: Die englische Version ist besser)
Zuletzt geändert von Joghurt am Samstag 21. Januar 2006, 16:46, insgesamt 1-mal geändert.
modelnine
User
Beiträge: 670
Registriert: Sonntag 15. Januar 2006, 18:42
Wohnort: Celle
Kontaktdaten:

Du meinst nicht die Syntax, sondern die Semantik eines Dekorators.

Die Syntax beschreibt nur @<blah> (was tatsächlich auch IMHO nicht "intuitiv" ist), die Semantik die Funktionsweise. Aus diesem Grund kriegst Du auch keinen kompilier Fehler (SyntaxError) bei Deinem Dekorator, sondern während der Laufzeit einen Fehler. Laufzeitfehler sind semantisch.

--- Heiko.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Joghurt hat geschrieben:Python hat kein Currying eingebaut. Wenn du bei Haskell einer Funktion, die 4 Parameter nimmt, nur 3 übergibst, bekommst du als Rückgabewert eine Funktion, die einen Parameter nimmt und dann das Ergebnis der Ursprungsfunktion mit allen 4 Paramtern zurückgibt.
Naja, Python baut nach und nach immer mehr Features aus Funktionalen Programmiersprachen ein, siehe PEP 309 in Python 2.5.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

hat != wird niemals haben
BlackJack

Naja, das ist aber nur ein Modul mit einer Klasse und kein Sprachfeature. In Haskell ist "currying" ein Teil der Sprache.
N317V
User
Beiträge: 504
Registriert: Freitag 8. April 2005, 13:23
Wohnort: München

Nochmal zurück zum Decorator. Hab jetzt auch schon einiges gelesen u.a. das entsprechende PEP und http://de.wikipedia.org/wiki/Dekorierer für mich gehört das aber immernoch in die Kategorie "wenn Du nicht weißt wofür, brauchst Du's wahrscheinlich nicht". Oder verpass ich da was ganz Tolles?
Es gibt für alles eine rationale Erklärung.
Außerdem gibt es eine irrationale.

Wie man Fragen richtig stellt
BlackJack

Naja, man "braucht" sie ab und zu für `classmethod` oder `staticmethod`. Und einige Web-Frameworks machen ausgiebig Gebrauch davon. Letztlich ist es ja nur syntaktischer Zucker für Sachen die man voher auch ohne Dekoratoren gemacht hat, nun aber anders schreiben kann.
N317V
User
Beiträge: 504
Registriert: Freitag 8. April 2005, 13:23
Wohnort: München

BlackJack hat geschrieben:Naja, man "braucht" sie ab und zu für `classmethod` oder `staticmethod`.
OK, seh ich ein: die Frage war falsch gestellt. Ich hab schon nicht verstanden, wofür ich 'classmethod' und 'staticmethod' brauchen soll. Das einzige in der Doku, was mich aufhorchen lässt, ist der Satz
Guido van Rossum hat geschrieben:The instance is ignored except for its class.
Was mir das bringen soll, ist mir aber immernoch unklar. Vielleicht später, wenn der Kaffee wirkt... :)
Es gibt für alles eine rationale Erklärung.
Außerdem gibt es eine irrationale.

Wie man Fragen richtig stellt
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Guckst du:

Code: Alles auswählen

>>> class C(object):
...     def a(self):
...             print self
...     @classmethod
...     def b(cls):
...             print cls
...     @staticmethod
...     def c():
...             pass
...
>>> i = C()
>>> i.a()
<__main__.C object at 0xb7d804ac>
>>> i.b()
<class '__main__.C'>
>>> i.c()
>>> C.a()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: unbound method a() must be called with C instance as first argument (got nothing instead)
>>> C.b()
<class '__main__.C'>
>>> i.c()
>>>
TUFKAB – the user formerly known as blackbird
N317V
User
Beiträge: 504
Registriert: Freitag 8. April 2005, 13:23
Wohnort: München

Schönes Beispiel, blackbird, danke schön! Wie es grundsätzlich funktioniert hab ich schon verstanden. Gibt es zu dieser Lösung aber auch ein Problem? Oder anders gefragt: was wäre denn ein konkreter Anwendungsfall? Irgendwie lässt mich meine Phantasie diesbezüglich im Stich. ;-(
Es gibt für alles eine rationale Erklärung.
Außerdem gibt es eine irrationale.

Wie man Fragen richtig stellt
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

TarFile in der Standardbibliothek nutzt das, z.B.

Sieh dir mal den Quellcode an
Antworten