【原文链接】Pluggy源码解读----PluginManager类实例化
在解析PlugginManager类之前,首先再来看下一下pluggy应用实例代码,如下所示,通过前面的源码解析,至此已经存在两个实例hookspec和hookimpl,此外MySpec类中的myhook方法新增了一个myproject_spce的属性,属性值是一个字典,字典包含三个key,分表是firstresult,historic,warn_on_impl,而Plugin_1、Plugin_2、Plugin_3类中的myhook方法同样也都增加了一个myproject_impl属性,属性值也是一个字典,字典包含hookwrapper、optionalhook、tryfirst、trylast、specname五个key。其中Plugin_2中myhook的myproject_impl属性中的hookwrapper的值为True。这就是在类中使用装饰器的时候通过装饰器传入参数的方式设置的。
import pluggy
hookspec = pluggy.HookspecMarker("myproject")
hookimpl = pluggy.HookimplMarker("myproject")
class MySpec:
@hookspec
def myhook(self, arg1, arg2):
pass
class Plugin_1:
@hookimpl
def myhook(self, arg1, arg2):
print("in Plugin_1.myhook()")
return arg1 + arg2
class Plugin_2:
@hookimpl(hookwrapper=True)
def myhook(self, arg1, arg2):
print("in Plugin_2.myhook() before yield...")
output=yield
result=output.get_result()
print("in Plugin_2.myhook() after yield...")
print(result)
class Plugin_3:
@hookimpl
def myhook(self, arg1, arg2):
print("inside Plugin_3.myhook()")
return arg1 - arg2+10
pm = pluggy.PluginManager("myproject")
pm.add_hookspecs(MySpec)
pm.register(Plugin_1())
pm.register(Plugin_2())
pm.register(Plugin_3())
results = pm.hook.myhook(arg1=1, arg2=2)
print("after all run ...")
print(results)
然后就开始对PluginManager类进行了实例化,实例化的对象为pm,因为PluginManager类中的代码比较多,而实例化的时候只会调用类中的__init__方法,因此,这里仅列出PluginManager类中__init__方法的源码,如下所示,这里面首先对project_name属性赋值,比如这里赋值为myproject,然后初始化了几个变量,_name2plugin从名称可以推断出来是插件的名字和插件对象的映射关系,_plugin2hookcallers可以大概推断出来是插件和hookcaller的对应关系,_plugin_distinfo目前推断不出来具体功能,只可能猜测是插件的某方面的信息,这里先不管,只知道这也是一个列表,用于后续存储插件的某方面的信息。hook则是_HookRelay类的一个实例,_inner_hookexec则是一个函数,即是_multicall函数的别名,通过此文件的import部分,可以找到此函数在__callers.py文件中定义。trace通过名称也可以知道这个是一个帮准打印调用追踪栈的功能,从pluggy模块的主要功能来说,可以暂时不管此辅助功能。因此需要关注的主要就是hook和_inner_hookexec两个属性,其他属性基本都是初始化操作,待后续使用时再具体分析。
class PluginManager:
"""Core :py:class:`.PluginManager` class which manages registration
of plugin objects and 1:N hook calling.
You can register new hooks by calling :py:meth:`add_hookspecs(module_or_class)
<.PluginManager.add_hookspecs>`.
You can register plugin objects (which contain hooks) by calling
:py:meth:`register(plugin) <.PluginManager.register>`. The :py:class:`.PluginManager`
is initialized with a prefix that is searched for in the names of the dict
of registered plugin objects.
For debugging purposes you can call :py:meth:`.PluginManager.enable_tracing`
which will subsequently send debug information to the trace helper.
"""
def __init__(self, project_name):
self.project_name = project_name
self._name2plugin = {}
self._plugin2hookcallers = {}
self._plugin_distinfo = []
self.trace = _tracing.TagTracer().get("pluginmanage")
self.hook = _HookRelay()
self._inner_hookexec = _multicall
通过import部分可以查看到_HookRelay类是在_hook.py中定义的,而此类的定义如下,即是一个空类,因此可以知道此类应该只是为了组织存储数据的,需要后续动态的增加设置属性。
class _HookRelay:
"""hook holder object for performing 1:N hook calls where N is the number
of registered plugins.
"""
_inner_hookexec则指定的是_callers.py文件中的_multicall函数,_multicall函数的定义如下,这里暂时先不去详细的解释此函数的功能,但是粗滤的看一下可以猜测出,这个函数将是pluggy模块中调用函数执行的核心中的核心。这里暂时放在这,待后续调用时再继续详细解析。
def _multicall(hook_name, hook_impls, caller_kwargs, firstresult):
"""Execute a call into multiple python functions/methods and return the
result(s).
``caller_kwargs`` comes from _HookCaller.__call__().
"""
__tracebackhide__ = True
results = []
excinfo = None
try: # run impl and wrapper setup functions in a loop
teardowns = []
try:
for hook_impl in reversed(hook_impls):
try:
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
except KeyError:
for argname in hook_impl.argnames:
if argname not in caller_kwargs:
raise HookCallError(
f"hook call must provide argument {argname!r}"
)
if hook_impl.hookwrapper:
try:
gen = hook_impl.function(*args)
next(gen) # first yield
teardowns.append(gen)
except StopIteration:
_raise_wrapfail(gen, "did not yield")
else:
res = hook_impl.function(*args)
if res is not None:
results.append(res)
if firstresult: # halt further impl calls
break
except BaseException:
excinfo = sys.exc_info()
finally:
if firstresult: # first result hooks return a single value
outcome = _Result(results[0] if results else None, excinfo)
else:
outcome = _Result(results, excinfo)
# run all wrapper post-yield blocks
for gen in reversed(teardowns):
try:
gen.send(outcome)
_raise_wrapfail(gen, "has second yield")
except StopIteration:
pass
return outcome.get_result()