2017-07-06 73 views
0

我使用Python 3來模擬另一個工具提供的Python腳本接口。 (在編寫此工具腳本時,您將運行Python腳本,它可以執行import mytool來訪問腳本API。)如何執行一個文件並在Python 3中提供hooked導入?

我已經實現了此工具公開的接口,並且希望編寫一個您要調用的加載器如:

python run_mytool_script.py dump_menu_items.py 

該加載程序將允許您與該工具的某些功能進行交互,而無需實際安裝該工具。理想情況下,這應該允許我運行爲該工具設計的現有腳本,而無需進行修改。

run_mytool_script.py我想到:

  1. 初始化仿真腳本接口
  2. 準備進口鉤
  3. exec腳本dump_menu_items.py

不過,我不能完全弄清楚如何創建導入鉤子。我如何安裝一個鉤子,以便我的仿真腳本界面在腳本import mytool之後暴露爲mytool

請注意,模擬腳本接口必須在運行時初始化,因此安裝名爲mytool的程序包並不能解決問題。

回答

0

繼cPython測試here的例子後,我想出了以下可能的解決方案。我會更新這篇文章,因爲我認識到它的優點和缺點。

class HookedImporter(importlib.abc.MetaPathFinder, importlib.abc.Loader): 
    def __init__(self, hooks=None): 
     self.hooks = hooks 

    def find_spec(self, name, path, target=None): 
     if name not in self.hooks: 
      return None 

     return importlib.util.spec_from_loader(name, importlib.util.LazyLoader(self)) 

    def create_module(self, *args, **kwargs): 
     # this method req'd in 3.6 
     return None 

    def exec_module(self, module): 
     mod = self.hooks[module.__spec__.name] 
     # map the attributes from `mod` into the hooked module. 
     for attr in dir(mod): 
      if attr.startswith('__'): 
       continue 
      module.__dict__[attr] = getattr(mod, attr) 
     return 

    def install(self): 
     sys.meta_path.insert(0, self) 


... somewhere later on ... 


api = mytool.from_config(...) 
hooks = { 
    'mytool': api.mytool, 
} 

importer = HookedImporter(hooks=hooks) 
importer.install() 


with open(args.script_path, 'rb') as f: 
    g = { 
     '__name__': '__main__', 
    } 
    g.update(hooks) 
    exec(f.read(), g) 
2

那麼,有幾種方法可以做到這一點。讓我們從最複雜的一個開始 - 完全動態創建mytool。您可以使用imp模塊來創建一個新的模塊,然後定義它的結構,最後,將其添加到全局模塊列表,以便在一個統一的解釋堆棧上運行可以將其導入:

run_mytool_script.py

import imp 
import sys 

# lets first deal with loading of our external script so we don't waste cycles if a script 
# was not provided as an argument 
pending_script = None # hold a script path we should execute 
if __name__ == "__main__": # make sure we're running this script directly, not imported 
    if len(sys.argv) > 1: # we need at least one argument to tell us which script to run 
     pending_script = sys.argv[1] # set the script to run as the first argument 
    else: 
     print("Please provide a path for a script to run") # a script was not provided 
     exit(1) 

# now lets create the `mytool` module dynamically 
mytool = imp.new_module("mytool") # create a new module 

def hello(name): # define a local function 
    print("Hello {}!".format(name)) 
mytool.__dict__["hello"] = hello # add the function to the `mytool` module 

sys.modules["mytool"] = mytool # add 'mytool' to the global list so it can be imported 

# now everything running in the same Python interpreter stack is able to import mytool 
# lets run our `pending_script` if we're running the main script 
if pending_script: 
    try: 
     execfile(pending_script) # run the script 
    except NameError: # Python 3.x doesn't have execfile, use the exec function instead 
     with open(pending_script, "r") as f: 
      exec(f.read()) # read and run the script 

現在,您可以創建另一個腳本,相信你是有mytool模塊正在通過你的代理腳本運行時,如:

dump_menu_items.py

import mytool 
mytool.hello("Dump Menu Items") 

而現在,當你運行它:

$ python run_mytool_script.py dump_menu_items.py 
Hello Dump Menu Items! 

雖然整齊,這是一個相當繁瑣的工作 - 我建議你創建在run_mytool_script.py是同一個文件夾一個適當的mytool模塊,具有run_mytool_script.py初始化所有需要的東西,然後使用腳本的最後部分來運行你的外部腳本 - 這更容易管理,並且通常是更好的方法。

+0

哦,太棒了!我沒有意識到創建模塊對象非常容易。話雖如此,我會考慮您的建議來創建合適的模塊,因爲它聽起來像一個更簡單的解決方案。謝謝! –

相關問題