2010-06-23 83 views
49

TL/DR:卸載模塊在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 

UPDATE 我已經聯繫Python開發者對這個問題,事實上它是 「在未來五年」 not going to be possible to unload a module完全。 (請參閱鏈接)

請接受Python確實不支持在2.x中爲卸載嚴重的,基本的,難以克服的技術問題卸載模塊。


在我最近的追捕我的應用程序一個memleak,我已經將範圍縮小到模塊,即我不能垃圾收集空載模塊。使用任何下面列出的方法卸載模塊會在內存中留下數千個對象。換句話說 - 我不能在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非垃圾對象。 讓我們嘗試從sys.modules刪除httplib

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 

Hooray,下降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 

什麼..如果我們importedexec內的模塊?

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 

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 

好了,所有的嘗試後,最好是2675(將近+ 50%),從起點......這只是從一個模塊......這甚至都沒有什麼大的內...

好吧,現在認真,我的錯誤在哪裏? 如何卸載模塊並清除所有內容?或者Python的模塊是一個巨大的內存泄漏?在簡單的

完整的源代碼複製形式:http://gist.github.com/450606

回答

17

Python不支持卸載模塊。

但是,除非您的程序隨着時間的推移加載了無限數量的模塊,否則這不是內存泄漏的根源。模塊通常在啓動時加載一次,就是這樣。你的內存泄漏很可能在別處。

如果程序確實會隨着時間的推移加載無限數量的模塊,那麼您應該重新設計程序。 ;-)

+1

是的,它確實加載了合理無限數量的模塊 - 它是一個Web應用服務器,接受它自己的源代碼的新版本並重新加載它(這是非常標準的Web任務)。漏洞源於舊代碼仍然存在於內存中,即使被替換,即使無法訪問... – 2010-06-23 21:52:49

+0

Python確實支持卸載模塊。它們是垃圾收集的,就像Python中的其他對象一樣。 – 2010-06-23 22:56:24

+1

@Slava:你可能想看看'mod_python'的源代碼,它有自己的導入器,用於處理重裝模塊而不產生內存泄漏。那裏可能有一些你可以使用的代碼。 – 2010-06-23 23:00:33

0

(你應該嘗試編寫更簡潔的問題,我只讀過一開始和脫脂休息。)我看到一個簡單的問題,在開始:

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

只要我們將它從sys.modules中刪除,module1就會被卸載,因爲沒有剩餘的模塊引用。 (導入後做會工作,太 - 我只是把導入另一個函數爲清楚所有你需要做的就是放下你對它的引用。)

現在,這是一個有點棘手做到這一點在實踐中,因爲有兩個問題:

  • 爲了收集模塊,所有對模塊的引用必須是不可訪問的(就像收集任何對象一樣)。這意味着任何其他導入它的模塊也需要被解除引用和重新加載。
  • 如果您從sys.modules中刪除某個模塊,但仍然在其他地方引用該模塊時,您創建了一個不尋常的情況:模塊仍然被加載並被代碼使用,但模塊加載器不再瞭解它。下次您導入模塊時,您將不會獲得對現有模塊的引用(因爲您已刪除該模塊的記錄),因此它將加載模塊的第二個共存副本。這可能導致嚴重的一致性問題。因此,在最終從sys.modules中刪除它之前,請確保沒有剩餘的模塊引用。

有一些棘手的問題通常用於這個:檢測哪些模塊依賴於你卸載的模塊;知道是否可以卸載它們(很大程度上取決於您的使用情況);處理線程的同時檢查所有這些(查看imp.acquire_lock)等等。

我可以設法做一個這樣做可能有用的情況,但大多數情況下,我建議只是在代碼更改時重新啓動應用程序。你可能會讓自己頭疼。

+8

嗯,不是snyde,但你應該閱讀這個問題,或者至少在標題(或至少是標籤)中「完全」這個詞。問題不是我不想重新加載,問題是*內存泄漏*與任何(列出的)模塊刪除(包括您提出的那些*,*在我的問題中列出*)以及其他十幾個)。 其實我在很晚的階段添加了'sys.modules.copy()',刪除它不會改變任何東西(嘗試自己)。 – 2010-06-23 23:08:47

+1

來源,嘗試:http://gist.github.com/450606。嘗試刪除sys.modules.copy,即使刪除了所有對模塊的引用,您仍會看到對象增加了50%以上。 – 2010-06-23 23:19:58

+0

請參閱此處以瞭解缺點(使用您的代碼):http://gist.github.com/450726。我不嘗試加載 - 卸載'sys',因爲我們使用'sys.modules',所以我使用'httplib' - 你可以嘗試任何其他的。 – 2010-06-23 23:29:18

3

我不知道Python,但在其他語言中,調用的gc.collect()相當於不釋放未使用的內存 - 如果/當實際所需的內存也只會釋放內存。

否則,Python將模塊暫時保存在內存中是有意義的,以防需要再次加載模塊。

+0

問題是我需要用新版本替換它們。即使我用相同大小的模塊1對1替換它 - 內存使用增長(泄漏)...感謝您的建議,但。 – 2010-06-23 23:44:47