Wenn Funktion A nur von Funktion B benötigt wird, sollte A innerhalb von B definiert werden? [geschlossen]

147

Einfaches Beispiel. Zwei Methoden, eine von einer anderen aufgerufen:

def method_a(arg):
    some_data = method_b(arg)

def method_b(arg):
    return some_data

In Python können wir defin einem anderen deklarieren def. Also, wenn method_bes für nur erforderlich ist und von dort angerufen wird method_a, sollte ich es method_binnen deklarieren method_a? so was :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b(arg)

Oder sollte ich das vermeiden?

nukl
quelle
7
Sie sollten keine Funktion in einer anderen definieren müssen, es sei denn, Sie tun etwas WIRKLICH Funky. Bitte erläutern Sie jedoch, was Sie tun möchten, damit wir eine hilfreichere Antwort geben können
inspectorG4dget
6
Ist Ihnen klar, dass das zweite Beispiel anders ist, weil Sie nicht anrufen method_b ? (@inspector: Das müssen Sie genau genommen, aber es ist immens nützlich, wenn Sie sich mit funktionaler Programmierung beschäftigen, insbesondere mit Schließungen).
3
@ Delnan: Ich denke, Sie meinten "Sie müssen nicht , genau genommen, aber ..."
Martineau
4
Die Anwendungsfälle für innere Funktionen sind im folgenden Link wunderbar zusammengefasst: https://realpython.com/blog/python/inner-functions-what-are-they-good-for/ . Wenn Ihre Verwendung in keinen der Fälle passt, vermeiden Sie es besser.
1
Tolle Frage, aber keine wirkliche Antwort, wie es scheint ...
Mayou36

Antworten:

136
>>> def sum(x, y):
...     def do_it():
...             return x + y
...     return do_it
... 
>>> a = sum(1, 3)
>>> a
<function do_it at 0xb772b304>
>>> a()
4

Ist es das, wonach du gesucht hast? Es heißt eine Schließung .

user225312
quelle
14
Dies ist eine viel bessere Erklärung. Ich habe meine Antwort gelöscht
pyfunc
Warum nicht einfach def sum (x, y): x + y zurückgeben?
Mango
4
@mango: Dies ist nur ein Spielzeugbeispiel, um das Konzept zu vermitteln - im tatsächlichen Gebrauch wäre das, was dies do_it()tut, vermutlich etwas komplizierter als das, was von einer Arithmetik in einer einzigen returnAnweisung gehandhabt werden kann .
Martineau
2
@mango Beantwortete Ihre Frage mit einem etwas nützlicheren Beispiel. stackoverflow.com/a/24090940/2125392
CivFan
10
Es beantwortet die Frage nicht.
Hunsu
49

Auf diese Weise gewinnen Sie nicht wirklich viel. Tatsächlich wird es langsamer method_a, da die andere Funktion bei jedem Aufruf definiert und neu kompiliert wird. Angesichts dessen wäre es wahrscheinlich besser, dem Funktionsnamen nur einen Unterstrich voranzustellen, um anzuzeigen, dass es sich um eine private Methode handelt - d _method_b. H.

Ich nehme an, Sie möchten dies möglicherweise tun, wenn die Definition der verschachtelten Funktion aus irgendeinem Grund jedes Mal geändert wird. Dies kann jedoch auf einen Fehler in Ihrem Design hinweisen. Es gibt jedoch einen triftigen Grund, dies zu tun, damit die verschachtelte Funktion Argumente verwenden kann, die an die äußere Funktion übergeben, aber nicht explizit an sie weitergegeben wurden. Dies tritt beispielsweise manchmal beim Schreiben von Funktionsdekoratoren auf. Es ist das, was in der akzeptierten Antwort gezeigt wird, obwohl ein Dekorateur nicht definiert oder verwendet wird.

Aktualisieren:

Hier ist der Beweis, dass das Verschachteln langsamer ist (mit Python 3.6.1), obwohl dies in diesem trivialen Fall zugegebenermaßen nicht viel ist:

setup = """
class Test(object):
    def separate(self, arg):
        some_data = self._method_b(arg)

    def _method_b(self, arg):
        return arg+1

    def nested(self, arg):

        def method_b2(self, arg):
            return arg+1

        some_data = method_b2(self, arg)

obj = Test()
"""
from timeit import Timer
print(min(Timer(stmt='obj.separate(42)', setup=setup).repeat()))  # -> 0.24479823284461724
print(min(Timer(stmt='obj.nested(42)', setup=setup).repeat()))    # -> 0.26553459700452575

