Python 插件式程序结构设计

小姐姐,你的插件掉了

本文源码在 github/wangshub

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

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

  • Emacs
  • Vs-code
  • Atom

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

项目文件结构

1
2
3
4
5
6
7
8
9
10
├── app1
│ └── plugins
│ └── secret.py
├── app2
│ └── plugins
│ └── randomstr.py
├── builtin_plugins
│ ├── lowercase.py
│ └── uppercase.py
└── example.py

插件管理器

  • 获取当前项目的路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

内置插件

1
2
3
4
5
6
def make_lowercase(s):
return s.lower()


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

调用你的插件

1
2
3
4
5
6
7
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('')

相关参考链接