Funktionen mit und ohne Klammern aufrufen

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
chevallier
User
Beiträge: 22
Registriert: Sonntag 3. Mai 2015, 16:26

Hallo,

wieso führt beides zum gleichen Ergebnis (man achte auf die Klammern hinter der Funktion, Zeile 3 und 7):

Code: Alles auswählen

def outerdec(func):
	def innerdec():
		return func + 1
	return innerdec()
def normalfunc():
	return 3
newfunc=outerdec(normalfunc())
print(newfunc)
:: UND ::

Code: Alles auswählen

def outerdec(func):
	def innerdec():
		return func() + 1
	return innerdec()
def normalfunc():
	return 3
newfunc=outerdec(normalfunc)
print(newfunc)
Wie kann man logisch an die Sache herangehen? Wieso gehts mal ohne, mal mit Klammern. Ich weiß, das hat etwas damit zu tun, dass Funktionen Objekte sind und man ohne Klammern nur die Objekte aufruft, aber trotzdem blicke ich da nicht durch.

Und wieso kriege ich eine Fehlermeldung wenn ich Versuche den Wrapper am Ende durch
print(newfunc())
aufzurufen (mit Klammern)?

Danke und Grüße,
Chevallier
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Scharf überlegen:

Code: Alles auswählen

outerdec(normalfunc)() 
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

chevallier hat geschrieben: Wie kann man logisch an die Sache herangehen? Wieso gehts mal ohne, mal mit Klammern. Ich weiß, das hat etwas damit zu tun, dass Funktionen Objekte sind und man ohne Klammern nur die Objekte aufruft, aber trotzdem blicke ich da nicht durch.
Im Grunde genommen ist das recht einfach und Du hast es ja schon erkannt: Der Name einer Funktion verweist auch nur auf das Funktionsobjekt, genau wie bei anderen Zuweisungen auch (Nicht durch die [zahl] verwirren lassen - das sind nur Anzeigen der Shell IPython!):

Code: Alles auswählen

In [3]: a = 4

In [4]: b = list()

In [5]: c = lambda x: x ** 2

In [7]: def foo():
   ...:     print("foo wurde aufgerufen")
   ...:

In [8]: d = foo

In [9]: foo
Out[9]: <function __main__.foo>

In [12]: d
Out[12]: <function __main__.foo>
Man spricht auch davon, dass ein Objekt an einen Namen "gebunden" wird. Bei Funktionen passiert das mittels ``def`` auf spezielle Art und Weise. Man kann das aber auch explizit erledigen, wie eben in [8] gezeigt, wo das Funktionsobjekt zusätzlich zum Namen ``foo`` auch an den Namen ``d`` gebunden wird.

Will man eine Funktion (oder Methode oder allgemein: *Callable*) ausführen, so teilt man das dem Interpreter mit, indem man ein rundes Klammernpaar hinter den Namen setzt:

Code: Alles auswählen

In [10]: c(3)
Out[10]: 9

In [11]: d()
foo wurde aufgerufen
Will man ein Funktions*objekt* als Parameter übergeben, so darf man es *nicht* aufrufen, sondern muss den Namen übergeben:

Code: Alles auswählen

In [13]: from operator import add, sub

In [15]: def operate(func, a, b):
   ....:     return func(a, b)
   ....:

In [16]: operate(add, 2, 1)
Out[16]: 3

In [17]: operate(sub, 2, 1)
Out[17]: 1
So einfach ist das und so ist das auch in Deinen Beispielen. Geh die mal *genau* durch, dann siehst Du, was passiert.

Eine Funktion erzeugen diese beiden Varianten nämlich *nicht* als Rückgabewert, weil jeweils an falscher Stelle der Funktionsaufruf erfolgt!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

chevallier hat geschrieben:Und wieso kriege ich eine Fehlermeldung wenn ich Versuche den Wrapper am Ende durch
print(newfunc())
aufzurufen (mit Klammern)?
Weil es noch niemand explizit gesagt hat: `outerdec(normalfunc())` verletzt die Annamen von `outerdec`, naemlich, dass es ein Funktionsobjekt bekommt. Den Fehler siehst du erst beim aufruf von `newfunc`, weil bis dahin nicht die Annahme ueberprueft wird.

In Zukunft bitte auch gleich die Fehlermeldung mitposten. Auch wenn du (noch) nicht viel mit dem Fehler anfangen kannst, dass man Zahlen nicht wie eine Funktion aufrufen kann, koennen es doch andere ;)
chevallier
User
Beiträge: 22
Registriert: Sonntag 3. Mai 2015, 16:26

@ Hyperion
Sehr gut erklärt, VIELEN DANK.
:idea: Die Frage, die man sich bei Funktions-Objekten stellen muss lautet:
Übergeben oder ausführen?

Dementsprechend muss der Code so aussehen:

Code: Alles auswählen

def outerdec(func):
	def innerdec():
		return func() + 1		# ausführen
	return innerdec			# übergeben
def normalfunc():
	return 3
newfunc=outerdec(normalfunc)	# übergeben
print(newfunc())				# ausführen
cofi hat geschrieben:In Zukunft bitte auch gleich die Fehlermeldung mitposten. Auch wenn du (noch) nicht viel mit dem Fehler anfangen kannst, dass man Zahlen nicht wie eine Funktion aufrufen kann, koennen es doch andere ;)
Wird gemacht.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

chevallier hat geschrieben: :idea: Die Frage, die man sich bei Funktions-Objekten stellen muss lautet: Übergeben oder ausführen?
Ich würde sagen zusammen mit dem Löschen sind das einfach die Optionen, die man mit Callables allgemein hat ;-)

Und nun kommen wir noch mal zur Decorator-Syntax:

Code: Alles auswählen

@outerdec
def answer():
    return 42

answer()
> 43
Der Dekoratorausdruck (über der Funktionsdefinition) entspricht also Deiner Zeile 7:

Code: Alles auswählen

newfunc = outerdec(normalfunc)
Es ist nur "syntaktischer Zucker" - nichts anderes :-)

@cofi: Jetzt hatte ich so viel geschrieben, dass mir die Frage zum Fehler vollkommen entgangen ist :mrgreen: Gut dass Du aufgepasst hast!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten