IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> Pytest核心原理之Pluggy插件源码解读(1)HookspecMarker类和HookimplMarker类分析 -> 正文阅读

[Python知识库]Pytest核心原理之Pluggy插件源码解读(1)HookspecMarker类和HookimplMarker类分析

Pytest核心原理之Pluggy插件源码解读(1)HookspecMarker类和HookimplMarker类分析

Pytest核心原理之Pluggy插件源码解读(2)PluginManager类实例化

Pytest核心原理之Pluggy插件源码解读(3)add_hookspecs增加自定义的接口类

Pytest核心原理之Pluggy插件源码解读(4)register注册插件源码解析

Pytest核心原理之Pluggy插件源码解读(5)hook钩子函数调用执行过程分析

Pytest核心原理之Pluggy插件源码解读(6)PluginManager类的其他功能

(1) pluggy简介

pluggy是一个非常优秀的插件系统,它是理解pytest的核心,只有理解了pluggy的原理,才能更好的理解和使用pytest,否则见到了pytest的很多应用都会感觉很难理解

pluggy插件总共的代码量不足一千行,而实现的功能却是如此的强大和好用,这不由得让我们对pytest的源码实现充满了好奇,接下来一段时间就详细的由浅入深的来解读pluggy源码,这个过程中,同样会继续总结一些基础的或者高级的python的知识点。

当然随着对pluggy源码的深入,也会发现很多在网上书上博客上看不到的pluggy的高级应用,同样本系列都会使用实例演示和分析。

(2)pluggy的简单应用
import pluggy

# HookspecMarker 和 HookimplMarker 实质上是一个装饰器带参数的装饰器类,作用是给函数增加额外的属性设置
hookspec = pluggy.HookspecMarker("myproject")
hookimpl = pluggy.HookimplMarker("myproject")

# 定义自己的Spec,这里可以理解为定义接口类
class MySpec:
    # hookspec 是一个装饰类中的方法的装饰器,为此方法增额外的属性设置,这里myhook可以理解为定义了一个接口
    @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
    def myhook(self, arg1, arg2):
        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)

执行结果如下:

inside Plugin_2.myhook()
inside Plugin_1.myhook()
[-1, 3]
(3)pluggy的文件组织结构:

pluggy模块总共就有如下6个文件,总共代码行数不到1k行,这6个文件中,caller、hooks.py和manager.py是pluggy的最核心的部分

pluggy
  |--------__init__.py
  |--------_result.py
  |--------_tracing.py
  |--------caller.py
  |--------hooks.py
  |--------manager.py
(4)HookspecMarker类和HookimplMarker类分析

从上面的使用举例看,我们首先是看到了实例化了这两个类的实例,所以,我们就先从这两个类开始分析
下面看下HookspecMarker类的源码(在hooks.py文件中)

阅读源码有一个好处就是能发现一些比较高级的语法和比较好的用法,如果觉得读源码有难度,至少说明一点,我们对python的基础语法或者高级语法掌握的还不是很到位。比如下面
HookspecMarker类的定义,这里面涉及到python的两个相对高级一定的语法,一个是类中call魔法函数的作用,一个就是装饰器类,如果说对这两个知识点不清楚的话,那看到这个类就会有点头大了

class HookspecMarker:
    """ Decorator helper class for marking functions as hook specifications.

    You can instantiate it with a project_name to get a decorator.
    Calling :py:meth:`.PluginManager.add_hookspecs` later will discover all marked functions
    if the :py:class:`.PluginManager` uses the same project_name.
    """

    def __init__(self, project_name):
        self.project_name = project_name

    def __call__(
        self, function=None, firstresult=False, historic=False, warn_on_impl=None
    ):
        """ if passed a function, directly sets attributes on the function
        which will make it discoverable to :py:meth:`.PluginManager.add_hookspecs`.
        If passed no function, returns a decorator which can be applied to a function
        later using the attributes supplied.

        If ``firstresult`` is ``True`` the 1:N hook call (N being the number of registered
        hook implementation functions) will stop at I<=N when the I'th function
        returns a non-``None`` result.

        If ``historic`` is ``True`` calls to a hook will be memorized and replayed
        on later registered plugins.

        """

        def setattr_hookspec_opts(func):
            if historic and firstresult:
                raise ValueError("cannot have a historic firstresult hook")
            setattr(
                func,
                self.project_name + "_spec",
                dict(
                    firstresult=firstresult,
                    historic=historic,
                    warn_on_impl=warn_on_impl,
                ),
            )
            return func

        if function is not None:
            return setattr_hookspec_opts(function)
        else:
            return setattr_hookspec_opts

为了彻底理解上面这个类的定义,下面先搞定几个基础的语法知识:

  • call魔法函数的作用就是类的实例可以继续像函数一样调用,像函数一样调用对象的时候就会走到类的call魔法函数了
    下面用一个实例演示:
class Test(object):
    def __init__(self,a,b=10):
        self.a=a
        self.b=b

    def __call__(self,c,d=20):
        print(self.a+c)
        print(self.b+d)

test=Test(10,20)
test(100,200)

可以看到,test本来是Test类的一个实例,下面紧接着把test当函数一样调用,而且传入了参数,执行结果如下:

