百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

如何将 Python 的一个类方法变为多个方法?

toyiye 2024-09-04 20:15 4 浏览 0 评论

作者 | 豌豆花下猫

责编 | 郭芮

在 Python 中,实现参数化测试的几个库,是如何做到把一个方法变成多个方法,并且将每个方法与相应的参数绑定起来的呢?

我们再提炼一下:在一个类中,如何使用装饰器把一个类方法变成多个类方法(或者产生类似的效果)?

# 带有一个方法的测试类
class TestClass:
def test_func(self):
pass

# 使用装饰器,生成多个类方法
class TestClass:
def test_func1(self):
pass
def test_func2(self):
pass
def test_func3(self):
pass

Python 中装饰器的本质就是移花接木,用一个新的方法来替代被装饰的方法。在实现参数化的过程中,我们介绍过的几个库到底用了什么手段/秘密武器呢?

ddt 如何实现参数化?

先回顾一下上篇文章中 ddt 库的写法:

import unittest
from ddt import ddt,data,unpack
@ddt
class MyTest(unittest.TestCase):
@data((3, 1), (-1, 0), (1.2, 1.0))
@unpack
def test(self, first, second):
pass

ddt 可提供 4 个装饰器:1 个加在类上的 @ddt,还有 3 个加在类方法上的 @data、@unpack 和 @file_data(前文未提及)。

先看看加在类方法上的三个装饰器的作用:

# ddt 版本(win):1.2.1
def data(*values):
global index_len
index_len = len(str(len(values)))
return idata(values)

def idata(iterable):
def wrapper(func):
setattr(func, DATA_ATTR, iterable)
return func
return wrapper

def unpack(func):
setattr(func, UNPACK_ATTR, True)
return func

def file_data(value):
def wrapper(func):
setattr(func, FILE_ATTR, value)
return func
return wrapper

它们的共同作用是在类方法上 setattr 添加属性。至于这些属性在什么时候使用?下面看看加在类上的 @ddt 装饰器源码:

第一层 for 循环遍历了所有的类方法,然后是 if/elif 两条分支,分别对应 DATA_ATTR/FILE_ATTR,即对应参数的两种来源:数据(@data)和文件(@file_data)。

elif 分支有解析文件的逻辑,之后跟处理数据相似,所以我们把它略过,主要看前面的 if 分支。这部分的逻辑很清晰,主要完成的任务如下:

  • 遍历类方法的参数键值对

  • 根据原方法及参数对,创建新的方法名

  • 获取原方法的文档字符串

  • 对元组和列表类型的参数作解包

  • 在测试类上添加新的测试方法,并绑定参数与文档字符串

分析源码,可以看出,@data、@unpack 和 @file_data 这三个装饰器主要是设置属性并传参,而 @ddt 装饰器才是核心的处理逻辑。

这种将装饰器分散(分别加在类与类方法上),再组合使用的方案,很不优雅。为什么就不能统一起来使用呢?后面我们会分析它的难言之隐,先按下不表,看看其它的实现方案是怎样的?

parameterized 如何实现参数化?

先回顾一下上篇文章中 parameterized 库的写法:

import unittest
from parameterized import parameterized
class MyTest(unittest.TestCase):
@parameterized.expand([(3,1), (-1,0), (1.5,1.0)])
def test_values(self, first, second):
self.assertTrue(first > second)

它提供了一个装饰器类 @parameterized,源码如下(版本 0.7.1),主要做了一些初始的校验和参数解析,并非我们关注的重点,略过。

我们主要关注这个装饰器类的 expand 方法,它的文档注释中写到:

A "brute force" method of parameterizing test cases. Creates new test cases and injects them into the namespace that the wrapped function is being defined in. Useful for parameterizing tests in subclasses of 'UnitTest', where Nose test generators don't work.

关键的两个动作是:“creates new test cases(创建新的测试单元)”和“inject them into the namespace…(注入到原方法的命名空间)”。

关于第一点,它跟 ddt 是相似的,只是一些命名风格上的差异,以及参数的解析及绑定不同,不值得太关注

最不同的则是,怎么令新的测试方法生效?

parameterized 使用的是一种“注入”的方式:

inspect 是个功能强大的标准库,在此用于获取程序调用栈的信息。前三句代码的目的是取出 f_locals,它的含义是“local namespace seen by this frame”,此处 f_locals 指的就是类的局部命名空间。

说到局部命名空间,你可能会想到 locals,但是,我们之前有文章提到过“locals 与 globals 的读写问题”,locals 是可读不可写的,所以这段代码才用了 f_locals。

pytest 如何实现参数化?

按惯例先看看上篇文章中的写法:

import pytest
@pytest.mark.parametrize("first,second", [(3,1), (-1,0), (1.5,1.0)])
def test_values(first, second):
assert(first > second)

首先看到“mark”,pytest 里内置了一些标签,例如 parametrize、timeout、skipif、xfail、tryfirst、trylast 等,还支持用户自定义的标签,可以设置执行条件、分组筛选执行,以及修改原测试行为等等。

用法也是非常简单的,然而,其源码可复杂多了。我们这里只关注 parametrize,先看看核心的一段代码:

根据传入的参数对,它复制了原测试方法的调用信息,存入待调用的列表里。跟前面分析的两个库不同,它并没有在此创建新的测试方法,而是复用了已有的方法。在 parametrize 所属的 Metafunc 类往上查找,可以追踪到 _calls 列表的使用位置:

最终是在 Function 类中执行:

好玩的是,在这里我们可以看到几行神注释……

阅读(粗浅涉猎) pytest 的源码,真的是自讨苦吃……不过,依稀大致可以看出,它在实现参数化时,使用的是生成器的方案,遍历一个参数则调用一次测试方法,而前面的 ddt 和 parameterized 则是一次性把所有参数解析完,生成 n 个新的测试方法,再交给测试框架去调度。

对比一下,前两个库的思路很清晰,而且由于其设计单纯是为了实现参数化,不像 pytest 有什么标记和过多的抽象设计,所以更易读易懂。前两个库发挥了 Python 的动态特性,设置类属性或者注入局部命名空间,而 pytest 倒像是从什么静态语言中借鉴的思路,略显笨拙。

小结

回到标题中的问题“如何将一个方法变为多个方法?”除了在参数化测试中,不知还有哪些场景会有此诉求?欢迎留言讨论。

本文分析了三个测试库的装饰器实现思路,通过阅读源码,我们可以发现它们各有千秋,这个发现本身还挺有意思。在使用装饰器时,表面看它们差异不大,但是真功夫的细节都隐藏在底下。

作者:豌豆花下猫,生于广东毕业于武大,现为苏漂程序员,有一些极客思维,也有一些人文情怀,有一些温度,还有一些态度,公众号「Python猫」(python_cat)。

声明:本文系作者投稿,版权归作者所有。

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码