python 優雅地實現插件架構


近日,決定用 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__.pyapp/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']
  • 插件是冷插拔的
  • 插件不是懶加載

優化方向

  • 熱插拔
  • 懶加載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM