协议和鸭子类型
Python 里有大量魔法方法。所谓基于协议编程,就是依赖这些魔法方法。
什么意思呢?
比如我们想实现一个对象,让它有序列的特征(可切片、可遍历等),只要实现 __len__ 和 __getitem__ 两个方法就行。任何类,只要用标准的签名和语义实现了这两个方法,就能用在任何期待序列的地方。是不是哪个类的子类,无关紧要,提供了所需的方法即可。
如下例:
classFrenchDeck:
def__init__(self, seq):
self._cards = list(seq)
def__len__(self):
returnlen(self._cards)
def__getitem__(self, position):
returnself._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
classMyAbstractClass(ABC):
@abstractmethod
defmy_abstract_method(self):
pass
在上面的代码中,MyAbstractClass 继承自 ABC 类,并且使用了 @abstractmethod 装饰器来声明一个抽象方法 my_abstract_method()。注意到在该函数内部并没有实现任何具体的逻辑,而是使用了 pass 语句进行占位符处理。这表明该方法必须在子类中被重写。
当我们定义完抽象基类之后,我们可以将其作为其他类的基类,使得子类必须实现抽象基类中定义的抽象方法,并且在运行时检查子类是否实现了这些方法。如果子类没有实现完整的抽象方法,那么在实例化时就会引发 TypeError 异常。
需要注意的是抽象类可能同时包含具体方法和抽象方法。
使用抽象基类
具体化抽象类可以有两种方式,一种通过注册(register),另外一种通过继承。
直接继承
当一个类继承了抽象基类,那么子类就必须实现抽象基类里的抽象方法,否则报错。
from abc import ABC, abstractmethod
classShape(ABC):
@abstractmethod
defarea(self):
pass
@abstractmethod
defperimeter(self):
pass
classSquare(Shape):
def__init__(self, side):
self.side = side
defarea(self):
returnself.side ** 2
defperimeter(self):
returnself.side * 4
s = Square(5)
print(s.area()) # 输出 25
print(s.perimeter()) # 输出 20
在上面的代码中,定义了一个抽象基类 Shape,并在其中定义了两个抽象方法 area 和 perimeter。然后,我们定义了一个 Square 类,继承自 Shape 类并实现了其中的两个抽象方法。
当我们创建 Square 类的对象时,它将拥有 Shape 类中定义的接口和行为。通过这种方式,我们可以强制要求子类定义特定的方法,并在需要时使用它们。
通过注册(register)
Python 的 abc 模块提供了一个 register 方法,可以用于将类注册为抽象基类的虚拟子类,即使它没有明确继承该类也可以被视为其子类。
下面是一个示例代码:
from abc import ABC, abstractmethod
classShape(ABC):
@abstractmethod
defarea(self):
pass
@abstractmethod
defperimeter(self):
pass
classSquare:
def__init__(self, side):
self.side = side
defarea(self):
returnself.side ** 2
defperimeter(self):
returnself.side * 4
Shape.register(Square)
s = Square(5)
print(isinstance(s, Shape)) # 输出 True
print(s.area()) # 输出 25
print(s.perimeter()) # 输出 20
在这个示例中,我们定义了一个 Shape 类作为抽象基类,并定义了两个抽象方法 area 和 perimeter。然后,我们定义了一个 Square 类,它没有明确继承自 Shape 类,但我们通过调用 Shape.register 方法将其注册为 Shape 类的虚拟子类。因此,即使 Square 类没有显式地继承 Shape 类,我们仍然可以将其视为 Shape 类的实例,并在其中调用 Shape 类中定义的方法。
在 Python 3.3 后,也可以通过装饰器的方式:
@Shape.register
classSquare:
def__init__(self, side):
self.side = side
defarea(self):
returnself.side ** 2
defperimeter(self):
returnself.side * 4
通过使用 register 方法,我们可以更灵活地定义抽象基类和子类之间的关系,并确保所有必需的方法都得到了正确实现。
__subclasshook__ 魔法方法
在 Python 中,__subclasshook__ 方法是一个特殊方法,它可以用来控制子类的继承关系。当一个类被调用了 issubclass() 函数时,Python 会自动搜索其基类中是否定义了 __subclasshook__ 方法。如果找到了,就会用该方法返回的布尔值来判断该类是否为子类。
几点说明:
该方法定义在抽象基类中
该方法必须定义为类方法
该方法有三个返回值
True:如果测试类被认为是子类
False:如果测试类不被认为是子类
NotImplemented:子类未实现执行方法
先看下 Python 里标准库自己实现的一个类 Sized,其源码如下(摘自 collections.abc):
from abc import ABC, abstractmethod
classSized(ABC):
__slots__ = ()
@abstractmethod
def__len__(self):
return0
@classmethod
def__subclasshook__(cls, C):
if cls is Sized:
ifhasattr(C, "__len__"):
returnTrue
returnNotImplemented
如上所示,我们看到了它实现了 __subclasshook__ 方法,检查测试类是否实现了 __len__ 方法。当然我们不必继承 Sized,而是使用虚拟子类(virtual subclass)技术,只实现 __len__ 协议,就可以隐式继承了 Sized。
下面我们仿造 Sized 自定义一个抽象基类:
import abc
classBase(abc.ABC):
@abc.abstractmethod
defmy_protocol(self):
"""自定义协议"""
@classmethod
def__subclasshook__(cls, subclass):
if cls is Base:
ifany("my_protocol"in B.__dict__ for B in subclass.__mro__):
returnTrue
returnNotImplemented
接下来我们定义一个子类,隐式继承 Base:
classMyClass:
defmy_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_registry)
如上所示,我们只需要实现 my_protocol 协议,就会隐式继承自抽象基类,这样就实现了虚拟子类的创建。