Frage

Die Kombination von Koroutinen und Ressourcenerfassung scheint, wie es einige unbeabsichtigte (oder unintuitive) Folgen haben könnte.

Die grundlegende Frage ist, ob etwas funktioniert wie folgt:

def coroutine():
    with open(path, 'r') as fh:
        for line in fh:
            yield line

Was es tut. (Sie können es testen!)

Je tiefer Sorge ist, dass with soll etwas eine Alternative sein, um zu finally, wo Sie sicherstellen, dass eine Ressource am Ende des Blocks freigegeben wird. Koroutinen kann die Ausführung von in der with Block, so Anhalten und Fortsetzen, wie der Konflikt gelöst?

Zum Beispiel, wenn Sie eine Datei mit Lese- öffnen / schreiben sowohl innerhalb als auch außerhalb eines Koroutine während der Koroutine noch nicht zurückgekehrt sind:

def coroutine():
    with open('test.txt', 'rw+') as fh:
        for line in fh:
            yield line

a = coroutine()
assert a.next() # Open the filehandle inside the coroutine first.
with open('test.txt', 'rw+') as fh: # Then open it outside.
    for line in fh:
        print 'Outside coroutine: %r' % repr(line)
assert a.next() # Can we still use it?

Update

Ich wollte für Schreib gesperrte Datei-Handelte Anstoß im vorherigen Beispiel, aber da die meisten OSes Dateihandies pro Prozess zuweisen wird es keine Konkurrenz da sein. (Kudos für den Hinweis auf das Beispiel @Miles nicht allzu viel Sinn.) Hier ist meine überarbeitete Beispiel, die eine echte Deadlock-Bedingung zeigt:

import threading

lock = threading.Lock()

def coroutine():
    with lock:
        yield 'spam'
        yield 'eggs'

generator = coroutine()
assert generator.next()
with lock: # Deadlock!
    print 'Outside the coroutine got the lock'
assert generator.next()
War es hilfreich?

Lösung

ich nicht wirklich verstehen, was Konflikt Sie sind gefragt, noch das Problem mit dem Beispiel:. Es ist in Ordnung zwei koexistieren, unabhängige Griffe in derselben Datei zu haben,

Eine Sache, die ich nicht wusste, dass ich als Antwort auf Ihre Frage gelernt, dass es eine neue Nähe () -Methode für Generatoren:

  

close() wirft eine neue GeneratorExit Ausnahme im Inneren des Generators die Iteration zu beenden. Auf diese Ausnahme empfängt, muss der Code des Generators entweder erhöhen oder GeneratorExit StopIteration.

     

close() aufgerufen wird, wenn ein Generator Garbage Collection ist, so bedeutet dies, den Code des Generators eine letzte Chance bekommt zu laufen, bevor der Generator zerstört wird. Diese letzte Chance bedeutet, dass try...finally Aussagen in Generatoren können jetzt arbeiten garantiert werden; die finally Klausel erhält jetzt immer eine Chance zu laufen. Dies scheint wie ein kleines bisschen Sprache trivia, aber unter Verwendung von Generatoren und try...finally ist tatsächlich notwendig, um die with Aussage von PEP 343 beschrieben zu implementieren.

     

http://docs.python.org/ whatsnew / 2.5.html # pep-342-new-Generator-Funktionen

Damit behandelt die Situation, in der eine with Anweisung in einem Generator verwendet wird, aber es ergibt sich in der Mitte, aber nie wieder-die __exit__ Methode des Kontext-Manager wird aufgerufen, wenn der Generator Garbage Collection ist.


Bearbeiten :

Im Hinblick auf die Datei-Handle Frage: Ich vergesse manchmal, dass es Plattformen existieren, die nicht POSIX-like sind. :)

Was Schleusen gehen, ich glaube, Rafał Dowgird trifft den Kopf auf den Nagel, wenn er sagt: „Sie müssen nur bewusst sein, dass der Generator wie jedes andere Objekt ist, das Mittel hält.“ Ich glaube nicht, das with Aussage ist wirklich, dass hier relevant, da diese Funktion von den gleichen Deadlock Problemen leidet:

def coroutine():
    lock.acquire()
    yield 'spam'
    yield 'eggs'
    lock.release()

generator = coroutine()
generator.next()
lock.acquire() # whoops!

Andere Tipps

