Pluggy源码解读----hook函数调用执行过程分析

【原文链接】Pluggy源码解读----hook函数调用执行过程分析

首先看一下pluggy应用代码中执行hook函数调用的代码,如下所示,在分析add_hookspecs方法的时候,我们曾经分析过,pm有一个属性时hook,而hook实质上是_HookRelay类的实例对象,但是_HookRelay类是一个空类,在add_hookspecs中对hook这个对象设置了一个属性,属性名是myhook,而此属性的值是hc,而hc实质上是_HookCaller类的一个实例,换一句话说pm.hook.myhook就是_HookCaller类的一个实例,那么这里将实例当做函数调用,很显然,在python中这种用法实质上是在调用_HookCaller类中的__call__魔法函数。

results = pm.hook.myhook(arg1=1, arg2=2)

进入_HookCaller类中,确实可以找到__call__魔法函数,代码试下如下,这里可以看到首先是获取firstresult是否设置为True,如果没有设置,则直接将firstresult设置为False,然后就调用_hookexec方法,而在_HookCaller类的初始化函数中,可以看出_hookexec方法就是传递进来的PluginManager类的_inner_hookexec属性,亦即_multicall函数。

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError("hook calling supports only keyword arguments")
        assert not self.is_historic()

        # This is written to avoid expensive operations when not needed.
        if self.spec:
            for argname in self.spec.argnames:
                if argname not in kwargs:
                    notincall = tuple(set(self.spec.argnames) - kwargs.keys())
                    warnings.warn(
                        "Argument(s) {} which are declared in the hookspec "
                        "can not be found in this hook call".format(notincall),
                        stacklevel=2,
                    )
                    break

            firstresult = self.spec.opts.get("firstresult")
        else:
            firstresult = False

        return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)

因此pm.hook.myhook实质上就是调用了_multicall函数,在前面也曾经说过,_multicall函数是整个pluggy模块的调用执行的核心,这里就将详细的介绍此函数的实现,_multicall的函数代码如下,这里首先可以看到在for循环的时候,是将hook_impls使用reverse进行了反转,这就与我们前面分析到的在添加执行函数的时候好像使用了先进后出队列,那么之类可以看到,并没有使用队列的数据结构,而是使用了列表,只是在这里对列表进行了反转,在每个循环中判断hookwrapper的值是否为True,因为如果hookwrapper为True,则表示方法中有yield,此时就需要将yield之后的调用提前存入这里teardowns列表,同时执行所有的函数,此外这里同时可以看到,在判断firstresult,如果firstresult为True,当有一个结果时就会停止执行了,在finally部分可以看到,这里又将teardowns进行反转然后再依次执行,这就做到了当有多个插件类的方法中使用yield时,先注册的插件类中的yield之后的代码是后执行的,而这个功能对于pytest中的teardown就很有用处。

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

至此,hook函数调用执行的源码就解析完成了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

redrose2100

您的鼓励是我最大的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值