Выгрузить модуль в Python - PullRequest
52 голосов
/ 24 июня 2010

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

ОБНОВЛЕНИЕ Я связался с разработчиками Python по этой проблеме, и действительно не удастся выгрузить модуль полностью "в ближайшие пять лет".(см. ссылку)

Пожалуйста, примите, что Python действительно не поддерживает разгрузку модулей для серьезных, фундаментальных, непреодолимых технических проблем в 2.x.


Во время моей недавней охоты на утечку памяти в моем приложении я сузил ее до модулей, а именно из-за неспособности собрать мусор выгруженного модуля.Использование любого метода, указанного ниже, для выгрузки модуля оставляет в памяти тысячи объектов.Другими словами - я не могу выгрузить модуль в Python ...

Остальная часть вопроса - попытка каким-то образом собрать мусор модуля.

Давайте попробуем:

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

Давайте сохраним копию sys.modules, чтобы попытаться восстановить ее позже.Итак, это базовые 4074 объекта.В идеале мы должны как-то вернуться к этому.

Давайте импортируем модуль:

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

Мы до 7K объектов без мусора.Давайте попробуем удалить httplib из sys.modules.

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

Ну, это не сработало.Хм, а разве нет ссылки в __main__?Ах да:

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

Ура, вниз 300 объектов.Тем не менее, нет сигары, это более 4000 оригинальных предметов.Давайте попробуем восстановить sys.modules из копии.

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

Хммм, ну, это было бессмысленно, без изменений .. Может быть, если мы уничтожим глобалы ...

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

местных жителей?

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

Что за ... что если мы imported модуль внутри 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

Теперь, это не честно, он импортировал его в __main__, почему?Он никогда не должен был покидать local_dict ... Аааа!Мы возвращаемся к полностью импортированным httplib.Может быть, если мы заменим его фиктивным объектом?

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

Кровавый ..... !!

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

Умрите модули, умрите !!

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

Хорошо, после всех попыток, лучшее - это +2675 (почти + 50%) от начальной точки ... Это только от одного модуля ... У него даже нет ничего большого ...

Хорошо, теперь серьезно, где моя ошибка?Как выгрузить модуль и уничтожить все его содержимое?Или модули Python - это гигантская утечка памяти?

Полный исходный код в более простой для копирования форме: http://gist.github.com/450606

Ответы [ 3 ]

16 голосов
/ 24 июня 2010

Python не поддерживает выгрузку модулей.

Однако, если ваша программа не загружает неограниченное количество модулей с течением времени, это не источник утечки памяти. Модули обычно загружаются один раз при запуске и все. Ваша утечка памяти, скорее всего, находится в другом месте.

В том маловероятном случае, когда ваша программа действительно загружает неограниченное количество модулей с течением времени, вам, вероятно, следует изменить дизайн своей программы. ; -)

3 голосов
/ 24 июня 2010

Я не уверен насчет Python, но в других языках вызов эквивалента gc.collect() делает не освобождение неиспользуемой памяти - она ​​освободит эту память только в том случае, если она действительно нужна.

В противном случае Python имеет смысл на некоторое время сохранить модули в памяти на случай, если их потребуется снова загрузить.

0 голосов
/ 24 июня 2010

(Вы должны попытаться написать более сжатые вопросы; я только прочитал начало и просмотрел остальные.) В начале я вижу простую проблему:

sm = sys.modules.copy()

Вы сделали копию sys.modules, так что теперь ваша копия имеет ссылку на модуль - поэтому, конечно, она не будет собрана. Вы можете увидеть, что на это указывает, с помощью gc.get_referrers.

Это прекрасно работает:

# 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 выгружается, как только мы удаляем его из sys.modules, потому что нет никаких ссылок на модуль. (Выполнение module1 = None после импорта также будет работать - для ясности я просто поместил импорт в другую функцию. Все, что вам нужно сделать, это сбросить ссылки на него.)

Теперь это немного сложно сделать на практике из-за двух проблем:

  • Чтобы собрать модуль, все ссылки на модуль должны быть недоступны (как при сборе любого объекта). Это означает, что любые другие модули, которые импортировали его, также должны быть разыменованы и перезагружены.
  • Если вы удалите модуль из sys.modules, когда на него все еще ссылаются, вы создали необычную ситуацию: модуль все еще загружается и используется кодом, но загрузчик модулей больше об этом не знает. В следующий раз, когда вы импортируете модуль, вы не получите ссылку на существующий (поскольку вы удалили запись об этом), поэтому он загрузит вторую сосуществующую копию модуля. Это может вызвать серьезные проблемы с согласованностью. Поэтому убедитесь, что нет никаких ссылок на модуль, прежде чем окончательно удалить его из sys.modules.

В общем, есть некоторые хитрые проблемы: определить, какие модули зависят от выгружаемого модуля; зная, нормально ли их выгружать (сильно зависит от вашего варианта использования); обработка потоков при проверке всего этого (взгляните на imp.acquire_lock) и т. д.

Я мог бы придумать случай, когда это было бы полезно, но большую часть времени я бы рекомендовал просто перезапустить приложение, если его код изменится. Вы, вероятно, просто будете испытывать головную боль.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...