QQ登录

只需一步,快速开始

开启左侧

Python学习第七十五天—l类和对象24-抽象基类

[复制链接]
15271953841 发表于 2024-4-21 22:21:36 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

x
本帖最后由 15271953841 于 2024-4-21 22:27 编辑

抽象基类

上一节课,利用元类,就可以阻止类被实例化。
前面Mixin类,它的作用为一些不相关的子类提供一些额外功能的实现(参考第56课时),XX一说插件或者外挂的存在,所以对于Mixin类来说,我们是不建议对它进行直接实例化的。要用它里面提供的方法作为父类将它插入就可以了,那么这节课,我们还会介绍另一种不能被直接实例化的类——抽象基类。
1.抽象基类不能被直接实例化
2.子类必须实现抽象基类中定义的抽象方法。否则,无法被实例化。比如说,我们要定义一个“水果”的类,那么这个水果本身应该是一个抽象类,因为你不能直接把水果实例化对象,实际上有苹果,香蕉,有梨,这些都是水果,但是没有一种叫水果的水果,那么水果就是一个抽象的概念,那么我们现在就拿水果来作为例子,演示一下,如何实现抽象基类?要使用ABC模块abc—AbstractBaseClasses,Python早就准备好了,这个模式里面最常见的两个代表:ABCMeta,abstractmethod.

Python 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license()" for more information.
>>>from abc import ABCMeta,abstractmethod      #从abc模块中导入成功
>>>class Fruit(metaclass=ABCMeta):
    def __init__(self,name):
        self.name = name
    @abstractmethod            #用这个装饰器来指定抽象方法
    def good_for_health(self):
        pass

   
>>>fruit = Fruit("水果")
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    fruit = Fruit("水果")
TypeError: Can't instantiate abstract class Fruit without an implementation for abstract method 'good_for_health'  
报错原因:如果一个类被定义为抽象基类,那么它首先不能被实例化的,抽象基类只能被继承使用,这是抽象基类的第一个特点。
  
>>>class Banana(Fruit):
    pass

>>>banana = Banana("香蕉")
Traceback (most recent call last):
  File "<pyshell#12>", line 1, in <module>
    banana = Banana("香蕉")
TypeError: Can't instantiate abstract class Banana without an implementation for abstract method 'good_for_health'
报错原因:虽然子类Banana继承了抽象基类Fruit,但是没有实现抽象基类里面的抽象方法,所以也是无法实例化的,正确的作法是:在子类中国重写一遍good_for_health(self)这么一个抽象方法,如果抽象基类中定义了抽象方法,那么子类中必须要实现它,不然就会出错。
>>>class Banana(Fruit):
    def good_for_health(self):
        print("只要吃香蕉,人就会变得开心~")

        
>>>banana = Banana("香蕉")
>>>banana.good_for_health()
只要吃香蕉,人就会变得开心~
查看官方文档:
abstract base class
Abstract base classes complement duck-typing by providing a way to defineinterfaces when other techniques like hasattr () would be clumsy or subtlywrong (for example with magic methods). ABCs introduce virtual subclasses,which are classes that don’t inherit from a class but are still recognizedby isinstance() and issubclass(); see the abc module documentation. Python comeswith many built-in ABCs for data structures (in the collections.abc module),numbers (in the numbers module),streams (in the io module), import findersand loaders (in the importlib.abc module). You can create your own ABCs withthe abc module.
官方文档说抽象基类是鸭子类型的一个补充,就是在讲多态时介绍过的,(参考第57课时多态和鸭子类型)如果一个鸟,走起路来像鸭子,叫声听起来也像鸭子,那么就可以认为它就是一只鸭子。
回顾一下,之前举过的例子(在编辑模式下):
class Cat:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def intro(self):
        print(f"我是一只沙猫咪,我叫{self.name},今年{self.age}岁~")
    def say(self):
        print("mua~")

