Décharger un module en Python

TL / DR:

import gc, sys

print len(gc.get_objects()) # 4073 objects in memory

# Attempt to unload the module

import httplib
del sys.modules["httplib"]
httplib = None

gc.collect()
print len(gc.get_objects()) # 6745 objects in memory

mise à jour J'ai contacté les développeurs Python à propos de ce problème et en effet il ne sera pas possible de décharger complètement un module "dans les cinq prochaines années". (voir le lien)

Veuillez accepter que Python en effet ne supporte pas les modules de déchargement pour des problèmes techniques graves, fondamentaux, insurmontables, dans 2.X.


Au cours de ma récente chasse pour un memleak dans mon application, je l'ai réduit à les modules, à savoir mon incapacité à ordures recueillir un déchargement de module. L'utilisation de toute méthode listée ci-dessous pour décharger un module laisse des milliers d'objets en mémoire. En d'autres termes - Je ne peux pas décharger un module en Python...

Le reste de la question Est de tenter de récupérer un module d'une manière ou d'une autre.

, essayons:

import gc
import sys

sm = sys.modules.copy()  # httplib, which we'll try to unload isn't yet 
                         # in sys.modules, so, this isn't the source of problem

print len(gc.get_objects()) # 4074 objects in memory

Sauvegardons une copie de {[12] } pour tenter de la restaurer plus tard. Donc, c'est une ligne de base 4074 objets. Nous devrions idéalement y revenir d'une certaine manière.

Importons un module:

import httplib
print len(gc.get_objects()) # 7063 objects in memory

Nous sommes JUSQU'à 7K objets non-ordures. Essayons de supprimer httplib de sys.modules.

sys.modules.pop('httplib')
gc.collect()
print len(gc.get_objects()) # 7063 objects in memory

Eh Bien, cela ne fonctionne pas. Hmm, mais n'y a-t-il pas une référence dans __main__? Ah, oui:

del httplib
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

Hourra, vers le bas 300 objets. Pourtant, pas de cigare, c'est beaucoup plus de 4000 objets originaux. Essayons de restaurer {[12] } à partir de la copie.

sys.modules = sm
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

Hmmm, Eh bien c'était inutile, pas de changement.. Peut être que si on efface mondial...

globals().clear()
import gc # we need this since gc was in globals() too
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

Locaux?

locals().clear()
import gc # we need this since gc was in globals() too
gc.collect()
print len(gc.get_objects()) # 6746 objects in memory

Qu'est-ce que le.. si nous imported un module à l'intérieur de exec?

local_dict = {}
exec 'import httplib' in local_dict
del local_dict
gc.collect()
print len(gc.get_objects())  # back to 7063 objects in memory

Maintenant, ce n'est pas juste, il l'a importé __main__, pourquoi? Il n'aurait jamais dû quitter le local_dict... Argh! Nous revenons à entièrement importé httplib. Peut-être que si nous l'avons remplacé par un objet factice?

from types import ModuleType
import sys
print len(gc.get_objects())  # 7064 objects in memory

Sanglante.....!!

sys.modules['httplib'] = ModuleType('httplib')
print len(gc.get_objects())  # 7066 objects in memory

Mourir modules, mourir!!

import httplib
for attr in dir(httplib):
    setattr(httplib, attr, None)
gc.collect()
print len(gc.get_objects())  # 6749 objects in memory

Bon, après toutes les tentatives, le meilleur est + 2675 (près de +50%) du point de départ... C'est juste à partir d'un module... Qui n'a même pas quelque chose de grand à l'intérieur...

Ok, maintenant, sérieusement, où est mon erreur? Comment puis-je décharger un module et effacer tout son contenu? Ou les modules de Python sont-ils une fuite de mémoire géante?

Source complète sous forme plus simple à copier: http://gist.github.com/450606

50
demandé sur unutbu 2010-06-24 01:46:04

3 réponses

Python ne prend pas en charge le déchargement des modules.

Cependant, à moins que votre programme ne charge un nombre illimité de modules au fil du temps, ce n'est pas la source de votre fuite de mémoire. Les Modules sont normalement chargés une fois au démarrage et c'est tout. Votre fuite de mémoire se trouve probablement ailleurs.

Dans le cas peu probable que votre programme charge réellement un nombre illimité de modules au fil du temps, vous devriez probablement redessiner votre programme. ;-)

16
répondu Daniel Stutzbach 2010-06-23 21:50:07

Je ne suis pas sûr de Python, mais dans d'autres langages, appeler l'équivalent de gc.collect() Ne pas libère de la mémoire inutilisée - il ne libère cette mémoire que si/quand la mémoire est réellement nécessaire.

Sinon, il est logique pour Python de garder les modules en mémoire pour le moment, au cas où ils auraient besoin d'être chargés à nouveau.

3
répondu BlueRaja - Danny Pflughoeft 2010-06-23 23:49:05

(vous devriez essayer d'écrire des questions plus concises; Je n'ai lu que le début et écrémé le reste.) Je vois un problème simple au début:

sm = sys.modules.copy()

Vous avez fait une copie de sys.modules, donc maintenant votre copie a une référence au module - donc bien sûr il ne sera pas collecté. Vous pouvez voir ce qui en fait référence avec gc.get_referrers.

Cela fonctionne bien:

# module1.py
class test(object):
    def __del__(self):
        print "unloaded module1"
a = test()

print "loaded module1"

.

# testing.py
def run():
    print "importing module1"
    import module1
    print "finished importing module1"

def main():
    run()
    import sys
    del sys.modules["module1"]
    print "finished"

if __name__ == '__main__':
    main()

Module1 est déchargé dès que nous le retirons de sys.les modules, car il ne reste pas d' références au module. (Faire module1 = None après l'importation fonctionnerait aussi-je viens de mettre l'importation dans une autre fonction pour plus de clarté. Tout ce que vous avez à faire est de laisser tomber vos références.)

Maintenant, c'est un peu compliqué de le faire en pratique, à cause de deux problèmes:

  • pour collecter un module, toutes les références au module doivent être inaccessibles (comme pour collecter n'importe quel objet). Cela signifie que tous les autres modules qui l'ont importé doivent être déréférencés et rechargés, trop.
  • Si vous supprimez un module de sys.modules quand il est encore référencé ailleurs, vous avez créé une situation inhabituelle: le module est toujours chargé et utilisé par le code, mais le chargeur de module ne le sait plus. La prochaine fois que vous importerez le module, vous n'obtiendrez pas de référence à celui existant (puisque vous avez supprimé l'enregistrement de celui-ci), donc il chargera une deuxième copie coexistante du module. Cela peut causer de graves problèmes de cohérence. Donc, assurez-vous qu'il n'y a pas références restantes au module avant de finalement le supprimer de sys.module.

Il y a quelques problèmes difficiles à utiliser en général: détecter quels modules dépendent du module que vous déchargez; savoir s'il est correct de les décharger aussi (dépend fortement de votre cas d'utilisation); gérer le threading tout en examinant tout cela (jetez un oeil à imp.acquire_lock), et ainsi de suite.

Je pourrais imaginer un cas où cela pourrait être utile, mais la plupart du temps, je recommanderais juste redémarrer l'application si son code change. Vous aurez probablement juste vous donner des maux de tête.

0
répondu Glenn Maynard 2010-06-23 22:55:15