27 votes

générateur de python garbage collection

Je pense que ma question est liée à cela, mais pas exactement similaires. Considérer ce code:

def countdown(n):
    try:
        while n > 0:
            yield n
            n -= 1
    finally:
        print('In the finally block')

def main():
    for n in countdown(10):
        if n == 5:
            break
        print('Counting... ', n)
    print('Finished counting')

main()

La sortie de ce code est:

Counting...  10      
Counting...  9       
Counting...  8       
Counting...  7       
Counting...  6       
In the finally block 
Finished counting  

Est-il garanti que la ligne "Dans le bloc finally" va être imprimé avant "Fini le comptage"? Ou est-ce à cause de disponible détail de l'implémentation qu'un objet sera d'ordures collectées lorsque la référence du compte à rebours atteint 0.

Aussi, je suis curieux sur la façon dont finally bloc de l' countdown générateur est exécuté? par exemple, si je change le code de l' main de

def main():
    c = countdown(10)
    for n in c:
        if n == 5:
            break
        print('Counting... ', n)
    print('Finished counting')

ensuite, je ne vois Finished counting imprimé avant d' In the finally block. Comment le garbage collector aller directement à l' finally bloc? Je pense que j'ai toujours pris try/except/finally de sa valeur nominale, mais la pensée dans le contexte de générateurs est de me faire réfléchir à deux fois à ce sujet.

24voto

abarnert Points 94246

Vous êtes, comme vous le souhaitiez, en s'appuyant sur la mise en œuvre spécifique de comportement de Disponible de comptage de référence.1

En fait, si vous exécutez ce code, disons, PyPy, la sortie sera généralement:

Counting...  10
Counting...  9
Counting...  8
Counting...  7
Counting...  6
Finished counting
In the finally block

Et si vous l'exécutez en mode interactif PyPy session, que la dernière ligne peut venir de nombreuses lignes plus loin, ou même lorsque vous enfin sortie.


Si vous regardez la façon dont les générateurs sont mis en œuvre, ils ont des méthodes peu près comme ceci:

def __del__(self):
    self.close()
def close(self):
    try:
        self.raise(GeneratorExit)
    except GeneratorExit:
        pass

Disponible supprime les objets immédiatement lorsque le compteur de référence est à zéro (il dispose également d'un garbage collector pour briser cyclique des références, mais ce n'est pas pertinente ici). Dès que le générateur est hors de portée, il est supprimé, de sorte qu'il devient fermé, il soulève GeneratorExit dans la carcasse de l'alternateur et reprend. Et bien sûr, il n'y a pas de gestionnaire pour l' GeneratorExit, de sorte que l' finally clause est exécuté et le contrôle passe de la pile, où l'exception est avalé.

Dans PyPy, qui utilise un hybride garbage collector, le générateur ne sont pas supprimées jusqu'à ce que la prochaine fois que le GC décide de numérisation. Et dans une session interactive, avec une faible pression de mémoire, qui pourrait être aussi tard que le temps de la sortie. Mais une fois ce ne, la même chose arrive.

Vous pouvez le voir par la manipulation de l' GeneratorExit explicitement:

def countdown(n):
    try:
        while n > 0:
            yield n
            n -= 1
    except GeneratorExit:
        print('Exit!')
        raise
    finally:
        print('In the finally block')

(Si vous laissez l' raise off, vous obtiendrez les mêmes résultats que pour des raisons un peu différentes.)


Vous pouvez explicitement close d'un générateur et, contrairement aux les trucs ci-dessus, c'est une partie de l'interface publique du type de générateur:

def main():
    c = countdown(10)
    for n in c:
        if n == 5:
            break
        print('Counting... ', n)
    c.close()
    print('Finished counting')

Ou, bien sûr, vous pouvez utiliser un with déclaration:

def main():
    with contextlib.closing(countdown(10)) as c:
        for n in c:
            if n == 5:
                break
            print('Counting... ', n)
    print('Finished counting')

1. Comme Tim Peters réponse de points, vous êtes aussi fier de la mise en œuvre spécifique de comportement de la Disponible compilateur dans le deuxième essai.

16voto

Tim Peters Points 16225

Je soutiens @abarnert réponse, mais depuis que je l'ai déjà tapé ce ...

Oui, le comportement dans votre premier exemple est un artefact de l' CPython's de référencement de comptage. Lorsque vous sortir de la boucle, de l'anonyme générateur itérateur de l'objet countdown(10) retourné perd sa dernière référence, et est donc le garbage collector à la fois. Qui à son tour déclenche le générateur de l' finally: suite.

Dans ton deuxième exemple, le générateur itérateur reste lié à l' c jusqu'à ce que votre main() sorties, donc autant que l' CPython vous connaît peut reprendre c à tout moment. Ce n'est pas de "déchets" jusqu' main() des sorties. Un amateur de compilateur peut remarquer que c n'est jamais référencé après la boucle se termine, et décider efficacement del c avant cette date, mais CPython ne fait aucune tentative pour prédire l'avenir. Tous les noms restent liées jusqu'à ce que vous avez explicitement séparer vous-même, ou le domaine dans lequel ils sont locaux se termine.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X