假設模塊文件名是data_used_to_test.py,放在tests文件夾下
文件夾結構如下:
project
|-tests
|-data_used_to_test.py
文件內包含一個test_class類:
class test_class():
def test_func(arg):
return "hello {}".format(arg)
代碼全部基於 Python3.6.4
-
使用imp
-
用imp.find_module查找模塊
In [1]:file, pathname, description = imp.find_module('data_used_to_test', path=['tests/']) In [2]: file Out[2]: <_io.TextIOWrapper name='tests/data_used_to_test.py' mode='r' encoding='utf-8'> In [3]: pathname Out[3]: 'tests/data_used_to_test.py' In [4]: description Out[4]: ('.py', 'r', 1)
-
用imp.load_module將找到的模塊加載到sys.modules中
In [5]: mod = imp.load_module('test_load', file, pathname, description) In [6]: mod Out[6]: <module 'test_load' from 'tests/data_used_to_test.py'>
這時候sys.modules里會多一條'test_load'的記錄,值就是mod的值。
-
這時候就可以直接通過mod訪問包內的對象了
In [7]: mod.test_class().test_func('x') Out[7]: 'hello x'
這個方法的優點是
- 簡單,容易實現。
- 不用對pyc文件做特殊處理
缺點是可定制性太低。不適合框架使用
- 無法動態修改模塊源代碼, 開放的api必須要很穩定,不會經常變動。
- 可以訪問的對象一開始就要制定成固定名稱。無法動態注冊訪問。
-
上面的問題2可以用getattr解決,讓我們更進一步。
In [8]: tmp = getattr(mod, 'test_class') In [9]: tmp Out[9]: test_load.test_class In [10]: tmp().test_func('l') Out[10]: 'hello l'
這樣就可以通過事先在外部模塊中調用准備好的注冊函數,
把外部模塊中的類或函數注冊到一個全局的單例變量中,
實現動態的模塊加載和對象訪問。
但仍然無法解決問題 1.
所以就需要另一種模塊加載方法。
-
-
顯式的定義一個加載函數
import imp import sys def load_module(module_name, module_content): if fullname in sys.modules: return sys.modules[fullname] mod = sys.modules.setdefault(url, imp.new_module(module_name)) mod.__file__ = module_name mod.__package__ = '' # if *.py code = compile(module_content, url, 'exec') # if *.pyc 有問題,我運行一直報錯 # code = marshal.loads(module_content[8:]) exec(code, mod.__dict__) # 2的寫法是 exec code in mod.__dict__ # 其實就是讓code在環境里運行一下,所以這里可能會有注入漏洞 return mod
這個函數接受模塊的名字和源碼內容,並使用 compile() 將其編譯到一個代碼對象中, 然后在一個新創建的模塊對象的字典中來執行它。
下面是這個函數的使用方式:
In[1]: module_content = open('tests/data_used_to_test.py').read() In[2]: mod = load_module('test_import', module_content)
后面就和 1.4 一樣了。
這樣可以同時解決 1.3 中提出的兩個問題。
因為你是先將源碼作為普通文件讀進來的,也就可以做各種修改后再注冊到sys.modules中。
Pocsuite使用的就是這種方法。雖然我覺得他們當時可能也沒太弄明白。
當然這還是有不足的。
- 上面的兩種方法都只支持簡單的模塊。並沒有嵌入到通常的import語句中。並不支持更高級的結構比如包。
- 不夠酷
-
自定義導入器
PythonCookbook上給了自定義導入器的兩種方式
- 創建一個元路徑導入器
- 編寫一個鈎子直接嵌入到 sys.path 變量中去
還沒看明白,PEP302的文檔太長了,大概知道是繼承importlib.abc來做的。