仅需两行代码即可在 Python 中实现插件架构! 仅需两行代码即可在 Python 中实现插件架构!

仅需两行代码即可在 Python 中实现插件架构!

Python Logo我最近一直在写大量的 Python 代码,其中我非常欣赏的一点是,实现插件系统是多么容易。

什么情况下会用到这个功能?如果你要发布代码,可能需要提供一些接口,方便最终用户扩展你的代码。如果你编写了插件接口文档,他们也可以编写自己的自定义代码。

就我而言,我使用 Python 的 multiprocessing 模块来启动各种任务。核心引擎变化不大,但我编写了各种各样的任务。插件架构允许我保持引擎不变,并在插件中完成我的工作。这是一种非常好的代码拆分方式(虽然这里我不会详细介绍,但使用 Python 的异常处理机制,您可以确保即使插件出现问题,也不会导致其余代码崩溃)。

插件使用起来非常简单,我已经在各种代码中都用到了它们。让我来演示一下它们的工作原理。你真正需要的只是:(1) importlib 模块,以及 (2) 一个定义良好的接口。

在这个例子中,我将实现一个简单的计算器。加、减、乘、除运算将分别由一个插件实现(理论上我们可以扩展它的功能)。由于这只是一个示例,每个插件只会对两个数字执行相应的运算。我也省略了所有的错误检查和异常处理(例如,如果有人用字符串而不是整数调用插件,或者只传递一个参数,会发生什么情况等等)。

我创建了一个名为 /zhujimao 的目录,并在该目录下创建了一个名为 'plugins' 的目录。该目录中包含 'add.py' 插件:

 #!/usr/bin/python3

类插件:

    def calculate(self, *args):
        返回 int(args[0]) + int(args[1])

非常简单,但有两点关键:

  1. 插件类将与add.py、sub.py、multiply.py和divide.py中定义的类相同。稍后您就会明白这一点。
  2. 每个插件的 calculate() 方法签名都相同。这就是我们实际执行插件的方式。

sub.py 等程序的代码除了 calculate() 函数中的数学运算不同之外,其他部分都相同,所以我就不再赘述了。

现在让我们来看一下 calc.py,这是我们将使用这些插件的计算器。

 #!/usr/bin/python3

plugin_dir = '/zhujimao/plugins'
导入 importlib、os、re、sys
sys.path.append(plugin_dir)

print("计算器开始。  正在加载插件:”)
插件 = {}
for plugin_file in sorted(os.listdir(plugin_dir)):
    如果 re.search('\.py$', plugin_file):
        module_name = re.sub('\.py$', '', plugin_file)
        module = importlib.import_module(module_name)
        plugins[module_name] = module.Plugin()
        打印 (”  %s" % (模块名称))

print("10 + 20 = %d" % plugins['add'].calculate(10,20))
print("18 - 11 = %d" % plugins['sub'].calculate(18, 11))
print("-3 * 6 = %d" % plugins['multiply'].calculate(-3, 6))
print("12 / 2 = %d" % plugins['divide'].calculate(12, 2))

我说的是“用两行代码实现”,但显然不止两行。我个人更倾向于写得更清晰明了的代码,而不是更简洁的代码,但如果你真的必须,也可以把加粗部分精简到两行:

 for plugin_file in glob.glob("%s/*.py" % ( plugin_dir )):
    plugins[ re.sub('\.py$', '', os.path.basename(plugin_file)) ] = 
        importlib.import_module( re.sub('\.py$', '', 
        os.path.basename(plugin_file))).Plugin()

我折叠了一些行,让它们能放进去。但我认为原文更容易阅读!或许有更简单的方法来缩短这段文字——我以前用 Perl,所以习惯用正则表达式,但也可以考虑上下文(“with”)和 filter() 函数。

好吧,好吧,还有“import importlib”语句,以及你在插件文件里写的任何内容。我用的是标题党。来告我啊。

但我跑题了。

以下是输出结果:

计算器启动。  正在加载插件:
   添加
   划分
   
   

10 + 20 = 30
18 - 11 = 7
-3 * 6 = -18
12 / 2 = 6

让我们逐条分析要点。

插件 = {}

我选择将插件放在一个字典中,格式为插件名称 -> 插件模块。

 for plugin_file in sorted(os.listdir(plugin_dir)):
    如果 re.search('\.py$', plugin_file):
        module_name = re.sub('\.py$', '', plugin_file)
        module = importlib.import_module(module_name)
        plugins[module_name] = module.Plugin()
        print(" %s" % ( module_name ))

这是代码的核心部分。我们检查插件目录,并对每个文件使用正则表达式(re.search)确保其以“.py”结尾(这样可以跳过 pycache 目录、旧的 vim 交换文件等)。

对于每个 .py 文件(例如 'add.py'),我们去掉“.py”后缀以获取模块名称。然后,我们将该模块导入到名为“module”的变量中。在下一行代码中,实例化该模块中的 Plugin() 类,并将其添加到我们的插件字典中。

你也可以这样做:

 plugins[module_name] = importlib.import_module(module_name)
adder = plugins[module_name].Plugin()
sum = adder.calculte(3, 5)

在第二个例子中,你将模块存储在字典中,而在我的实现中,我们将实例化的插件对象存储在字典中。如果你想实例化插件的多个实例,你应该存储模块本身。

现在让我们来实际操作一下。对于每个操作,我们使用字典找到插件对象,然后调用它的 calculate() 方法。

 print("10 + 20 = %d" % plugins['add'].calculate(10,20))
print("18 - 11 = %d" % plugins['sub'].calculate(18, 11))
print("-3 * 6 = %d" % plugins['multiply'].calculate(-3, 6))
print("12 / 2 = %d" % plugins['divide'].calculate(12, 2))

只需几行代码,我们就拥有了一个功能齐全的插件系统。不妨试一试,看看这种方法能为你的代码带来什么改变!