Hinweis: Ich selfhabe Ihren Beispielfunktionen einige Argumente hinzugefügt , um sie eher wie echte Methoden zu gestalten (obwohl method_b2dies technisch immer noch keine Methode der TestKlasse ist). Auch die verschachtelte Funktion wird in dieser Version im Gegensatz zu Ihrer tatsächlich aufgerufen.

Martineau
quelle
21
Die innere Funktion wird nicht jedes Mal vollständig kompiliert, wenn die äußere Funktion aufgerufen wird, obwohl ein Funktionsobjekt erstellt werden muss, was einige Zeit in Anspruch nimmt. Auf der anderen Seite wird der Funktionsname eher lokal als global, sodass die Funktion schneller aufgerufen werden kann. In meinen Versuchen ist es zeitlich im Grunde die meiste Zeit eine Wäsche; Mit der inneren Funktion kann es sogar schneller sein, wenn Sie sie mehrmals aufrufen.
Kindall
7
Ja, Sie benötigen mehrere Aufrufe der inneren Funktion. Wenn Sie es in einer Schleife oder nur ein paar Mal aufrufen, überwiegt der Vorteil eines lokalen Namens für die Funktion die Kosten für die Erstellung der Funktion. In meinen Versuchen geschieht dies, wenn Sie die innere Funktion ungefähr 3-4 Mal aufrufen. Natürlich können Sie den gleichen Vorteil erzielen (ohne annähernd so hohe Kosten), indem Sie einen lokalen Namen für die Funktion definieren, z. B. method_b = self._method_bund dann aufrufen method_b, um die wiederholte Suche nach Attributen zu vermeiden. (Es kommt vor, dass ich in letzter Zeit eine
Menge
3
@kindall: Ja, das stimmt. Ich habe meinen Timing-Test so geändert, dass 30 Aufrufe an die sekundäre Funktion gesendet wurden und die Ergebnisse umgekehrt wurden. Ich werde meine Antwort löschen, nachdem Sie diese Antwort gesehen haben. Danke für die Erleuchtung.
Martineau
2
Ich wollte nur meine -1 erklären, weil dies dennoch eine informative Antwort ist: Ich habe diese -1 gegeben, weil sie sich auf einen geringfügigen Leistungsunterschied konzentriert (in den meisten Szenarien nimmt die Codeobjekterstellung nur einen Bruchteil der Ausführungszeit der Funktion in Anspruch ). In diesen Fällen ist es wichtig zu prüfen, ob eine Inline-Funktion die Lesbarkeit und Wartbarkeit von Code verbessern kann, was meiner Meinung nach häufig der Fall ist, da Sie keine Dateien durchsuchen / scrollen müssen, um den relevanten Code zu finden.
Blixt
2
@Blixt: Ich denke, Ihre Logik ist fehlerhaft (und die Abstimmung ist unfair), weil es äußerst unwahrscheinlich ist, dass eine andere Methode derselben Klasse sehr "weit" von einer anderen Methode in derselben Klasse entfernt ist, selbst wenn sie nicht verschachtelt ist (und dies äußerst unwahrscheinlich ist) in einer anderen Datei sein). Auch der allererste Satz in meiner Antwort lautet "Sie gewinnen nicht wirklich viel, wenn Sie dies tun", was darauf hinweist, dass es sich um einen trivialen Unterschied handelt.
Martineau
27

Eine Funktion innerhalb einer Funktion wird üblicherweise für Verschlüsse verwendet .

(Es gibt eine Menge Streit über was genau macht einen Verschluss einen Verschluss .)

Hier ist ein Beispiel mit dem eingebauten sum(). Es definiert starteinmal und verwendet es von da an:

def sum_partial(start):
    def sum_start(iterable):
        return sum(iterable, start)
    return sum_start

In Benutzung:

>>> sum_with_1 = sum_partial(1)
>>> sum_with_3 = sum_partial(3)
>>> 
>>> sum_with_1
<function sum_start at 0x7f3726e70b90>
>>> sum_with_3
<function sum_start at 0x7f3726e70c08>
>>> sum_with_1((1,2,3))
7
>>> sum_with_3((1,2,3))
9

Eingebauter Python-Verschluss

functools.partial ist ein Beispiel für eine Schließung.

In den Python-Dokumenten entspricht dies in etwa:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

(Ein großes Lob an @ user225312 unten für die Antwort. Ich finde dieses Beispiel einfacher herauszufinden und werde hoffentlich helfen, den Kommentar von @ mango zu beantworten.)

