代码案例

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import pluggy # HookspecMarker 和 HookimplMarker 实质上是一个装饰器带参数的装饰器类,作用是给函数增加额外的属性设置 hookspec = pluggy.HookspecMarker("myproject") hookimpl = pluggy.HookimplMarker("myproject") ''' HookspeckMarker:     传入firstresult=True时,获取第一个plugin执行结果后就停止继续执行 @hookspec(firstresult=True)     historic - 表示这个 hook 是需要保存call history 的,当有新的 plugin 注册的时候,需要回放历史 hookimpl:     当传入tryfirst=True时,表示这个类的hook函数会优先执行,其他的仍然按照后进先出的顺序执行     当传入trylast=True,表示当前插件的hook函数会尽可能晚的执行,其他的仍然按照后进先出的顺序执行     当传入hookwrapper=True时,需要在这个plugin中实现一个yield,plugin先执行yield之前的代码,         然后去执行其他的pluggin,然后再回来执行yield之后的代码,同时通过yield可以获取到其他插件执行的结果 ''' # 定义自己的Spec,这里可以理解为定义接口类 class MySpec:     # hookspec 是一个装饰类中的方法的装饰器,为此方法增额外的属性设置,这里myhook可以理解为定义了一个接口     # 会给当前方法添加属性  键为 {self.project_name + "_spec"} 值是装饰器传入的参数     @hookspec     def myhook(self, arg1, arg2):         pass # 定义了一个插件 class Plugin_1:     # 插件中实现了上面定义的接口,同样这个实现接口的方法用 hookimpl装饰器装饰,功能是返回两个参数的和     @hookimpl     def myhook(self, arg1, arg2):         print("inside Plugin_1.myhook()")         return arg1 + arg2 # 定义第二个插件 class Plugin_2:     # 插件中实现了上面定义的接口,同样这个实现接口的方法用 hookimpl装饰器装饰,功能是返回两个参数的差     @hookimpl(hookwrapper=True)     def myhook(self, arg1, arg2):         out = yield         print("inside Plugin_2.myhook()")         return arg1 - arg2 # 实例化一个插件管理的对象,注意这里的名称要与文件开头定义装饰器的时候的名称一致 pm = pluggy.PluginManager("myproject") # 将自定义的接口类加到钩子定义中去 pm.add_hookspecs(MySpec) # 注册定义的两个插件 pm.register(Plugin_1()) pm.register(Plugin_2()) # 通过插件管理对象的钩子调用方法,这时候两个插件中的这个方法都会执行,而且遵循后注册先执行即LIFO的原则,两个插件的结果讲义列表的形式返回 results = pm.hook.myhook(arg1=1, arg2=2) print(results)

实例化:

初始化一些参数,如_name2plugin:存放后续注册 plugin

添加到钩子定义中 (add_hookspecs)

将定义的类已参数的方式传递进去 (module_or_class)遍历类中全部的方法判断: getattr(method, self.project_name + "_spec", None),判断类方法中是否有当前定义 myproject+spec 的属性,

如果有则返回装饰器所得到的参数,没有则返回 None,其实就是判断有没有被@hookspec装饰,因为装饰了会设置上 myproject+spec 这个属性以及相对应的值如果有被装饰: 判断一下 self.hook 中是否以及存在了这个 spec如果不存在: 创建一个_HookCaller(spec 名字,_hookexec(本质就是一个执行 hook 的方法),传递进来的 spec 对象,第三步获得的参数,也就是通过装饰器 set 到方法中的一些参数) 对象

init: 判断 spec 对象是否为空,如果不为空:

​ 先判断参数是否存在,如果存在,创建一个 HookSpec 给 self.spec,传递参数为 当前 spec 对象,当前 spec 名字,参数,self.function 就是对应的那个被装饰的方法,最后判断一下这个 spec 需不需要保存历史,如果需要,初始化一个列表将上面初始化的对象,通过 setattr 的方式存在到 self.hook 中,名字就是被装饰方法的名字,值是刚刚创建的对象最后会在 names 的列表中把这个添加的方法的名字添加进去,判断一个 names 是否为空,如果为空,则抛出异常添加 add_hookspecs 的步骤全部完成

注册插件 register

注册插件 (register): 传递实现插件的实体类对象

