Python侧开28期-偕行-学习笔记-python抽象类

前言

抽象基类(abstract base class,ABC),提到这个概念应该会马上联想到面向对象、继承。作为继承的一种,它拥有继承中代码共享、提高代码的重用性等优点。例如,下面示例:

class Animal(object):
    def eat(self, kind):
        print("{} eat food....".format(kind))
​
class Dog(Animal):
    pass
​
class Cat(Animal):
    pass
    
dog = Dog()
cat = Cat()
dog.eat("dog")
cat.eat("cat")
​
# 输出
dog eat food....
cat eat food....

狗(Dog)和猫(Cat)都属于动物(Animal),它们有很多类似的属性和动作,我们可以在父类中实现这些方法,在子类中直接继承或者重载父类中的方法,这样减少了代码的重复性,提高了代码的共享能力。

作为继承的一种,抽象基类有用继承的上述这些优点,但是它与普通的继承也有不同之处。

抽象基本类的几大特点:

  • python中抽象基类必须继承自ABC(Abstract Base Class)类;
  • 可以包含类方法、静态方法、普通方法,但是必须至少有一个抽象方法
  • 抽象基类不能实例化;
  • 子类需要实现基类中定义的所有抽象方法;

看到这里应该会意识到,抽象基类有一种接口的感觉,没错,抽象基类的出现主要是功能就是类似于Java等编程语言中的接口。但是需要明确一点,Python语言中没有interface这个概念,只是这是一种约定俗成的编程规范,就如同Python也没有真实意义上的私有变量,我们在编程中可以规范的使用下划线来表示某个变量为私有变量。

尽管Python中没有接口这个关键字,但是抽象基类实现的功能主要围绕接口在展开,因此,首先类比Java来阐述一下编程语言中接口的概念,然后介绍一下Python中如何实现抽象基类。

接口

接口(Interface)是对象公开方法的一种集合,在Java中通常以interface关键字来定义,接口虽然实现过程中和相似,但是却具有不同的概念。具体而言,类与接口主要有以下几点不同之处:

  • 类实现了对象的属性和方法,而接口指定了使用该接口需要实现哪些方法;
  • 类可以实例化,而接口不可以被实例化;
  • 类中的方法可以是实现,接口中的方法都是抽象方法;
    • 抽象方法 :抽象方法的概念是父类中只负责声明该方法,但不具体实现这个方法,实现部分由继承该类的子类负责实现。

如果觉得上述描述有点云里雾里、对接口的概念依然不是非常清楚,不妨来试想一个场景:如果你是一个测试经理,在进行UI自动化或者接口自动化的时候,为了规范测试组的代码结构,你可以按照PageObject或者APIObject模式先定义一个抽象基类,里面定义出需要实现的具体方法,然后分配任务下去让对应的组员各自去完成对应的page或者api,之后去完成对应的测试用例的变现。

抽象基类

虽然Python中抽象基类和接口概念非常相近,但是它们还是有一些不同之处,例如:

  • 接口需要被实现的子类完成接口中指定的所有方法,而抽象基类不是,抽象基类则没有这么严格的要求;
  • 接口需要所有方法都是抽象方法,而抽象基类中有抽象方法,也有自己实现的方法;

正是因为抽象基类和接口的不同之处使得接口之所以称为接口、抽象基类之所以称为抽象基类。

为什么使用抽象基类?

抽象基类的存在自然有它的价值。当你学会一种编程语言的语法时,你可以轻松的完成一项功能的开发,但是如果希望把代码完成的更加优美高效,那么就需要在设计模式等方面下一些功夫,抽象基类就是其中的一个选择,抽象基类具有以下优点:

  • 处理继承问题方面更加规范、系统
  • 明确调用之间的相互关系
  • 使得继承层次更加清晰
  • 限定子类实现的方法

什么是抽象基类?

必须包含一个抽象函数(纯虚函数),它是一个不完整的类,它有已经被实现的方法,也有需要子类重写的方法。

Python抽象基类

抽象基类:通过继承abc模块中的ABC类来实现抽象基类。

抽象方法:通过装饰器@abstractmethod来定义抽象方法,也就是需要子类实现的方法。

  • 装饰器@abstractmethod除了可以实现抽象方法外,还可以装饰类方法(@classmethod)、静态方法(@staticmethod)、属性(@property)。

实现抽象基类:

from abc import ABC
from abc import abstractmethod

class Database(ABC):
    
    def register(self, host, user, password):
        print("Host : {}".format(host))
        print("User : {}".format(user))
        print("Password : {}".format(password))
        print("Register Success!")

    @abstractmethod
    def query(self, *args):
        """
        传入查询数据的SQL语句并执行
        """
        pass

    @staticmethod
    @abstractmethod
    def execute(sql_string):
        """
        执行SQL语句
        """
        pass
    

从抽象基类Database 的实现可以看出,它共包含3个方法,其中register 是每个子类都需要的,直接实现在抽象基类里,是一个普通的类方法。queryexecute 只是在基类中进行类声明,给出了描述,但并没有实现,它限定了继承Database 的子类必须实现这两个方法。

两个子类:

class Component1(Database):
    def __init__(self, host, user, password):
        self.register(host, user, password)

    @staticmethod
    def execute(sql_string):
        print(sql_string)

    def query(self, *args):
        sql_string = "SELECT ID FROM db_name"
        self.execute(sql_string)

class Component2(Database):
    def __init__(self, host, user, password):
        self.register(host, user, password)

    @staticmethod
    def execute(sql_string):
        print(sql_string)

    def query(self, *args):
        sql_string = "SELECT NAME FROM db_name"
        self.execute(sql_string)

if __name__ == '__main__':
    comp1 = Component1("00.00.00.00", "abc", "000000")
    comp2 = Component2("11.11.11.11", "ABC", "111111")
    comp1.query()
    comp2.query()

    """
    输出:
    Host : 00.00.00.00
    User : abc
    Password : 000000
    Register Success!
    Host : 11.11.11.11
    User : ABC
    Password : 111111
    Register Success!
    SELECT ID FROM db_name
    SELECT NAME FROM db_name
    """