Python中的抽象基类

1.说在前头#

"抽象基类"这个词可能听着比较"深奥",其实"基类"就是"父类","抽象"就是"假"的意思,

"抽象基类"就是"假父类."

2.对之前元类的一点补充#

之前说过通过元类实例化类的语法是

Copy
变量名 = type("类名", ("继承的类",), {"属性名":"属性值"})

现在介绍另一种方法

Copy
class 类名(metaclass=元类名): ...

举个例子:

Copy
#!/usr/bin/python3 # -*- coding: utf-8 -*- # __author__: kainhuck # 定义一个元类 class MyMetaClass(type): def __new__(cls, name, bases, attrs): ''' :param name: 类名 :param bases: 继承的基类 :param attrs: 拥有的属性 要求必须含有name属性 ''' name = attrs.get("name", None) if name and not callable(name): return type.__new__(cls, name, bases, attrs) raise NotImplementedError("必须含有name属性") # 定义普通类 # class MyClassA(metaclass=MyMetaClass): # 报错,没有定义name属性 # pass # 定义普通类 # class MyClassB(metaclass=MyMetaClass): # 报错,没有定义name属性 # def name(self): # pass # # class MyClassC(metaclass=MyMetaClass): # 报错,没有定义name属性 # def __init__(self): # self.name = "kainhuck" # class MyClassD(metaclass=MyMetaClass): # 没有报错 name = "kainhuck"

3.鸭子类型#

鸭子类型:如果一个东西看起来想一个鸭子,叫起来像一个鸭子,那么它大概就是一只鸭子.

在Python中有些时候我们需要一个有某个功能(比如说:鸭子叫)的对象,那我们可以通过判断这个对象是不是一只鸭子来检测是否满足我们的需求;但仔细想想这有些缺陷,因为我们真正需要的是鸭子叫这个方法,一个对象无论是不是鸭子只要他会像鸭子一样叫就可以啦.

这话可能有些绕,让我来举一个例子

有这么一个函数:

Copy
def func(something): print(something[0])

这个函数会打印出参数的第一个元素,其实这里隐含着一个条件--参数支持下标索引.为了使代码完善我们应该对该函数做点修改.

方案一.

Copy
def func(something): if isinstance(something, (list, tuple, set)): # 这些方法支持下标索引 print(something[0]) else: print("Error")

方案一的缺点:

这样写就默认把something的类型限定了,拓展性很差

我们知道只要自定义的类实现了__getitem__方法就可以使其支持下标索引.

方案二.

Copy
def func(something): if hasattr(something, '__getitem__'): print(something[0]) else: print("Error")

方案二的缺点:

并不是所有实现__getitem__的方法都可以支持下标索引,比如字典类型

这样似乎没有解决方案了..其实抽象基类就完美的解决了这问题

4.抽象基类#

1. 抽象基类的定义:#

由abc.ABCMeta这个元类实现的类就是抽象基类,如

Copy
class AbstractClass(metaclass=abc.ABCMeta): pass

2. register方法#

定义好的抽象基类通过register方法可以成为别的类的父类

举个例子:

Copy
import abc # 定义一个抽象基类 class AbstractClass(metaclass=abc.ABCMeta): pass # 定义一个普通类继承自object class MyClass(object): pass # 把我们定义的抽象基类注册为MyClass的父类 AbstractClass.register(MyClass) mc = MyClass() print(issubclass(MyClass, AbstractClass)) # 输出True print(isinstance(mc, AbstractClass)) # 输出True # 将我们定义的抽象基类注册到系统定义的类 AbstractClass.register(list) print(isinstance([], AbstractClass)) # 输出True

说明一点:抽象基类虽然可以成为别的类的父类,但是别的类并不会继承抽象基类的方法和属性

3. 对前面例子的方案三实现#

方案三.

Copy
from abc import ABCMeta class MySequence(metaclass=ABCMeta): pass MySequence.register(list) # 注册为列表的父类 MySequence.register(tuple) # 注册为元组的父类 ''' 也可以自定义一个类,将MySequence注册为其父类 '''
Copy
def func(something): if isinstance(something, AbstractClass): # AbstractClass的子类 print(something[0]) else: print("Error")

4.__subclasshook__魔法方法#

看过上面的例子你们肯定会觉得,给每个类都注册一遍抽象基类太麻烦了,没错Python的开发者也这么觉得,于是__subclasshook__这个方法出现了

几点说明:

  1. 该方法定义在抽象基类中
  2. 该方法必须定义为类方法
  3. 该方法有三个返回值
    1. True: 如果测试类被认为是子类
    2. False: 如果测试类不被认为是子类
    3. NotImplemented: 这个后面讲

定义一个抽象基类:

Copy
import abc class AbstractDuck(metaclass=abc.ABCMeta): @classmethod def __subclasshook__(cls, subclass): quack = getattr(subclass, 'quack', None) # 取出subclass的 quack 属性,如果不存在则返回 None return callable(quack) # 返回quack是否可以调用(是否是个方法)

定义两个测试类

Copy
class Duck(object): def quack(self): pass class NotDuck(object): quack = "foo"

判断是否是AbstractDuck的子类

Copy
print(issubclass(Duck, AbstractDuck)) # 输出 True print(issubclass(NotDuck, AbstractDuck)) # 输出 False

注意:__subclasshook__方法的优先级大于register

举个例子解释NotImplemented返回值

Copy
In [6]: import abc In [7]: class AbstractDuck(metaclass=abc.ABCMeta): ...: @classmethod ...: def __subclasshook__(cls, subclass): ...: quack = getattr(subclass, 'quack', None) # 取出subclass的 quack 属性,如果不存在则返回 None ...: if callable(quack): ...: return True ...: return NotImplemented ...: In [8]: class Duck(object): ...: def quack(self): ...: pass ...: In [9]: class NotDuck(object): ...: quack = "foo" ...: In [10]: issubclass(NotDuck, AbstractDuck) Out[10]: False In [11]: AbstractDuck.register(NotDuck) Out[11]: __main__.NotDuck In [12]: issubclass(NotDuck, AbstractDuck) Out[12]: True
posted @   KainHuck  阅读(4969)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示
CONTENTS