Verschachtelte Funktion in Python

83

Welchen Nutzen oder welche Auswirkungen könnten wir mit Python-Code wie diesem erzielen:

class some_class(parent_class):
    def doOp(self, x, y):
        def add(x, y):
            return x + y
        return add(x, y)

Ich fand dies in einem Open-Source-Projekt, das innerhalb der verschachtelten Funktion etwas Nützliches tat, aber absolut nichts außerhalb davon tat, außer es aufzurufen. (Den eigentlichen Code finden Sie hier .) Warum könnte jemand ihn so codieren? Gibt es einen Vorteil oder Nebeneffekt beim Schreiben des Codes innerhalb der verschachtelten Funktion und nicht in der äußeren, normalen Funktion?

Hosam Aly
quelle
4
Ist dies der tatsächliche Code, den Sie gefunden haben, oder nur ein vereinfachtes Beispiel, das Sie erstellt haben?
MAK
Es ist ein vereinfachtes Beispiel. Den aktuellen Code finden Sie hier: bazaar.launchpad.net/%7Eopenerp/openobject-server/trunk/…
Hosam Aly
2
Ihr Link (der auf HEAD verweist) ist jetzt nicht korrekt. Versuchen Sie: bazaar.launchpad.net/~openerp/openobject-server/trunk/annotate/…
Craig McQueen
Du hast recht, Craig. Vielen Dank.
Hosam Aly

Antworten:

111

Normalerweise tun Sie dies, um Verschlüsse zu machen :

def make_adder(x):
    def add(y):
        return x + y
    return add

plus5 = make_adder(5)
print(plus5(12))  # prints 17

Innere Funktionen können über den umschließenden Bereich (in diesem Fall die lokale Variable x) auf Variablen zugreifen . Wenn Sie nicht auf Variablen aus dem umschließenden Bereich zugreifen, handelt es sich nur um normale Funktionen mit einem anderen Bereich.

Adam Rosenfield
quelle
5
Dafür würde ich Teiltöne bevorzugen : plus5 = functools.partial(operator.add, 5). Dekorateure wären ein besseres Beispiel für Verschlüsse.
Jochen Ritzel
4
Danke, aber wie Sie in dem von mir geposteten Snippet sehen können, ist dies hier nicht der Fall: Die verschachtelte Funktion wird einfach in der äußeren Funktion aufgerufen.
Hosam Aly
57

Abgesehen von Funktionsgeneratoren, bei denen die interne Funktionserstellung fast die Definition eines Funktionsgenerators ist, ist der Grund, warum ich verschachtelte Funktionen erstelle, die Verbesserung der Lesbarkeit. Wenn ich eine winzige Funktion habe, die nur von der äußeren Funktion aufgerufen wird, füge ich die Definition ein, damit Sie nicht herumspringen müssen, um festzustellen, was diese Funktion tut. Ich kann die innere Methode immer außerhalb der Kapselungsmethode verschieben, wenn ich die Funktion zu einem späteren Zeitpunkt wiederverwenden muss.

Spielzeugbeispiel:

import sys

def Foo():
    def e(s):
        sys.stderr.write('ERROR: ')
        sys.stderr.write(s)
        sys.stderr.write('\n')
    e('I regret to inform you')
    e('that a shameful thing has happened.')
    e('Thus, I must issue this desultory message')
    e('across numerous lines.')
Foo()
Ross Rogers
quelle
4
Das einzige Problem ist, dass dies manchmal den Unit-Test erschwert, da Sie nicht auf die innere Funktion zugreifen können.
Challenger5
1
Sie sehen so schön aus!
Verjüngung
FWIY @ Challenger5 Wenn die innere Funktion analog zu Funktionen der privaten Klasse ist, werden sie ohnehin nicht auf Einheiten getestet. Ich habe damit begonnen, Methoden lesbarer zu machen und gleichzeitig die Definition nahe an der betreffenden Methode zu halten.
Mitchell Currie
26

Ein potenzieller Vorteil der Verwendung innerer Methoden besteht darin, dass Sie lokale Variablen der äußeren Methode verwenden können, ohne sie als Argumente zu übergeben.

def helper(feature, resultBuffer):
  resultBuffer.print(feature)
  resultBuffer.printLine()
  resultBuffer.flush()

def save(item, resultBuffer):

  helper(item.description, resultBuffer)
  helper(item.size, resultBuffer)
  helper(item.type, resultBuffer)

kann wie folgt geschrieben werden, was wohl besser liest

def save(item, resultBuffer):

  def helper(feature):
    resultBuffer.print(feature)
    resultBuffer.printLine()
    resultBuffer.flush()

  helper(item.description)
  helper(item.size)
  helper(item.type)
Kuba
quelle
8

Ich kann mir keinen guten Grund für solchen Code vorstellen.

Vielleicht gab es einen Grund für die innere Funktion in älteren Revisionen, wie bei anderen Ops.

Dies ist beispielsweise etwas sinnvoller:

class some_class(parent_class):
    def doOp(self, op, x, y):
        def add(x, y):
            return x + y
        def sub(x,y):
            return x - y
        return locals()[op](x,y)

some_class().doOp('add', 1,2)

aber dann sollte die innere Funktion stattdessen ("private") Klassenmethoden sein:

class some_class(object):
    def _add(self, x, y):
        return x + y
    def doOp(self, x, y):
        return self._add(x,y)
Jochen Ritzel
quelle
Ja, vielleicht hat doOp eine Zeichenfolge verwendet, um anzugeben, welcher Operator für die Argumente verwendet werden soll ...
Skilldrick
1
@ArtOfWarfare Es wird bevorzugt, einfach Module zu verwenden. Klassen sind für Staat.
Aehlke
6

Die Idee hinter lokalen Methoden ähnelt lokalen Variablen: Verschmutzen Sie nicht den größeren Namensraum. Offensichtlich sind die Vorteile begrenzt, da die meisten Sprachen solche Funktionen nicht auch direkt bereitstellen.

avguchenko
quelle
1

Sind Sie sicher, dass der Code genau so war? Der normale Grund für so etwas ist das Erstellen eines Teils - einer Funktion mit eingebauten Parametern. Das Aufrufen der äußeren Funktion gibt eine aufrufbare Funktion zurück, die keine Parameter benötigt und daher gespeichert und an einem Ort verwendet werden kann, an dem es unmöglich ist, Parameter zu übergeben. Der von Ihnen gepostete Code führt dies jedoch nicht aus. Er ruft die Funktion sofort auf und gibt das Ergebnis und nicht das aufrufbare zurück. Es kann nützlich sein, den tatsächlichen Code zu veröffentlichen, den Sie gesehen haben.

Daniel Roseman
quelle
1
Ich habe in einem Kommentar zu meiner Frage einen Link zum Originalcode hinzugefügt. Wie Sie sehen können, ist mein Beispiel ein vereinfachtes Beispiel, aber es ist immer noch fast dasselbe.
Hosam Aly