Python 类继承的常见用法

前言

最近很多小伙伴问Python继承的问题以及继承的使用场景。我们常说面向对象的三个基本特性:封装、继承、多态。

封装这个其实没啥好说的,而多态在Python其实是不存在的,Python崇尚的是鸭子类型,一个对象只要看着像鸭子,走起来像鸭子,那它就是鸭子了。

而关于继承,我就不详细说继承的各种特性和特点了,网上一大堆,我聊聊他在自动化领域的常见用法。

简单的继承用法

假设我们现在正在编写测试用例脚本,写了一个测试类:

import pytest
import requests


class TestLogin:
    def request(self):
        """业务代码"""
        url = "https://www.baidu.com/s?ie=utf-8&wd=python"
        response = requests.request(method="get", url=url)

    def test_run(self):
        self.request()


if __name__ == '__main__':
    pytest.main(["-v", "-s"])

当上面这个测试类丢给测试引擎(pytest、unittest)去执行的时候,测试引擎会去执行test开头的测试方法,然后test_run()放就会去调用request()方法去发起请求。

那么现在有一个问题,如果我有多个测试脚本要写,那每个测试脚本都要写一遍上面的代码吗?这显然不科学,所以很多小伙伴就会用到继承。

我们稍微改一下代码,写一个父类,然后分别写一个请求百度的类,一个请求qq的类去继承这个父类,如下:

import pytest
import requests


class CaseBase:
    def request(self):
        pass

    def test_run(self):
        self.request()


class TestBaidu(CaseBase):
    def request(self, *args, **kwargs):
        url = "https://www.baidu.com/"
        response = requests.request(method="get", url=url)


class TestQQ(CaseBase):
    def request(self, *args, **kwargs):
        url = "https://www.qq.com/"
        response = requests.request(method="get", url=url)


if __name__ == '__main__':
    pytest.main(["-v", "-s"])

如上代码,TestBaidu和TestQQ两个类都继承CaseBase,我们只需要重写父类的request()方法,把自己的请求过程写上去就好了。 然后将TestRegister和TestLogin丢给测试引擎,你会发现因为继承了父类CaseBase,我们不需要修改test_run()方法的内容,测试引擎依然会去执行并且调用request()也是正常的。

抽象基类

现在问题来了,如果你是跟别人一起协作写的代码,你已经写好父类了,虽然你的小伙伴继承了你的父类,但他非常头铁,本来只需要重写request方法就好了,他非得写一个reques_baidu()的方法。代码如下:


import pytest
import requests


class CaseBase:
    def request(self):
        pass

    def test_run(self):
        self.request()


class TestBaidu(CaseBase):
    def request_baidu(self, *args, **kwargs):
        url = "https://www.baidu.com/"
        response = requests.request(method="get", url=url)


if __name__ == '__main__':
    pytest.main(["-v", "-s"])

那么运行结果会怎样?

结果就是啥也不会发生,因为即没有重写父类的reques()方法,也没有重写test_run()方法,这个时候把测试类丢给测试引擎,测试引擎依然是去执行test_run()方法,然后就会去调用父类的request()方法,并且由于request()方法里面的代码是空的,因此啥也不会发生。

这个时候你的小伙伴跑过来找你,“跟你说你的框架太菜了吧,我写了请求方法,它都不去执行,真是渣渣!”(是不是有种想刀了他的冲动?)

那么,为了避免这种情况,我们可以使用Python的抽象基类,具体需要使用到Python内置的abc模块。

接下来,我们还是用回上面头铁小伙伴写的代码,但我们稍微对父类进行一些改动:

from abc import ABCMeta, abstractmethod
import pytest
import requests


class CaseBase(metaclass=ABCMeta):
    @abstractmethod
    def request(self):
        pass

    def test_run(self):
        self.request()


class TestBaidu(CaseBase):
    def request_baidu(self, *args, **kwargs):
        url = "https://www.baidu.com/"
        response = requests.request(method="get", url=url)


if __name__ == '__main__':
    pytest.main(["-v", "-s"])

这个时候,你的小伙伴再运行代码,就会出现异常:

TypeError: Can't instantiate abstract class TestBaidu with abstract method request

翻译过来就是,TestBaidu这个子类没有重写父类request方法。

这就很完美的解决了小伙伴头铁的情况了,毕竟我规定好的事情,你得按照我的要求去做,不能头铁~

首先class CaseBase(metaclass=ABCMeta)这里的意思是将CaseBase以元类的方式进行创建并指定ABCMeta来限制创建类的方式。

@abstractmethod则是父类利用这个装饰器去限制子类必须要重写被装饰的方法,否则就抛出异常。

以上内容就是最常见的用法,但使用场景不设限,因为不只是编写测试脚本的时候,在很多场景都会用得上继承和抽象基类。

最后请记住:Python面向对象编程的语言。

回帖
请输入回帖内容 ...