class Dog:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def intro(self):
        print(f"我是一只小狗,我叫{self.name},今年{self.age}岁~")
    def say(self):
        print("呦吼~")

class Pig:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def intro(self):
        print(f"我是一只小猪,我叫{self.name},今年{self.age}岁~")
    def say(self):
        print("oink")

c = Cat("web",4)
d = Dog("布布",7)
p = Pig("大肠",5)
def animal(x):
    x.intro()
x.say()
这就是鸭子类型,因为就算我们添加一个自行车,其它乱七八糟的类,只要实现了intro()和say()这两个方法,它就可以被animal()函数所调用,
对于animal()函数来说,它只关注的是方法有没有实现,不关注你到底定义的是什么类,这就是鸭子类型,
所谓的鸭子类型,讲得就是一种编程风格,由于Python对多态的广泛支持,使得鸭子类型在Python中遍地开花。
如果一不小心手滑,把class Dog类中的say打成say_hi,run后,在互动模式下,我们
====================== RESTART: D:/谷兴武/创客/python学习/鸭子类型.py =====================
animal(d)
我是一只小狗,我叫布布,今年7岁~
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    animal(d)
  File "D:/谷兴武/创客/python学习/鸭子类型.py", line 33, in animal
    x.say()
AttributeError: 'Dog' object has no attribute 'say'
(后面就报错了)

可以看出,intro它还可以调用到了,因为say,一不小心手滑打成了say_hi,所以就报错了,那么这里作为鸭子类型的补充,我们就可以使用抽象基类,让这个bug提前暴露出来。
(编辑模式下:)
>>>from abc import ABCMeta,abstractmethod
>>>class Animal(metaclass = ABCMeta):
    def __init__(self,name,age):
        self.name = name
        self.age = age
    @abstractmethod
    def intro(self):
        pass
    @abstractmethod
    def say(self):
        pass

>>>class Cat(Animal):
    def intro(self):
        print(f"我是一只沙猫咪,我叫{self.name},今年{self.age}岁~")
    def say(self):
        print("mua~")

>>>class Dog(Animal):
    def intro(self):
        print(f"我是一只小狗,我叫{self.name},今年{self.age}岁~")
    def say_hi(self):
        print("呦吼~")

>>>class Pig(Animal):
    def intro(self):
        print(f"我是一只小猪,我叫{self.name},今年{self.age}岁~")
    def say(self):
        print("oink")

>>>c = Cat("web",4)
>>>d = Dog("布布",7)
>>>p = Pig("大肠",5)
>>>def animal(x):
    x.intro()
    x.say()
在编辑模式下运行一下代码,会发现直接就报错了。
=================== RESTART: D:/谷兴武/创客/python学习/鸭子类型和抽象基类.py ===================
Traceback (most recent call last):
  File "D:/谷兴武/创客/python学习/鸭子类型和抽象基类.py", line 31, in <module>
    d = Dog("布布",7)
TypeError: Can't instantiate abstract class Dog without an implementation for abstract method 'say'

由于抽象基类的限制,bug被提前触发了,我们连代码也没办法跑起来了,这就是使用抽象基类的一个优势。
抽象基类其实也是一种编程规范的实施手段。
如果Python没有对缩进有近乎于强迫症的要求,那还有人在乎代码的缩进吗,这种编程规范在个人开发中还没办法显示出优势,但是在团队协同开发中说它是举足轻重也不为过,利用抽象基类,我们就可以明确清晰地定义代码之间的接口,这样,大家协同开发起来就不容易出问题。

18827526543 发表于 13 分钟前 | 显示全部楼层
学到了,厉害
客服热线
400-1234-888 周一至周日:09:00 - 21:00
公司地址:襄阳市樊城区长虹路现代城5号楼188

创客帮MAKER.BAND青少年创客创意社区是一个融教育、科技、体育资讯为一体的综合服务平台,专注于教育创新、专注于科技体育、专注于教育资讯。

Powered by Discuz! X3.4 © 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表