CivFan
quelle
-1 Ja, sie werden üblicherweise als Verschlüsse verwendet. Lesen Sie jetzt die Frage noch einmal. Er fragte grundsätzlich, ob das von ihm gezeigte Konzept für Fall b verwendet werden könne. Ihm zu sagen, dass dies oft für Fall a verwendet wird, ist keine schlechte Antwort, sondern die falsche für diese Frage. Er ist interessiert, ob es zum Beispiel gut ist, das Obige für die Kapselung zu tun.
Mayou36
@ Mayou36 Um fair zu sein, ist die Frage eher offen - anstatt zu versuchen, jeden möglichen Fall zu beantworten, hielt ich es für das Beste, sich auf einen zu konzentrieren. Auch die Frage ist nicht sehr klar. Zum Beispiel bedeutet dies eine Schließung im zweiten Beispiel, obwohl dies wahrscheinlich nicht gemeint war.
CivFan
@ Mayou36 „ fragte er im Grunde , wenn das Konzept , das er zeigt kann für den Fall b verwendet werden.“ Nein, die Frage fragt, ob es verwendet werden soll. OP weiß bereits, dass es verwendet werden kann.
CivFan
1
Nun, "wenn method_b für method_a erforderlich ist und nur von method_a aufgerufen wird, sollte ich method_b in method_a deklarieren?" ist ganz klar und hat nichts mit Verschlüssen zu tun, oder? Ja, ich stimme zu, ich habe Dose in der Art von sollte verwendet . Das hat aber nichts mit Verschlüssen zu tun ... Ich bin nur überrascht, dass so viele Antworten auf Verschlüsse und deren Verwendung geben, wenn das OP eine ganz andere Frage stellt.
Mayou36
@ Mayou36 Es kann hilfreich sein, die Frage so umzuformulieren, wie Sie sie sehen, und eine andere Frage zu öffnen, um sie zu beantworten.
CivFan
17

Nein, definieren Sie im Allgemeinen keine Funktionen innerhalb von Funktionen.

Es sei denn, Sie haben einen wirklich guten Grund. Was du nicht tust.

Warum nicht?

Was ist ein wirklich guter Grund, Funktionen innerhalb von Funktionen zu definieren?

Wenn Sie tatsächlich einen Dingdang- Verschluss wollen .

CivFan
quelle
1
Diese Antwort ist für Sie @ Mayou36.
CivFan
2
Ja, danke, das ist die Antwort, nach der ich gesucht habe. Dies beantwortet die Frage bestmöglich. Obwohl es nicht streng das eine oder andere sagt, entmutigt es die Inline-Definitionen stark, indem es Gründe angibt. So sollte eine (pythonische :) Antwort sein!
Mayou36
10

Es ist eigentlich in Ordnung, eine Funktion in einer anderen zu deklarieren. Dies ist besonders nützlich, um Dekorateure zu erstellen.

Als Faustregel gilt jedoch, dass es bei einer komplexen Funktion (mehr als 10 Zeilen) möglicherweise besser ist, sie auf Modulebene zu deklarieren.

vz0
quelle
2
Es ist möglich, aber ich stimme zu, Sie brauchen einen guten Grund dafür. Es wäre mehr Python, einen vorhergehenden Unterstrich für eine Funktion zu verwenden, die nur in Ihrer Klasse verwendet werden sollte.
Chmullig
3
Innerhalb einer Klasse, ja, aber was ist nur innerhalb einer Funktion ? Die Kapselung ist hierarchisch.
Paul Draper
@PaulDraper "Kapselung ist hierarchisch" - Nein! Wer sagt das? Die Kapselung ist ein viel umfassenderes Prinzip als nur eine Vererbung.
Mayou36
7

Ich habe diese Frage gefunden, weil ich eine Frage stellen wollte, warum es Auswirkungen auf die Leistung gibt, wenn verschachtelte Funktionen verwendet werden. Ich habe Tests für die folgenden Funktionen mit Python 3.2.5 auf einem Windows-Notebook mit einem Quad Core 2,5-GHz-Intel i5-2530M-Prozessor durchgeführt

def square0(x):
    return x*x

def square1(x):
    def dummy(y):
        return y*y
    return x*x

