python中的抽象基类

协议和鸭子类型

python中有大量的魔法方法,python所谓基于协议编程,就是依赖这些魔法方法。

什么意思呢?

比如我们想要实现一个对象,让他可以有序列的特征(可切片、可遍历等特征),那么我们只要实现__len____getitem__ 两个方法即可。任何类, 要使用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。是不是哪个类的子类无关紧要,只要提供了所需的方法即可。

如下例:

class FrenchDeck:

    def __init__(self, seq):
        self._cards = list(seq)

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]


if __name__ == "__main__":
    obj = FrenchDeck([1, 2, 3, 4, 5])
    # 可切片
    print(obj[2])
    # 可遍历
    for i in obj:
        print(i)

FrenchDeck对象没有任何人告诉过它是一个序列,但是由于python基于协议来表现,它的表现却很像一个序列,那么就可以说FrenchDeck对象是一个序列。这就是鸭子类型。

可以通俗理解为:不管这个类是不是一个鸭子,只要它的表现像鸭子(有羽毛、会游泳,嘎嘎叫),那么就可以称之为鸭子。

抽象基类

前面也了解到python是基于协议的,引入抽象基类之前,Python 就已经非常成功了,即便现在也很少有代码使用抽象基类。

一般也不建议我们自己去定义抽象基类,抽象基类一般只会在框架中看到,这里只是为了学习知识。

抽象类有什么作用:抽象基类是一种特殊的类,它只用于作为其他类的基础(父类)。它定义了一些必须要实现的方法,而这些方法在子类中必须被重写,以便于子类能够正常工作。它提供了一种方式来定义和强制执行类必须实现的方法,一般自己不能被实例化。

定义抽象基类

其实python中并没有像java中有interface关键字可以来定义抽象基类。python中定义抽象基类一般是通过abc模块创建,它提供了ABC(Abstract Base Class)类来实现这个功能。要定义一个抽象基类,请从ABC类派生一个类,并使用@abstractmethod装饰器将方法标记为抽象方法。

示例如下:

from abc import ABC, abstractmethod

class MyAbstractClass(ABC):
    @abstractmethod
    def my_abstract_method(self):
        pass

在上面的代码中,MyAbstractClass继承自ABC类,并且使用了@abstractmethod装饰器来声明一个抽象方法my_abstract_method()。注意到在该函数内部并没有实现任何具体的逻辑,而是使用了pass语句进行占位符处理。这表明该方法必须在子类中被重写。

当我们定义完抽象基类之后,我们可以将其作为其他类的基类,使得子类必须实现抽象基类中定义的抽象方法,并且在运行时检查子类是否实现了这些方法。如果子类没有实现完整的抽象方法,那么在实例化时就会引发TypeError异常。

需要注意的是抽象类可能同时包含具体方法和抽象方法。

使用抽象基类

具体化抽象类可以有两种方式,一种通过注册(register),另外一种通过继承。

直接继承

当一个类继承了抽象基类,那么子类就必须实现抽象基类里的抽象方法,否则报错。

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
    
    @abstractmethod
    def perimeter(self):
        pass
    
class Square(Shape):
    def __init__(self, side):
        self.side = side
        
    def area(self):
        return self.side ** 2
    
    def perimeter(self):
        return self.side * 4

s = Square(5)
print(s.area()) # 输出 25
print(s.perimeter()) # 输出 20

在上面的代码中,定义了一个抽象基类Shape,并在其中定义了两个抽象方法areaperimeter。然后,我们定义了一个Square类,继承自Shape类并实现了其中的两个抽象方法。

当我们创建Square类的对象时,它将拥有Shape类中定义的接口和行为。通过这种方式,我们可以强制要求子类定义特定的方法,并在需要时使用它们。

通过注册(register)

Python的abc模块提供了一个register方法,可以用于将类注册为抽象基类的虚拟子类,即使它没有明确继承该类也可以被视为其子类。

下面是一个示例代码:

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
    
    @abstractmethod
    def perimeter(self):
        pass

class Square:
    def __init__(self, side):
        self.side = side
        
    def area(self):
        return self.side ** 2
    
    def perimeter(self):
        return self.side * 4
    
Shape.register(Square)

s = Square(5)
print(isinstance(s, Shape)) # 输出 True
print(s.area()) # 输出 25
print(s.perimeter()) # 输出 20

在这个示例中,我们定义了一个Shape类作为抽象基类,并定义了两个抽象方法areaperimeter。然后,我们定义了一个Square类,它没有明确继承自Shape类,但我们通过调用Shape.register方法将其注册为Shape类的虚拟子类。因此,即使Square类没有显式地继承Shape类,我们仍然可以将其视为Shape类的实例,并在其中调用Shape类中定义的方法。

在python3.3后,也可以通过装饰器的方式:

@Shape.register
class Square:
	...

通过使用register方法,我们可以更灵活地定义抽象基类和子类之间的关系,并确保所有必需的方法都得到了正确实现。

__subclasshook__魔法方法

在 Python 中,__subclasshook__方法是一个特殊方法,它可以用来控制子类的继承关系。当一个类被调用了 issubclass() 函数时,Python 会自动搜索其基类中是否定义了 subclasshook 方法。如果找到了,就会用该方法返回的布尔值来判断该类是否为子类。

几点说明:

  • 该方法定义在抽象基类中
  • 该方法必须定义为类方法
  • 该方法有三个返回值
    True: 如果测试类被认为是子类
    False: 如果测试类不被认为是子类
    NotImplemented: 子类未实现执行方法

先看下python里自己实现的一个类Sized,其源码如下:

class Sized(metaclass=ABCMeta):
    __slots__ = ()
    @abstractmethod
    def __len__(self):
        return 0
    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            return _check_methods(C, "__len__")
        return NotImplemented

如上所示,我们看到了它实现了__subclasshook__方法,检查子类和子类的mro上所有的类是否有__len__方法,如果没有,返回NotImplemented。当然我们不必继承Sized,而是使用虚拟子类(virtual subclass)技术,只实现__len__协议,就可以隐式继承了Sized

下面我们仿造Sized自定义一个抽象基类

import abc
class Base(abc.ABC):
    @abc.abstractmethod
    def my_protocol(self):
        """自定义协议"""
    @classmethod
    def __subclasshook__(cls, subclass):
        if cls is Base:
            if any("my_protocol" in B.__dict__ for B in subclass.__mro__):
                return True
        return NotImplemented

接下来我们定义一个子类,隐式继承Base

class MyClass:
    def my_protocol(self):
        pass


if __name__ == '__main__':
    obj = MyClass()
    # isinstance查询示例obj是否是Base的实例
    print(isinstance(obj, Base))  # True
    # issubclass查询MyClass是否是Base的子类
    print(issubclass(MyClass, Base))  # True
    # 查询Base类的虚拟子类
    print(Base._abc_impl)

如上所示,我们只需要实现my_protocol协议,就会隐式继承自抽象基类,这样就实现了虚拟子类的创建。

参考:

https://www.python51.com/jc/17075.html

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ethan-running

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值