你不一定全知道的四种Python装饰器实现详解

☞ ░ 前往老猿Python博客 https://blog.youkuaiyun.com/LaoYuanPython

一、引言

老猿一直想写一篇比较完整的装饰器介绍的博文,从开始写到现在至少过去有半年了,一直都还未写完,因为还有一些知识点没有研究透彻,因此一直在草稿箱放着。在写这个材料的时候,发现Python中的装饰器网上介绍的材料很多,大多数都是介绍的装饰器函数,但也有极少数介绍了装饰器类或用装饰器函数装饰类。老猿想来,装饰器按照装饰器本身和被装饰对象来区分类和函数,应该有4种组合,前面说的有三种,应该还有一种装饰器和被装饰对象都是类的组合。但公开资料中未查到是否可以有类的类装饰器,即装饰器和被装饰对象都是类。老猿参考类的函数装饰器、函数的类装饰器做了很多测试,一度以为没办法实现类的类装饰器,准备放弃,隔了很长一段时间后,最近又花了两天时间进行研究测试,终于弄通了。基于上述研究,老猿决定先单独写一篇关于装饰器四种类型的详细介绍。

装饰器的概念就不介绍了,按照装饰器的类型、被装饰对象的类型,老猿将装饰器分为如下四种:

  1. 函数的函数装饰器:装饰器和被装饰对象都为函数;
  2. 类的函数装饰器:装饰器为函数,被装饰对象为类;
  3. 函数的类装饰器:装饰器为类,被装饰对象为函数;
  4. 类的类装饰器:装饰器和被装饰对象都为类。

二、函数的函数装饰器

装饰器包含装饰对象和被装饰对象,最简单的装饰器是用装饰器函数装饰被装饰函数,在这种场景下,装饰器为函数装饰器,被装饰对象也是函数。

2.1、概述

函数装饰器就是一个特殊的函数,该函数的参数就是一个函数,在装饰器函数内重新定义一个新的函数,并且在其中执行某些功能前后或中间来使用被装饰的函数,最后返回这个新定义的函数。装饰器也可以称为函数的包装器,实际上就是在被装饰的函数执行前或后增加一些单独的逻辑代码,以使得被装饰函数执行后最终的结果受到装饰函数逻辑的影响以改变或限定被装饰函数的执行结果。

2.2、装饰器定义语法
@decoratorName
def originalFunction(*args,**kwargvs):
    函数体
