Methode einer Klasse als Argument einer anderen Methode verwenden

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
Gnus
User
Beiträge: 3
Registriert: Sonntag 22. November 2015, 12:04

Hallo zusammen!

In Python ist es ja möglich, als Argument einer Funktion eine andere Funktion anzugeben.

Code: Alles auswählen

def f(a):
    return a

def f2(b):
    return b

print(f2(f(2)))
Diese Eingabe wirft mir eine zwei aus. Ich würde dies gerne analog mit Methoden einer Klasse machen, scheitere daran aber. So habe ich mir das vorgestellt:

Code: Alles auswählen

class Test:

    def f(self):
        return "Hallo"
        
    def f2(self, x=f(self)):
        return x
Es erscheint "NameError: name 'self' is not defined", wenn ich self als argument angebe und "TypeError: f() missing 1 required positional argument: 'self'" wenn ich es nicht tue. Vermutlich ist es offensichtlich, warum das nicht funktioniert, aber ich habe leider noch nicht so viel Ahnung von Python. Ich erkenne irgendwie keinen Unterschied zwischen dem ersten und dem zweiten Beispiel. Wäre nett, wenn mir jemand einen Stoss in die richtige Richtung geben könnte.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Der entscheidende konzeptionelle Unterschied zwischen deinen Funktionen und den Funktionen in deiner Klasse ist, dass letztere keine Funktionen, sondern Methoden sind. Das heisst es ist Code der nur ausgefuehrt werden kann zusammen mit einer Instanz der dazugehoerigen Klasse. In Python wird das explizit verdeutlicht durch den ersten Parameter self (der auch pillepalle heissen koennte, aber die Konvention, von der man auch nicht abweichen sollte, ist halt self).

Und das ist auch sinnvoll so: eine Funktion zum oeffnen einen Tuer eines Autos muss wissen, auf welches Auto sich die Tueroeffnung beziehen soll.

Ein weiteres Puzzleteil ist die Frage, wann die Zeile "def f2(self...)" denn ausgefuehrt wird. Die darin angegebenen Namen sind entweder positionale Argumente (muessen also auch immer uebergeben werden beim Aufruf), oder benannte Argumente mit einem Standardwert. Und bei letzteren haben sich die Urvaeter von Python dafuer entschieden, dass diese Standardwerte *bei Definition* und nicht erst bei einem spaeteren Aufruf ausgewertet werden. Also nur einmal statt viele male. Das ist halt so (auch mit gutem Grund, aber das fuehrt zu weit).

Damit kannst du dich aber in dieser Zeile nicht auf self beziehen: das existiert ja erst, wenn du

Code: Alles auswählen

t = Test()
machst. Und wenn du self weglaesst, dann rufst du f auf, aber f ist eine Methode, und zu dem Zeitpunkt hast du genauso wenig eine Instanz.

was du tun kannst, ist folgendes (nur der relevante Teil dargestellt):

Code: Alles auswählen

def f2(self, a):
      return self.f(a)
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Gnus: Dein erstes Beispiel passt nicht zu Deiner Beschreibung. Zuerst wird `f(2)` ausgewertet und das Ergebnis an f2 übergeben. Was Du eigentlich zeigen wolltest ist:

Code: Alles auswählen

def some_function(a):
    return 2*a

def other_function(func, b):
    return func(b) + 3

print(other_function(some_function, b))
Und mit Instanzmethoden geht das genauso:

Code: Alles auswählen

class SomeClass:
    def some_method(self, a):
        return 2*a

    def other_method(self, func, b):
        return func(b) + 3

    def run(self):
        print(self.other_function(self.some_function, 5))
Gnus
User
Beiträge: 3
Registriert: Sonntag 22. November 2015, 12:04

Ich glaube ich habe mein Vorhaben nicht so gut rübergebracht. Aber die Antworten haben mir glaub ich dennoch schon geholfen. Was ich mit dem "x = f(self)" im zweiten Beispiel eigentlich erreichen wollte ist, dass ich ein Argument x habe, der als Standardwert die Methode f() hat. Der Benutzer soll also die Möglichkeit haben, die Methode f2(x) ohne Angabe eines Arguments auszuführen. die Angabe eines Arguments soll hier nur nötig werden, wenn der Benutzer wissen möchte, was passieren würde, wenn er vom Standardwert abweichen würde.

Code: Alles auswählen

class Test:

    def f(self):
        return "Hallo"
       
    def f2(self, x = None):
        if x == None:
            return self.f()
        else:
            return x
Ich glaube so könnte das funktionieren, sofern "None" keine sinnvolle Eingabe ist, der Benutzer also stets zum Beispiel eine Zahl eingeben müsste, wenn er vom Standardwert abweichen möchte. Ist das eine "saubere" Lösung? Irgendwie erscheint mir die Angabe eines unsinnigen Wertes zur Überprüfung, ob nun der Standardwert verwendet werden soll oder nicht, etwas gebastelt.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Ja, das ist der uebliche Weg. Bis auf den Punkt, das Vergleiche mit `None` ueblicherweise per Identitaet, also `is`, gemacht werden.

Ich wuerde `None` hier auch nicht als "unsinnig" oder kuenstlich bezeichnen, da es semantisch dafuer steht, dass ich tatsaechlich nichts uebergebe und damit eben den Default ausloese.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Gnus: Nein, das ist nicht gebastelt. Es ist der Standardweg in Python, wie man sowas macht. Allerdings sollte man gegen None nicht mittels == testen, sondern mittels is:

Code: Alles auswählen

if x is None:
    ...
Siehe auch PEP8.
In specifications, Murphy's Law supersedes Ohm's.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

cofi hat ja schon erwaehnt - das ist das uebliche Vorgehen.

Fuer die Trickkiste, falls man mal in der Situation ist, dass None zu den Werten der Domaene gehoert, sprich "erlaubt" ist:

Code: Alles auswählen

SENTINEL = object()

def foo(arg=SENTINEL):
      if arg is SENTINEL:
             ....
Der Trick ist den is-Operator zu verwenden (wie man das auch bei None machen soll), und ein Objekt SENTINEL zu erzeugen, welches garantiert eindeutig ist - es wird niemals einen anderen Wert geben, der an derselben Stelle im Speicher steht. Darum ist das eindeutig.

Der 99%-Fall ist allerdings None.
Gnus
User
Beiträge: 3
Registriert: Sonntag 22. November 2015, 12:04

Sehr schön, wieder was gelernt. Vielen Dank für die schnelle Hilfe!
Antworten