2016-05-15 28 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