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('')