Answers:
基本上,我的目录是一个名为“插件”的目录,主应用程序可以对其进行轮询,然后使用imp.load_module拾取文件,查找可能带有模块级配置参数的知名入口点,然后从那里进入。我使用文件监视功能进行一定程度的动态处理,使插件处于活动状态,但这很不错。
当然,任何伴随着“我不需要[大,复杂的东西] X;我只想要轻量级的东西”的需求都会冒着重新实现X一次发现的需求的风险。但这并不是说您无法获得任何乐趣:)
imp
不建议使用此模块,而建议importlib
从python 3.4
imp.load_module
。
module_example.py
:
def plugin_main(*args, **kwargs):
print args, kwargs
loader.py
:
def load_plugin(name):
mod = __import__("module_%s" % name)
return mod
def call_plugin(name, *args, **kwargs):
plugin = load_plugin(name)
plugin.plugin_main(*args, **kwargs)
call_plugin("example", 1234)
它肯定是“最小的”,它绝对没有错误检查,可能有无数的安全问题,不是很灵活-但它应该向您展示Python中的插件系统可以多么简单。
你可能要考虑的小鬼模块也一样,虽然你可以做很多事只是__import__
,os.listdir
和一些字符串操作。
def call_plugin(name, *args)
为def call_plugin(name, *args, **kwargs)
,然后更改plugin.plugin_main(*args)
为plugin.plugin_main(*args, **kwargs)
imp
在赞成不赞成importlib
看一下现有插件框架/库的概述,这是一个很好的起点。我非常喜欢yapsy,但这取决于您的用例。
尽管这个问题确实很有趣,但我认为如果没有更多细节,很难回答。这是什么样的应用程序?它有GUI吗?它是命令行工具吗?一组脚本?具有唯一入口点的程序,等等。
鉴于我所掌握的信息很少,我将以非常通用的方式回答。
您必须添加插件是什么意思?
在纯代码/设计实践中,您必须明确确定要用户扩展的行为/特定操作。确定将始终被覆盖的公共入口点/一组功能,并确定这些操作中的组。完成此操作后,扩展应用程序应该很容易,
受钩子的示例,该示例受MediaWiki(PHP,但语言真的很重要吗?)的启发:
import hooks
# In your core code, on key points, you allow user to run actions:
def compute(...):
try:
hooks.runHook(hooks.registered.beforeCompute)
except hooks.hookException:
print('Error while executing plugin')
# [compute main code] ...
try:
hooks.runHook(hooks.registered.afterCompute)
except hooks.hookException:
print('Error while executing plugin')
# The idea is to insert possibilities for users to extend the behavior
# where it matters.
# If you need to, pass context parameters to runHook. Remember that
# runHook can be defined as a runHook(*args, **kwargs) function, not
# requiring you to define a common interface for *all* hooks. Quite flexible :)
# --------------------
# And in the plugin code:
# [...] plugin magic
def doStuff():
# ....
# and register the functionalities in hooks
# doStuff will be called at the end of each core.compute() call
hooks.registered.afterCompute.append(doStuff)
另一个例子,灵感来自水银。在这里,扩展仅将命令添加到hg命令行可执行文件,从而扩展了行为。
def doStuff(ui, repo, *args, **kwargs):
# when called, a extension function always receives:
# * an ui object (user interface, prints, warnings, etc)
# * a repository object (main object from which most operations are doable)
# * command-line arguments that were not used by the core program
doMoreMagicStuff()
obj = maybeCreateSomeObjects()
# each extension defines a commands dictionary in the main extension file
commands = { 'newcommand': doStuff }
对于这两种方法,您可能需要对扩展进行通用的初始化和终结处理。您可以使用所有扩展程序都必须实现的通用接口(更适合第二种方法; Mercurial使用所有扩展程序都需要使用的reposetup(ui,repo)),也可以使用带有某种挂钩的方法hooks.setup钩子。
但是同样,如果您想要更有用的答案,则必须缩小问题的范围;)
Marty Allchin的简单插件框架是我满足自己需求的基础。我真的建议您看一下它,如果您想要简单易行的内容,那真的是一个不错的开始。您也可以将其作为Django代码段找到。
我是一位退休的生物学家,负责处理数字微图,发现自己必须编写图像处理和分析程序包(从技术上来说不是库),才能在SGi机器上运行。我用C语言编写了代码,并使用Tcl作为脚本语言。这样的GUI就是使用Tk完成的。Tcl中出现的命令的格式为“ extensionName commandName arg0 arg1 ... param0 param1 ...”,即,用空格分隔的简单单词和数字。当Tcl看到“ extensionName”子字符串时,控制权传递给C包。依次通过lexer / parser(在lex / yacc中完成)运行命令,然后根据需要调用C例程。
可以通过GUI中的窗口逐个运行用于操作软件包的命令,但是批处理作业是通过编辑文本文件完成的,这些文本文件是有效的Tcl脚本;您将选择执行所需的文件级操作的模板,然后编辑一个副本以包含实际的目录和文件名以及package命令。它就像一种魅力。直到 ...
1)世界转向PC,2)当Tcl顽强的组织能力开始成为真正的不便时,脚本的长度超过了500行。时间飞逝 ...
我退休了,Python被发明了,它看起来像是Tcl的完美继承者。现在,我从未做过移植工作,因为我从未面临过在PC上编译(相当大的)C程序,用C包扩展Python以及在Python / Gt?/ Tk?/?中进行GUI的挑战。 ?但是,拥有可编辑模板脚本的旧想法似乎仍然可行。同样,以本机Python形式输入包命令也不会带来太大负担,例如:
packageName.command(arg0,arg1,...,param0,param1,...)
一些额外的圆点,括号和逗号,但这些不是最常用的。
我记得曾经看到有人在Python中完成了lex和yacc的版本(尝试:http : //www.dabeaz.com/ply/),所以如果仍然需要它们,那就可以了。
这种杂乱无章的要点是,在我看来,Python本身是科学家可以使用的理想“轻量级”前端。我很想知道您为什么不这样认为,我是认真的意思。
稍后添加:应用程序gedit预计会添加插件,并且它们的站点中有关于我在几分钟内发现的简单插件过程的最清晰的解释。尝试:
https://wiki.gnome.org/Apps/Gedit/PythonPluginHowToOld
我仍然想更好地了解您的问题。我不清楚您是1)希望科学家能够以各种方式简单地使用您的(Python)应用程序,还是2)是否允许科学家为您的应用程序添加新功能。选择#1是我们面对图像的情况,这导致我们使用通用脚本,我们对该脚本进行了修改以满足当前需求。是选择2使您想到插件的想法,还是应用程序的某些方面使向其发出命令不可行?
当我搜索Python Decorators时,发现了一个简单但有用的代码段。它可能不适合您的需求,但很有启发性。
class TextProcessor(object):
PLUGINS = []
def process(self, text, plugins=()):
if plugins is ():
for plugin in self.PLUGINS:
text = plugin().process(text)
else:
for plugin in plugins:
text = plugin().process(text)
return text
@classmethod
def plugin(cls, plugin):
cls.PLUGINS.append(plugin)
return plugin
@TextProcessor.plugin
class CleanMarkdownBolds(object):
def process(self, text):
return text.replace('**', '')
processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")
WordProcessor.plugin
不返回任何内容(None
),因此CleanMdashesExtension
稍后导入该类仅是import None
。如果插件类本身有用,则使用.plugin
class方法return plugin
。
我来到这里寻找的是最小的插件体系结构,发现很多东西对我来说似乎太过分了。因此,我已经实现了Super Simple Python Plugins。要使用它,您需要创建一个或多个目录,并__init__.py
在每个目录中放置一个特殊文件。导入这些目录将导致所有其他Python文件作为子模块加载,并且它们的名称将放置在__all__
列表中。然后由您来验证/初始化/注册这些模块。自述文件中有一个示例。
实际上,setuptools可与“插件目录”一起使用,如以下示例摘自项目文档:http : //peak.telecommunity.com/DevCenter/PkgResources#locating-plugins
用法示例:
plugin_dirs = ['foo/plugins'] + sys.path
env = Environment(plugin_dirs)
distributions, errors = working_set.find_plugins(env)
map(working_set.add, distributions) # add plugins+libs to sys.path
print("Couldn't load plugins due to: %s" % errors)
从长远来看,setuptools是一个更安全的选择,因为它可以加载插件而不会发生冲突或缺少需求。
另一个好处是,插件本身可以使用相同的机制进行扩展,而原始应用程序不必关心它。
作为插件系统的另一种方法,您可以检查Extend Me project。
例如,让我们定义简单的类及其扩展
# Define base class for extensions (mount point)
class MyCoolClass(Extensible):
my_attr_1 = 25
def my_method1(self, arg1):
print('Hello, %s' % arg1)
# Define extension, which implements some aditional logic
# or modifies existing logic of base class (MyCoolClass)
# Also any extension class maby be placed in any module You like,
# It just needs to be imported at start of app
class MyCoolClassExtension1(MyCoolClass):
def my_method1(self, arg1):
super(MyCoolClassExtension1, self).my_method1(arg1.upper())
def my_method2(self, arg1):
print("Good by, %s" % arg1)
并尝试使用它:
>>> my_cool_obj = MyCoolClass()
>>> print(my_cool_obj.my_attr_1)
25
>>> my_cool_obj.my_method1('World')
Hello, WORLD
>>> my_cool_obj.my_method2('World')
Good by, World
并显示隐藏在幕后的内容:
>>> my_cool_obj.__class__.__bases__
[MyCoolClassExtension1, MyCoolClass]
extend_me库通过元类操纵类的创建过程,因此在上面的示例中,当MyCoolClass
我们创建新实例时,由于Python的多重继承,我们得到了新类的实例,该实例是这两者的子类MyCoolClassExtension
并且MyCoolClass
具有两者的功能
为了更好地控制类的创建,此库中定义了一些元类:
ExtensibleType
-通过子类实现简单的可扩展性
ExtensibleByHashType
-与ExtensibleType相似,但具有构建类的专用版本的能力,从而允许对基类进行全局扩展和对类的专用版本进行扩展
这个库在OpenERP Proxy Project中使用,似乎工作得很好!
有关用法的真实示例,请查看OpenERP Proxy'field_datetime'扩展名:
from ..orm.record import Record
import datetime
class RecordDateTime(Record):
""" Provides auto conversion of datetime fields from
string got from server to comparable datetime objects
"""
def _get_field(self, ftype, name):
res = super(RecordDateTime, self)._get_field(ftype, name)
if res and ftype == 'date':
return datetime.datetime.strptime(res, '%Y-%m-%d').date()
elif res and ftype == 'datetime':
return datetime.datetime.strptime(res, '%Y-%m-%d %H:%M:%S')
return res
Record
这是易燃物品。RecordDateTime
是扩展名。
要启用扩展,只需导入包含扩展类的模块,以及(在上述情况下)导入的所有Record
对象,在其后创建的所有对象将在基类中具有扩展类,从而具有其所有功能。
该库的主要优点是,操作可扩展对象的代码不需要了解扩展,扩展可以更改可扩展对象中的所有内容。
my_cool_obj = MyCoolClassExtension1()
而不是my_cool_obj = MyCoolClass()
__new__
方法,因此它会自动查找所有子类,并构建新类(即所有子类),并返回此创建的类的新实例。因此,原始应用程序不需要了解所有扩展名。在构建库时,此方法很有用,以允许最终用户轻松修改或扩展其行为。在上面的示例中,MyCoolClass可以在库中定义并由其使用,而MyCoolClassExtension可以由最终用户定义。
入口点是发行版“公告” Python对象(例如函数或类)供其他发行版使用的简单方法。可扩展的应用程序和框架可以从特定发行版或sys.path上的所有活动发行版中搜索具有特定名称或组的入口点,然后随意检查或加载广告对象。
如果您使用pip或virtualenv,此软件包始终可用。
扩展@edomaur的答案,我建议您看一下simple_plugins(无耻插件),这是一个受Marty Alchin启发的简单插件框架。
一个基于项目自述文件的简短用法示例:
# All plugin info
>>> BaseHttpResponse.plugins.keys()
['valid_ids', 'instances_sorted_by_id', 'id_to_class', 'instances',
'classes', 'class_to_id', 'id_to_instance']
# Plugin info can be accessed using either dict...
>>> BaseHttpResponse.plugins['valid_ids']
set([304, 400, 404, 200, 301])
# ... or object notation
>>> BaseHttpResponse.plugins.valid_ids
set([304, 400, 404, 200, 301])
>>> BaseHttpResponse.plugins.classes
set([<class '__main__.NotFound'>, <class '__main__.OK'>,
<class '__main__.NotModified'>, <class '__main__.BadRequest'>,
<class '__main__.MovedPermanently'>])
>>> BaseHttpResponse.plugins.id_to_class[200]
<class '__main__.OK'>
>>> BaseHttpResponse.plugins.id_to_instance[200]
<OK: 200>
>>> BaseHttpResponse.plugins.instances_sorted_by_id
[<OK: 200>, <MovedPermanently: 301>, <NotModified: 304>, <BadRequest: 400>, <NotFound: 404>]
# Coerce the passed value into the right instance
>>> BaseHttpResponse.coerce(200)
<OK: 200>
当我不时地在Python中搜索插件框架时,我花了一些时间阅读该线程。我用了一些,但是它们有缺点。这是我在2017年对您进行详细审查时想出的结果,这是一个无接口,松耦合的插件管理系统:稍后再加载。这是有关如何使用它的教程。
您可以使用pluginlib。
插件易于创建,可以从其他软件包,文件路径或入口点加载。
创建一个插件父类,定义任何必需的方法:
import pluginlib
@pluginlib.Parent('parser')
class Parser(object):
@pluginlib.abstractmethod
def parse(self, string):
pass
通过继承父类来创建插件:
import json
class JSON(Parser):
_alias_ = 'json'
def parse(self, string):
return json.loads(string)
加载插件:
loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))
foo
,您可能会有一个名为的模块foo.parents
,您可以在其中定义父类。然后,您的插件将导入foo.parents
。对于大多数用例来说,这很好用。因为'foo'本身也被导入,所以为了避免循环导入的可能性,许多项目将模块的根目录留空,并使用__main__.py
文件或入口点启动应用程序。
我花了很多时间试图找到适合我的Python小型插件系统。但是然后我只是想,如果已经有了一个自然而灵活的继承,为什么不使用它。
对插件使用继承的唯一问题是您不知道最具体的(继承树上最低的)插件类是什么。
但这可以通过元类来解决,它可以跟踪基类的继承,并可以构建类,该类继承自大多数特定的插件(下图为“ Root extended”)
因此,我通过编码这样的元类提供了一个解决方案:
class PluginBaseMeta(type):
def __new__(mcls, name, bases, namespace):
cls = super(PluginBaseMeta, mcls).__new__(mcls, name, bases, namespace)
if not hasattr(cls, '__pluginextensions__'): # parent class
cls.__pluginextensions__ = {cls} # set reflects lowest plugins
cls.__pluginroot__ = cls
cls.__pluginiscachevalid__ = False
else: # subclass
assert not set(namespace) & {'__pluginextensions__',
'__pluginroot__'} # only in parent
exts = cls.__pluginextensions__
exts.difference_update(set(bases)) # remove parents
exts.add(cls) # and add current
cls.__pluginroot__.__pluginiscachevalid__ = False
return cls
@property
def PluginExtended(cls):
# After PluginExtended creation we'll have only 1 item in set
# so this is used for caching, mainly not to create same PluginExtended
if cls.__pluginroot__.__pluginiscachevalid__:
return next(iter(cls.__pluginextensions__)) # only 1 item in set
else:
name = cls.__pluginroot__.__name__ + 'PluginExtended'
extended = type(name, tuple(cls.__pluginextensions__), {})
cls.__pluginroot__.__pluginiscachevalid__ = True
return extended
因此,当您具有由元类构成的Root基础并且具有从其继承的插件树时,您可以自动获取类,该类可以通过子类化而从最特定的插件继承:
class RootExtended(RootBase.PluginExtended):
... your code here ...
代码库很小(约30行纯代码),并且在继承允许的范围内具有足够的灵活性。
如果您有兴趣,请参与@ https://github.com/thodnev/pluginlib
您也可以看看Groundwork。
这个想法是围绕可重用的组件(称为模式和插件)构建应用程序。插件是从派生的类GwBasePattern
。这是一个基本示例:
from groundwork import App
from groundwork.patterns import GwBasePattern
class MyPlugin(GwBasePattern):
def __init__(self, app, **kwargs):
self.name = "My Plugin"
super().__init__(app, **kwargs)
def activate(self):
pass
def deactivate(self):
pass
my_app = App(plugins=[MyPlugin]) # register plugin
my_app.plugins.activate(["My Plugin"]) # activate it
还有更高级的模式可以处理例如命令行界面,信令或共享对象。
Groundwork可以通过以编程方式将其绑定到应用程序(如上所示)或通过来自动找到其插件setuptools
。包含插件的Python包必须使用特殊的入口点来声明它们groundwork.plugin
。
这是文档。
免责声明:我是Groundwork的作者之一。
在当前的医疗保健产品中,我们具有使用接口类实现的插件体系结构。我们的技术堆栈是在Python之上的Django(用于API)和Nuxtjs(在nodejs的在前端)。
我们为我们的产品编写了一个插件管理器应用程序,该应用程序基本上是pip和npm软件包,遵循Django和Nuxtjs。
对于新的插件开发(pip和npm),我们将插件管理器作为依赖项。
在Pip包中:在setup.py的帮助下,您可以添加插件的入口点,以使用插件管理器(注册表,启动等)进行某些操作 https://setuptools.readthedocs.io/zh/latest/setuptools .html#automatic-script-creation
在npm软件包中:与pip相似,npm脚本中有钩子可以处理安装。 https://docs.npmjs.com/misc/scripts
我们的用例:
插件开发团队现已与核心开发团队分离。插件开发的范围是用于与在产品的任何类别中定义的第三方应用程序集成。插件界面的分类例如:-传真,电话,电子邮件...等插件管理器可以增强到新的类别。
在您的情况下:也许您可以编写一个插件,然后将其重复用于做事。
如果插件开发人员需要使用重用核心对象,则可以通过在插件管理器中进行一定程度的抽象来使用该对象,以便任何插件都可以继承这些方法。
只是分享我们在产品中的实现方式,希望对您有所帮助。