Est-il possible d'avoir une fuite de mémoire réelle en Python à cause de votre code?

Je n'ai pas d'exemple de code, mais je suis curieux de savoir s'il est possible d'écrire du code Python qui entraîne essentiellement une fuite de mémoire.

22
demandé sur orokusaki 2010-01-07 03:27:33

4 réponses

C'est possible, Oui.

Cela dépend du type de fuite de mémoire dont vous parlez. Dans le code python pur, il n'est pas possible d'oublier de libérer de la mémoire comme en C, mais il est possible de laisser une référence suspendue quelque part. Quelques exemples de tels:

Un objet traceback non géré qui maintient une trame de pile entière en vie, même si la fonction n'est plus en cours d'exécution

while game.running():
    try:
        key_press = handle_input()
    except SomeException:
        etype, evalue, tb = sys.exc_info()
        # Do something with tb like inspecting or printing the traceback

Dans cet exemple stupide d'une boucle de jeu peut-être, nous avons attribué "bt" pour un local. Nous avions de bonnes intentions, mais cette tb contient des informations de trame sur la pile de tout ce qui se passait dans notre handle_input jusqu'à ce que cela appelle. En supposant que votre jeu continue, cette "tb" est maintenue en vie même lors de votre prochain appel à handle_input, et peut-être pour toujours. Les documents pour exc_info parlent maintenant de ce problème de référence circulaire potentiel et recommandent simplement de ne pas assigner tb Si vous n'en avez pas absolument besoin. Si vous avez besoin d'un retraçage considérez par exemple traceback.format_exc

Stocker des valeurs dans une classe ou une portée globale au lieu de la portée de l'instance, et ne pas la réaliser.

Celui-ci peut se produire de manière insidieuse, mais se produit souvent lorsque vous définissez des types mutables dans votre portée de classe.

class Money(object):
    name = ''
    symbols = []   # This is the dangerous line here

    def set_name(self, name):
        self.name = name

    def add_symbol(self, symbol):
        self.symbols.append('$')

Dans l'exemple ci-dessus, dites que vous avez fait

m = Money()
m.set_name('Dollar')
m.add_symbol('$')

Vous trouverez probablement Ce bogue particulier rapidement, mais dans ce cas, vous mettez une valeur mutable à la portée de la classe et même si vous y accédez correctement à la portée de l'instance, elle "passe" à l'objet de classe __dict__.

Ceci utilisé dans certains contextes comme la détention d'objets pourrait potentiellement causer des choses qui font croître le tas de votre application pour toujours, et causerait des problèmes, par exemple, une application web de production qui ne redémarrait pas ses processus de temps en temps.

Références cycliques dans les classes qui ont également une méthode __del__.

Paradoxalement, l'existence d'un __del__ rend impossible pour le garbage collector cyclique de nettoyer une instance. Disons que vous aviez quelque chose où vous vouliez faire un destructeur à des fins de finalisation:

class ClientConnection(...):
    def __del__(self):
        if self.socket is not None:
            self.socket.close()
            self.socket = None

Maintenant, cela fonctionne bien tout seul, et vous pouvez être amené à croire qu'il est un bon gestionnaire des ressources du système d'exploitation pour s'assurer que le socket est 'éliminé'.

Cependant, si ClientConnection conservait une référence pour dire, User et que L'utilisateur conservait une référence à la connexion, vous pourriez être tenté de dire que lors du nettoyage, ayons l'utilisateur dé-référencer la connexion. c'est en fait le défaut, cependant: le GC cyclique ne connaît pas l'ordre correct des opérations et ne peut pas le nettoyer.

La solution à ceci est de vous assurer de faire le nettoyage sur disons, déconnecter les événements en appelant une sorte de close, mais nommez cette méthode autre chose que __del__.

Extensions C mal implémentées, ou n'utilisant pas correctement les bibliothèques C comme elles sont supposées l'être.

En Python, vous faites confiance à la poubelle collecteur à jeter des choses que vous n'utilisez pas. Mais si vous utilisez une extension C qui enveloppe une bibliothèque C, la majorité du temps, vous êtes responsable de vous assurer de fermer ou de désallouer explicitement les ressources. La plupart du temps, cela est documenté, mais un programmeur python qui a l'habitude de ne pas avoir à faire cette désallocation explicite peut jeter le handle (comme le retour d'une fonction ou autre) à cette bibliothèque sans savoir que les ressources sont conservées.

Étendues qui contiennent des fermetures qui contiennent beaucoup plus que ce que vous auriez pu prévoir

class User:
    def set_profile(self, profile):
        def on_completed(result):
            if result.success:
                self.profile = profile

        self._db.execute(
            change={'profile': profile},
            on_complete=on_completed
        )

Dans cet exemple artificiel, nous semblons utiliser une sorte d'appel' async ' qui nous rappellera à on_completed lorsque l'appel DB est terminé (l'implémentation aurait pu être prometteuse, elle se termine avec le même résultat).

Ce que vous ne pouvez pas réaliser, c'est que la fermeture on_completed lie une référence à self afin d'exécuter l'affectation self.profile. Maintenant, peut-être que le client DB garde une trace de active requêtes et pointeurs vers les fermetures à appeler quand elles sont terminées (car c'est asynchrone) et dire qu'il se bloque pour une raison quelconque. Si le client DB ne nettoie pas correctement les rappels, etc., dans ce cas, le client DB a maintenant une référence à on_completed qui a une référence à User qui conserve un _db - Vous avez maintenant créé une référence circulaire qui peut ne jamais être collectée.

(même sans référence circulaire, le fait que les fermetures lient les locaux et même les instances peut parfois provoquer des valeurs vous pensiez que les collecteurs vivaient depuis longtemps, ce qui pourrait inclure des sockets, des clients, de grands tampons et des arbres entiers de choses)

Paramètres par défaut qui sont des types mutables

def foo(a=[]):
    a.append(time.time())
    return a

C'est un exemple artificiel, mais on pourrait être amené à croire que la valeur par défaut de a étant une liste vide signifie y ajouter, alors qu'il s'agit en fait d'une référence à la même liste. Cela pourrait encore provoquer une croissance illimitée sans savoir que vous l'avez fait que.

59
répondu Crast 2018-05-10 20:08:22

La définition classique d'une fuite de mémoire est la mémoire qui a été utilisée une fois, et qui ne l'est pas maintenant, mais qui n'a pas été récupérée. C'est presque impossible avec du code Python pur. Mais comme le souligne Antoine, vous pouvez facilement avoir pour effet de consommer toute votre mémoire par inadvertance en permettant aux structures de données de se développer sans limite, même si vous n'avez pas besoin de garder toutes les données autour.

Avec les extensions C, bien sûr, vous êtes de retour en territoire non géré, et tout est possible.

10
répondu Ned Batchelder 2010-01-07 00:39:51

Bien sûr que vous pouvez. L'exemple typique d'une fuite de mémoire est si vous créez un cache que vous ne videz jamais manuellement et qui n'a pas de stratégie d'expulsion automatique.

7
répondu Antoine P. 2010-01-07 00:33:27

Dans le sens de l'orphelinat des objets alloués après qu'ils soient hors de portée parce que vous avez oublié de les désallouer, Non; Python désallouera automatiquement les objets hors de portée (Garbage Collection). Mais dans le sens où @Antione parle, oui.

1
répondu Rob Curtis 2010-01-07 00:39:12