Allgemeine Verständnisfrage bzgl. der verschachtelten Funktion

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
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

da bin ich wieder. Ich habe mir ursprünglich bei stackoverflow How can I store multiple function as a value of the dictionary? Hilfe geholt. Es geht darum mehrere Funktionen in einem Wörterbuch zu speichern. Die Antwort ist sehr hilfreich, allerdings bin ich dabei, diesen verdichteten Quelltext aufzudröseln und zu kapieren. Dabei bin ich insebesondere bei Zeile 32 stecken geblieben. Die Frage habe in auf Englisch im Kommentar direkt dort hingeschrieben. Ich frage mich, wieso der f-Funktion zwei Argumenteparameter übergeben werden, die im Grunde leer sind? Wenn ich *args und **kwargs lösche, werden meine beiden Argumente (test_1 und test_2) trotzdem mitgeliefert und an die jeweiligen test-Funktionen übergeben.

Und wenn wir schon dabei sind. In der chain_funcs-Funktion ist ja eine weitere Funktion (call_funcs). Aber wieso führt chain_funcs die weitere Funktion nicht aus, sobald chain_funcs aufgerufen wird? Ich dachte immer, sobald eine Funktion aufgerufen wird, wird alles innerhalb des Korpus der Funktion ausgeführt. Alles was in der Funktion ist wird ausgeführt. Durch die print-Anweisungen sehe ich aber, dass chain_funcs ab Zeile 16 direkt zum return springt, dann die call_funcs()-Funktion zurückliefert. Und da in Zeile 53 die zurückgelieferte Funktion mittels der runden Klammern aufgerufen wird, so wird die call_funcs()-Funktion aufgerufen. Aber die chain_funcs()-Funktion selbst ruft beim Aufruf die call_funcs()-Funktion nicht auf.

Code: Alles auswählen

from functools import partial
            
def chain_funcs(*funcs):
    """This closure returns a callable to call multiple functions"""
    # *args is used to send a non-keyworded variable
    # length argument list to the function.
    
    print "STEP 01: chain_funcs is called"
    print "STEP 02: given *funcs", funcs # (<functools.partial object at 0x02DEA600>,
                          #  <functools.partial object at 0x02DEA690>)
                          
    print "STEP 03: given *funcs type", type(funcs) # <type 'tuple'>

    # chain_funcs skips this block and returns call_funcs
    
    def call_funcs(*args, **kwargs):
        """ Here a nested function is created. """
        # *args is used to send a non-keyworded variable
        # length argument list to the function and **kwargs
        # allows you to pass keyworded variable length of arguments
        # to a function
        
        print "STEP 04: call_funcs is called"
        print "STEP 05: given *args content", args # output: ()
        print "STEP 06: given *args type", type(args) # output: <type 'tuple'>
        print "STEP 07: given **kwargs content", kwargs # output: {}
        print "STEP 08: given *kwargs type", type(kwargs) # output: <type 'dict'>
        for f in funcs:
            print "STEP 09: f", f # output: <functools.partial object at 0x0190A600>
            print "STEP 10: f type", f # output: <functools.partial object at 0x0190A600>
            
            f(*args, **kwargs)  # <-- We know that *args and **kwargs are empty,
                                # but why are you giving these empty arguments?
                                # When I remove these arguments (*args and **kwargs),
                                # the given arguments 'test_1' and 'test_2' are still passed
                                # and printed.
                                
          
    print "STEP 11: return call funcs", call_funcs
    print "STEP 12: return call funcs type", type(call_funcs)
    return call_funcs

def test_1(arg_1 = None):
     print "printing from test_1 func with text: {}".format(arg_1)

def test_2(arg_2 = None):
     print "printing from test_2 func with text: {}".format(arg_2)

# Here we store multiple functions in a dictionary
dic = {'a': chain_funcs(partial(test_1, arg_1='test_1'),
                        partial(test_2, arg_2='test_2'))}

dic['a']() # First I use the the key to obtain its value.
           # When attempting to access the 'value' the chain_funcs-function is called.
           # The called function returns a callable-object.
           # An now the returned callable will be called - with round brackets.





Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das ist tatsächlich etwas verwirrend, da beim Antwortenden wohl die Annahme bestand, dass du verschiedene Funktionen neben ihren spezifischen Argumenten auch noch mit jeweils gleichen zusätzlichen Parametern aufrufen willst. Wenn du dieses Verhalten nicht benötigst, dann lässt es sich auch so vereinfachen:

Code: Alles auswählen

def chain_funcs(funcs):
    def caller():
        for func in funcs:
            func()
    return caller
Wobei funcs hier auch wieder Rückgaben von partial() sein können, um die Aufrufparameter vorab zu definieren. Die innere Funktion caller() ist halt nötig, weil sie in deinem Dict landet und später aufgerufen wird. Anders "weiß" der Code später ja nicht, welche Funktionen er aufrufen soll.

