python:动态加载一次性插件



我正在编写一个python应用程序,我想在其中使用动态的、一次性可运行的插件。

我的意思是,在这个应用程序运行的不同时间,它会在特定位置查找具有特殊名称的python源文件。如果找到任何这样的源文件,我希望我的应用程序加载它,在其中运行一个预先命名的函数(如果存在这样的函数),然后忘记那个源文件。

在应用程序运行的后期,该文件可能已经更改,我希望我的python应用程序重新加载它,执行它的方法,然后像以前一样忘记它。

标准导入系统在初始加载后保持模块驻留,这意味着后续的"import"或"__import__"调用在初始导入后不会重新加载同一模块。因此,在第二次到第n次导入期间,将忽略此源文件中对python代码的任何更改。

为了每次都能唯一地加载这样的包,我想出了以下程序。这很有效,但对我来说有点"粗鲁"。有什么更优雅或更喜欢的方法吗?(注意,以下是一个过于简化的示例)

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的函数():

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.

非常感谢。

我使用以下代码来做这类事情。

请注意,我实际上并没有将代码作为模块导入,而是在特定的上下文中执行代码。这让我可以定义一组插件自动可用的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()

此方法适用于python2.6+和python3.3+

您使用的方法非常好。对于这个问题,

这样做的一个问题是func()在一个单独的模块上下文中运行,并且它无法访问调用方空间中的任何函数或变量。

最好使用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

这种方法的一个潜在问题是命名空间污染,因为每个文件都在当前命名空间中运行,这增加了名称冲突的机会。

最新更新