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.
Gast

Samstag 21. Januar 2006, 12:21

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:

Samstag 21. Januar 2006, 12:32

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

Samstag 21. Januar 2006, 13:31

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

Samstag 21. Januar 2006, 15:45

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:

Samstag 21. Januar 2006, 16:02

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

Samstag 21. Januar 2006, 16:03

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:

Samstag 21. Januar 2006, 16:09

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

Samstag 21. Januar 2006, 16:34

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

Samstag 21. Januar 2006, 16:36

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:

Samstag 21. Januar 2006, 16:38

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
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Samstag 21. Januar 2006, 21:38

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 Modvoice
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

Samstag 21. Januar 2006, 22:52

hat != wird niemals haben
BlackJack

Sonntag 22. Januar 2006, 23:20

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

Dienstag 21. März 2006, 16:50

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

Mittwoch 22. März 2006, 07:24

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.
Antworten