Oder nochmal anders erklärt: chain_funcs() erzeugt eine weitere Funktion, sobald es aufgerufen wird und der Sinn ist, diese neu erzeugte Funktion zurück zu geben. Die Funktion wird dabei nicht ausgeführt, sondern ihr Verhalten wird lediglich definiert. Die Ausführung des Codes erfolgt erst über dein Dict.
Sophus hat geschrieben:Ich dachte immer, sobald eine Funktion aufgerufen wird, wird alles innerhalb des Korpus der Funktion ausgeführt. Alles was in der Funktion ist wird ausgeführt.
Das ist richtig. Hier wird halt - abstrakt gesprochen - ein Code-Objekt erzeugt (könnte z.B. auch eine Klasse anstelle einer inneren Funktion sein) und dieses Objekt wird zurückgeliefert. Das ist ähnlich wie beim Erstellen von Quellcode. Der wird ja auch erst ausgeführt, wenn er explizit aufgerufen wird. Ist das Verhalten für dich jetzt etwas verständlicher geworden?
Zuletzt geändert von snafu am Montag 19. Februar 2018, 11:21, insgesamt 1-mal geändert.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@snafu: Erst einmal vielen Dank für deine rasche Antwort: Ich arbeite selten mit verschachtelten Funktionen. Jetzt stoße ich wohl in ein weiteres mir unbekanntes Feld vor. Es ist also in Python so, dass di innere Funktion nicht von der übergeordneten Funktion aufgerufen wird, sondern das die innere Funktion von außen extra aufgerufen werden muss? Ich nahm bisher an, dass alles, was sich im Korpus einer Funktion steckt, direkt ausgeführt wird - schließlich sind die Funktionen dazu da, alles innerhalb ihres Blockes auszuführen, so meine Annahme. Daher war ich verwirrt, dass der caller nicht direkt aufgerufen wird.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sophus hat geschrieben:Es ist also in Python so, dass di innere Funktion nicht von der übergeordneten Funktion aufgerufen wird, sondern das die innere Funktion von außen extra aufgerufen werden muss?
In diesem Fall will man die innere Funktion ja an anderer Stelle ausführen. Daher wird es dem Aufrufer überlassen, wann sie ausgeführt wird. Man reicht die innere Funktion quasi zur weiteren Bearbeitung weiter, indem man die Funktion selbst anstelle ihrer Rückgabe ausliefert. Grundsätzlich kann man natürlich auch eine innere Funktion direkt aufrufen, sofern es denn Sinn macht.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

snafu hat geschrieben:Ist das Verhalten für dich jetzt etwas verständlicher geworden?
Aaaah, ich glaube, ich habe es kapiert. Es ist so, als würde man eine Instanz von einer Klasse erstellen? Oder um auf die Funktion zurück zu kommen, könnte man es auch so ausdrücken:

Code: Alles auswählen

def func(*args):
     # do something

my_func = func() # Hier habe ich eine Funktion erzeugt. Und mit next() könnte ich dann mit der erzeugten Funktion arbeiten?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Ok, eine letzte kleine Frage habe ich noch. Der chain_funcs() wird beim Aufruf ja eine Liste von Argumenten übergeben. chain_funcs() selbst braucht diese Argumente gar nicht, sondern nimmt sie nur entgegen. Sobald aber die innere Funktion call_funcs() zum späteren Zeitpunkt aufgerufen wird, arbeiten sie mit den an chain_funcs() übergebenen Argumenten - indem Falle mit den partial-Objekten. Was mich wundert, ist, dass chain_funcs() die übergebenen Argumente nicht vergisst? Ich meine, ich nahm an, dass, sobald die Funktion mit ihrer Arbeit fertig ist, dass die Funktion die lokalen Variablen oder die übergebenen Argumente "vergisst". Aber die innere Funktion call_funcs() arbeitet zum späteren Zeitpunkt mit den Argumenten, die der chain_funcs() einst übergeben wurde. Werden durch diese Konstruktion (innere und äußere Funktionen) die Argumente irgendwo gespeichert? Denn ich verstehe das so. Sobald chain_funcs() das Funktion-Objekt zurückgeliefert hat, hat sie somit ihre Arbeit erledigt und ihr Korpus wird verlassen?
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Sophus: chain_funcs erhält eine Liste von Funktionen, die später ausgeführt werden sollen. Der Rückgabewert von chain_funcs (call_funcs) kann diese Funktionen dann mit den an call_funcs übergebenen Parametern aufrufen (da die an chain_funcs übergebenen Funktionen in einer closure stecken und für call_funcs weiterhin bekannt sind). Die Verwendung von partial in den Zeilen 50 und 51 sowie die Nutzung des Dictionary erschweren das Verständnis dessen was da passiert nur unnötig.
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

next() hat nichts damit zu tun. Du scheinst hier generatoren und Funktionen durcheinander zu werfen.

Und das Verhalten, das die erzeugte Funktion auf Werte der umgebenden zurueckgreifen kann nennt man "Closure" bzw. Abschluss, und es wird erreicht indem die innere Funktion eben nicht nur eine Code-Objekt darstellt, sondern auch ein __closure__-Attribut hat.

Code: Alles auswählen

>>> def bar(arg):
...     def baz():
...         print(arg)
...     return baz
...
>>> baz = bar(10)
>>> baz.__closure__
(<cell at 0x102c73918: int object at 0x10028cda0>,)
>>>
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@ snafu, kbr und __deets__: vielen Dank euch.
Antworten