Angenommen, ich habe ein Manager-Objekt. Die API dieses Objekts hat eine Funktion main_hook
, die eine andere Funktion f
als Argument erhält und die angegebene f
in einer Schleife ausführt und dabei zwischen den einzelnen Iterationen einige Dinge tut:
Nun habe ich auch (genauer gesagt, möchte ) eine Funktion stop_and_do_stuff
, die einmal aufgerufen hat, main_hook
dead in ihren Spuren stoppt und das Steuerelement an den aufgerufenen Funktionsnamen zurückgibt main_hook
, und nachdem das func beendet hat, was es macht, steuere die Kontrolle zurück zu main_hook und mach weiter. Grundsätzlich ist das Ergebnis dasselbe wie
Mit Ausnahme von yield
möchte ich einen Aufruf von f()
haben, während ich f
die Option gebe, self.stop_and_do_stuff()
Ich kann das nicht umgehen, indem ich aus zwei Gründen f auch einen Generator mache:
1. f
ist nicht Teil meiner API - sie wird mir von einem Benutzer gegeben, der meine lib verwendet
2. Selbst wenn er ihn bitten könnte, die Rendite zu verwenden, wird der Platz im Code, in dem er stop_and_do_stuff
aufrufen muss, nicht direkt in f sein, sondern an irgendeiner Stelle im Funktionsstapel, der innerhalb von% sein wird. co_de%, aber nicht direkt drin, zB
Wenn ich also f()
zu einem Generator machen möchte, muss ich auch f
zu einem Generator machen und auch g
, sonst wird dieser Trick nicht funktionieren.
Gibt es eine Lösung für all das? vielleicht versuche ich es falsch zu lösen?
(Ich weiß, dass diese Frage lang und hässlich ist - es ist das Beste, was ich tun könnte. Wenn etwas nicht klar ist, bitte sag es mir und ich werde es klären)
Vielleicht ist pep 342 die Lösung?
Ich glaube, ich sollte auch eine Antwort vom anderen Standpunkt aus hinzufügen, dh nicht versuchen zu erklären, wie Sie erreichen könnten, was wir von dem, was Sie tun wollen, verstehen, aber warum yield
definitiv nicht funktionieren könnte .
Wenn eine Funktion das Schlüsselwort yield
enthält, wird sie stark verändert. Es ist immer noch eine aufrufbare, aber keine normale Funktion: Es wird zu einer Factory, die einen Iterator zurückgibt.
Aus Sicht des Aufrufers gibt es keinen Unterschied zwischen den drei folgenden Implementierungen (außer dass yield
eins so viel einfacher ist).
Dann sollten Sie verstehen, dass es beim Ertrag nicht viel um Kontrollfluss geht, sondern darum, den Aufrufkontext in Variablen zu halten. Sobald es verstanden wird, müssen Sie entscheiden, ob die API von main_loop wirklich einen Iterator für den Aufrufer bereitstellen möchte. Wenn das der Fall ist, wenn f eine Schleife ist, dann muss es auch ein Iterator sein (und es sollte eine Schleife um Aufrufe von f () wie unten) geben.
%Vor%Aber es sollte dir egal sein, wenn f () innere Funktionen g () usw. aufrufen muss. Das ist völlig irrelevant. Sie stellen eine Lib bereit und es ist Ihr Benutzerproblem, mit einem geeigneten iterablen Aufruf zu beginnen. Wenn Sie glauben, dass Ihr lib-Benutzer nicht in der Lage ist, müssen Sie das Gesamtdesign ändern.
Ich hoffe, es hilft.
Ich verstehe auch nicht das ganze (wie sieht der main_hook-Aufrufer aus?), aber ich würde sagen, wirf eine StopNow-Ausnahme, wenn du aufhören solltest, genau wie du StopIteration werfen solltest, wenn dein Generator fertig ist.
hier ist, wie ich das Ding verstanden habe und was ich tun würde.
%Vor%Ich bin nicht ganz klar, was genau Sie versuchen, zu erreichen, so kann es sein, wenn Sie das Problem mehr erklären können, anstatt eine Lösung zu geben, die besser wäre.
Aus meinem partiellen Verständnis, warum machst du so etwas nicht
%Vor% Im Grunde gibt f
ein Flag zurück, um zu stoppen oder nicht, und wenn es stop heißt, geben wir eine Funktion, die main_hook aufruft, und diese Funktion kann weitergehen, nachdem sie etwas getan hat
z.B.
%Vor%Das von Ihnen beschriebene Verhalten ähnelt einem einfachen Funktionsaufruf. Wie unten.
%Vor% Kein Problem, wenn f()
von einem anderen Benutzer zur Verfügung gestellt wird, wenn stop_and_do_stuff von einer inneren Funktion aufgerufen wird. Wenn Sie auch möchten, dass der Manager Stapel von stop_and_do_stuff
abwickeln kann und in einigen Fällen wirklich beendet wird, kein Problem. Heben Sie einfach eine Ausnahme auf und Sie fangen sie mit main_hook oder dem oberen Code ab.
Sie können von stop_and_and_do_stuff()
aus alles tun, was auch immer Sie von dem Aufrufer des Haupt-Hooks machen wollen. Wenn nicht sollten Sie erklären warum.
Was in der Frage unklar ist, ist, was auf der Anruferseite von main_hook () passiert und warum Sie die main_hook-Schleife beenden möchten, aber nicht wirklich. Entweder erwartet der main_loop-Aufrufer einen Generator oder nicht. Sie müssen diesen Teil erklären, wenn Sie eine vernünftige Antwort erhalten möchten (einige Kontextinformationen wären auch nett, wenn Sie WTF wirklich erklären, was Sie versuchen, und Ihre wirklichen Einschränkungen - Sie sagten, dass f von einem anderen Benutzer und main_hook bereitgestellt wird ist in einer lib, was von main_hook caller? - es gibt wahrscheinlich bekannte Lösungen bekannt).
Ich habe seit einiger Zeit mit dem gleichen Problem zu kämpfen. Ich habe PEP 342 gesehen, aber es ist bereits seit 2.5 in Python implementiert und macht die Dinge nützlicher für das, was wir wollen, aber es tut nicht das, was Sie beschreiben.
Bisher glaube ich, dass es keine Lösung gibt. Dies bedeutet, dass eine ziemlich hässliche Problemumgehung erforderlich ist. Hier ist, was ich gerade mache:
Jede Funktion, die (direkt oder indirekt) nachgeben muss, muss mit der Zeile
beginnen %Vor%Der Aufruf einer solchen "Funktion" erfolgt mit:
%Vor% call()
kann auch aus regulären Funktionen verwendet werden; In diesem Fall sollte None
anstelle von resuminfo übergeben werden. Es ist technisch unmöglich, in diesem Fall nachzugeben, daher sollte die zweite Zeile nicht verwendet werden, und die Funktion wird im "Hintergrund" ausgeführt (das heißt, der Anrufer wird bei der Rückkehr nicht benachrichtigt oder in der Pause angehalten Laufen). In den meisten Fällen ist die Zuweisung zu c
in diesem Fall nicht sinnvoll.
Um normal zurückzukehren, verwenden Sie yield
anstelle von return
. Wenn Sie das Ende einer Funktion erreichen, wird None wie gewöhnlich zurückgegeben.
Um die zurückgegebene Funktion nach dem Aufruf abzurufen, verwenden Sie c.ret()
.
Wenn Sie in der Hauptfunktion (oder von wo auch immer) eine angehaltene Funktion fortsetzen möchten, müssen Sie die Resume-Info haben. Um es wieder aufzunehmen, rufen Sie einfach resumeinfo[0]()
auf und übergeben optional ein einzelnes Argument.
Wenn Sie yield WAIT
ohne zusätzlichen Code verwenden, wird der Aufruf fortgesetzt, wenn resumeinfo[0]()
aufgerufen wird. Das Argument (falls vorhanden) ist das Ergebnis dieses Ertragsausdrucks.
Die Implementierung von all dem ist natürlich in der Klasse call()
. Sie finden es unter Ссылка . Dieser Code ist größtenteils unabhängig vom Rest dieser Datei (er verwendet einige Konstanten, aber Sie können sie separat definieren).
Andererseits ist es alles ziemlich hässlich. Sie möchten vielleicht nur einen anderen Ansatz verwenden. In vielen Fällen ist das möglich. In einigen Fällen ist es nicht. (Multithreading ist nie eine Option IMO.)
Meine vorherige Antwort beschreibt, wie man das in Python2 macht, was sehr hässlich ist. Aber jetzt stieß ich auf PEP 380 : Syntax zum Delegieren an einen Subgenerator. Das tut genau das, was du fragst. Das einzige Problem ist, dass Python3 benötigt wird. Aber das sollte eigentlich kein Problem sein.
So funktioniert es:
%Vor%Das Ergebnis ist:
%Vor%Ausnahmen werden so weitergegeben, wie Sie es erwarten würden.