Продолжая говорить о наследовании, давайте обсудим такие механизмы, как абстрактные и виртуальные классы и методы. Не каждому классу нужны свои экземпляры, иногда мы можем создать класс, просто чтобы описать некоторый общий интерфейс, которым будут пользоваться все его потомки. Кроме того, следуя принципу полиморфизма, мы можем общаться со всеми потомками этого класса, используя вот этот вот интерфейс. В таком случае, нам может захотеться явно запретить создание экземпляров этого класса. Тут и используется механизм абстрактных классов. При объявлении класса абстрактным мы как бы говорим системе, которая выполняет наш код: следи, чтобы экземпляров этого класса в программе не было. Можно пойти еще дальше: при составлении иерархии классов может возникнуть ситуация, когда мы хотим определить интерфейс некоторого класса, но реализацию этого интерфейса хотим оставить его потомкам. По аналогии с абстрактными классами, методы, которые при этом используются, мы назовем также абстрактными. Так как мы хотим оставить реализацию потомкам, мы можем не реализовывать абстрактные методы в родительском классе, но при этом мы должны внимательно следить, чтобы наследники действительно реализовали эти методы. Было бы идеально, если бы в нашу систему разработки был напрямую встроен механизм, позволяющий отслеживать наличие реализации. Рассмотрим другое понятие: виртуальные методы и классы. Это сложнее объяснить на примере Python, ведь в нем нет явного управления памятью, но давайте попробуем разобраться. Класс-наследник во многих объектно-ориентированных языках программирования является просто копией родительского класса с некоторыми дополнительными методами. При множественном наследовании это может приводить к весьма неочевидным проблемам. Например, в схеме ромба смерти, о котором говорилось в предыдущем видео, среда выполнения не будет знать, какую реализацию метода выбрать не только в том случае, когда эта реализация была переопределена в B и C, но и даже, когда она была определена только в D, ведь в B и C будут содержаться копии этого метода, каждую из которых среда выполнения будет считать отдельным методом. В Python такой проблемы, можно сказать, нет. Теперь коротко поговорим про виртуальные методы. Об этом тоже сложно говорить на примере языка Python, но общую концепцию понять надо. Принцип полиморфизма говорит, что программа может использовать наследников некоторого базового класса, даже не зная об этом. Для соблюдения этого самого принципа важно, чтобы мы могли выбирать, какую реализацию метода запускать, из какого класса ее брать, не на этапе компиляции, а на этапе выполнения кода. Ведь если мы определим выполнение методов на этапе компиляции, то, скорее всего, будет использована реализация, которая содержится в нашем базовом классе. Для решения этой задачи и используются виртуальные методы. Мы говорим, что в данном месте будет вызван некоторый метод, но какой именно метод мы хотим определить, только когда выполнение дойдет до этого места. В Python такая концепция не рассматривается, поскольку все методы в нем считаются виртуальными. Мы разобрались с абстрактными и виртуальными классами и методами. Давайте теперь поговорим про то, как абстрактные классы и методы можно реализовать с использованием языка программирования Python. Перейдем к коду. Давайте разберемся, как создавать абстрактные классы и методы с использованием Python. Для того, чтобы это сделать, импортируем абстрактный базовый класс и абстрактный метод из библиотеки "abc". "from abc import ABC, abstractmethod". Создадим класс А. Этот класс у нас должен будет являться абстрактным и мы в нем объявим абстрактный метод "do_something(self)". "@abstractmethod" является декоратором. "do_something(self)". Он будет что-нибудь печатать. Попробуем создать экземпляр этого класса. Мы предполагаем, что экземпляр создан не будет, но, тем не менее, он создан. Давайте для проверки попробуем вызвать этот самый метод. Он печатает: "Hi!". Это совсем не то, чего мы хотели. Мы хотели, чтобы нельзя было создать экземпляр такого класса, ведь в нем содержится абстрактный метод. Этот метод должен быть реализован не в самом классе, а в его потомках. Давайте, чтобы это сделать, и наследуем наш класс A от абстрактного базового класса ABC. Наследуем от ABC, определяем в нем абстрактный метод, сейчас точно так же. Попробуем создать экземпляр. Как мы видим, вылетает ошибка. Мы не можем создать экземпляр класса, в котором содержится абстрактный метод не реализованный. Реализуем этот самый метод в классе B. Он будет наследоваться от A. Давайте мы в нем сначала объявим какую-нибудь другую функцию, "do_something_else(self)", которая будет что-то печатать. Мы не реализовали метод "do_something(self)". Попробуем создать экземпляр: "b = B()". И нам вылетает та же ошибка, у нас абстрактный метод все еще не реализован. Давайте все-таки реализуем его: "def do_something(self):", "print("Hi2!")". Вот теперь у нас получается создать экземпляр. Давайте попробуем вызвать наш метод "do_something(self)", который мы реализовали. Как мы видим, он печатает то, что мы, в общем-то, в нем и написали. Попробуем теперь вызвать для того, чтобы проверить, что все работает, наш второй метод класса B. Он также работает. Мы научились реализовывать абстрактные базовые классы и методы с использованием языка программирования Python и библиотеки "abc".