Ich glaube nicht, dass es ein echter Konflikt. Sie müssen nur bewusst sein, dass der Generator wie jedes anderes Objekt ist, das Mittel hält, so ist es in der Verantwortung des Erstellers um sicherzustellen, dass es richtig abgeschlossen ist (und Konflikte / Deadlock mit den Ressourcen, die von dem Objekt gehalten zu vermeiden). Der einzige (kleine) Problem, das ich hier sehe ist, dass Generatoren nicht implementieren das Kontextmanagementprotokoll (zumindest von Python 2.5), so kann man nicht nur:

with coroutine() as cr:
  doSomething(cr)

, sondern müssen:

cr = coroutine()
try:
  doSomething(cr)
finally:
  cr.close()

Der Garbage Collector funktioniert die close() sowieso, aber es ist eine schlechte Praxis auf, dass verlassen Ressourcen für die Freigabe.

Da yield beliebigen Code ausführen kann, würde ich sehr vorsichtig sein, von einer Sperre über eine yield-Anweisung zu halten. Sie können einen ähnlichen Effekt in vielen anderen Möglichkeiten zu bekommen, obwohl, ein Verfahren oder Funktionen einschließlich der Aufforderung, die oder auf andere Weise modifiziert überschrieben worden sein werden.

Generatoren sind jedoch immer (fast immer) „geschlossen“, entweder mit einem expliziten close() Anruf, oder einfach nur von Garbage Collection zu sein. einen Generator Schließen wirft einen GeneratorExit Ausnahme im Inneren des Generators und somit läuft schließlich Klauseln, mit Anweisung Bereinigung, etc. Sie die Ausnahme abfangen können, aber Sie müssen die Funktion (das heißt werfen eine StopIteration Ausnahme), anstatt Ertrag werfen oder verlassen. Es ist wahrscheinlich eine schlechte Praxis auf den Garbage Collector verlassen, um den Generator in Fällen zu schließen, wie Sie geschrieben haben, denn das später passieren könnte, als Sie vielleicht, und wenn jemand anruft sys._exit (), dann Bereinigung möglicherweise nicht passieren .

Das wäre, wie ich die Dinge erwartet zu funktionieren. Ja, wird der Block nicht seine Ressourcen freigeben, bis er abgeschlossen ist, so in diesem Sinne hat sich die Ressource entkam es lexikalische Verschachtelung ist. Doch dies ist jedoch nicht anders einen Funktionsaufruf zu machen, die die gleiche Ressource innerhalb eines mit dem Block zu verwenden versuchten - nichts, was man in dem Fall hilft, wo der Block nicht noch beendet, für was Grund. Es ist nicht wirklich etwas Bestimmtes zu Generatoren.

Eine Sache, die über wert sein könnte, sich Gedanken obwohl das Verhalten ist, wenn der Generator nie wieder aufgenommen. Ich hätte den with Block erwartet wie ein finally Block zu wirken und den __exit__ Teil auf Kündigung nennen, aber das scheint nicht der Fall zu sein.

Für eine TLDR, auf diese Weise auf sie aussehen:

with Context():
    yield 1
    pass  # explicitly do nothing *after* yield
# exit context after explicitly doing nothing

Die Context endet nach pass getan wird (das heißt nichts), pass führt nach yield getan wird (das heißt die Ausführung wieder aufnimmt). Also, die with Ende nach Steuerung bei yield wieder aufgenommen.

TLDR. Ein with Kontext bleibt gehalten, wenn yield Mitteilungen Kontrolle


Es gibt eigentlich nur zwei Regeln, die hier relevant sind:

  1. Wann with seine Ressource veröffentlichen?

    Es tut so einmal und direkt nach sein Block durchgeführt wird. Ersteres bedeutet, es entbindet nicht in ein yield, so dass mehrmals passieren könnte. Die spätere bedeutet es entbindet nach yield abgeschlossen hat.

  2. Wann wird yield vervollständigen?

    Denken Sie an yield als Reverse-Aufruf: die Steuerung an einen Aufrufer übergeben oben, nicht nach unten zu einem gerufenen ein. In ähnlicher Weise vervollständigt yield, wenn die Steuerung zurück, um es übergeben wird, wie wenn eine Anrufsteuerung zurück.

Hinweis

, dass sowohl with und yield arbeiten wie hier bestimmt! Der Punkt eines with lock ist eine Ressource zu schützen und es bleibt während eines yield geschützt. Sie können immer explizit diesen Schutz freigeben:

def safe_generator():
  while True:
    with lock():
      # keep lock for critical operation
      result = protected_operation()
    # release lock before releasing control
    yield result
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top