判断是否传递插件名字,如果没传,就获取对象的name属性,如果还没有就直接用 id() 生产一个随机字符串做当前对象在插件中的名字 判断名字是否存在,或者是否已被注册: self._name2plugin 和 self._plugin2hookcaller,前者是用 plugin_name 做 key,后者是用 plugin object 做 key,判断是否已经注册过重复的 plugin self._name2plugin[plugin_name(插件名字)] = plugin(传递的实体类对象) self._plugin2hookcallers[plugin(传递的实体类对象)] = hookcallers = [],其实就是初始化一下 self._plugin2hookcallers[plugin],因为列表的引用传递,所有直接修改 hookcallers 也可以作用在 self 中 遍历实体类对象的方法列表,判断是否被 impl 装饰:

a.先获取到方法对象b.判断对象是否是内置函数、函数、方法或者方法描述符,如果不是直接返回c.获取该方法的属性 hook 对象创建时传递的名字 (myproject) + "_impl" ,没有则返回 Noned.判断获取到的值是不是 None 并且不是一个字典,则将获取到的 res 赋值为 Nonee. 最后返回 res,其实就是 hookimpl 装饰器,如果你不给值就给一堆默认值 先判断参数列表是否为空: 如果不为空,进行设置默认值 (其实正常是不会出现没有值的情况),然后从实体类对象中获取到该方法的对象

在创建一个新的对象 (HookImpl):init(self,传递进来的实体类对象,hook 名字 (第一步获取或者 id 生成),method 对象,第四步返回的参数字典),并且将参数列表跟新到 self.dict中 判断 self.hook 中是否以及注册了当前插件 (就是 add_hookspecs 注册的 spec 中是否有当前方法)

如果没有注册,会直接注册一个,这样注册 specmodule_or_class 参数会为空,意为着不会有额外的一些参数 eg:tryfirst hook.has_spec() 判断注册 spec 的 spec 属性不为空

self._verify_hook(hook(spec 对象), hookimpl(插件对象)) a.先判断当前对象中是否有 (_call_history 属性),历史 和是否 需要使用 yield b. 判断 hookimpl 和 hook.spec 的参数列表是否相等,如果不相等报错 hook._maybe_apply_history(hookimpl) a.判断是否有_call_history 这个属性 hook._add_hookimpl(hookimpl): a.判断是否为 hookwrapper 为 True,添加到不同的 wrappers 中 b.判断是否有 trylast tryfirst 属性,将 hookimpl 存放到对应位置 c.将 hook 添加到 hookcallers 中 遍历结束后,返回 plugin_name(第一步产生)

运行插件 pm.hook.myhook

运行插件 pm.hook.myhook(arg1=1, arg2=2):本质就是调用对象的call方法

先判断是否有顺序参数,如果有直接报错在判断是否有_call_history 这个属性判断实际传入参数,是否和插件需要参数一样self._hookexec(self(hook 对象), self.get_hookimpls()(全部的已经注册的插件), kwargs(传入的参数))

self._inner_hookexec(hook(hook 对象), methods(插件), kwargs(参数))

1 2 3 4 5 6 # 实际调用,也就是hook.multicall的方法 self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(             methods,             kwargs,             firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,         )

_multicall(hook_impls(插件), caller_kwargs(参数), firstresult=False(@hookspec传入,默认 False))

1. 先将 hook_impls 变成一个可迭代对象 (reversed(hook_impls))

2. 先把顺序参数的参数列表,拿到 (列表推导式)

3. 判断需不需要将其他插件执行的结果传递进去

- 需要 - 先从 hook_impl 中拿出对应的方法并且传递参数,执行关键字 yield 前面部分 - 然后 next() - 最后将这个方法添加到 teardowns 列表中去

- 不需要 - 先从 hook_impl 中拿出对于的方法并且传递参数 - 判断执行后的返回值是不是为空,不为空则添加到 results 列表中 - 最后判断是否有 firstresult 属性,如果有直接结束循环

4. 最后执行 (finally 中代码)

- 如果 firstresult 为 true,那么直接返回第一个插件返回的结果即可 - 执行 teardowns 列表中的需要最后执行的插件 - 通过迭代器的 send 方法,将上几个插件的结果传递进去

5. 返回 result 对象 :会判断是否有报错 如果没有直接返回结果列表,如果有报错会抛出异常

​现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。

如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受

可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛

分享他们的经验,还会分享很多直播讲座和技术沙龙

可以免费学习!划重点!开源的!!!

qq群号:485187702【暗号:csdn11】

最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走! 希望能帮助到你!【100%无套路免费领取】

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: