Python LanguageАбстрактные базовые классы (abc)

Установка метакласса ABCMeta

Абстрактные классы - это классы, которые должны быть унаследованы, но избегать реализации определенных методов, оставляя за собой только сигнатуры методов, которые должны реализовывать подклассы.

Абстрактные классы полезны для определения и обеспечения соблюдения абстракций класса на высоком уровне, аналогично понятию интерфейсов на типизированных языках, без необходимости реализации метода.

Один концептуальный подход к определению абстрактного класса состоит в том, чтобы заглушить методы класса, а затем поднять NotImplementedError при доступе. Это предотвращает доступ дочерних классов к родительским методам без переопределения их в первую очередь. Вот так:

class Fruit:
    
    def check_ripeness(self):
        raise NotImplementedError("check_ripeness method not implemented!")


class Apple(Fruit):
    pass


a = Apple()
a.check_ripeness() # raises NotImplementedError

Создание абстрактного класса таким образом предотвращает неправильное использование методов, которые не переопределяются, и, конечно же, поощряет методы, которые должны быть определены в дочерних классах, но не обеспечивает их определения. С помощью модуля abc мы можем предотвратить создание экземпляров дочерних классов, когда они не могут переопределить методы абстрактного класса своих родителей и предков:

from abc import ABCMeta

class AbstractClass(object):
    # the metaclass attribute must always be set as a class variable 
    __metaclass__ = ABCMeta

   # the abstractmethod decorator registers this method as undefined
   @abstractmethod 
   def virtual_method_subclasses_must_define(self):
       # Can be left completely blank, or a base implementation can be provided
       # Note that ordinarily a blank interpretation implicitly returns `None`, 
       # but by registering, this behaviour is no longer enforced.

Теперь можно просто подклассы и переопределить:

class Subclass(AbstractClass):
    def virtual_method_subclasses_must_define(self):
        return

Почему / Как использовать ABCMeta и @abstractmethod

Абстрактные базовые классы (ABC) обеспечивают, какие производные классы реализуют определенные методы из базового класса.

Чтобы понять, как это работает и почему мы должны его использовать, давайте рассмотрим пример, который понравится Ван Россу. Предположим, у нас есть базовый класс «MontyPython» с двумя методами (joke & punchline), которые должны быть реализованы всеми производными классами.

class MontyPython:
    def joke(self):
        raise NotImplementedError()

    def punchline(self):
        raise NotImplementedError()

class ArgumentClinic(MontyPython):
    def joke(self):
        return "Hahahahahah"

Когда мы создаем экземпляр объекта и называем его двумя методами, мы получим ошибку (как и ожидалось) с помощью метода punchline() .

 >>> sketch = ArgumentClinic() 
 >>> sketch.punchline() 
NotImplementedError 

Тем не менее, это все еще позволяет нам создать объект класса ArgumentClinic, не получив ошибку. На самом деле мы не получаем ошибку, пока не найдем punchline ().

Этого можно избежать, используя модуль абстрактного базового класса (ABC). Посмотрим, как это работает с тем же примером:

from abc import ABCMeta, abstractmethod

class MontyPython(metaclass=ABCMeta):
    @abstractmethod
    def joke(self):
        pass

@abstractmethod
def punchline(self):
    pass

class ArgumentClinic(MontyPython):
    def joke(self):
        return "Hahahahahah"

На этот раз, когда мы пытаемся создать экземпляр объекта из неполного класса, мы сразу получаем TypeError!

>>> c = ArgumentClinic()
TypeError:
"Can't instantiate abstract class ArgumentClinic with abstract methods punchline"

В этом случае легко завершить класс, чтобы избежать каких-либо TypeErrors:

class ArgumentClinic(MontyPython):
    def joke(self):
        return "Hahahahahah"

    def punchline(self):
        return "Send in the constable!"

На этот раз, когда вы создаете экземпляр объекта, он работает!