第三周:Python抽象类

Python抽象类

在Python中抽象类只能被继承不能被实例化

并且,抽象类中只有抽象方法普通方法


定义抽象类和抽象方法

Python的抽象类的定义需要abc模块。(= =...)

# 导入抽象类需要用到的库
from abc import ABCMeta, abstractmethod

class Person(metaclass=ABCMeta):
    """使用元类(模板类)"""
    
    pname = "这是Person抽象类"  # 可以定义属性
    
    # 定义抽象方法,抽象方法不需要方法体
    # 需要在方法前加@abstractmethod装饰器
    @abstractmethod
    def run(self):
        pass  # 不需要写方法体
    
    @classmethod  # 可以定义类方法
    def eat(cls):
        print("在吃东西啊")
        
    @staticmethod
    def drink():  # 可以定义静态方法
        print("在喝东西啊")
        
    def sleep(self):  # 可以定义普通方法
        print("在睡觉啊")

抽象方法不需要写方法体,但并不是不能写。可以写,但是没有用。

抽象类中可以定义属性、类方法、静态方法


使用抽象类和抽象方法

class Student(Person):
    sno = 123  # 学号
    # 不会提示要要重写方法
    
    def run(self):
        print("跑")
    
    
# 不重写抽象方法就无法实例化对象,并且抛异常
s = Student()
s.run()  # 跑
s.eat()  # 在吃东西啊
s.sleep()  # 在睡觉啊
print(s.pname)  # 这是Person抽象类
s.drink()  # 在喝东西啊

# p = Person() 运行时抛异常	

继承了抽象类,就必须重写抽象类中的抽象方法,否则无法实例化对象,并抛异常。


应该尽量避免多继承

如果出现多继承,那么对于同名抽象方法,哪个继承类靠前就是实现的哪个类的抽象方法。

这对于抽象方法来说没什么意义。

但是!

由于抽象类不止能写抽象方法,还可以写属性、类方法、静态方法、普通方法,这些方法如果同名,就是按继承顺序来继承和重写的了。

class A(metaclass=ABCMeta):

    num = 10
    
    def __init__(self):
        print("这是A的构造方法")
    
    @abstractmethod
    def show(self):
        pass

    @classmethod  # 可以定义类方法
    def eat(cls):
        print("吃 1")

    @staticmethod
    def drink():  # 可以定义静态方法
        print("喝 1")

    def sleep(self):  # 可以定义普通方法
        print("睡 1")
   

class B(metaclass=ABCMeta):

    num = 20
    
    def __init__(self):
        print("这是B的构造方法")
        
    @abstractmethod
    def show2(self):
        pass

    @classmethod  # 可以定义类方法
    def eat(cls):
        print("吃 2")

    @staticmethod
    def drink():  # 可以定义静态方法
        print("喝 2")

    def sleep(self):  # 可以定义普通方法
        print("睡 2")
    
class C(B, A):
    
    def show(self):
        print("showA")
        
    def show2(self):
        print("showB")
   
        
c = C()
# 在打印20前,先打印了 这是B的构造方法  但是没有打印 这是A的构造方法
print(c.num)  # 20
c.show()  # showA
c.eat()  # 吃 2
c.drink()  # 喝 2
c.sleep()  # 睡 2
c.show2() # showB

抽象类中可以写构造方法,但是子类继承时只会调用第一个继承类的构造方法


抽象类方法和抽象静态方法

class A和class B中增添这两个方法

@classmethod
@abstractmethod
def swim(cls):  # 抽象 类方法
    pass

@staticmethod
@abstractmethod
def run():  # 抽象 静态方法
    pass

与抽象方法一样,方法体可以不写,能写,但没必要。

子类必须实现这两个方法才能实例化。

不过!

子类实现后,就是普通方法了。与普通抽象方法的实现没啥区别。

class C(B, A):
    
    def show(self):
        print("show")
        
    def swim(self):
        print("swim")
        
    def run(self):
        print("run")

# 不能这样永类名调用
C.swim()
C.run()

所以还要在实现的方法上加对应的装饰器。

@classmethod
def swim(cls):
    print("swim")
    
@staticmethod
def run():
    print("run")

然而这时写反装饰器(不按抽象类中的来),也不会有啥事。

所以说抽象类只保证了抽象类本身不能实例化,和子类必须实现与抽象的方法(抽象方法、抽象类方法、抽象静态方法)同名的方法

但是不保证子类的实现方法和原抽象的方法是同种类型

简单来说,只要子类有同名的方法就行,管它是不是为了实现抽象的方法的,还是自己本身独有的。


抽象“属性”

Python中有个个特殊的装饰器@property。用了它,方法就可以当属性一样用(调用时不需要加(),直接.属性名

@property
@abstractmethod
def age(self):  # 抽象属性
    pass

但是在实现时也必须要加上@property,否则也就当成普通方法了。

@property
def age(self):
    print("age")
    return 22

print(c.age)  # 先打印 22 再打印 age
print(type(c.age))  # <class 'int'>
c.age = 30  # 这样会抛异常,只读不能修改
print(c.age)

这个属性是只读的,不能修改。

当然可以配合@X.setter@X.deleter来使得该“抽象”属性可读可写可删除。(其中X@property修饰的方法的方法名)

这里不演示了,有兴趣的可以去查看@property/@X.setter/@X.deleter三个装饰器的使用情况和方法。

也一定要注意子类中实现时,对应也要加上装饰器修饰,否则会被当成普通方法。


使用抽象类的好处

对于一些有相似属性和方法类,可以统一把这些属性和方法抽出来放在一个类中。

这样可以很好的理清类直接的继承关系、方法属性的意义、做到了解耦合

比如说,猫和狗都是动物,都有年龄、性别等属性、都有吃喝拉撒睡等动作(假设这些共有动作两者是不同的)。那么就可以把这些属性动作抽出来放在一个动物类中(class Animal)。让猫类(class Cat)和狗类(class Dog)都继承这个动物类,然后根据自身特点实现这些属性和动作即可。

其实如果动作相同的话就没必要写成抽象方法,而是写成普通方法即可,这样不需要重写方法,直接调用就行。

抽象就是一种规范,让继承了 抽象类 后的子类必须实现抽象方法才能够实例化。


接口?

Python中没有接口的概念,但是鉴于Java的接口理念,Python可以用abc模块像实现抽象类那样,来实现接口类。

只要在类中只写抽象的方法、static和final属性(用@property等和@staticmethod配合着使用),那么就可以。

当然肯定没有Java中那么严格的检验,不过也勉强能做到。

但最终还是用看开发的需求来决定是否要这样去写,不能为了炫技而去写一些只能自己才能看懂的代码啊,那就失去了编程的初衷了。

原文地址:https://www.cnblogs.com/jiyou/p/14024324.html