近日,決定用 python 實現插件架構,於是上 stackoverflow 逛了一下,在這里發現一段代碼,非常喜歡。
提醒各位大俠注意,我對這段代碼作了一點小小的改動:原 PLUGINS
是 list 對象,改動后 PLUGINS
是 dict 對象。
代碼先貼出來,以饗觀眾:
''' 插件架構 '''
# 平台
class TextProcessor(object):
PLUGINS = {}
def process(self, text, plugins=()):
if plugins is ():
for plugin_name in self.PLUGINS.keys():
text = self.PLUGINS[plugin_name]().process(text)
else:
for plugin_name in plugins:
text = self.PLUGINS[plugin_name]().process(text)
return text
@classmethod
def plugin_register(cls, plugin_name):
def wrapper(plugin):
cls.PLUGINS.update({plugin_name:plugin})
return plugin
return wrapper
# 插件
@TextProcessor.plugin_register('plugin1')
class CleanMarkdownBolds(object):
def process(self, text):
return text.replace('**', '')
# 測試
processor = TextProcessor()
print(processor.PLUGINS) # {’plugin1': <class '__main__.CleanMarkdownBolds'>}
processed = processor.process(text="**foo bar**", plugins=('plugin1', ))
processed = processor.process(text="**foo bar**")
這段代碼運行良好!但是它是單文件,不適合實際使用。
在實際項目中,上面的三個注釋下面的部分一定是拆開的,其中插件一般都約定俗成地放到 plugins 子目錄下。
為了實現這個想法,走了很多彎路,花了兩天時間!這期間查閱了__metaclass__
原理, __subclass__()
函數, package的組織方式等等。最后真的靈光一閃,成功實現!
項目結構:
├─ myproject
├─ run.py
├─ app
├─ __init__.py
├─ main.py
├─ platform.py
├─ plugins
├─ __init__.py
├─ plugin1.py
├─ plugin2.py
完整代碼
# mpyproject/app/platform.py
class TextProcessor(object):
PLUGINS = {}
def process(self, text, plugins=()):
if plugins is ():
for plugin_name in self.PLUGINS.keys():
text = self.PLUGINS[plugin_name]().process(text)
else:
for plugin_name in plugins:
text = self.PLUGINS[plugin_name]().process(text)
return text
@classmethod
def plugin_register(cls, plugin_name):
def wrapper(plugin):
cls.PLUGINS.update({plugin_name:plugin})
return plugin
return wrapper
# mpyproject/app/plugins/plugin1.py
from ..platform import TextProcessor
@TextProcessor.plugin_register('plugin1')
class CleanMarkdownBolds(object):
def process(self, text):
return text.replace('**', '')
# mpyproject/app/plugins/plugin2.py
# 第二個插件!
from ..platform import TextProcessor
@TextProcessor.plugin_register('plugin2')
class CleanMarkdownItalic(object):
def process(self, text):
return text.replace('--', '')
# mpyproject/app/main.py
from .platform import TextProcessor
def test():
processor = TextProcessor()
print(processor.PLUGINS) # {’plugin1': <class '__main__.CleanMarkdownBolds'>}
processed = processor.process(text="**foo bar**", plugins=('plugin1', ))
processed = processor.process(text="--foo bar--")
# mpyproject/app/__init__.py
from .plugins import *
# mpyproject/app/plugins/__init__.py
__all__ = ['plugin1', 'plugin2']
# mpyproject/run.py
from app.main import test
test()
說明:
- 優雅地實現插件架構,
app/__init__.py
與app/plugins/__init__.py
兩個文件起了相互呼應的作用 - 在 app 目錄下,除了
app/__init__.py
,不需要在別的任何地方顯式地導入插件:from .plugins import *
或from .plugins import plugin1
- 若想添加插件 plugin3.py,可將其復制到 plugins 目錄下,然后修改
app/plugins/__init__.py
文件為__all__ = ['plugin1', 'plugin2', 'plugin3']
- 插件是冷插拔的
- 插件不是懶加載
優化方向
- 熱插拔
- 懶加載