今天在看开源代码库的时候,遇到了抽象基类的使用,之前缺乏了解,因此在此记录一下抽象基类的适用场景以及在python中的使用。
抽象类(abstract class)可以被视为其他类的模板。它可以创建一组必须在抽象类构建的任何子类中创建的方法。包含一个或多个抽象方法的类被称为抽象类。抽象方法是具有声明但没有实现的方法。使用抽象类的场景有两个:大型函数设计,为组件的不同实现提供公共接口。
抽象基类有时候,写代码需要检查使用的对象是否遵守特定规范,即验证对象是否实现给定的方法或属性。可以使用Hasattr或Isinstance方法来检查输入是否符合特定的标识。但有时使用这些方法来检查数量巨大的不同性质和方法是不方便的。为此,Python引入了一个名为抽象基类(Abstract base Class,ABC)的概念(from abc import ABC)。它可以为子类定义一个公共应用程序接口(API)。ABC通过基类装饰方法作为抽象,然后将具体的类注册为抽象基类的实现。
抽象基类的主要目标是提供一种标准化方法来测试对象是否遵守给定规范。它还可以防止任何尝试实例化不覆盖超类中的特定方法的子类。在抽象类中,使用关键字@abstractmethod装饰,将方法变为抽象方法。
为什么要用抽象类?比如有以下的代码:
class Lion:
def give_food(self):
print("Feeding a lion with raw meat!")
class Panda:
def feed_animal(self):
print("Feeding a panda with some tasty bamboo!")
class Snake:
def feed_snake(self):
print("Feeding a snake with mice!")
# Animals of our zoo:
leo = Lion()
po = Panda()
sam = Snake()
leo.give_food()
po.feed_animal()
sam.feed_snake()
本例中的代码是要实现给不同的动物喂养不同的食物的功能。可以发现,这种实现方式在动物数量增多时,会出现很多冗余的代码。现在的代码逻辑可以表示为:
为了减少代码冗余,更好维护代码,可以通过循环的方式实现流程化。假设想实现以下逻辑的代码:
# Put all the animals in a list: zoo = [leo, po, sam] # Could be many more animals there! # Loop through the animals and feed them for animal in zoo: animal.feed() # This will throw an AttributeError!
但是直接循环会出现问题,因为每个动物的类中都是不同的喂养函数实现的。为了解决这个问题可以通过抽象基类实现。
from abc import ABC, abstractmethod
# abc is a builtin module, we have to import ABC and abstractmethod
class Animal(ABC): # Inherit from ABC(Abstract base class)
@abstractmethod # Decorator to define an abstract method
def feed(self):
pass
class Lion(Animal):
def feed(self):
print("Feeding a lion with raw meat!")
class Panda(Animal):
def feed(self):
print("Feeding a panda with some tasty bamboo!")
class Snake(Animal):
def feed(self):
print("Feeding a snake with mice!")
zoo = [Lion(), Panda(), Snake()]
for animal in zoo:
animal.feed() # Now this won't throw an error!
这样就可以实现上述的代码逻辑,从而减少代码冗余,提高代码的可维护性。需要注意的是,在子类中继承抽象基类时,子类中重写的方法需要跟父类中的命名保持一致,否则会报错。以下是错误的写法:
class Panda(Animal): # If a class inherits from an ABC, it must implement all it's abstract methods!
def wrong_name(self): # The method's name must match the name of the ABC's method
print("Feeding a panda with some tasty bamboo!")
带有参数的抽象方法实现
from abc import ABC, abstractmethod
class Animal(ABC):
@property
def food_eaten(self):
return self._food
@food_eaten.setter
def food_eaten(self, food):
if food in self.diet:
self._food = food
else:
raise ValueError(f"You can't feed this animal with {food}.")
@property
@abstractmethod
def diet(self):
pass
@abstractmethod
def feed(self, time):
pass
class Lion(Animal):
@property
def diet(self):
return ["antelope", "cheetah", "buffaloe"]
def feed(self, time):
print(f"Feeding a lion with {self._food} meat! At {time}")
class Snake(Animal):
@property
def diet(self):
return ["frog", "rabbit"]
def feed(self, time):
print(f"Feeding a snake with {self._food} meat! At {time}")
leo = Lion()
leo.food_eaten = "antelope"
leo.feed("10:10 AM")
adam = Snake()
adam.food_eaten = "frog"
adam.feed("10:20 AM")
Feeding a lion with antelope meat! At 10:10 AM Feeding a snake with frog meat! At 10:10 AM
如果运行一下代码则会报错:
leo = Lion()
leo.food_eaten = "carrot"
leo.feed("10:10 AM")
You can't feed this animal with carrot.抽象基类中的具体方法
# Python program invoking a
# method using super()
import abc
from abc import ABC, abstractmethod
class R(ABC):
def rk(self):
print("Abstract base Class")
class K(R):
def rk(self):
super().rk()
print("subclass ")
# Driver code
r = K()
r.rk()
Abstract base Class subclass抽象属性
抽象类除了方法外还有属性,可以通过@abstractproperty定义具体类中的属性。
# Python program showing # abstract properties import abc from abc import ABC, abstractmethod class parent(ABC): @abc.abstractproperty def geeks(self): return "parent class" class child(parent): @property def geeks(self): return "child class" try: r =parent() print( r.geeks) except Exception as err: print (err) r = child() print (r.geeks)
Can't instantiate abstract class parent with abstract methods geeks child class
在上面的示例中,无法实例化基类,因为它只有属性获取方法的抽象版本。
抽象类实例化抽象类是不完整的,因为它们的方法仅仅是形式上的,没有具体的定义(函数体为pass)。如果Python允许为抽象类创建对象,调用抽象方法时使用该对象,但没有实际实现调用。因此,将抽象类作为模板,并根据需要,将其扩展并在使用它之前构建它。抽象类不是一个具体的类,它无法实例化。当为抽象类创建一个对象时,它会引发错误。
# Python program showing
# abstract class cannot
# be an instantiation
from abc import ABC,abstractmethod
class Animal(ABC):
@abstractmethod
def move(self):
pass
class Dog(Animal):
def move(self):
print("I can bark")
class Lion(Animal):
def move(self):
print("I can roar")
c=Animal()
TypeError Traceback (most recent call last)metaclass=abc.ABCmetain 21 print("I can roar") 22 ---> 23 c=Animal() TypeError: Can't instantiate abstract class Animal with abstract methods move
有些比较早的教程中会出现以下的实现方式,在最新的python实现中,使用上述的方式即可。
metaclass提供了一种称为寄存器方法的方法,可以通过其实例调用。通过使用该寄存器方法,任何抽象基类都可以成为任何任意具体的类的祖先。
import abc
class AbstractClass(metaclass=abc.ABCmeta):
def abstractfunc(self):
return None
print(AbstractClass.register(dict))
参考:
https://www.geeksforgeeks.org/abstract-classes-in-python/
https://www.geeksforgeeks.org/abstract-base-class-abc-in-python/
https://blog.teclado.com/python-abc-abstract-base-classes/



