Python 插件式程序结构设计

小姐姐,你的插件掉了

本文源码在 github/wangshub

最近有个问题一直困扰着我,我经常会遇到对以前的程序或者代码进行功能的扩充,但是由于前期没有太多程序架构上的设计,导致功能扩充时冗余代码太多,而且不方便维护( YC 创始人 Paul Graham 其实推荐这种做法,产品快速上线,获取用户反馈进行修改)。

知名的插件式设计的项目有

  • Emacs
  • Vs-code
  • Atom

为了拓展软件的功能,经常会将软件设计成插件式结构,所以就从我熟悉的 Python 开始了解这部分知识。 通过 Google, 发现了 Pluginbase Python 插件框架,可以快速实现一个 Python 插件式结构,这篇博客作为阅读笔记。

项目文件结构

├── app1
│   └── plugins
│       └── secret.py
├── app2
│   └── plugins
│       └── randomstr.py
├── builtin_plugins
│   ├── lowercase.py
│   └── uppercase.py
└── example.py

插件管理器

  • 获取当前项目的路径
import os
from functools import partial
from pluginbase import PluginBase


# For easier usage calculate the path relative to here.
here = os.path.abspath(os.path.dirname(__file__))
# 函数包装,重新定义函数签名,减少调用参数
get_path = partial(os.path.join, here)
print('here:', here)
print('get_path:', get_path)

# Setup a plugin base for "example.modules" and make sure to load
# all the default built-in plugins from the builtin_plugins folder.
plugin_base = PluginBase(package='example.plugins',
                         searchpath=[get_path('./builtin_plugins')])


class Application(object):
    """Represents a simple example application."""

    def __init__(self, name):
        # Each application has a name
        self.name = name

        # And a dictionary where it stores "formatters".  These will be
        # functions provided by plugins which format strings.
        self.formatters = {}

        # and a source which loads the plugins from the "{app_name}/plugins"
        # folder.  We also pass the application name as identifier.  This
        # is optional but by doing this out plugins have consistent
        # internal module names which allows pickle to work.
        self.source = plugin_base.make_plugin_source(
            searchpath=[get_path('./%s/plugins' % name)],
            identifier=self.name)

        # Here we list all the plugins the source knows about, load them
        # and the use the "setup" function provided by the plugin to
        # initialize the plugin.
        for plugin_name in self.source.list_plugins():
            plugin = self.source.load_plugin(plugin_name)
            plugin.setup(self)

    def register_formatter(self, name, formatter):
        """A function a plugin can use to register a formatter."""
        self.formatters[name] = formatter

实现插件

插件 1 : App1

import string


def make_secret(s):
    chars = list(s)
    for idx, char in enumerate(chars):
        if char not in string.punctuation and not char.isspace():
            chars[idx] = 'x'
    return ''.join(chars)


def setup(app):
    app.register_formatter('secret', make_secret)

插件 2 : App2

import random
import string


def make_random(s):
    chars = list(s)
    for idx, char in enumerate(chars):
        if char not in string.punctuation and not char.isspace():
            chars[idx] = random.choice(string.ascii_letters)
    return ''.join(chars)


def setup(app):
    app.register_formatter('random', make_random)

内置插件

def make_lowercase(s):
    return s.lower()


def setup(app):
    app.register_formatter('lowercase', make_lowercase)

调用你的插件

def run_demo(app, source):
    """Shows all formatters in demo mode of an application."""
    print('Formatters for %s:' % app.name)
    print('       input: %s' % source)
    for name, fmt in sorted(app.formatters.items()):
        print('  %10s: %s' % (name, fmt(source)))
    print('')

相关参考链接