我目前正在编写一个应用程序,允许用户通过“插件”类型架构扩展它。他们可以根据我提供的BaseClass对象编写其他python类,并根据各种应用程序信号加载这些类。在启动应用程序之前,作为插件加载的类的确切数量和名称是未知的,但仅在启动时加载一次。
在研究解决这个问题的最佳方法时,我提出了两个常见的解决方案。
选项1 - 使用imp,pkgutil等滚动自己
例如,请参阅this answer或this one。
选项2 - 使用插件管理器库
随机挑选一对
我的问题是 - 条件是必须重新启动应用程序以加载新插件 - 上述方法的任何好处都取决于this SO answer和this one的启发,例如:< / p>
import inspect
import sys
import my_plugins
def predicate(c):
# filter to classes
return inspect.isclass(c)
def load_plugins():
for name, obj in inspect.getmembers(sys.modules['my_plugins'], predicate):
obj.register_signals()
与上述方法相比,这种方法有任何缺点吗? (除了所有插件必须在同一个文件中)谢谢!
修改
评论请求进一步的信息......我能想到的另一件事就是插件使用blinker库来提供他们订阅的信号。每个插件可以订阅不同类型的不同信号,因此必须具有其自己的特定“寄存器”方法。
答案 0 :(得分:7)
metaclass approach对于Python中的这个问题很有用。 3.6(参见@ quasoft对Python 3.6+的回答)。它非常简单,可以在任何导入的模块上自动执行。此外,复杂的逻辑可以很少的努力应用于插件注册。它要求:
metaclass方法的工作原理如下:
1)定义了一个自定义PluginMount
元类,它维护所有插件的列表
2)定义Plugin
类,将PluginMount
设置为其元类
3)当导入源自Plugin
的对象 - 例如MyPlugin
时,它会触发元类上的__init__
方法。这会注册插件并执行任何特定于应用程序的逻辑和事件订阅。
或者,如果您将PluginMount.__init__
逻辑放在PluginMount.__new__
中,则会在创建Plugin
派生类的新实例时调用它。
class PluginMount(type):
"""
A plugin mount point derived from:
http://martyalchin.com/2008/jan/10/simple-plugin-framework/
Acts as a metaclass which creates anything inheriting from Plugin
"""
def __init__(cls, name, bases, attrs):
"""Called when a Plugin derived class is imported"""
if not hasattr(cls, 'plugins'):
# Called when the metaclass is first instantiated
cls.plugins = []
else:
# Called when a plugin class is imported
cls.register_plugin(cls)
def register_plugin(cls, plugin):
"""Add the plugin to the plugin list and perform any registration logic"""
# create a plugin instance and store it
# optionally you could just store the plugin class and lazily instantiate
instance = plugin()
# save the plugin reference
cls.plugins.append(instance)
# apply plugin logic - in this case connect the plugin to blinker signals
# this must be defined in the derived class
instance.register_signals()
然后是一个基本的插件类,它看起来像:
class Plugin(object):
"""A plugin which must provide a register_signals() method"""
__metaclass__ = PluginMount
最后,实际的插件类如下所示:
class MyPlugin(Plugin):
def register_signals(self):
print "Class created and registering signals"
def other_plugin_stuff(self):
print "I can do other plugin stuff"
可以从导入Plugin
的任何python模块访问插件:
for plugin in Plugin.plugins:
plugin.other_plugin_stuff()
答案 1 :(得分:6)
自Python 3.6以来,添加了一个新的类方法__init_subclass__
,无论何时创建新的子类,都会在基类上调用它。
通过删除元类,此方法可以进一步简化上述will-hart提供的解决方案。
__init_subclass__
引入了PEP 487: Simpler customization of class creation方法。 PEP附带了一个插件架构的最小示例:
现在可以在不使用a的情况下自定义子类创建 元类。将调用新的
__init_subclass__
类方法 创建新子类时的基类:class PluginBase: subclasses = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) cls.subclasses.append(cls) class Plugin1(PluginBase): pass class Plugin2(PluginBase): pass
上面的PEP示例存储对Plugin.plugins
字段中的类的引用。
如果要存储插件类的实例,可以使用如下结构:
class Plugin:
"""Base class for all plugins. Singleton instances of subclasses are created automatically and stored in Plugin.plugins class field."""
plugins = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.plugins.append(cls())
class MyPlugin1(Plugin):
def __init__(self):
print("MyPlugin1 instance created")
def do_work(self):
print("Do something")
class MyPlugin2(Plugin):
def __init__(self):
print("MyPlugin2 instance created")
def do_work(self):
print("Do something else")
for plugin in Plugin.plugins:
plugin.do_work()
输出:
MyPlugin1 instance created
MyPlugin2 instance created
Do something
Do something else
答案 2 :(得分:0)
来自will-hart的方法对我来说是最有用的方法! 因为我需要更多控制,我将Plugin Base类包装在一个函数中:
Password:="____"
然后:
PDW=____
这允许添加其他基类或切换到另一个元类。