以下內容來自:http://blog.csdn.net/gqtcgq/article/details/49620279
stevedore是用來實現動態載入代碼的開源模組。它是在OpenStack中用來載入外掛程式的公用模組。可以獨立於OpenStack而安裝使用:https://pypi.Python.org/pypi/stevedore/
stevedore使用setuptools的entry points來定義並載入外掛程式。entry point引用的是定義在模組中的對象,比如類、函數、執行個體等,只要在import模組時能夠被建立的對象都可以。
一:外掛程式的名字和命名空間
一般來講,entry point的名字是公開的,使用者可見的,經常出現在設定檔中。而命名空間,也就是entry point組名卻是一種實現細節,一般是面向開發人員而非終端使用者的。可以用Python的包名作為entry point命名空間,以保證唯一性,但這不是必須的。
entry points的主要特徵就是,它可以是獨立註冊的,也就是說外掛程式的開發和安裝可以完全獨立於使用它的應用,只要開發人員和使用者在命名空間和API上達成一致即可。
命名空間被用來搜尋entry points。entry points的名字在給定的發布包中必須是唯一的,但在一個命名空間中可以不唯一。也就是說,同一個發布包內不允許出現同名的entry point,但是如果是兩個獨立的發布包,卻可以使用完全相同的entrypoint組名和entry point名來註冊外掛程式。
二:外掛程式的使用方式
在stevedore中,有三種使用外掛程式的方式:Drivers、Hooks、Extensions
1:Drivers
一個名字對應一個entry point。使用時根據外掛程式的命名空間和名字,定位到單獨的外掛程式:
2:Hooks,一個名字對應多個entry point。允許同一個命名空間中的外掛程式具有相同的名字,根據給定的命名空間和名字,載入該名字對應的多個外掛程式。
3:Extensions,多個名字,多個entry point。給定命名空間,載入該命名空間中所有的外掛程式,當然也允許同一個命名空間中的外掛程式具有相同的名字。
三:定義並註冊外掛程式
在經過了大量的實驗和總結教訓之後,發現定義API最簡單的方式是遵循下面的步驟:
a:使用abc模組,建立一個抽象基類來定義外掛程式API的行為;雖然開發人員無需繼承一個基類,但是這種方式自有它的好處;
b:通過繼承基類並實現必要的方法來建立外掛程式
c:為每個API定義一個命名空間。可以將應用或者庫的名字,以及API的名字結合起來,這種方式通俗易懂,如 “cliff.formatters”或“ceilometer.pollsters.compute”。
本節例子中建立的外掛程式,用來對資料進行格式化輸出,每個格式化方法接受一個字典作為輸入,然後按照一定的規則產生要輸出的字串。格式化類可以有一個最大輸出寬度的參數。
1:首先定義一個基類,其中的API需要由外掛程式來實現
[python] view plain copy # example/pluginbase.py import abc import six @six.add_metaclass(abc.ABCMeta) class FormatterBase(object): """Base class for example plugin used in the tutorial.""" def __init__(self, max_width=60): self.max_width = max_width @abc.abstractmethod def format(self, data): """Format the data and return unicode text. :param data: A dictionary with string keys and simple types as values. :type data: dict(str:?) :returns: Iterable producing the formatted text. """
2:定義外掛程式1
開始定義具體的外掛程式類,這些類需要實現format方法。下面是一個簡單的外掛程式,它產生的輸出都在一行上。
[python] view plain copy # example/simple.py import pluginbase class Simple(pluginbase.FormatterBase): """A very basic formatter. """ def format(self, data): """Format the data and return unicode text. :param data: A dictionary with string keys and simple types as values. :type data: dict(str:?) """ for name, value in sorted(data.items()): line = '{name} = {value}\n'.format( name=name, value=value, ) yield line
3:註冊外掛程式1
本例中,使用” stevedoretest.formatter”作entry points的命名空間,也就是entry points組名,源碼樹如下:
[plain] view plain copy setup.py example/ __init__.py pluginbase.py simple.py
該發布包的setup.py內容如下:
[python] view plain copy from setuptools import setup, find_packages setup( name='stevedoretest1', version='1.0', packages=find_packages(), entry_points={ 'stevedoretest.formatter': [ 'simple = example.simple:Simple', 'plain = example.simple:Simple', ], }, )
每個entry point都以” name = module:importable ”的形式進行註冊,name就是外掛程式的名字,module就是python模組,importable就是模組中可引用的對象。
這裡註冊了兩個外掛程式,simple和plain,這兩個外掛程式所引用的Python對象是一樣的,都是example.simple:Simple類,因此plain只是simple的別名而已。
定義好setup.py之後,運行python setup.py install即可安裝該發布包。安裝成功後,在該發布的egg目錄中存在檔案entry_points.txt,其內容如下:
[plain] view plain copy [stevedoretest.formatter] plain = example.simple:Simple simple = example.simple:Simple
運行時,pkg_resources在所有已安裝包的entry_points.txt中尋找外掛程式,因此不要手動編輯該檔案。
4:定義外掛程式2
使用entry points建立外掛程式的好處之一就是,可以為一個應用獨立的開發不同的外掛程式。因此可以在另外一個發布包中定義第二個外掛程式:
[python] view plain copy #example2/fields.py import textwrap from example import pluginbase class FieldList(pluginbase.FormatterBase): """Format values as a reStructuredText field list. For example:: : name1 : value : name2 : value : name3 : a long value will be wrapped with a hanging indent """ def format(self, data): """Format the data and return unicode text. :param data: A dictionary with string keys and simple types as values. :type data: dict(str:?) """ for name, value in sorted(data.items()): full_text = ': {name} : {value}'.format( name=name, value=value, ) wrapped_text = textwrap.fill( full_text, initial_indent='', subsequent_indent=' ', width=self.max_width, ) yield wrapped_text + '\n'
5:註冊外掛程式2
外掛程式2的源碼樹如下:
[plain] view plain copy setup.py example2/ __init__.py fields.py
在setup.py中,同樣要使用”stevedoretest.formatter”作為entry points組名,該發布包的setup.py內容如下:
[python] view plain copy from setuptools import setup, find_packages setup( name='stevedoretest2', version='1.0', packages=find_packages(), entry_points={ 'stevedoretest.formatter': [ 'fields = example2.fields:FieldList' ], }, )
這裡註冊了外掛程式fields,它引用的是example2.fields:FieldList類。定義好setup.py之後,運行python setup.py install即可安裝該發布包。在該發布的entry_points.txt檔案內容如下:
[plain] view plain copy [stevedoretest.formatter] fields = example2.fields:FieldList
四:載入外掛程式
1:Drivers載入
最常見的使用外掛程式的方式是作為單獨的驅動來使用,這種情境中,可以有多種外掛程式,但只需要載入和調用其中的一個,這種情況下,可以使用stevedore的DriverManager 類。下面就是一個使用該類的例子:
[python] view plain copy from __future__ import print_function import argparse from stevedore import driver if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument( 'format', nargs='?', default='simple', help='the output format', ) parser.add_argument( '--width', default=60, type=int, help='maximum output width for text', ) parsed_args = parser.parse_args() data = { 'a': 'A', 'b': 'B', 'long': 'word ' * 80, } mgr = driver.DriverManager( namespace='stevedoretest.formatter', name=parsed_args.format, invoke_on_load=True, invoke_args=(parsed_args.width,), ) for chunk in mgr.driver.format(data): print(chunk, end='')
其中的parser主要用來解析命令列參數的,該指令碼接受三個參數,一個是format,也就是要使用的外掛程式名字,這裡預設是simple;另一個參數是--width,是外掛程式方法可能會用到的參數,這裡預設是60,該指令碼還可以通過--help參數列印協助資訊:
[python] view plain copy # python load_as_driver.py --help usage: load_as_driver.py [-h] [--width WIDTH] [format] positional arguments: format the output format optional arguments: -h, --help show this help message and exit --width WIDTH maximum output width for text
在該指令碼中,driver.DriverManager以外掛程式的命名空間以及外掛程式名來尋找外掛程式,也就是entry points組名和entry points本身的名字。也就是希望通過組名和entry point本身的名字來唯一定位一個外掛程式,但是因為相同的entry points組中可以有同名的entry point,所以,對於DriverManager來說,如果通過entry points組名和entry points本身的名字找到了多個註冊的外掛程式,則會報錯。比如本例中,如果在”stevedoretest.formatter”中,有多個發布模組註冊了名為”simiple”的entry point,則執行該指令碼時就會報錯: