2017-04-23 53 views
2

使用已棄用的模塊imp,我可以編寫一個自定義導入掛鉤,在Python導入/執行之前即時修改模塊的源代碼。由於源代碼爲一個名爲下面source字符串,來創建一個模塊所需的基本代碼如下:如何實現一個可以使用importlib實時修改源代碼的導入鉤子?

module = imp.new_module(name) 
sys.modules[name] = module 
exec(source, module.__dict__) 

由於imp已過時,我願做importlib類似的東西。 [編輯:還有其他imp方法,需要更換以建立一個自定義的導入鉤子 - 所以我正在尋找的答案不是簡單地取代上述代碼。]

但是,我一直無法瞭解如何做到這一點。 importlib documentation有一個function to create modules from "specs",據我所知,它是包含自己的裝載機的對象,沒有明顯的方式來重新定義它們,以便能夠從字符串創建模塊。

我已經創建了一個minimal example來演示這個;有關詳細信息,請參閱自述文件。

+0

如果你看看'imp.new_module'文檔,你會發現'從3.4版開始棄用:使用types.ModuleType來代替。'這不能解決你的問題嗎? –

+0

我看到imp.modules必須以這種方式替換,但文檔指示使用module_from_spec(來自importlib)。我使用imp中的3個方法來執行自定義鉤子導入程序,並且需要找到importlib的等效項。 –

回答

5

find_moduleload_module都被棄用。您需要分別切換到find_spec和(create_moduleexec_module)模塊。有關詳細信息,請參閱importlibdocumentation

您還需要檢查是否要使用MetaPathFinderPathEntryFinder作爲調用它們的系統是不同的。也就是說,元路徑搜索器首先進入並可以覆蓋內置模塊,而路徑入口搜索器專門用於在sys.path上找到的模塊。

以下是一個非常基本的進口商,試圖取代整個進口機械。它顯示如何使用這些功能(find_spec,create_moduleexec_module)。

import sys 
import os.path 

from importlib.abc import Loader, MetaPathFinder 
from importlib.util import spec_from_file_location 

class MyMetaFinder(MetaPathFinder): 
    def find_spec(self, fullname, path, target=None): 
     if path is None or path == "": 
      path = [os.getcwd()] # top level import -- 
     if "." in fullname: 
      *parents, name = fullname.split(".") 
     else: 
      name = fullname 
     for entry in path: 
      if os.path.isdir(os.path.join(entry, name)): 
       # this module has child modules 
       filename = os.path.join(entry, name, "__init__.py") 
       submodule_locations = [os.path.join(entry, name)] 
      else: 
       filename = os.path.join(entry, name + ".py") 
       submodule_locations = None 
      if not os.path.exists(filename): 
       continue 

      return spec_from_file_location(fullname, filename, loader=MyLoader(filename), 
       submodule_search_locations=submodule_locations) 

     return None # we don't know how to import this 

class MyLoader(Loader): 
    def __init__(self, filename): 
     self.filename = filename 

    def create_module(self, spec): 
     return None # use default module creation semantics 

    def exec_module(self, module): 
     with open(self.filename) as f: 
      data = f.read() 

     # manipulate data some way... 

     exec(data, vars(module)) 

def install(): 
    """Inserts the finder into the import machinery""" 
    sys.meta_path.insert(0, MyMetaFinder()) 

下一個稍微更微妙的版本,試圖重用更多的進口機械。因此,您只需要定義如何獲取模塊的來源。

import sys 
from os.path import isdir 
from importlib import invalidate_caches 
from importlib.abc import SourceLoader 
from importlib.machinery import FileFinder 


class MyLoader(SourceLoader): 
    def __init__(self, fullname, path): 
     self.fullname = fullname 
     self.path = path 

    def get_filename(self, fullname): 
     return self.path 

    def get_data(self, filename): 
     """exec_module is already defined for us, we just have to provide a way 
     of getting the source code of the module""" 
     with open(filename) as f: 
      data = f.read() 
     # do something with data ... 
     # eg. ignore it... return "print('hello world')" 
     return data 


loader_details = MyLoader, [".py"] 

def install(): 
    # insert the path hook ahead of other path hooks 
    sys.path_hooks.insert(0, FileFinder.path_hook(loader_details)) 
    # clear any loaders that might already be in use by the FileFinder 
    sys.path_importer_cache.clear() 
    invalidate_caches() 
相關問題