Question

La combinaison de coroutines et l'acquisition des ressources semble que cela pourrait avoir des non intentionnelles (ou peu intuitives) conséquences.

La question fondamentale est de savoir si quelque chose comme cela fonctionne:

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

Ce qu'il fait. (Vous pouvez le tester!)

La préoccupation profonde est que with est censé être une alternative à quelque chose finally, où vous assurer qu'une ressource est libérée à la fin du bloc. Coroutines peut suspendre et reprendre l'exécution de dans les le bloc with, donc comment le conflit résolu?

Par exemple, si vous ouvrez un fichier en lecture / écriture à la fois à l'intérieur et à l'extérieur d'un coroutine alors que le coroutine n'a pas encore retourné:

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?

Mise à jour

J'allais pour la poignée de fichier verrouillé en écriture contention dans l'exemple précédent, mais puisque la plupart des systèmes d'exploitation allouent par processus handles de fichiers il n'y aura pas de conflit là. (Kudos à @Miles pour remarquer l'exemple ne pas faire trop de sens.) Voici mon exemple révisé, ce qui montre une véritable condition de blocage:

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()
Était-ce utile?

La solution

Je ne comprends pas vraiment ce conflit vous poser des questions sur, ni le problème avec l'exemple. Il est bien d'avoir deux coexistant, poignées indépendantes dans le même fichier

Une chose que je ne savais pas que j'ai appris en réponse à votre question, il qu'il ya une nouvelle méthode close () sur les générateurs:

  

close() soulève une nouvelle exception GeneratorExit à l'intérieur du générateur de mettre fin à l'itération. A la réception de cette exception, le code du générateur doit soit augmenter GeneratorExit ou StopIteration.

     

close() est appelée lorsqu'un générateur est ramasse-miettes, donc cela signifie le code du générateur obtient une dernière chance de courir avant que le générateur est détruit. Cette dernière chance signifie que les déclarations de try...finally dans les générateurs peuvent désormais être garantis au travail; la clause finally va maintenant toujours une chance de courir. Cela semble vraiment nécessaire est comme un peu mineur de trivia de langue, mais en utilisant des générateurs et try...finally afin de mettre en œuvre la déclaration de with décrit par PEP 343.

     

http://docs.python.org/ whatsnew / 2.5.html # pep-342-nouvelle-générateur caractéristiques

qui gère la situation dans laquelle une déclaration de with est utilisé dans un générateur, mais il cède au milieu, mais ne retourne-la méthode de __exit__ de gestionnaire de contexte sera appelé lorsque le générateur est collecté des déchets.


Modifier :

En ce qui concerne la question de la poignée de fichiers: J'oublie parfois qu'il existe des plates-formes qui ne sont pas comme POSIX. :)

En ce qui concerne les verrous vont, je pense que Rafał Dowgird frappe la tête sur l'ongle quand il dit: « Il vous suffit d'être conscient que le générateur est comme tout autre objet qui contient des ressources. » Je ne pense pas que la déclaration de with est vraiment pertinent en l'espèce, puisque cette fonction souffre des mêmes problèmes de blocage:

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

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

Autres conseils

Je ne pense pas qu'il y ait un vrai conflit. Il vous suffit d'être conscient que le générateur est comme tout autre objet qui contient des ressources, il est de la responsabilité du créateur pour vous assurer qu'il est correctement mis au point (et pour éviter les conflits / impasse avec les ressources détenues par l'objet). Le seul problème (mineur) que je vois ici est que les générateurs ne mettent pas en œuvre le protocole de gestion de contexte (au moins de Python 2.5), de sorte que vous ne pouvez pas simplement:

with coroutine() as cr:
  doSomething(cr)

mais plutôt avoir à:

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

Le garbage collector ne le close() de toute façon, mais il est une mauvaise pratique de compter sur cela pour libérer des ressources.

Parce que yield peut exécuter du code arbitraire, je serais très prudent de tenir un verrou sur une déclaration de rendement. Vous pouvez obtenir un effet similaire dans beaucoup d'autres façons, cependant, y compris appeler une méthode ou des fonctions qui pourraient être ont été redéfinis autrement modifiées.

Les générateurs, cependant, sont toujours (presque toujours) « fermée », que ce soit avec un appel close() explicite, ou tout simplement en étant le ramasse-miettes. La fermeture d'un générateur jette une exception GeneratorExit à l'intérieur du générateur et va donc enfin clauses, avec le nettoyage des états, etc. Vous pouvez attraper l'exception, mais vous devez lancer ou quitter la fonction (à savoir lancer une exception StopIteration), plutôt que de céder. Il est pratique probablement pauvre de compter sur le garbage collector pour fermer le générateur dans des cas comme vous avez écrit, parce que cela pourrait se produire plus tard que vous voudrez peut-être, et si quelqu'un appelle, alors votre nettoyage sys._exit () pourrait ne pas se produire du tout .

Ce serait ce que je pensais des choses à travailler. Oui, le bloc ne libérera pas ses ressources jusqu'à ce qu'il complète, donc dans ce sens que la ressource a échappé à son imbrication lexical. Cependant, mais cela ne diffère pas de faire un appel de fonction qui a essayé d'utiliser la même ressource dans un avec bloc - rien vous aide dans le cas où le bloc a pas encore terminée, pour quelle que soit raison. Ce n'est pas vraiment quoi que ce soit spécifique aux producteurs.

Une chose qui pourrait être utile se soucier si le comportement si le générateur est jamais a repris. Je me serais attendu le bloc with d'agir comme un bloc de finally et appeler la partie __exit__ à la résiliation, mais cela ne semble pas être le cas.

Pour un TLDR, regardez cette façon:

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

Le Context se termine après pass est fait (à savoir rien), pass exécute après yield est fait (à savoir l'exécution reprend). Ainsi, les extrémités de with après contrôle reprend à yield.

TLDR. Un contexte with reste maintenu lorsque yield rend le contrôle


Il y a en fait seulement deux règles qui sont pertinentes en l'espèce:

  1. Quand est-ce with libérer ses ressources?

    Il le fait une fois et directement après son bloc est fait. Le premier signifie qu'il ne libère pas pendant un yield, car cela pourrait se produire plusieurs fois. Ce dernier signifie qu'il ne libère après yield est terminée.

  2. Quand est-yield complète?

    Pensez à yield comme un appel inverse: le contrôle est passé à un appelant, pas à une appelée un. De même, lorsque le contrôle yield complète est repassée à elle, tout comme lorsqu'un appel retourne le contrôle.

Notez que les deux with et yield fonctionnent comme prévu ici! Le point d'un with lock est de protéger une ressource et il reste protégé pendant une yield. Vous pouvez toujours libérer explicitement cette protection:

def safe_generator():
  while True:
    with lock():
      # keep lock for critical operation
      result = protected_operation()
    # release lock before releasing control
    yield result
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top