2016-05-15 69 views
0

我正在寫一個python應用程序,我想在其中使用動態的,一次運行的插件。python:動態加載一次性插件?

我的意思是,在這個應用程序的運行過程中不同的時間,它看起來與在特定地區的特殊名稱的Python源文件。如果找到任何這樣的源文件,我希望我的應用程序加載它,在其中運行一個預先命名的函數(如果存在這樣的函數),然後忘記該源文件。應用程序的運行過程中後來

,該文件可能已經改變了,我想我的Python應用程序重新加載它,執行它的方法,然後忘掉它,像以前一樣。

標準導入系統保持模塊居民初始加載後,這意味着隨後的「進口」或「__import__」調用不會加載其初始導入後,在同一個模塊。因此,在源文件中對Python代碼所做的任何更改在第二次到第n次導入時都將被忽略。

爲了使這種封裝唯一每次被加載,我想出了下面的過程。它的工作原理,但對我來說似乎有些「哈克」。有沒有更優雅或更喜歡的方式來做到這一點? (請注意,下面是一個簡化的過度,說明性示例)

import sys 
import imp 
# The following module name can be anything, as long as it doesn't 
# change throughout the life of the application ... 
modname = '__whatever__' 
def myimport(path): 
    '''Dynamically load python code from "path"''' 
    # get rid of previous instance, if it exists 
    try: 
     del sys.modules[modname] 
    except: 
     pass 
    # load the module 
    try: 
     return imp.load_source(modname, path) 
    except Exception, e: 
     print 'exception: {}'.format(e) 
     return None 

mymod = myimport('/path/to/plugin.py') 
if mymod is not None: 
    # call the plugin function: 
    try: 
     mymod.func() 
    except: 
     print 'func() not defined in plugin: {}'.format(path) 

附錄:一個問題這是FUNC()一個單獨的模塊上下文中運行,並且它具有內的任何函數或變量沒有訪問來電者的空間。因此,我必須做類似下面的東西不雅如果我想 func_one(),func_two()和ABC的調用FUNC的 ()內爲可訪問:

def func_one(): 
    # whatever 

def func_two(): 
    # whatever 

abc = '123' 

# Load the module as shown above, but before invoking mymod.func(), 
# the following has to be done ... 

mymod.func_one = func_one 
mymod.func_two = func_two 
mymod.abc  = abc 

# This is a PITA, and I'm hoping there's a better way to do all of 
# this. 

非常感謝你。

+0

雖然有點哈克,我看不出有什麼毛病你的方法。不過,就你的代碼而言,你絕不應該用'except:'來捕獲所有異常。相反,要捕獲特定的異常,例如'除了KeyError:'或'除了IOError:'外。 –

+0

謝謝。正如我所說的,我的代碼過於簡單化了。在現實世界中,我的錯誤檢查更爲廣泛。但是,請閱讀我的附錄,在您發表評論之後,我添加了該附錄。 – HippoMan

+0

爲什麼不使用'reload(module)'內建函數? https://docs.python.org/2/library/functions.html#reload – gdlmx

回答

2

我用下面的代碼來做這樣的事情。

注意,我並沒有真正導入代碼作爲一個模塊,但在特定情況下,而不是執行代碼。這使我可以定義一系列可自動供插件使用的API函數,而無需用戶輸入任何內容。

def load_plugin(filename, context): 
    source = open(filename).read() 
    code = compile(source, filename, 'exec') 
    exec(code, context) 
    return context['func'] 

context = { 'func_one': func_one, 'func_two': func_two, 'abc': abc } 
func = load_plugin(filename, context) 
func() 

此方法在Python 2.6+和python 3.3+

+0

這就是我需要的。謝謝你,那不勒斯。而且比我自己的哈克解決方案更「優雅」。 – HippoMan

+0

PS:...如果我想我的插件函數有權訪問我當前名稱空間中的所有內容,我想我可以這樣做:load_plugin(filename,globals())... correct? – HippoMan

+0

你必須小心''globals()'。如果我做's = 5; globals()['s'] = 6',那麼's'將在調用者的名字空間中爲6。也就是說,至少在cpython中,一個插件可能會弄亂另一個插件的命名空間。如果你改用'context = globals()。copy()',那麼你可以在插件完成時將你想要的符號拉出來,而不影響你的命名空間或其他插件的命名空間。 – Neapolitan

1

您使用的方法是完全正確的。對於這個問題

one problem with this is that func() runs within a separate module context, and it has no access to any functions or variables within the caller's space.

這可能是更好的使用execfile功能:

# main.py 
def func1(): 
    print ('func1 called') 
exec(open('trackableClass.py','r').read(),globals()) # this is similar to import except everything is done in the current module 
#execfile('/path/to/plugin.py',globals()) # python 2 version 
func() 

測試:

#/path/to/plugin.py 
def func(): 
    func1() 

結果:

python main.py 
# func1 called 

一個與這個潛在的問題方法是名稱空間污染,因爲每個文件都在當前命名空間中運行,這增加了名稱衝突的可能性。

+0

我以前有execfile()的問題,這就是讓我想到的方法。但也許這些是我的編碼問題。我會做一些測試,然後很快回來,或者解釋我的execfile()問題,或者聲明問題已經解決。 – HippoMan

+0

execfile在3.x中不可用;我需要加載文件,編譯然後執行。也許六個有兼容性功能。 – Neapolitan

+0

...是的,我現在重新發現了我的execfile()問題。 execfile()的第2到第n次調用不會覆蓋初始execfile調用()的負載。換句話說,如果文件是通過execfile()加載的,並且func()最初返回1,然後如果我更改文件以便func()返回2,則後續的execfile()調用仍會導致func()返回1。 – HippoMan