PHP, C #, Python und wahrscheinlich einige andere Sprachen haben ein yield
Schlüsselwort, mit dem Generatorfunktionen erstellt werden.
In PHP: http://php.net/manual/en/language.generators.syntax.php
In Python: https://www.pythoncentral.io/python-generators-and-yield-keyword/
In C #: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/yield
Ich bin besorgt, dass als Sprachfeature / yield
-einrichtung einige Konventionen verletzt werden. Eines davon ist "Gewissheit". Diese Methode gibt bei jedem Aufruf ein anderes Ergebnis zurück. Mit einer regulären Nicht-Generator-Funktion können Sie sie aufrufen. Wenn sie denselben Eingang erhält, gibt sie denselben Ausgang zurück. Mit Yield gibt es je nach internem Status unterschiedliche Ausgaben zurück. Wenn Sie also die Generierungsfunktion zufällig aufrufen und ihren vorherigen Status nicht kennen, können Sie nicht erwarten, dass sie ein bestimmtes Ergebnis zurückgibt.
Wie passt eine solche Funktion in das Sprachparadigma? Verstößt es tatsächlich gegen Konventionen? Ist es eine gute Idee, diese Funktion zu haben und zu nutzen? (um ein Beispiel dafür zu geben, was gut und was schlecht ist, goto
war einst ein Merkmal vieler Sprachen und ist es immer noch, aber es wird als schädlich angesehen und als solches aus einigen Sprachen wie Java ausgelöscht). Müssen Programmiersprachen-Compiler / -Interpreter aus Konventionen ausbrechen, um eine solche Funktion zu implementieren? Muss eine Sprache beispielsweise Multithreading implementieren, damit diese Funktion funktioniert, oder kann dies ohne Threading-Technologie durchgeführt werden?
yield
ist im Wesentlichen eine staatliche Engine. Es ist nicht beabsichtigt, jedes Mal das gleiche Ergebnis zurückzugeben. Was es wird mit absoluter Sicherheit zu tun ist das nächste Element in einem enumerable Rückkehr jedes Mal aufgerufen wird. Threads sind nicht erforderlich; Sie benötigen eine Schließung (mehr oder weniger), um den aktuellen Status beizubehalten.yield
Schlüsselwort wie Python hat. Es hat eine statische Methodestd::this_thread::yield()
, aber das ist kein Schlüsselwort. Dasthis_thread
würde also fast jedem Aufruf vorangestellt, was ziemlich offensichtlich macht, dass es sich um eine Bibliotheksfunktion handelt, die nur zum Erteilen von Threads dient, und nicht um eine Sprachfunktion zum Erzielen des Kontrollflusses im Allgemeinen.Antworten:
Vorsichtsmaßnahmen zuerst - C # ist die Sprache, die ich am besten kenne, und obwohl sie eine
yield
Sprache hatyield
, die anderen Sprachen sehr ähnlich zu sein scheint , kann es subtile Unterschiede geben, die ich nicht kenne .Quatsch. Erwarten oder erwarten Sie wirklich jedes Mal das gleiche Ergebnis, wenn Sie sie anrufen? Wie wäre es mit Restanrufen? Authentifizierung? Artikel aus einer Sammlung holen? Es gibt alle möglichen (guten, nützlichen) Funktionen, die unrein sind.
Random.Next
Console.ReadLine
Ja,
yield
spielt wirklich schlecht mittry/catch/finally
und ist nicht erlaubt ( https://blogs.msdn.microsoft.com/ericlippert/2009/07/16/iterator-blocks-part-three-why-no-yield-infinally/ for Mehr Info).Es ist sicherlich eine gute Idee, diese Funktion zu haben. Dinge wie C # 's LINQ sind wirklich nett - die träge Auswertung von Sammlungen bietet einen großen Leistungsvorteil und
yield
ermöglicht, dass solche Dinge in einem Bruchteil des Codes mit einem Bruchteil der Fehler ausgeführt werden, die ein handgerollter Iterator verursachen würde.Das heißt, es gibt nicht viele Verwendungszwecke für die
yield
Verarbeitung von Sammlungen außerhalb des LINQ-Stils. Ich habe es für die Validierungsverarbeitung, die Generierung von Zeitplänen, die Randomisierung und einige andere Dinge verwendet, aber ich gehe davon aus, dass die meisten Entwickler es nie verwendet (oder missbraucht) haben.Nicht genau. Der Compiler generiert einen Zustandsmaschinen-Iterator, der verfolgt, wo er gestoppt wurde, damit er beim nächsten Aufruf dort erneut starten kann. Der Prozess für die Codegenerierung ähnelt dem Continuation Passing Style, bei dem der Code nach dem
yield
in einen eigenen Block gezogen wird (und wenn er einenyield
s hat, einen anderen Unterblock usw.). Dies ist ein bekannter Ansatz, der in der funktionalen Programmierung häufiger verwendet wird und auch in der asynchronen / wartenden Kompilierung von C # angezeigt wird.Es ist kein Threading erforderlich, erfordert jedoch bei den meisten Compilern einen anderen Ansatz zur Codegenerierung und weist Konflikte mit anderen Sprachfunktionen auf.
Alles in allem handelt es sich jedoch
yield
um eine Funktion mit relativ geringen Auswirkungen, die bei einer bestimmten Teilmenge von Problemen wirklich hilfreich ist.quelle
yield
Schlüsselwort ähnelt Coroutinen, ja, oder etwas anderem? Wenn ja, wünschte ich, ich hätte eine in C! Ich kann mir zumindest einige anständige Codeabschnitte vorstellen, die mit einer solchen Sprachfunktion viel einfacher zu schreiben gewesen wären.async
/await
der Sprache hinzugefügt wurde, hat es jemand mit implementiertyield
.Ich möchte dies aus Python-Sicht mit einem nachdrücklichen Ja beantworten , es ist eine großartige Idee .
Ich werde zunächst einige Fragen und Annahmen in Ihrer Frage ansprechen und später die Verbreitung von Generatoren und ihre unangemessene Nützlichkeit in Python demonstrieren.
Das ist falsch. Methoden an Objekten können als Funktionen selbst mit ihrem eigenen internen Zustand betrachtet werden. In Python können Sie, da alles ein Objekt ist, tatsächlich eine Methode von einem Objekt abrufen und diese Methode weitergeben (die an das Objekt gebunden ist, von dem sie stammt, sodass sie sich an ihren Status erinnert).
Andere Beispiele umfassen absichtlich zufällige Funktionen sowie Eingabemethoden wie das Netzwerk, das Dateisystem und das Terminal.
Wenn das Sprachparadigma beispielsweise erstklassige Funktionen unterstützt und die Generatoren andere Sprachfunktionen wie das Iterable-Protokoll unterstützen, fügen sie sich nahtlos ein.
Nein. Da es in die Sprache eingebettet ist, basieren die Konventionen auf der Verwendung von Generatoren (oder erfordern diese!).
Wie bei jeder anderen Funktion muss der Compiler lediglich so konzipiert sein, dass er die Funktion unterstützt. Im Fall von Python sind Funktionen bereits Objekte mit Status (wie die Standardargumente und Funktionsanmerkungen).
Unterhaltsame Tatsache: Die Standard-Python-Implementierung unterstützt das Threading überhaupt nicht. Es verfügt über eine globale Interpreter-Sperre (GIL), sodass nichts gleichzeitig ausgeführt wird, es sei denn, Sie haben einen zweiten Prozess gestartet, um eine andere Instanz von Python auszuführen.
Hinweis: Beispiele finden Sie in Python 3
Jenseits der Ausbeute
Während das
yield
Schlüsselwort in jeder Funktion verwendet werden kann, um daraus einen Generator zu machen, ist dies nicht die einzige Möglichkeit, einen zu erstellen. Python bietet Generator-Ausdrücke, eine leistungsstarke Methode, um einen Generator in Bezug auf eine andere Iteration (einschließlich anderer Generatoren) klar auszudrücken.Wie Sie sehen können, ist nicht nur die Syntax sauber und lesbar, sondern auch integrierte Funktionen wie
sum
Akzeptieren von Generatoren.Mit
Überprüfen Sie den Python-Erweiterungsvorschlag für die With-Anweisung . Es ist ganz anders, als Sie es von einer With-Anweisung in anderen Sprachen erwarten. Mit ein wenig Hilfe aus der Standardbibliothek arbeiten Pythons Generatoren hervorragend als Kontextmanager für sie.
Natürlich ist das Drucken das Langweiligste, was Sie hier tun können, aber es zeigt sichtbare Ergebnisse. Weitere interessante Optionen sind die automatische Verwaltung von Ressourcen (Öffnen und Schließen von Dateien / Streams / Netzwerkverbindungen), das Sperren für Parallelität, das vorübergehende Umschließen oder Ersetzen einer Funktion sowie das Dekomprimieren und erneute Komprimieren von Daten. Wenn das Aufrufen von Funktionen dem Einfügen von Code in Ihren Code gleicht, entspricht das Aufrufen von Anweisungen dem Umschließen von Teilen Ihres Codes in anderen Code. Wie auch immer Sie es verwenden, es ist ein solides Beispiel für einen einfachen Einstieg in eine Sprachstruktur. Ertragsbasierte Generatoren sind nicht die einzige Möglichkeit, Kontextmanager zu erstellen, aber sie sind sicherlich eine bequeme.
Für und teilweise Erschöpfung
For-Schleifen in Python funktionieren auf interessante Weise. Sie haben das folgende Format:
Zuerst wird der Ausdruck, den ich aufgerufen habe,
<iterable>
ausgewertet, um ein iterierbares Objekt zu erhalten. Zweitens hat das iterable es__iter__
aufgerufen, und der resultierende Iterator wird hinter den Kulissen gespeichert. Anschließend__next__
wird der Iterator aufgerufen, um einen Wert zu erhalten, der an den von Ihnen eingegebenen Namen gebunden werden soll<name>
. Dieser Schritt wird wiederholt, bis der Aufruf zum__next__
Auslösen von aStopIteration
. Die Ausnahme wird von der for-Schleife verschluckt und die Ausführung von dort aus fortgesetzt.Zurück zu den Generatoren: Wenn Sie
__iter__
einen Generator aufrufen , gibt er sich einfach selbst zurück.Dies bedeutet, dass Sie die Iteration über etwas von dem trennen können, was Sie damit tun möchten, und dieses Verhalten auf halbem Weg ändern können. Beachten Sie unten, wie derselbe Generator in zwei Schleifen verwendet wird und in der zweiten beginnt er dort auszuführen, wo er von der ersten aufgehört hat.
Faule Bewertung
Eine der Nachteile von Generatoren im Vergleich zu Listen ist das einzige, worauf Sie in einem Generator zugreifen können, das nächste, was dabei herauskommt. Sie können nicht zurückgehen und wie bei einem vorherigen Ergebnis oder zu einem späteren springen, ohne die Zwischenergebnisse durchzugehen. Die Kehrseite davon ist, dass ein Generator im Vergleich zu seiner entsprechenden Liste fast keinen Speicher belegen kann.
Generatoren können auch träge verkettet werden.
Die erste, zweite und dritte Zeile definieren jeweils nur einen Generator, erledigen aber keine wirkliche Arbeit. Wenn die letzte Zeile aufgerufen wird, fragt sum numericcolumn nach einem Wert, numericcolumn benötigt einen Wert aus lastcolumn, lastcolumn fragt nach einem Wert aus logfile, der dann tatsächlich eine Zeile aus der Datei liest. Dieser Stapel wird abgewickelt, bis die Summe ihre erste Ganzzahl erhält. Dann wird der Vorgang für die zweite Zeile erneut ausgeführt. Zu diesem Zeitpunkt hat die Summe zwei Ganzzahlen und addiert sie. Beachten Sie, dass die dritte Zeile noch nicht aus der Datei gelesen wurde. Die Summe fordert dann weiterhin Werte von der numerischen Spalte an (ohne den Rest der Kette zu beachten) und fügt sie hinzu, bis die numerische Spalte erschöpft ist.
Der wirklich interessante Teil hier ist, dass die Zeilen einzeln gelesen, verbraucht und verworfen werden. Zu keinem Zeitpunkt befindet sich die gesamte Datei auf einmal im Speicher. Was passiert, wenn diese Protokolldatei beispielsweise ein Terabyte ist? Es funktioniert nur, weil es jeweils nur eine Zeile liest.
Fazit
Dies ist keine vollständige Überprüfung aller Verwendungen von Generatoren in Python. Insbesondere habe ich unendliche Generatoren, Zustandsautomaten, die Rückgabe von Werten und deren Beziehung zu Coroutinen übersprungen.
Ich glaube, es reicht aus, um zu demonstrieren, dass Sie Generatoren als sauber integrierte, nützliche Sprachfunktion haben können.
quelle
Wenn Sie an klassische OOP-Sprachen und Generatoren gewöhnt sind,
yield
kann dies zu Problemen führen, da der veränderbare Status eher auf Funktionsebene als auf Objektebene erfasst wird.Die Frage der "Gewissheit" ist jedoch ein roter Hering. Es wird normalerweise als referenzielle Transparenz bezeichnet und bedeutet im Grunde, dass die Funktion immer das gleiche Ergebnis für die gleichen Argumente zurückgibt. Sobald Sie einen veränderlichen Status haben, verlieren Sie die referenzielle Transparenz. In OOP haben Objekte häufig einen veränderlichen Status, was bedeutet, dass das Ergebnis eines Methodenaufrufs nicht nur von den Argumenten abhängt, sondern auch vom internen Status des Objekts.
Die Frage ist, wo der veränderliche Zustand erfasst werden soll. In einem klassischen OOP existiert ein veränderlicher Zustand auf Objektebene. Wenn eine Sprachunterstützung geschlossen wird, haben Sie möglicherweise einen veränderlichen Status auf Funktionsebene. Zum Beispiel in JavaScript:
Kurz gesagt,
yield
ist natürlich in einer Sprache, die Schließungen unterstützt, aber in einer Sprache wie der älteren Version von Java, in der der veränderbare Status nur auf Objektebene existiert , fehl am Platz .quelle
Meiner Meinung nach ist es keine gute Funktion. Es ist eine schlechte Eigenschaft, vor allem, weil es sehr sorgfältig unterrichtet werden muss und jeder es falsch lehrt. Menschen verwenden das Wort "Generator", das zwischen der Generatorfunktion und dem Generatorobjekt nicht eindeutig ist. Die Frage ist: Nur wer oder was macht den tatsächlichen Ertrag?
Dies ist nicht nur meine Meinung. Sogar Guido gibt in dem PEP-Bulletin, in dem er darüber entscheidet, zu, dass die Generatorfunktion kein Generator, sondern eine "Generatorfabrik" ist.
Das ist irgendwie wichtig, findest du nicht? Wenn Sie jedoch 99% der Dokumentation lesen, haben Sie den Eindruck, dass die Generatorfunktion der eigentliche Generator ist, und sie ignorieren tendenziell die Tatsache, dass Sie auch ein Generatorobjekt benötigen.
Guido überlegte, "def" durch "gen" für diese Funktionen zu ersetzen und sagte Nein. Aber ich würde argumentieren, dass das sowieso nicht genug gewesen wäre. Es sollte wirklich sein:
quelle