测试开发技术网站
博客
设计
设计
开发
Python
测试
unittest
运维
Linux基础应用
CI/CD
CI/CD
数据库
数据库
云计算
云计算
云原生
云原生
爬虫
爬虫
数据分析
数据分析
人工智能
人工智能
登录
注册
Pluggy源码解读----hook函数调用执行过程分析
收藏本文
作者:redrose2100 类别: 日期:2022-12-10 11:32:23 阅读:1152 次 消耗积分:0 分
[TOC]  首先看一下pluggy应用代码中执行hook函数调用的代码,如下所示,在分析add_hookspecs方法的时候,我们曾经分析过,pm有一个属性时hook,而hook实质上是_HookRelay类的实例对象,但是_HookRelay类是一个空类,在add_hookspecs中对hook这个对象设置了一个属性,属性名是myhook,而此属性的值是hc,而hc实质上是_HookCaller类的一个实例,换一句话说pm.hook.myhook就是_HookCaller类的一个实例,那么这里将实例当做函数调用,很显然,在python中这种用法实质上是在调用_HookCaller类中的__call__魔法函数。 ```python results = pm.hook.myhook(arg1=1, arg2=2) ``` 进入_HookCaller类中,确实可以找到__call__魔法函数,代码试下如下,这里可以看到首先是获取firstresult是否设置为True,如果没有设置,则直接将firstresult设置为False,然后就调用_hookexec方法,而在_HookCaller类的初始化函数中,可以看出_hookexec方法就是传递进来的PluginManager类的_inner_hookexec属性,亦即_multicall函数。 ```python 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就很有用处。 ```python 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函数调用执行的源码就解析完成了。
始终坚持开源开放共享精神,同时感谢您的充电鼓励和支持!
版权所有,转载本站文章请注明出处:redrose2100, http://blog.redrose2100.com/article/494
上一篇:
Pluggy源码解读----register注册插件源码解析
下一篇:
Pluggy源码解读----PluginManager类的其他功能
搜索
个人成就
出版书籍
《Pytest企业级应用实战》
测试开发技术全栈公众号
测试开发技术全栈公众号
DevOps技术交流微信群
加微信邀请进群
常用网站链接
开源软件洞察
云原生技术栈全景图
Python语言官方文档
Golang官方文档
Docker官方文档
Jenkins中文用户手册
Scrapy官方文档
VUE官方文档
Harbor官方文档
openQA官方文档
云原生开源社区
开源中国
Kubernetes中文文档
Markdown语法官方教程
Kubernetes中文社区
Kubersphere官方文档
BootStrap中文网站
JavaScript中文网
NumPy官方文档
Pandas官方文档
GitLink确实开源网站
数据库排名网站
编程语言排名网站
SEO综合查询网站
数学加减法练习自动生成网站
Kickstart Generator
文章分类
最新文章
最多阅读
特别推荐
×
Close
登录
注册
找回密码
登录邮箱:
登录密码:
图片验证码:
注册邮箱:
注册密码:
邮箱验证码:
发送邮件
注册邮箱:
新的密码:
邮箱验证码:
发送邮件