Знание того, как создать абстрактный класс в Python, является обязательным для разработчиков Python. В этом руководстве вы узнаете, как определять и использовать абстрактные классы.
Но прежде всего, что такое абстрактный класс?
Абстрактный класс — это шаблон, который обеспечивает общий интерфейс и заставляет классы, которые наследуют его, реализовывать набор методов и свойств. Модуль Python abc предоставляет функциональные возможности для определения и использования абстрактных классов.
Абстрактные классы предоставляют нам стандартный способ разработки кода, даже если над проектом работает несколько разработчиков.
Давайте узнаем, как использовать их в ваших программах!
Простой пример абстрактного класса в Python
Абстрактные классы взяты из PEP 3119. PEP означает Python Enhancement Proposal и представляет собой тип проектного документа, используемого для объяснения новых функций сообществу Python.
Прежде чем начать, я хочу отметить, что в этом уроке я буду использовать следующую версию Python 3:
$ python --version
Python 3.7.4
Причина, по которой я это подчеркиваю, заключается в том, что способ определения абстрактных классов может меняться в зависимости от используемой версии Python. Это относится, например, к Python 2, но также и к более ранним версиям Python 3 (ниже 3.4).
Для начала давайте определим простой класс под названием Aircraft:
class Aircraft:
def fly(self):
pass
Я могу создать экземпляр этого класса и вызвать для него метод fly без каких-либо ошибок:
aircraft1 = Aircraft()
aircraft1.fly()
Теперь предположим, что я хочу преобразовать этот класс в абстрактный класс, поскольку я хочу использовать его как общий интерфейс для классов, представляющих различные типы самолетов.
Вот как мы можем это сделать…
- Импортируйте ABC и abstractmethod из модуля Python abc.
- Выведем наш класс «Самолет» из ABC.
- Добавьте декоратор @abstractmethod к методу fly.
from abc import ABC, abstractmethod
class Aircraft(ABC):
@abstractmethod
def fly(self):
pass
Вы можете сделать метод абстрактным в Python, добавив к нему декоратор @abstractmethod.
Теперь при создании экземпляра Aircraft мы видим следующую ошибку:
aircraft1 = Aircraft()
Traceback (most recent call last):
File "aircraft.py", line 10, in <module>
aircraft1 = Aircraft()
TypeError: Can't instantiate abstract class Aircraft with abstract methods fly
Как видите, я не могу создать экземпляр нашего абстрактного класса.
Абстрактный класс — это класс, содержащий один или несколько абстрактных методов. Абстрактные классы не могут быть созданы.
Это означает, что мы не можем создать объект из абстрактного класса…
Итак, как мы можем их использовать?
Абстрактные классы могут использоваться только посредством наследования, а их конкретные дочерние классы должны предоставлять реализацию для всех абстрактных методов.
В следующем разделе вы поймете, что я имею в виду, говоря о бетоне.
Наследование абстрактного класса Python
Давайте посмотрим, что произойдет, если вместо создания экземпляра нашего абстрактного класса мы создадим дочерний класс, производный от него.
from abc import ABC, abstractmethod
class Aircraft(ABC):
@abstractmethod
def fly(self):
pass
class Jet(Aircraft):
pass
Давайте попробуем создать экземпляр Jet:
jet1 = Jet()
Traceback (most recent call last):
File "aircraft.py", line 13, in <module>
jet1 = Jet()
TypeError: Can't instantiate abstract class Jet with abstract methods fly
Хм… ошибка, похожая на ту, что мы видели раньше, с той разницей, что теперь она относится к классу Jet.
Почему?
Это связано с тем, что для создания объекта типа Jet нам необходимо обеспечить реализацию абстрактного метода fly() в этом классе.
Давайте попробуем:
class Jet(Aircraft):
def fly(self):
print("My jet is flying")
На этот раз я могу создать экземпляр типа Jet и выполнить метод fly:
jet1 = Jet()
jet1.fly()
$ python aircraft.py
My jet is flying
Класс, который наследует абстрактный класс и реализует все его абстрактные методы, называется конкретным классом. В конкретном классе все методы имеют реализацию, тогда как в абстрактном классе некоторые или все методы являются абстрактными.
Теперь я добавлю еще один абстрактный метод с именем land() к нашему абстрактному классу Aircraft, а затем попытаюсь снова создать экземпляр Jet:
class Aircraft(ABC):
@abstractmethod
def fly(self):
pass
@abstractmethod
def land(self):
pass
Вот что происходит, когда я создаю экземпляр класса Jet, реализация которого не изменилась:
jet1 = Jet()
Traceback (most recent call last):
File "aircraft.py", line 18, in <module>
jet1 = Jet()
TypeError: Can't instantiate abstract class Jet with abstract methods land
Причина ошибки в том, что мы не предоставили конкретную реализацию для метода land в подклассе Jet. Это показывает, что для создания экземпляра класса, производного от абстрактного класса, мы должны предоставить реализацию для всех абстрактных методов, унаследованных от родительского абстрактного класса.
Дочерний класс абстрактного класса может быть создан только в том случае, если он переопределяет все абстрактные методы родительского класса.
Термин override в наследовании Python указывает на то, что дочерний класс реализует метод с тем же именем, что и метод, реализованный в его родительском классе. Это базовая концепция в объектно-ориентированном программировании.
Имеет ли это смысл?
Итак, реализуем метод land() в классе Jet:
class Jet(Aircraft):
def fly(self):
print("My jet is flying")
def land(self):
print("My jet has landed")
Я запущу оба метода в классе Jet, чтобы убедиться, что все работает так, как и ожидалось:
jet1 = Jet()
jet1.fly()
jet1.land()
$ python aircraft.py
My jet is flying
My jet has landed
Все хорошо!
Использование Super для вызова метода из абстрактного класса
Абстрактный метод в Python не обязательно должен быть полностью пустым.
Он может содержать реализации, которые могут быть повторно использованы дочерними классами путем вызова абстрактного метода с помощью super(). Это не исключает того факта, что дочерние классы все равно должны реализовывать абстрактный метод.
Вот пример…
Мы внесем следующие изменения:
- Добавьте оператор печати к методу land абстрактного класса Aircraft.
- Вызовите метод land абстрактного класса из дочернего класса Jet перед выводом сообщения «Мой самолет приземлился».
from abc import ABC, abstractmethod
class Aircraft(ABC):
@abstractmethod
def fly(self):
pass
@abstractmethod
def land(self):
print("All checks completed")
class Jet(Aircraft):
def fly(self):
print("My jet is flying")
def land(self):
super().land()
print("My jet has landed")
Вот что получилось:
jet1 = Jet()
jet1.land()
$ python aircraft.py
All checks completed
My jet has landed
Из выходных данных видно, что экземпляр jet1 конкретного класса Jet сначала вызывает метод land абстрактного класса с помощью super(), а затем выводит собственное сообщение.
Это может быть удобно, чтобы избежать повторения одного и того же кода во всех дочерних классах нашего абстрактного класса.
Как реализовать абстрактное свойство в Python
Точно так же, как мы определили абстрактные методы, мы можем определить абстрактные свойства в нашем абстрактном классе.
Давайте добавим атрибут под названием speed к нашему абстрактному базовому классу Aircraft, а также методы свойств для чтения и изменения его значения.
В Python можно определить методы свойств для чтения и изменения значения скорости следующим образом:
class MyClass:
...
...
@property
def speed(self):
return self.__speed
@speed.setter
def speed(self, value):
self.__speed = value
Метод с декоратором @property используется для получения значения скорости (геттер). Метод с декоратором @speed.setter позволяет обновить значение скорости (сеттер).
В этом случае мы хотим, чтобы эти два метода были абстрактными, чтобы обеспечить их реализацию в каждом подклассе Aircraft. Кроме того, учитывая, что они абстрактны, мы не хотим иметь для них никакой реализации…
…мы будем использовать оператор pass.
Мы также добавляем абстрактный конструктор, который может вызываться его подклассами.
Честно говоря, у меня возникло искушение удалить этот конструктор, учитывая, что абстрактный класс не должен создаваться.
В то же время конструктор может работать как руководство для подклассов, которые должны будут его реализовать. Я пока его сохраню.
Итак, класс «Самолеты» выглядит так:
class Aircraft(ABC):
@abstractmethod
def __init__(self, speed):
self.__speed = speed
@property
@abstractmethod
def speed(self):
pass
@speed.setter
@abstractmethod
def speed(self, value):
pass
@abstractmethod
def fly(self):
pass
@abstractmethod
def land(self):
print("All checks completed")
Как видите, я добавил декоратор @abstractmethod к методам свойств.
Примечание: важно указать декоратор @abstractmethod после декораторов @property и @speed.setter. Если я этого не сделаю, то получу следующую ошибку:
$ python aircraft.py
Traceback (most recent call last):
File "aircraft.py", line 3, in <module>
class Aircraft(ABC):
File "aircraft.py", line 10, in Aircraft
@property
File "/Users/codefathertech/opt/anaconda3/lib/python3.7/abc.py", line 23, in abstractmethod
funcobj.__isabstractmethod__ = True
AttributeError: attribute '__isabstractmethod__' of 'property' objects is not writable
Давайте также переопределим конструктор в классе Jet:
class Jet(Aircraft):
def __init__(self, speed):
self.__speed = speed
def fly(self):
print("My jet is flying")
Мне любопытно посмотреть, что произойдет, если мы попытаемся создать экземпляр класса Jet после добавления абстрактных методов свойств к его родительскому абстрактному классу.
Обратите внимание, что я передаю скорость при создании экземпляра Jet, учитывая, что мы только что добавили к нему конструктор, который принимает скорость в качестве аргумента:
jet1 = Jet(900)
$ python aircraft.py
Traceback (most recent call last):
File "aircraft.py", line 45, in <module>
jet1 = Jet(900)
TypeError: Can't instantiate abstract class Jet with abstract methods speed
Сообщение об ошибке говорит нам, что нам необходимо реализовать методы свойств в конкретном классе Jet, если мы хотим создать его экземпляр.
Давай сделаем это!
class Jet(Aircraft):
def __init__(self, speed):
self.__speed = speed
@property
def speed(self):
return self.__speed
@speed.setter
def speed(self, value):
self.__speed = value
def fly(self):
print("My jet is flying")
def land(self):
super().land()
print("My jet has landed")
Вот результат, когда мы используем конкретные методы свойств в классе Jet для чтения и обновления значения скорости:
jet1 = Jet(900)
print(jet1.speed)
jet1.speed = 950
print(jet1.speed)
$ python aircraft.py
900
950
Код работает отлично!
Python также предоставляет декоратор, который называется @abstractproperty. Он нам нужен?
Согласно официальной документации Python, этот декоратор устарел с версии 3.3, и вы можете придерживаться того, что мы видели до сих пор.
Как определить абстрактные классы в более ранних версиях Python
В этом уроке мы сосредоточились на Python 3.4 или выше, когда дело доходит до определения абстрактных классов. В то же время я хочу показать вам, как можно определить абстрактный класс с Python 3.0+ и с Python 2.
Я также включу в примеры Python 3.4+, чтобы вы могли быстро сравнить его с двумя другими способами определения абстрактного класса.
Во-первых, давайте вспомним реализацию, которую мы использовали в этом уроке, с использованием Python 3.4+.
Для простоты я покажу:
- Импорт из модуля Python abc.
- Определение абстрактного класса.
- Определение абстрактного метода fly().
from abc import ABC, abstractmethod
class Aircraft(ABC):
@abstractmethod
def fly(self):
pass
Вот тот же код для Python 3.0+:
from abc import ABCMeta, abstractmethod
class Aircraft(metaclass=ABCMeta):
@abstractmethod
def fly(self):
pass
ABCMeta — это метакласс, позволяющий определять абстрактные базовые классы. Метакласс — это класс, позволяющий создавать другие классы.
Мне любопытно узнать больше об этом метаклассе. Давайте посмотрим на его MRO (Method Resolution Order):
from abc import ABCMeta
print(ABCMeta.__mro__)
(<class 'abc.ABCMeta'>, <class 'type'>, <class 'object'>)
Как вы видите, оно происходит от слова «type».
А теперь перейдем к Python 2. Я использую версию 2.7.14:
from abc import ABCMeta, abstractmethod
class Aircraft:
__metaclass__ = ABCMeta
@abstractmethod
def fly(self):
pass
Это поможет вам определить абстрактные классы с помощью разных версий Python!
Вызов NotImplementedError вместо использования декоратора abstractmethod
Прежде чем завершить этот урок, я хочу показать вам подход, который можно использовать для получения аналогичного поведения в абстрактном классе, предоставляемом декоратором abstractmethod.
Давайте возьмем подмножество нашего абстрактного класса Aircraft и удалим декоратор @abstractmethod из метода fly():
from abc import ABC, abstractmethod
class Aircraft(ABC):
def fly(self):
pass
В том виде, как выглядит наш код сейчас, мы можем создать экземпляр нашего абстрактного класса. Когда я выполняю следующее, я не вижу никаких ошибок:
aircraft1 = Aircraft()
Очевидно, это не то, что нам нужно, учитывая, что абстрактный класс не предназначен для создания экземпляров.
Что мы можем сделать, чтобы предотвратить создание объектов типа «Самолет»?
Мы могли бы реализовать конструктор, который вызывает исключение при вызове.
from abc import ABC, abstractmethod
class Aircraft(ABC):
def __init__(self):
raise TypeError("TypeError: This is an Abstract Class and it cannot be instantiated")
def fly(self):
pass
Давайте попробуем создать экземпляр сейчас:
aircraft1 = Aircraft()
Traceback (most recent call last):
File "abstract_test.py", line 12, in <module>
aircraft1 = Aircraft()
File "abstract_test.py", line 6, in __init__
raise TypeError("TypeError: This is an Abstract Class and it cannot be instantiated")
TypeError: TypeError: This is an Abstract Class and it cannot be instantiated
Он не позволил мне создать экземпляр. Это хорошо!
Очевидно, что наиболее правильным вариантом будет использование декоратора @abstractmethod. На этом примере мы просто хотим научиться придумывать разные подходы в наших программах.
Давайте посмотрим, что произойдет, если наш подкласс Jet унаследует эту версию класса Aircraft. На данный момент у меня нет конструктора в классе Jet.
class Jet(Aircraft):
def fly(self):
print("My jet is flying")
Когда я создаю экземпляр Jet, происходит вот что:
jet1 = Jet()
Traceback (most recent call last):
File "abstract_test.py", line 20, in <module>
jet1 = Jet()
File "abstract_test.py", line 6, in __init__
raise TypeError("TypeError: This is an Abstract Class and it cannot be instantiated")
TypeError: TypeError: This is an Abstract Class and it cannot be instantiated
Та же ошибка, что и раньше, но на этот раз она не имеет особого смысла. Сообщение об ошибке говорит, что это абстрактный класс, и я не могу создать его экземпляр.
Давайте посмотрим, сможем ли мы улучшить это исключение и сообщение об ошибке…
Вместо этого мы могли бы вызвать NotImplementedError, чтобы четко показать, что методы в абстрактном классе должны быть реализованы в его подклассах.
from abc import ABC, abstractmethod
class Aircraft(ABC):
def __init__(self):
raise NotImplementedError("NotImplementedError: Implementation required for __init__() method")
def fly(self):
raise NotImplementedError("NotImplementedError: Implementation required for fly() method")
class Jet(Aircraft):
def fly(self):
print("My jet is flying")
Теперь давайте создадим экземпляр Jet:
jet1 = Jet()
Traceback (most recent call last):
File "new_abstract.py", line 17, in <module>
jet1 = Jet()
File "new_abstract.py", line 6, in __init__
raise NotImplementedError("NotImplementedError: Implementation required for __init__() method")
NotImplementedError: NotImplementedError: Implementation required for __init__() method
Теперь послание стало яснее…
…давайте определим конструктор в подклассе Jet:
class Jet(Aircraft):
def __init__(self, speed):
self.__speed = speed
def fly(self):
print("My jet is flying at a speed of {} km/h".format(self.__speed))
Теперь мы можем создать экземпляр Jet и выполнить метод fly:
jet1 = Jet(900)
jet1.fly()
My jet is flying at a speed of 900 km/h
На этот раз все хорошо!
Заключение
Вам понравился этот урок? Мы прошли довольно много!
Мы начали с объяснения концепции абстрактных классов, которые представляют собой общий интерфейс для создания классов, которые следуют четко определенным критериям. Имя модуля Python, который мы использовали во всех примерах, — ABC (Abstract Base Classes).
Абстрактные классы не могут быть созданы и предназначены для расширения конкретными классами, которые должны обеспечить реализацию всех абстрактных методов в своем родительском классе.
Вы также узнали, что декоратор @abstractmethod позволяет вам определять, какие методы являются абстрактными. Он также применяется к методам свойств для обеспечения реализации геттеров и сеттеров.
Наконец, мы увидели, как определять абстрактные классы с помощью различных версий Python и как мы можем «моделировать» поведение декоратора @abstractmethod, вызывая исключения в методах нашего абстрактного класса.
Вы новичок в абстрактных классах или уже использовали их раньше?
Дайте мне знать в комментариях ниже!
Еще одной важной концепцией объектно-ориентированного программирования в Python является наследование (в этом уроке мы также рассмотрели некоторые концепции наследования).