2.3、装饰器语法解释
  1. 参数是一个函数对象;
  2. 封闭函数内部存在一个嵌套函数,该嵌套函数内会调用封闭函数参数指定的函数,并添加额外的其他代码(这些代码就是装饰);
  3. 嵌套函数的参数必须包含originalFunction的参数,但不能带被装饰对象originalFunction;
  4. 嵌套函数返回值必须与封闭函数参数指定函数的返回值类似,二者符合鸭子类型要求(关于鸭子类型请参考《https://blog.youkuaiyun.com/LaoYuanPython/article/details/91350122 第7.3节 Python特色的面向对象设计:协议、多态及鸭子类型》);
  5. 封闭函数的返回值必须是嵌套函数。
  • 装饰器函数的定义参考如下形式:
def decoratorName(originalFunction,*args,**kwargvs):
    def closedFunction(*args,**kwargvs):
        ...  #originalFunction函数执行前的一些装饰代码
        ret = originalFunction(*args,**kwargvs)
        ... #originalFunction函数执行的一些装饰代码
        return ret
    return closedFunction

其中decoratorName是装饰器函数,originalFunction是被装饰的函数,closedFunction是装饰器函数内的嵌套函数。

  • 装饰器定义的语法本质上等同于如下语句:
    originalFunction = decoratorName(originalFunction)
2.4、多层装饰器的使用

在一个函数外,可以顺序定义多个装饰器,类似如:

@decorator1
@decorator2
@decorator3
def originalFunction(*args,**kwargvs):
    函数体

这种多个装饰器实际上就是叠加作用,且在上面的装饰器是对其下装饰器的包装,以上定义语句效果等同于如下语句:

originalFunction = decorator3(originalFunction)
originalFunction = decorator2(originalFunction)
originalFunction = decorator1(originalFunction)

也即等价于:

originalFunction = decorator1(decorator2(decorator3(originalFunction)))

三、类的函数装饰器

3.1、定义

函数装饰器除了给函数加装饰器(使用函数名作为装饰器函数的参数)外,还可以给类加函数装饰器,给类加函数装饰器时,将类名作为装饰器函数的参数,并在装饰器函数内定义一个类如wrapClass,该类称为包装类,包装类的构造函数中必须调用被装饰类来定义一个实例变量,装饰器函数将返回包装类如wrapClass。

3.2、类的函数装饰器案例1
def decorateFunction(fun, *a, **k):
    class wrapClass():
        def __init__(self, *a, **k):
            self.wrappedClass=fun(*a, **k)
        def fun1(self,*a, **k):
            print("准备调用被装饰类的方法fun1")
            self.wrappedClass.fun1(*a, **k)
            print("调用被装饰类的方法fun1完成")
    return wrapClass

@decorateFunction
class wrappedClass:
    def __init__(self ,*a, **k):
        print("我是被装饰类的构造方法")
        if a:print("构造方法存在位置参数:",a)
        if k:print("构造方法存在关键字参数:",k)
        print("被装饰类构造方法执行完毕")
    def fun1(self,*a, **k):
        print("我是被装饰类的fun1方法")
        if a:print("fun1存在位置参数:",a)
        if k:print("fun1存在关键字参数:",k)
        print("被装饰类fun1方法执行完毕")

    def fun2(self,*a, **k):
        print("我是被装饰类的fun2方法")

针对以上被装饰函数装饰的类wrappedClass,我们执行如下语句:

>>> c1 = wrappedClass('testPara',a=1,b=2)
我是被装饰类的构造方法
构造方法存在位置参数: ('testPara',)
构造方法存在关键字参数: {'a': 1, 'b': 2}
被装饰类构造方法执行完毕
>>> c1.fun1()
准备调用被装饰类的方法fun1
我是被装饰类的fun1方法
被装饰类fun1方法执行完毕
调用被装饰类的方法fun1完成
>>> c1.fun2()
Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    c1.fun2()
AttributeError: 'wrapClass' object has no attribute 'fun2'
>>> 

可以看到被装饰类的相关方法必须在装饰类中调用才能执行,装饰后的类如果装饰函数定义类时未定义被装饰类的同名函数,在装饰后返回的类对象无法执行被装饰类的相关方法。

3.3、类的函数装饰器案例2

上面的案例1是通过将被装饰类的方法在装饰器函数内部的装饰类中静态重新定义方式来实现对被包装类方法的支持,这种情况可以用于装饰器装饰后的类只需调用指定已知方法,但有时我们的装饰器可能用于装饰多个类,只针对构造方法和特定方法在装饰类中重写会导致被装饰类需要调用的功能不能调用,这时我们需要在装饰器中实现一个通用方法来保障被装饰类装饰后能执行被装饰类的所有方法。这就需要借助setattr进行类实例方法的动态定义。

def decorateFunction(fun, *a, **k):
    class wrapClass():
        def __init__(self, *a, **k):
            self.wrappedClass=fun(*a, **k)
            self.decorate() #针对没有重写定义的方法赋值给wrapClass作为实例变量,本案例中为涉及的为fun2方法
        def fun1(self,*a, **k):
            print("准备调用被装饰类的方法fun1")
            self.wrappedClass.fun1(*a, **k)
            print("调用被装饰类的方法fun1完成")
        def decorate(self):#针对没有重写定义的方法赋值给wrapClass作为实例变量
            for m in dir(self.wrappedClass):
                if not m.startswith('_')and m!='fun1':
                    fn = getattr(self.wrappedClass, m)
                    if callable(fn):
                    	 setattr(self, m,fn)
    return wrapClass


@decorateFunction
class wrappedClass:
    def __init__(self ,*a, **k):
        print("我是被装饰类的构造方法")
        self.name = a[0]
        if a:print("构造方法存在位置参数:",a)
        if k:print("构造方法存在关键字参数:",k)
        print("被装饰类构造方法执行完毕")
        
    def fun1(self,*a, **k):
        print("我是被装饰类的fun1方法")
        
        if a:print("fun1存在位置参数:",a)
        if k:print("fun1存在关键字参数:",k)
        print("我的实例名字为:",self.name)
        print("被装饰类fun1方法执行完毕")

    def fun2(self,*a, **k):
        print("我是被装饰类的fun2方法")
        if a:print("fun2方法存在位置参数:",a)
        if k:print("fun2存在关键字参数:",k)
        print("我的实例名字为:",self.name)

针对以上被装饰函数装饰的类wrappedClass,我们执行如下语句:

>>> c1 = wrappedClass('c1',a=1,b=2)
我是被装饰类的构造方法
构造方法存在位置参数: ('c1',)
构造方法存在关键字参数: {'a': 1, 'b': 2}
被装饰类构造方法执行完毕
>>> c2 = wrappedClass('c2',a=12,b=22)
我是被装饰类的构造方法
构造方法存在位置参数: ('c2',)
构造方法存在关键字参数: {'a': 12, 'b': 22}
被装饰类构造方法执行完毕
>>> c1.fun1()
准备调用被装饰类的方法fun1
我是被装饰类的fun1方法
我的实例名字为: c1
被装饰类fun1方法执行完毕
调用被装饰类的方法fun1完成
>>> c2.fun2()
我是被装饰类的fun2方法
我的实例名字为: c2
>>> c1.fun2()
我是被装饰类的fun2方法
我的实例名字为: c1
>>> 

可以看到,除了在装饰类中重写的fun1方法可以正常执行外,没有重写的方法fun2也可以正常执行。

四、函数的类装饰器

除了用函数作为装饰器装饰函数或者装饰类之外,也可以使用类作为函数的装饰器。将类作为函数的装饰器时,需将要装饰的函数作为装饰器类的实例成员,由于装饰后,调用相关方法时实际上调用的是装饰类的实例对象本身,为了确保类的实例对象可以调用,需要给类增加__call__方法。

案例:
class decorateClass:
    def __init__(self,fun):
        self.fun=fun

    def __call__(self, *a, **k):
        print("执行被装饰函数")
        return self.fun( *a, **k)

@decorateClass 
def fun( *a, **k):
    print(f"我是函数fun,带参数:",a,k)
    print("老猿Python博客文章目录:https://blog.youkuaiyun.com/LaoYuanPython/article/details/109160152,敬请关注同名微信公众号")

定义后执行相关调用情况如下:

>>> f = fun('funcation1',a=1,b=2)
执行被装饰函数
我是函数fun,带参数: ('funcation1',) {'a': 1, 'b': 2}
老猿Python博客文章目录:https://blog.csdn.net/LaoYuanPython/article/details/109160152,敬请关注同名微信公众号
>>> 

五、类的类装饰器

前面分别介绍了函数的函数装饰器、类的函数装饰器、函数的类装饰器,但公开资料中未查到是否可以有类的类装饰器,即装饰器和被装饰对象都是类。老猿参考类的函数装饰器、函数的类装饰器最终确认类的类装饰器也是可以支持的。

5.1、实现要点

要实现类的类装饰器,按老猿的研究,类的装饰器类的实现需要遵循如下要点:

  1. 装饰器类必须实现至少两个实例方法,包括__init__和__call__
  2. 在装饰器类的构造方法的参数包括self,wrapedClass,*a,**k,其中wrapedClass代表被装饰类,a代表被装饰类构造方法的位置参数,k代表被装饰类构造方法的关键字参数。关于位置参数和关键字参数请参考《https://blog.youkuaiyun.com/LaoYuanPython/article/details/90668385:第5章函数进阶 第5.1节 Python函数的位置参数、关键字参数精讲》;
  3. 在装饰器类的构造方法中定义一个包装类如叫wrapClass,包装类从装饰器类的构造方法的参数wrapedClass(即被装饰类)继承,包装类wrapClass的构造方法参数为self,*a,**k,相关参数含义同上;
  4. 在包装类的构造方法中调用父类的构造方法,传入参数a、k;
  5. 在装饰器类的构造方法中用实例变量(例如self.wrapedClass)保存wrapClass类;
  6. 在装饰器类的__call__方法中调用self.wrapedClass(*a,**k)创建被装饰类的一个对象,并返回该对象。

按照以上步骤创建的类装饰器,就可以用于装饰其他类。当然上述方法只是老猿自己研究测试的结论,是否还有其他方法老猿也不肯定。

5.2、类的类装饰器案例
class decorateClass: #装饰器类
    def __init__(self,wrapedClass,*a,**k): #wrapedClass代表被装饰类
        print("准备执行装饰类初始化")
        class wrapClass(wrapedClass):
            def __init__(self,*a,**k):
                print(f"初始化被封装类实例开始,位置参数包括:{a}, 关键字参数为{k}")
                super().__init__(*a,**k)
                print(f"初始化被封装类实例结束")
        self.wrapedClass=wrapClass
        print("装饰类初始化完成")

    def __call__(self, *a, **k):
        print("被装饰类对象初始化开始")
        wrapedClassObj = self.wrapedClass(*a,**k)
        print("被装饰类对象初始化结束")
        return wrapedClassObj

@decorateClass
class car:
    def __init__(self,type,weight,cost):
        print("class car __init__ start...")
        self.type = type
        self.weight = weight
        self.cost = cost
        self.distance = 0
        print("class car __init__ end.")

    def driver(self,distance):
        self.distance += distance
        print(f"{self.type}已经累计行驶了{self.distance}公里")
        print("老猿Python博客文章目录:https://blog.youkuaiyun.com/LaoYuanPython/article/details/109160152,敬请关注同名微信公众号")

c = car('爱丽舍','1.2吨',8)
c.driver(10)
c.driver(110)

执行以上代码,输出如下:

准备执行装饰类初始化
装饰类初始化完成
被装饰类对象初始化开始
初始化被封装类实例开始,位置参数包括:('爱丽舍', '1.2吨', 8), 关键字参数为{}
class car __init__ start...
class car __init__ end.
初始化被封装类实例结束
被装饰类对象初始化结束
爱丽舍已经累计行驶了10公里
爱丽舍已经累计行驶了120公里
老猿Python博客文章目录:https://blog.csdn.net/LaoYuanPython/article/details/109160152,敬请关注同名微信公众号

除了上述方法,老猿又找到了一种更简单的方法,具体请参考《https://blog.youkuaiyun.com/LaoYuanPython/article/details/111307103:类的类装饰器实现思路及案例》。

六、小结

本文详细介绍了Python中的四类装饰器,这四类装饰器根据装饰器和被装饰对象的类型分为函数的函数装饰器、类的函数装饰器、函数的类装饰器、类的类装饰器,文中详细介绍了四类装饰器的实现步骤,并提供了对应的实现案例,相关介绍有助于大家全面及详细地理解Python的装饰器。

写博不易,敬请支持:

如果阅读本文于您有所获,敬请点赞、评论、收藏,谢谢大家的支持!

关于老猿的付费专栏

  1. 付费专栏《https://blog.youkuaiyun.com/laoyuanpython/category_9607725.html 使用PyQt开发图形界面Python应用》专门介绍基于Python的PyQt图形界面开发基础教程,对应文章目录为《 https://blog.youkuaiyun.com/LaoYuanPython/article/details/107580932 使用PyQt开发图形界面Python应用专栏目录》;
  2. 付费专栏《https://blog.youkuaiyun.com/laoyuanpython/category_10232926.html moviepy音视频开发专栏 )详细介绍moviepy音视频剪辑合成处理的类相关方法及使用相关方法进行相关剪辑合成场景的处理,对应文章目录为《https://blog.youkuaiyun.com/LaoYuanPython/article/details/107574583 moviepy音视频开发专栏文章目录》;
  3. 付费专栏《https://blog.youkuaiyun.com/laoyuanpython/category_10581071.html OpenCV-Python初学者疑难问题集》为《https://blog.youkuaiyun.com/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的伴生专栏,是笔者对OpenCV-Python图形图像处理学习中遇到的一些问题个人感悟的整合,相关资料基本上都是老猿反复研究的成果,有助于OpenCV-Python初学者比较深入地理解OpenCV,对应文章目录为《https://blog.youkuaiyun.com/LaoYuanPython/article/details/109713407 OpenCV-Python初学者疑难问题集专栏目录 》。

前两个专栏都适合有一定Python基础但无相关知识的小白读者学习,第三个专栏请大家结合《https://blog.youkuaiyun.com/laoyuanpython/category_9979286.html OpenCV-Python图形图像处理 》的学习使用。

对于缺乏Python基础的同仁,可以通过老猿的免费专栏《https://blog.youkuaiyun.com/laoyuanpython/category_9831699.html 专栏:Python基础教程目录)从零开始学习Python。

如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。

如果对文章内容存在疑问,可以在博客评论区留言,或关注:老猿Python 微信公号发消息咨询。
在这里插入图片描述

老猿Python,跟老猿学Python!

☞ ░ 前往老猿Python博文目录 https://blog.youkuaiyun.com/LaoYuanPython
<think> 我们正在解决一个问题:pytest没有读取conftest文件的原因及解决办法。 根据之前提供的引用内容,我们知道conftest.py是pytest框架中用于共享fixture的配置文件,它需要满足一定的条件才能被pytest自动识别。 现在,根据引用[1][2][3][4]中的信息,我们可以总结出pytest读取conftest.py的规则和注意事项,进而分析可能的原因和解决办法。 原因分析: 1. 文件命名错误:conftest.py的文件名必须是“conftest.py”,不能有任何改动(包括大小写和扩展名)[^1][^2]。 2. 位置问题:conftest.py应该放在测试文件的同级目录或父目录中,且作用范围是其所在目录及其子目录[^3][^4]。如果测试文件不在conftest.py的作用范围内,则不会被读取。 3. 目录结构问题:如果conftest.py所在的目录不是一个Python包(即没有__init__.py文件),则可能不会被正确识别[^3](注意:pytest在最新版本中不一定需要__init__.py,但有时有它会更可靠)。 4. 作用域覆盖问题:如果存在多个conftest.py文件,那么距离测试文件最近的conftest.py会被优先使用,而更上层的conftest.py中的fixture可能被覆盖[^4]。如果测试文件没有使用预期的fixture,可能是因为被覆盖了。 5. 运行命令问题:运行pytest时指定的路径可能没有包含conftest.py所在的目录,导致pytest没有找到conftest.py。 6. 文件编码问题:如果conftest.py文件使用了非UTF-8编码,并且包含非ASCII字符,可能会导致解析错误(引用中未直接提及,但根据经验)。 7. 语法错误:如果conftest.py中存在语法错误,pytest会报错,并不会加载其中的fixture。 8. fixture定义问题:fixture的定义必须使用@pytest.fixture装饰器,并且函数名需要与测试用例中使用的参数名一致[^2][^3]。 9. 测试文件没有显式使用fixture:测试用例函数必须将fixture的函数名作为参数才能使用,否则即使定义了fixture也不会被调用[^2][^3]。 解决办法: 1. 检查文件名:确保文件名为“conftest.py”(小写)。 2. 检查位置:将conftest.py放在测试用例文件的同一目录或父目录(即测试用例的上级目录),并确保测试用例在conftest.py所在目录或其子目录中。 3. 检查目录结构:在conftest.py所在目录添加一个空的__init__.py文件,使其成为一个Python包(尽管pytest在最新版本中不一定需要,但这样做可以避免一些潜在问题)。 4. 检查作用域覆盖:如果存在多个conftest.py,请确认测试用例使用的fixture来自哪个conftest.py,并确保没有同名的fixture被覆盖。可以通过在fixture中打印日志来验证是否被调用。 5. 检查运行路径:在运行pytest时,确保当前工作目录或指定测试路径包含conftest.py所在的目录。例如,如果conftest.py在项目根目录,则运行pytest时应从根目录运行,或者指定测试路径为根目录下的测试目录。 6. 检查编码:将conftest.py文件保存为UTF-8编码(推荐),并在文件开头添加编码声明:`# -*- coding: utf-8 -*-`(如引用[2]中所示)。 7. 检查语法:运行pytest时,观察控制台输出,看是否有关于conftest.py的语法错误报告。修复这些错误。 8. 检查fixture定义:确保使用了@pytest.fixture装饰器,并且测试用例函数参数名与fixture函数名一致。 9. 在测试用例中显式使用fixture:在测试用例函数参数列表中加入fixture函数名。 调试技巧: - 使用`pytest --fixtures`命令可以列出当前目录下所有可用的fixtures,包括conftest.py中定义的fixture。如果未列出,则说明conftest.py没有被正确读取。 - 使用`pytest -v`可以输出详细信息,帮助发现问题。 示例: 假设项目结构如下: ``` project_root/ │ conftest.py │ ├── tests/ │ ├── conftest.py # 这个fixture只对tests目录下的测试生效 │ ├── test_demo.py │ └── sub_tests/ │ └── test_sub.py ``` 在根目录运行pytest:`pytest tests/`,那么根目录的conftest.py和tests目录下的conftest.py都会被加载,但tests目录下的conftest.py中的fixture优先级更高。 如果发现test_sub.py没有使用到conftest.py中的fixture,请检查: - test_sub.py是否在tests/sub_tests目录下?那么它可以使用tests/conftest.py和project_root/conftest.py中的fixture,但tests/conftest.py中的fixture优先级更高(更近)。 - 如果tests/conftest.py中定义了一个fixture,而在tests/sub_tests/conftest.py中又定义了一个同名fixture,则test_sub.py会使用tests/sub_tests/conftest.py中的fixture。 如果问题仍然存在,请尝试简化问题:创建一个最简单的conftest.py和一个测试文件,确保它们能正常工作,然后逐步调整到实际项目结构。 相关问题: 1. 如何在多个测试目录中共享同一个conftest.py文件? 2. 如何调试pytest中未加载夹具(fixture)的问题? 3. pytest的fixture覆盖规则是怎样的? 4. 如何在pytest中查看所有可用的fixture列表? 5. conftest.py文件在pytest中是如何被自动发现的? </think> ### pytest 未读取 conftest.py 的原因及解决办法 当 pytest 无法读取 conftest.py 文件时,通常由以下原因导致: #### 🛠 1. 文件位置错误(最常见原因) **原因**: conftest.py 必须位于测试文件的同级目录或其父目录中才能被识别,作用范围是当前目录及其子目录[^3][^4]。 **解决办法**: ```bash 项目结构示例: project/ ├── conftest.py # 作用于整个项目[^1] ├── tests/ │ ├── conftest.py # 仅作用于 tests 目录[^4] │ ├── test_module1.py │ └── subdir/ │ ├── conftest.py # 仅作用于 subdir 目录 │ └── test_sub.py ``` #### 🧩 2. 文件命名错误 **原因**: 文件名必须**严格命名为 `conftest.py`**(小写),任何其他命名如 `Conftest.py` 或 `confTest.py` 都无法被识别[^1][^2]。 **验证方法**: ```bash ls -l # 检查文件名大小写 ``` #### 📂 3. 目录未被识别为 Python 包 **原因**: 缺少 `__init__.py` 文件时,pytest 可能无法识别目录结构(虽然新版 pytest 不强制要求,但建议保留)[^3]。 **解决办法**: 在 conftest.py 所在目录创建空文件: ```bash touch __init__.py ``` #### 🔍 4. 作用域冲突 **原因**: 当多级目录存在同名 fixture 时,pytest 优先使用**最近层级**的 conftest.py[^4]。 **验证方法**: 使用 `pytest --fixtures` 查看实际加载的 fixture: ```bash pytest --fixtures -v ``` #### ️ 5. 语法错误或导入问题 **原因**: conftest.py 中存在语法错误或导入失败会导致整个文件被忽略。 **调试步骤**: 1. 直接运行 conftest.py:`python conftest.py` 2. 检查 pytest 启动日志: ```bash pytest -v --trace-config # 查看加载的配置文件 ``` #### 🚀 6. 运行路径错误 **原因**: 在错误的工作目录执行 pytest 可能导致文件未被扫描。 **正确做法**: ```bash # 在项目根目录执行 pytest # 指定测试路径时确保包含 conftest 目录 pytest tests/subdir/ ``` #### 💡 7. 缓存问题 **解决办法**: 清除 pytest 缓存: ```bash pytest --cache-clear ``` ### 调试检查清单 1. ✅ 确认文件名是 `conftest.py`(非 `conftest.py.txt` 等) 2. ✅ 检查文件层级关系(测试文件应在 conftest.py 同级或子目录) 3. ✅ 验证无语法错误(直接运行文件) 4. ✅ 使用 `pytest --fixtures` 查看加载的 fixture 5. ✅ 在项目根目录运行测试 > **最佳实践**:在项目根目录放置主 conftest.py,子目录按需添加专用配置,通过 `pytest --collect-only` 验证测试发现机制[^1]。 ### 📚 相关问题 1. 如何在多层级目录结构中管理不同的 conftest.py 文件? 2. pytest 的 fixture 作用域(session/function/module)如何影响 conftest 的加载? 3. 如何强制 pytest 忽略特定目录中的 conftest.py? 4. 在持续集成(CI)环境中如何验证 conftest 配置是否生效? 5. conftest.py 与其他 pytest 插件(如 pytest.ini)的优先级关系是怎样的? [^1]: conftest.py配置fixture注意事项 [^2]: pytest零基础入门到精通(04)conftest文件详解 [^3]: Pytest框架 — conftest.py文件的使用 [^4]: conftest.py中Fixture的作用域规则
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LaoYuanPython

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值