def square2(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    return x*x

def square5(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    def dummy3(y):
        return y*y
    def dummy4(y):
        return y*y
    def dummy5(y):
        return y*y
    return x*x

Ich habe die folgenden 20 Male gemessen, auch für Quadrat1, Quadrat2 und Quadrat5:

s=0
for i in range(10**6):
    s+=square0(i)

und bekam die folgenden Ergebnisse

>>> 
m = mean, s = standard deviation, m0 = mean of first testcase
[m-3s,m+3s] is a 0.997 confidence interval if normal distributed

square? m     s       m/m0  [m-3s ,m+3s ]
square0 0.387 0.01515 1.000 [0.342,0.433]
square1 0.460 0.01422 1.188 [0.417,0.503]
square2 0.552 0.01803 1.425 [0.498,0.606]
square5 0.766 0.01654 1.979 [0.717,0.816]
>>> 

square0hat keine verschachtelte Funktion, square1hat eine verschachtelte Funktion, square2hat zwei verschachtelte Funktionen und square5hat fünf verschachtelte Funktionen. Die verschachtelten Funktionen werden nur deklariert, aber nicht aufgerufen.

Wenn Sie also 5 verschachtelte Funktionen in einer Funktion definiert haben, die Sie nicht aufrufen, beträgt die Ausführungszeit der Funktion das Doppelte der Funktion ohne verschachtelte Funktion. Ich denke, sollte vorsichtig sein, wenn verschachtelte Funktionen verwendet werden.

Die Python-Datei für den gesamten Test, der diese Ausgabe generiert, befindet sich unter ideone .

Wunder173
quelle
5
Der Vergleich, den Sie durchführen, ist nicht wirklich nützlich. Es ist, als würde man der Funktion Dummy-Anweisungen hinzufügen und sagen, dass sie langsamer ist. Beispiel von Martineau verwendet tatsächlich die gekapselten Funktionen und ich bemerke keinen Leistungsunterschied, wenn ich sein Beispiel ausführe.
Kon Psych
-1 Bitte lesen Sie die Frage noch einmal. Obwohl Sie vielleicht sagen, dass es vielleicht etwas langsamer ist, fragte er, ob es eine gute Idee sei, dies zu tun, nicht hauptsächlich wegen der Leistung, sondern wegen der allgemeinen Praxis.
Mayou36
@ Mayou36 Entschuldigung, drei Jahre nachdem die Frage und die Antwort veröffentlicht wurden, werde ich nicht.
Wunder173
Ok, leider ist noch keine akzeptierte (oder gute) Antwort verfügbar.
Mayou36
4

Es ist nur ein Prinzip über Expositions-APIs.

Bei Verwendung von Python ist es eine gute Idee, die Expositions-API im Weltraum (Modul oder Klasse) zu vermeiden. Die Funktion ist ein guter Kapselungsort.

Es könnte eine gute Idee sein. wenn Sie sicherstellen

  1. Die innere Funktion wird NUR von der äußeren Funktion verwendet.
  2. Insider-Funktion hat einen guten Namen, um ihren Zweck zu erklären, weil der Code spricht.
  3. Code kann von Ihren Kollegen (oder anderen Codelesern) nicht direkt verstanden werden.

Auch wenn Missbrauch dieser Technik Probleme verursachen kann und einen Konstruktionsfehler impliziert.

Nur aus meiner Erfahrung, Vielleicht missverstehen Sie Ihre Frage.

chao787
quelle
4

Am Ende geht es also hauptsächlich darum, wie intelligent die Python-Implementierung ist oder nicht, insbesondere wenn die innere Funktion kein Abschluss ist, sondern lediglich ein Helfer, der nur für die Funktion benötigt wird.

In einem sauberen, verständlichen Design ist es ein gutes Design, Funktionen nur dort zu haben, wo sie benötigt werden und nicht an anderer Stelle verfügbar gemacht werden, unabhängig davon, ob sie in ein Modul, eine Klasse als Methode oder in eine andere Funktion oder Methode eingebettet sind. Wenn sie gut gemacht sind, verbessern sie wirklich die Klarheit des Codes.

Und wenn die innere Funktion ein Verschluss ist, der auch bei der Klarheit einiges helfen kann, selbst wenn diese Funktion nicht aus der enthaltenen Funktion zur anderweitigen Verwendung zurückgegeben wird.

Daher würde ich sagen, dass Sie sie im Allgemeinen verwenden, sich jedoch des möglichen Leistungseinbruchs bewusst sind, wenn Sie tatsächlich über die Leistung besorgt sind, und sie nur entfernen, wenn Sie eine tatsächliche Profilerstellung durchführen, die zeigt, dass sie am besten entfernt werden können.

Führen Sie keine vorzeitige Optimierung durch, indem Sie nur "innere Funktionen BAD" für den gesamten von Ihnen geschriebenen Python-Code verwenden. Bitte.


quelle
Wie andere Antworten zeigen, haben Sie nicht wirklich Leistungstreffer.
Mayou36
1

Es ist vollkommen in Ordnung, dies so zu tun, aber es sei denn, Sie müssen einen Abschluss verwenden oder die Funktion zurückgeben, die ich wahrscheinlich in die Modulebene eingefügt habe. Ich stelle mir im zweiten Codebeispiel vor, Sie meinen:

...
some_data = method_b() # not some_data = method_b

Andernfalls ist some_data die Funktion.

Wenn Sie es auf Modulebene haben, können andere Funktionen method_b () verwenden. Wenn Sie etwas wie Sphinx (und Autodoc) für die Dokumentation verwenden, können Sie auch method_b dokumentieren.

Möglicherweise möchten Sie auch die Funktionalität in zwei Methoden in einer Klasse einfügen, wenn Sie etwas tun, das von einem Objekt dargestellt werden kann. Dies enthält auch Logik, wenn das alles ist, wonach Sie suchen.

mikelikespie
quelle
1

Mach so etwas wie:

def some_function():
    return some_other_function()
def some_other_function():
    return 42 

Wenn Sie ausführen some_function()würden, würde es ausgeführt some_other_function()und 42 zurückgeben.

EDIT: Ich habe ursprünglich gesagt, dass Sie eine Funktion nicht innerhalb einer anderen definieren sollten, aber es wurde darauf hingewiesen, dass es manchmal praktisch ist, dies zu tun.

mdlp0716
quelle
Ich schätze die Anstrengungen, die die oben genannten Personen in ihre Antworten gesteckt haben, aber Ihre waren direkt und auf den Punkt. Nett.
C0NFUS3D
1
Warum? Warum sollten Sie das nicht tun? Ist das nicht eine großartige Kapselung? Mir fehlen irgendwelche Argumente. -1
Mayou36
@ Mayou36 Ich wusste nicht, was Kapselung zum Zeitpunkt des Schreibens meines Kommentars war, noch weiß ich wirklich, was es jetzt ist. Ich dachte nur, dass es nicht gut ist. Können Sie erklären, warum es vorteilhaft wäre, eine Funktion innerhalb einer anderen zu definieren, anstatt sie nur außerhalb zu definieren?
mdlp0716
2
Ja, ich kann. Sie können das Konzept der Kapselung nachschlagen, aber kurz gesagt: Verstecken Sie die nicht benötigten Informationen und legen Sie dem Benutzer nur das zur Verfügung, was er wissen muss. Das heißt, wenn Sie eine andere Funktion außerhalb definieren, wird dem Namespace etwas mehr hinzugefügt, das tatsächlich eng mit der ersten Funktion verbunden ist. Oder denken Sie in Variablen: Warum brauchen Sie lokale Variablen gegenüber globalen Variablen? Wenn möglich, ist es viel besser, alle Variablen innerhalb einer Funktion zu definieren, als globale Variablen für Variablen zu verwenden, die nur innerhalb dieser einen Funktion verwendet werden . Am Ende geht es darum, die Komplexität zu reduzieren.
Mayou36
0

Sie können es verwenden, um die Definition globaler Variablen zu vermeiden. Dies gibt Ihnen eine Alternative für andere Designs. 3 Entwürfe, die eine Lösung für ein Problem darstellen.

A) Verwenden von Funktionen ohne Globale

def calculate_salary(employee, list_with_all_employees):
    x = _calculate_tax(list_with_all_employees)

    # some other calculations done to x
    pass

    y = # something 

    return y

def _calculate_tax(list_with_all_employees):
    return 1.23456 # return something

B) Verwenden von Funktionen mit Globalen

_list_with_all_employees = None

def calculate_salary(employee, list_with_all_employees):

    global _list_with_all_employees
    _list_with_all_employees = list_with_all_employees

    x = _calculate_tax()

    # some other calculations done to x
    pass

    y = # something

    return y

def _calculate_tax():
    return 1.23456 # return something based on the _list_with_all_employees var

C) Verwenden von Funktionen innerhalb einer anderen Funktion

def calculate_salary(employee, list_with_all_employees):

    def _calculate_tax():
        return 1.23456 # return something based on the list_with_a--Lemployees var

    x = _calculate_tax()

    # some other calculations done to x
    pass
    y = # something 

    return y

Lösung C) ermöglicht die Verwendung von Variablen im Bereich der äußeren Funktion, ohne dass diese in der inneren Funktion deklariert werden müssen. Könnte in einigen Situationen nützlich sein.

Elmex80s
quelle
0

Funktion In Funktion Python

def Greater(a,b):
    if a>b:
        return a
    return b

def Greater_new(a,b,c,d):
    return Greater(Greater(a,b),Greater(c,d))

print("Greater Number is :-",Greater_new(212,33,11,999))
user12069064
quelle