Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs
Стандартная
библиотека Python содержит весьма
полезный модуль abc (Abstract Base Classes), с помощью которого очень
удобно описывать абстрактные методы. Об абстрактных методах мы с вами уже говорили
в курсе по ООП языка Python. Но для целостности восприятия,
повторим их еще раз.
Рассмотрим
классический пример. Пусть у нас имеется несколько классов геометрических
фигур: Line, Rect, Triangle. Каждый из них
имеет метод с одним и тем же именем draw.
Тогда, имея
набор из объектов этих классов:
figures = [Line(), Rect(), Triangle(), Line(), Rect()]
мы с помощью
единого интерфейса (метода) draw() можем отобразить их на экране:
for f in figures:
f.draw()
Но при такой
реализации классов сразу возникает один тонкий момент. Программист должен знать
и помнить, что в классах геометрических фигур обязательно должен присутствовать
метод draw() для ее
рисования. Эта проблема особенно актуальна при разработке больших проектов,
когда зачастую программа реализуется группой разработчиков, и все должны
согласованно взаимодействовать друг с другом. В этих условиях очень просто
прописать вместо метода draw() какой-то другой, или вовсе забыть его
реализовать внутри класса. Один из способов решения этой проблемы, унаследовать
все классы геометрических фигур от некоторого базового класса, в котором будет
определен метод draw(), как абстрактный метод. Особенность абстрактного
метода в том, что его необходимо переопределять в дочерних классах, иначе
возникнет ошибка при создании объекта класса. Соответственно, программист сразу
заметит, что было сделано неверно, и сможет оперативно подправить код программы.
Язык Python так устроен, что
в нем нельзя объявлять по настоящему абстрактные методы, как это делается в
некоторых других ЯП, например, в C++ или Java. Но можно
имитировать абстракцию. Ранее мы с вами делали это следующим образом. Создавали
базовый класс, например, с именем Geom, и в нем метод draw():
class Geom:
def draw(self):
raise NotImplementedError("В дочернем классе должен быть переопределен метод draw(self)")
Тогда, наследуя дочерние классы фигур от
базового класса Geom:
class Line(Geom):
def draw(self):
print("Line")
class Rect(Geom):
def draw(self):
print("Rect")
class Triangle(Geom):
def draw(self):
print("Triangle")
мы вынуждены для
корректной их работы переопределять метод draw(). Иначе, если
этого не сделать, то при обращении к этому методу:
figures = [Line(), Rect(), Triangle(), Line(), Rect()]
for f in figures:
f.draw()
возникнет
исключение (ошибка) с сообщением:
NotImplementedError: В дочернем
классе должен быть переопределен метод draw(self)
Но эту ошибку мы
увидим не на этапе создания объектов, а на этапе вызова метода draw() для
соответствующего объекта. Такую ошибку можно не сразу отследить, а в более
сложных случаях даже пропустить. Поэтому гораздо лучшим вариантом будет
использование модуля abc для определения
абстрактных методов.
Вначале следует
импортировать этот модуль, например, командой:
В этом модуле
имеется класс-обертка ABC, который позволяет имитировать
объявление абстрактных методов с помощью декоратора abstractmethod:
class Geom(abc.ABC):
@abc.abstractmethod
def draw(self):
pass
Теперь, если в
каком-либо дочернем классе не переопределять абстрактный метод draw(), например, в
классе Line:
class Line(Geom):
pass
# def draw_line(self):
# print("Line")
то при создании
объекта этого класса возникнет ошибка:
TypeError:
Can't instantiate abstract class Line with abstract method draw
Также нельзя создавать
отдельно и объекты класса Geom, так как он содержит абстрактный метод:
g = Geom() # TypeError: Can't instantiate abstract class Geom with abstract method draw
Если бы метод draw() не был
абстрактным, тогда никаких ошибок не возникало бы:
class Geom(abc.ABC):
def draw(self):
pass
g = Geom() # ok
Спрашивается,
зачем тогда наследоваться от класса ABC? Дело в том, что декоратор abstractmethod и другие
подобные работают только внутри классов, унаследованных от ABC. Если это
наследование убрать:
class Geom:
@abc.abstractmethod
def draw(self):
pass
то никакого
эффекта от декоратора abstractmethod не будет. Он
работает только в сочетании с базовым классом ABC.
Конечно, класс Geom может содержать
не только абстрактные методы, но и любые другие конструкции, допустимые внутри
классов, в том числе и обычные переменные и методы. Например:
class Geom(abc.ABC):
def __init__(self, color=0, width=1):
self.color = color
self.width = width
@abc.abstractmethod
def draw(self):
pass
def get_data(self):
return self.color, self.width
Имеем один
абстрактный метод draw(), обычный метод get_data() и стандартный
инициализатор __init__().
Абстрактными можно
также делать и методы уровня класса и статические методы. Для этого следует
последовательно прописать по два декоратора, например:
class Geom(abc.ABC):
def __init__(self, color=0, width=1):
self.color = color
self.width = width
@abc.abstractmethod
def draw(self):
pass
@classmethod
@abc.abstractmethod
def get_limits(cls):
pass
@staticmethod
@abc.abstractmethod
def get_info():
pass
def get_data(self):
return self.color, self.width
Соответственно,
в дочерних классах все абстрактные методы должны быть переопределены. Например:
class Line(Geom):
MIN_COORD = 0
MAX_COORD = 100
def draw(self):
print("Line")
@classmethod
def get_limits(cls):
return cls.MIN_COORD, cls.MAX_COORD
@staticmethod
def get_info():
return "Class Line"
Использование
абстрактных базовых классов весьма полезно при разработке больших проектов, где
важно обеспечить согласованность структуры классов и интерфейсов. Это помогает
избегать ситуаций, когда один разработчик ожидает определённый набор методов, а
другой забывает реализовать какой-то важный элемент функциональности.
Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs