La combinaison des coroutines et de l'acquisition de ressources semble pouvoir avoir des conséquences inattendues (ou peu intuitives).
La question fondamentale est de savoir si quelque chose comme cela fonctionne ou non :
def coroutine():
with open(path, 'r') as fh:
for line in fh:
yield line
Ce qu'il fait. (Vous pouvez le tester !)
L'inquiétude la plus profonde est que with
est censé être une alternative à finally
où vous vous assurez qu'une ressource est libérée à la fin du bloc. Les coroutines peuvent suspendre et reprendre l'exécution de sur le site with
bloc, donc comment le conflit est-il résolu ?
Par exemple, si vous ouvrez un fichier en lecture/écriture à l'intérieur et à l'extérieur d'une coroutine alors que la coroutine n'est pas encore revenue :
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
Dans l'exemple précédent, je voulais parler de la contention des poignées de fichiers verrouillées en écriture, mais comme la plupart des systèmes d'exploitation allouent les poignées de fichiers par processus, il n'y aura pas de contention. (Bravo à @Miles qui m'a fait remarquer que l'exemple n'avait pas beaucoup de sens). Voici mon exemple révisé, qui montre une vraie 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()
0 votes
@Miles a fait remarquer que l'exemple est quelque peu malformé. J'ai opté pour un gestionnaire de fichiers verrouillé en écriture, mais comme le système d'exploitation alloue probablement des gestionnaires de fichiers par processus, cela devrait fonctionner correctement.
0 votes
TL;DR
yield
yreturn
sont sûres (dans la mesure où elles finiront par libérer des ressources). Cependant,return
pourrait ne pas se comporter gentiment. Pensez àwith os.scandir() as entries: return entries
. Cela ne fonctionne tout simplement pas ! Utilisezwith os.scandir() as entries: yield from entries
ou simplementreturn os.scandir()
à la place. La deuxième solution vous obligera à appeler.close()
sur leScandirIterator
instance si elle n'est pas épuisée. Ce n'est qu'un exemple, mais il illustre ce qui peut se passer lorsque l'on retourne des ressources temporaires d'une instance dewith
déclaration.