2010-06-23 31 views
49

TL/DR:descargar un módulo en Python

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 

ACTUALIZACIÓN me he puesto en contacto desarrolladores de Python sobre este problema y de hecho es not going to be possible to unload a module por completo "en los próximos cinco años". (ver el enlace)

Acepte que Python de hecho no admite módulos de descarga para problemas técnicos graves, fundamentales, insuperables, en 2.x.


Durante mi reciente búsqueda de un memleak en mi aplicación, me he reducido a los módulos, a saber, mi incapacidad para recoger la basura un módulo de carga. Usando cualquier método listado a continuación para descargar un módulo deja miles de objetos en la memoria. En otras palabras - No puedo descargar un módulo en Python ...

El resto de la pregunta es intento de recoger la basura un módulo de alguna manera. try

Vamos:

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 

Salvemos una copia de sys.modules a intentar restaurarla más tarde. Entonces, esta es una referencia de 4074 objetos. Idealmente, deberíamos volver a esto de alguna manera.

Vamos a importar un módulo:

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

estamos hasta 7K objetos que no son basura. Probemos eliminando httplib de sys.modules.

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

Bueno, eso no funcionó. Hmm, ¿pero no hay una referencia en __main__? Oh, sí:

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

Hooray, baja 300 objetos. Aún así, no hay cigarros, eso es mucho más de 4000 objetos originales. Tratemos de restaurar sys.modules desde la copia.

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

Hmmm, así que no tenía sentido, ningún cambio .. Tal vez si eliminamos a variables globales ...

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

locales?

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

Lo que el .. ¿y si nos imported un módulo dentro 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 

Ahora, eso no es justo, lo que importó en __main__, ¿por qué? Nunca debería haber salido del local_dict ... Argh! Volvemos a totalmente importado httplib. ¿Tal vez si lo reemplazamos con un objeto ficticio?

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

Bloody ..... !!

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

Die modules, die !!

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

bien, después de todos los intentos, lo mejor es 2,675 (casi el + 50%) desde el punto de partida ... eso es sólo de un módulo ... Eso ni siquiera tiene nada grande por dentro ...

Bien, ahora en serio, ¿dónde está mi error? ¿Cómo descargo un módulo y borro todos sus contenidos? ¿O los módulos de Python son una pérdida de memoria gigante?

fuente completo en el más simple de copiar formulario: http://gist.github.com/450606

Respuesta

17

Python no soporta módulos de descarga.

Sin embargo, a menos que su programa cargue un número ilimitado de módulos a lo largo del tiempo, esa no es la fuente de su pérdida de memoria. Los módulos normalmente se cargan una vez al inicio y eso es todo. Su fuga de memoria probablemente se encuentre en otro lado.

En el caso improbable de que su programa realmente cargue un número ilimitado de módulos a lo largo del tiempo, probablemente debería rediseñar su programa. ;-)

+1

Sí, carga un número razonablemente ilimitado de módulos: es un servidor de aplicaciones web que acepta nuevas revisiones de su propio código fuente y lo vuelve a cargar (esta es una tarea web bastante estándar). La fuga ES del hecho de que el código anterior aún está en la memoria, incluso si se reemplaza, incluso si no se puede acceder ... –

+0

Python admite módulos de descarga. Son basura recolectada, como cualquier otro objeto en Python. –

+1

@Slava: Es posible que desee echar un vistazo al código fuente a 'mod_python', que tiene su propio importador que está diseñado para manejar los módulos de recarga sin producir pérdidas de memoria. Puede haber algún código allí que puedas usar. –

0

(Usted debe tratar de escribir más preguntas concisas; sólo he leído el principio y el resto desnatada.) Veo un problema sencillo al comienzo:

sm = sys.modules.copy() 

Se hizo una copia de sys.modules, por lo que ahora su copia tiene una referencia al módulo, por lo que no se recopilará. Puedes ver lo que se refiere a esto con gc.get_referrers.

Esto funciona 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() 

módulo 1 se descarga tan pronto como se lo quitamos de sys.modules, porque no hay restantes referencias al módulo. (Hacer después de importar funcionaría, también - acabo de poner la importación en otra función para mayor claridad Todo lo que tiene que hacer es soltar sus referencias a ella..)

Ahora, es un poco difícil de hacer esto en la práctica , debido a dos cuestiones:

  • el fin de recoger un módulo, todas las referencias al módulo debe ser inalcanzable (al igual que con la recogida de cualquier objeto). Eso significa que cualquier otro módulo que lo haya importado debe ser desreferenciado y recargado también.
  • Si elimina un módulo de sys.modules cuando todavía está referenciado en otro lugar, ha creado una situación inusual: el módulo todavía se carga y utiliza el código, pero el cargador de módulos ya no lo sabe. La próxima vez que importe el módulo, no obtendrá una referencia al existente (ya que eliminó el registro), por lo que cargará una segunda copia coexistente del módulo. Esto puede causar serios problemas de consistencia. Por lo tanto, asegúrese de que no haya referencias restantes al módulo antes de eliminarlo definitivamente de sys.modules.

Existen algunos problemas complicados para usar esto en general: detectar qué módulos dependen del módulo que está descargando; saber si está bien descargarlos también (depende en gran medida de su caso de uso); Enhebrar mientras examina todo esto (eche un vistazo a imp.acquire_lock), y así sucesivamente.

Podría idear un caso en el que hacer esto podría ser útil, pero la mayoría de las veces recomiendo simplemente reiniciar la aplicación si cambia su código. Probablemente te darás dolores de cabeza.

+8

Bueno, para no ser snyde, pero debería haber leído la pregunta, o al menos la palabra "completamente" en el título (o al menos las etiquetas). El problema no es que quiera volver a cargar, el problema es * pérdida de memoria * asociada con cualquier tipo de eliminación de módulo (incluida) (incluidas las que usted propuso, que * están * enumeradas en mi pregunta, junto con otras docenas) En realidad, agregué 'sys.modules.copy()' en una etapa muy avanzada, eliminarlo no cambia nada (pruébate). –

+1

La fuente, para probar: http://gist.github.com/450606. Intente eliminar sys.modules.copy y verá que todavía hay más del 50% de aumento en los objetos, incluso con todas las referencias al módulo eliminado. –

+0

Vea aquí para abreviar de lo que está mal (usando su código): http://gist.github.com/450726. No intento cargar-descargar 'sys', ya que estamos trabajando en' sys.modules', entonces uso 'httplib' - puedes probar cualquier otro. –

3

no estoy seguro sobre Python, pero en otros idiomas, llamando al equivalente de gc.collect() hace memoria no utilizada no liberación - que sólo dará a conocer que la memoria si/cuando la memoria es realmente necesario.

De lo contrario, tiene sentido para Python mantener los módulos en la memoria por el momento, en caso de que necesiten cargarse nuevamente.

+0

El problema es que tengo que reemplazarlos con nuevas versiones. E incluso cuando lo reemplazo de 1 a 1 con el mismo módulo de tamaño, el uso de memoria crece (pérdidas) ... Gracias por la sugerencia, sin embargo. –