110
220
  • 利用类中call模范函数的额特性,装饰器可以使用类实现,即装饰器类
    下面本质是就是实现了一个装饰器类,装饰器可以传入参数a和b,被装饰器对象可以是函数,即func,也可以传入参数,代码如下:
class Test(object):
    def __init__(self,a,b=10):
        self.a=a
        self.b=b

    def __call__(self,func):
        def wrapper(*args,**kwargs):
            print("before func {func}".format(func=func.__name__))
            results=func(*args,**kwargs)
            print("after func {func}".format(func=func.__name__))
            return results
        return wrapper

@Test(10,100)
def print_sum(v1,v2):
    print("in print_sum()...")
    print(v1+v2)

print_sum(20,200)

执行结果如下:

before func print_sum
in print_sum()...
220
after func print_sum

setattr的作用
setattr的作用即给一个对象设置属性,下面用一个最简单的例子展示:

class Test(object):
    pass

t=Test()
setattr(t,"a",10)

print(t.a)

结果会打印10,此时t这个实例就已经有了属性a,而且属性a的值为10
熟悉了上述python基础或高级语法后,再回过头看下HookspecMarker类的代码:
其实它的本质就是一个装饰器类,如果只把这个类实例化,即不用语法糖加在一个具体的函数上时,
即此时返回的是一个setattr_hookimpl_opts,它的参数是另外一个被装饰的函数func,作用就是给
被装饰的函数func设置一个属性,属性名就是初始化的时候传入的名称加上”_spec”,属性值时一个字典,字典
里面有三个字段,分别是firstresult,historic和warm_on_impl

如下是HookspecMarker类的两种使用方法

import pluggy

hookspec = pluggy.HookspecMarker("myproject")

@hookspec
def test1():
    pass

@pluggy.HookspecMarker("myproject2")
def test2():
    pass

print(test1.myproject_spec)
print(test2.myproject2_spec)

执行结果为:

{'firstresult': False, 'historic': False, 'warn_on_impl': None}
{'firstresult': False, 'historic': False, 'warn_on_impl': None}

所以HookspecMarker类的本质就是为了给被装饰的函数对象增加这么一个属性

同理HookimplMarker类的代码如下,也同样是一个装饰器,也是为了给函数增加一个属性,属性名称为HookimplMarker类初始化时给的project_name加上”_impl”,其值主要有5个参数,至于每个参数做什么用的,可以到后面分析manager文件的时候再回头看

class HookimplMarker:
    """ Decorator helper class for marking functions as hook implementations.

    You can instantiate with a ``project_name`` to get a decorator.
    Calling :py:meth:`.PluginManager.register` later will discover all marked functions
    if the :py:class:`.PluginManager` uses the same project_name.
    """

    def __init__(self, project_name):
        self.project_name = project_name

    def __call__(
        self,
        function=None,
        hookwrapper=False,
        optionalhook=False,
        tryfirst=False,
        trylast=False,
        specname=None,
    ):

        """ if passed a function, directly sets attributes on the function
        which will make it discoverable to :py:meth:`.PluginManager.register`.
        If passed no function, returns a decorator which can be applied to a
        function later using the attributes supplied.

        If ``optionalhook`` is ``True`` a missing matching hook specification will not result
        in an error (by default it is an error if no matching spec is found).

        If ``tryfirst`` is ``True`` this hook implementation will run as early as possible
        in the chain of N hook implementations for a specification.

        If ``trylast`` is ``True`` this hook implementation will run as late as possible
        in the chain of N hook implementations.

        If ``hookwrapper`` is ``True`` the hook implementations needs to execute exactly
        one ``yield``.  The code before the ``yield`` is run early before any non-hookwrapper
        function is run.  The code after the ``yield`` is run after all non-hookwrapper
        function have run.  The ``yield`` receives a :py:class:`.callers._Result` object
        representing the exception or result outcome of the inner calls (including other
        hookwrapper calls).

        If ``specname`` is provided, it will be used instead of the function name when
        matching this hook implementation to a hook specification during registration.

        """

        def setattr_hookimpl_opts(func):
            setattr(
                func,
                self.project_name + "_impl",
                dict(
                    hookwrapper=hookwrapper,
                    optionalhook=optionalhook,
                    tryfirst=tryfirst,
                    trylast=trylast,
                    specname=specname,
                ),
            )
            return func

        if function is None:
            return setattr_hookimpl_opts
        else:
            return setattr_hookimpl_opts(function)

下面用简单的代码演示HookimplMarker装饰器类给函数设置属性的结果

import pluggy

hookspec = pluggy.HookimplMarker("myproject")

@hookspec
def test1():
    pass

@pluggy.HookimplMarker("myproject2")
def test2():
    pass

print(test1.myproject_impl)
print(test2.myproject2_impl)

执行结果为:

{'hookwrapper': False, 'optionalhook': False, 'tryfirst': False, 'trylast': False, 'specname': None}
{'hookwrapper': False, 'optionalhook': False, 'tryfirst': False, 'trylast': False, 'specname': None}

至此 HookspecMarker类和HookimplMarker类的代码就分析完了

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2021-11-12 19:32:52  更:2021-11-12 19:34:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 23:55:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码