ООП. Абстрактный класс. Декомпозиция программы.
Абстрактным называется класс, который содержит один и более абстрактных методов. Абстрактным называется объявленный, но не реализованный метод. Абстрактные классы не могут быть инстанциированы, от них нужно унаследовать, реализовать все их абстрактные методы и только тогда можно создать экземпляр такого класса.
В python существует стандартная библиотека abc, добавляющая в язык абстрактные базовые классы (АБК). АБК позволяют определить класс, указав при этом, какие методы или свойства обязательно переопределить в классах-наследниках.
Возьмем для примера, шахматы. У всех шахматных фигур есть общий функционал, например — возможность фигуры ходить и быть отображенной на доске. Исходя из этого, мы можем создать абстрактный класс Фигура, определить в нем абстрактный метод (в нашем случае — ход, поскольку каждая фигура ходит по-своему) и реализовать общий функционал (отрисовка на доске).
from abc import ABC, abstractmethod class ChessPiece(ABC): # общий метод, который будут использовать все наследники этого класса def draw(self): print("Drew a chess piece") # абстрактный метод, который будет необходимо переопределять для каждого подкласса @abstractmethod def move(self): pass
a = ChessPiece() # Если мы попытаемся инстанциировать данный класс, логично получим ошибку.
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) in () ----> 1 a = ChessPiece() # Если мы попытаемся инстанциировать данный класс, логично получим ошибку. TypeError: Can't instantiate abstract class ChessPiece with abstract methods move
Как видите, система не дает нам создать экземпляр данного класса. Теперь нам необходимо создать конкретный класс, например, класс ферзя, в котором мы реализуем метод move.
class Queen(ChessPiece): def move(self): print("Moved Queen to e2e4") # Мы можем создать экземпляр класса q = Queen() # И нам доступны все методы класса q.draw() q.move()
Drew a chess piece Moved Queen to e2e4
Обратите внимание, абстрактный метод может быть реализован сразу в абстрактном классе, однако, декоратор abstractmethod, обяжет программистов, реализующих подкласс либо реализовать собственную версию абстрактного метода, либо дополнить существующую. В таком случае, мы можем переопределять метод как в обычном наследовании, а вызывать родительский метод при помощи super().
from abc import ABC, abstractmethod class Basic(ABC): @abstractmethod def hello(self): print("Hello from Basic class") class Advanced(Basic): def hello(self): super().hello() print("Enriched functionality") a = Advanced() a.hello()
Hello from Basic class Enriched functionality
Таким образом, используя концепцию абстрактных классов, мы можем улучшить качество архитектуры приложения, уменьшить объем работы и при этом, обеспечить легкость дальнейшей поддержки кода.
Декомпозиция программы на модули
Модули и пакеты в Python – это прекрасные инструменты для управления сложностью в программном проекте.
Создадим модуль с именем simplemath.py, который будет содержать функции для выполнения простых арифметических действий.
Создадим ещё один модуль worker.py, который будет использовать функции из simplemath.py. Если мы хотим импортировать все функции, то оператор import для нас отлично подойдет. Это будет выглядеть так.
# представим, что эта ячейка - текстовый редактор, который мы сохраним под именем simplemath.py def add(a, b): return a + b def sub(a, b): return a - b def mul(a, b): return a * b def div(a, b): return a / b
# представим, что эта ячейка - текстовый редактор, который мы сохраним под именем simplemath.py #import simplemath #from simplemath inpord add,sub,mul,div #print(simplemath.add(1, 2)) # = 3 #print(simplemath.sub(1, 2)) # = -1 #print(simplemath.mul(1, 2)) # = 2 #print(simplemath.div(1, 2)) # = 0.5
Задачи:
Задача 1:
В файле вам даны 3 класса A , B , C , имеющие сходный (но не одинаковый) интерфейс. Вам необходимо создать абстрактный базовый класс Base и построить корректную схему наследования. При выполнении следует избегать дублирования кода, и стараться следовать SOLID принципам ООП.
Задача 2:
В файле вам дана программа. Необходимо провести её рефакторинг.
Для работы программы необходима библиотека PyGame. В открывшемся окне программы доступны следующие команды управления:
- — показать справку по командам
- — рестарт
- — пауза, снять/поставить
- — увеличить количество точек «сглаживания»
- — уменьшить количество точек «сглаживания»
- — добавить «опорную» точку
По умолчанию при старте программы «опорные» точки отсутствуют и программа находится в состоянии паузы (движение кривой выключено). Для добавления точек сделайте несколько кликов левой клавишей мыши в любом месте окна программы. Отрисовка кривой произойдет, когда точек на экране станет больше двух. Нажмите клавишу , чтобы включить движение кривой.
- Изучить документацию к библиотеке pygame и код программы. Понять механизм работы программы (как происходит отрисовка кривой, перерасчет точек сглаживания и другие нюансы реализации программы)
- Провести рефакторниг кода, переписать программу в ООП стиле с использованием классов и наследования. Реализовать класс 2-мерных векторов Vec2d . В классе следует определить методы для основных математических операций, необходимых для работы с вектором. Добавить возможность вычислять длину вектора с использованием функции len(a) и метод int_pair , который возвращает кортеж из двух целых чисел (текущие координаты вектора).
Реализовать класс замкнутых ломаных Polyline с методами отвечающими за добавление в ломаную точки ( Vec2d ) c её скоростью, пересчёт координат точек ( set_points ) и отрисовку ломаной ( draw_points ). Арифметические действия с векторами должны быть реализованы с помощью операторов, а не через вызовы соответствующих методов.
Реализовать класс Knot (наследник класса Polyline ), в котором добавление и пересчёт координат инициируют вызов функции get_knot для расчёта точек кривой по добавляемым «опорным» точкам.
Все классы должны быть самостоятельными и не использовать внешние функции.
Задача 3* ДНК
Реализуйте классы для ДНК (двойная цепочк) и РНК (одинарная цепочка). Данные структуры данных должны поддерживать следующие возможности:

1. Создавать структуру из строк. Обратите внимание, что в ДНК встречаются только азотистые основания ATGC, а в РНК (AUGC) поэтому если во входной строке содержались другие символы, необходимо поднимать ошибку (Exception). 2. Поддерживают индексацию. РНК по индексу возвращает i-ое азотистое основание, ДНК — пару азотистых оснований (соответствующие первой и второй цепочке) 3. РНК может возвращать комплиментарную ДНК (каждому азотистому основанию из РНК соответсвует соответсвующее основание для первой цепочки ДНК: A → T , U → A , G → C , C → G . Вторая цепочка ДНК строится комплиментарной первой строчке ДНК: A → T , T → A , G → C , C → G ) 4. РНК, как и ДНК, могут складываться путем склеивания («AUUGAACUA» + «CGGAAA» = «AUUGAACUACGGAAA»). У ДНК склеиваются соответствующие цепочки ([«ACG», «TGC»] + [«TTTAAT», «AAATTA»] = [«ACGTTTAAT», «TGCAAATTA»]) 5. РНК могут перемножаться друг с другом: каждое азотистое основание результирующей РНК получается случайным выбором одного из двух соответсвующих родительских азотистых оснований. Если одна из цепочек длиннее другой, то перемножение происходит с начала, когда одна из цепочек закончится оставшийся хвост другой переносится без изменений. 6. ДНК могут перемножаться друг с другом: ПЕРВЫЕ цепочки каждой из ДНК перемножаются по такому же приницпу, как перемножаются РНК выше. Вторая цепочка результирующей ДНК строится как комплиментарная первой 7. Цепочки РНК и первую и вторую у ДНК можно проверять на равенство 8. Оба класса должны давать осмысленный вывод как при print, так и просто при вызове в ячейке | Обдумайте и создайте необходимые и, возможно, вспомогательные классы, настройте наследование, если требуется. Полученная структура должна быть адекватной и удобной, готовой к простому расширению функционала, если потребуется
Сайт построен с использованием Pelican. За основу оформления взята тема от Smashing Magazine. Исходные тексты программ, приведённые на этом сайте, распространяются под лицензией GPLv3, все остальные материалы сайта распространяются под лицензией CC-BY.
Абстрактные классы в Python
Абстрактный класс — очень важная концепция объектно-ориентированного программирования. Это хорошая практика принципа «не повторяйся«. В большом проекте дублирование кода примерно равно повторному использованию ошибок, и один разработчик не может запомнить детали всех классов. Поэтому очень полезно использовать абстрактный класс для определения общего интерфейса для различных реализаций.
Абстрактный класс имеет некоторые особенности, а именно:
- Абстрактный класс не содержит всех реализаций методов, необходимых для полной работы, это означает, что он содержит один или несколько абстрактных методов. Абстрактный метод — это только объявление метода, без его подробной реализации.
- Абстрактный класс предоставляет интерфейс для подклассов, чтобы избежать дублирования кода. Нет смысла создавать экземпляр абстрактного класса.
- Производный подкласс должен реализовать абстрактные методы для создания конкретного класса, который соответствует интерфейсу, определенному абстрактным классом. Следовательно, экземпляр не может быть создан, пока не будут переопределены все его абстрактные методы.
Короче говоря, абстрактный класс определяет общий интерфейс для набора подклассов. Он предоставляет общие атрибуты и методы для всех подклассов, чтобы уменьшить дублирование кода. Он также заставляет подклассы реализовывать абстрактные методы, чтобы избежать каких-то несоответствий.
Определение абстрактного класса в Python.
Python поставляется с модулем под названием abc , который предоставляет полезные вещи для абстрактного класса. Абстрактный класс можно определить с помощью класса abc.ABC , а абстрактный метод определить с помощью abc.abstractmethod . ABC — это аббревиатура, сокращение от слов абстрактный базовый класс.
Примечание: Класс не является настоящим абстрактным, если он имеет абстрактные методы, но не наследуется от abc.ABC , это означает, что он может быть создан. Например:
from abc import ABC, abstractmethod class Animal(ABC): @abstractmethod def move(self): pass a = Animal() # TypeError: Can't instantiate abstract class Animal with abstract methods move class Animal(): @abstractmethod def move(self): pass a = Animal()
Определение абстрактного метода абстрактного класса.
Декоратор @abstractmethod может использоваться для объявления абстрактных методов свойств и дескрипторов требует, чтобы метакласс класса был ABCMeta или производным от него. Абстрактный класс не может быть создан, пока не будут переопределены все его абстрактные методы и свойства.
На самом деле абстрактный метод в Python не обязательно должен быть «полностью абстрактным«, что отличается от некоторых других объектно-ориентированных языков программирования. Можно определить некоторые общие вещи в абстрактном методе и использовать функцию super() для вызова его в подклассах.
from abc import ABC, abstractmethod class Animal(ABC): @abstractmethod def move(self): print('Animal moves') class Cat(Animal): def move(self): super().move() print('Cat moves') c = Cat() c.move() # Animal moves # Cat moves
Как показано в приведенном выше примере, абстрактный метод .move() может содержать некоторые функции и может вызываться подклассом с помощью super() . Хотя у него небольшая реализация, это все еще абстрактный метод, и пользователю необходимо полностью реализовать его в подклассах.
Совместно с декоратором @abstractmethod можно использовать такие декораторы, как @property , @classmethod и @staticmethod . Когда декоратор @abstractmethod применяется в сочетании с другими дескрипторами методов, его следует применять как самый внутренний декоратор
Определение абстрактного метода класса.
from abc import ABC, abstractmethod class C(ABC): @classmethod @abstractmethod def my_abstract_classmethod(cls, . ): .
Определение абстрактного статического метода класса.
from abc import ABC, abstractmethod class C(ABC): @staticmethod @abstractmethod def my_abstract_staticmethod(. ): .
Определение абстрактного дескриптора класса.
В приведенном примере определяется свойство только для чтения:
from abc import ABC, abstractmethod class C(ABC): @property @abstractmethod def my_abstract_property(self): .
Также можно определить абстрактное свойство для чтения и записи, соответствующим образом пометив один или несколько базовых методов как абстрактные:
from abc import ABC, abstractmethod class C(ABC): @property def x(self): . @x.setter @abstractmethod def x(self, val): .
Если только некоторые компоненты являются абстрактными, только эти компоненты необходимо обновить, чтобы создать конкретное свойство в подклассе:
class D(C): @C.x.setter def x(self, val): .
Динамическое добавление абстрактных методов после создания класса.
Динамическое добавление абстрактных методов в класс или попытка изменить статус абстракции метода или класса после его создания поддерживаются только с помощью функции abc.update_abstractmethods() .
abc.update_abstractmethods(cls) :
Новое в версии Python 3.10.
Функция abc.update_abstractmethods(cls) предназначена для пересчета статуса абстракции абстрактного класса. Эту функцию следует вызывать, если абстрактные методы класса были реализованы или изменены после их создания. Обычно эту функцию следует вызывать из декоратора класса.
Возвращает cls , чтобы разрешить использование в качестве декоратора класса.
Если cls не является экземпляром ABC , ничего не делает.
Примечание. Эта функция предполагает, что суперклассы cls уже обновлены. Она не обновляет никаких подклассов.
Общий пример концепции определения абстрактных классов.
from abc import ABC, abstractmethod class C(ABC): @abstractmethod def my_abstract_method(self, . ): . @classmethod @abstractmethod def my_abstract_classmethod(cls, . ): . @staticmethod @abstractmethod def my_abstract_staticmethod(. ): . @property @abstractmethod def my_abstract_property(self): . @my_abstract_property.setter @abstractmethod def my_abstract_property(self, val): . @abstractmethod def _get_x(self): . @abstractmethod def _set_x(self, val): . x = property(_get_x, _set_x)
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Пространство имен и область видимости в классах
- Определение классов
- Объект класса и конструктор класса
- Создание экземпляра класса
- Метод экземпляра класса
- Что такое метод класса и зачем нужен
- Что такое статический метод в классах Python и зачем нужен
- Атрибуты класса и переменные экземпляра класса
- Кэширование методов экземпляра декоратором lru_cache
- Закрытые/приватные методы и переменные класса Python
- Наследование классов
- Множественное наследование классов
- Абстрактные классы
- Перегрузка методов в классе Python
- Что такое миксины и как их использовать
- Класс Python как структура данных, подобная языку C
- Создание пользовательских типов данных
- Специальные (магические) методы класса Python
- Базовая настройка классов Python магическими методами
- Настройка доступа к атрибутам класса Python
- Дескриптор класса для чайников
- Протокол дескриптора класса
- Практический пример дескриптора
- Использование метода .__new__() в классах Python
- Специальный атрибут __slots__ класса Python
- Специальный метод __init_subclass__ класса Python
- Определение метаклассов metaclass
- Эмуляция контейнерных типов в классах Python
- Другие специальные методы класса
- Как Python ищет специальные методы в классах
- Шаблон проектирования Фабрика и его реализация
Абстрактные классы — Python: Погружаясь в классы
При проектировании сложных систем разработчикам часто нужно определить структуру и поведение классов, которые не предназначены для создания экземпляров, а служат только основой для наследования. Как же гарантировать, что эти классы не будут использованы напрямую, и как обозначить методы, которые обязательно должны быть реализованы в наследниках? Здесь на помощь приходят абстрактные классы.
В этом уроке мы рассмотрим, что такое абстрактные классы и как они помогают решить вышеуказанные проблемы.
Что такое абстрактные классы
В Python есть понятие абстрактного класса. Это класс, который не может быть инстанциирован напрямую — только через его наследников. Но во всем остальном, это обычный класс со своими методами и атрибутами:
from abc import ABC, abstractmethod class HTMLElement(ABC): def __init__(self, attributes=<>): self.attributes = attributes def get_attribute(self, key): return self.attributes.get(key)
В данном примере мы создаем абстрактный класс HTMLElement с помощью модуля abc . Этот класс содержит метод get_attribute , который возвращает значение атрибута элемента.
Абстрактные классы имеют смысл только в связке с наследованием. Они подразумевают, что от базового класса не будут создаваться объекты, поэтому его логично пометить как абстрактный.
Особенность абстрактных классов в Python связана с реализацией интерфейсов. В отличие от обычного класса, абстрактный класс не обязан реализовывать все методы интерфейса — это должны сделать его наследники:
from abc import ABC, abstractmethod class Showable(ABC): @abstractmethod def __str__(self): pass
В этом примере мы создаем абстрактный класс Showable с абстрактным методом __str__ . Этот метод должен быть реализован в любом классе, который наследует Showable .
Так каждый наследник класса HTMLElement должен реализовывать метод __str__() :
class HTMLElement(Showable): def __init__(self, attributes=<>): self.attributes = attributes def get_attribute(self, key): return self.attributes.get(key) def __str__(self): # реализация метода __str__ для HTMLElement return f"HTMLElement with attributes: self.attributes>"
В данном случае HTMLElement наследуется от Showable , что означает, что каждый класс, производный от HTMLElement , должен реализовывать метод __str__() .
Если класс наследник не реализует обязательный метод __str__() , то при попытке создания экземпляра этого класса Python выдаст ошибку.
Теперь подробнее рассмотрим, когда стоит использовать абстрактные классы, а когда нет, и как их совмещать с ООП.
Когда использовать абстрактные классы
С появлением абстрактных классов возникает множество новых вопросов: когда они нужны, а когда нет, можно ли использовать абстрактные классы вместо интерфейсов, как совмещать их с интерфейсами и так далее.
Абстрактные классы — не фундаментальная концепция и не обязательная часть ООП. Более того, в большинстве ООП-языков абстрактных классов нет. Однако нельзя сказать, что код на них пишется хуже. Не стоит придавать большое значение абстрактным классам. Их нужно использовать, когда класс имеет смысл пометить абстрактным, но не более того.
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Абстрактный класс ABC
Абстрактный класс – класс, содержащий один и более абстрактных методов.
Абстрактный метод – метод, который объявлен, но не реализован.
Абстрактный класс не может быть инстанциирован (создан его экземпляр). Нужно наследовать этот класс и реализовать (переопределить) все абстрактные методы, и только после этого можно создавать экземпляры такого наследника.
В Python нет синтаксической поддержки абстрактных классов, но есть встроенный модуль abc (расшифровка – abstract base classes), который помогает проектировать абстрактные сущности.
Абстрактный класс наследуют от ABC (Python 3.4+) или указывают метакласс ABCMeta (для Python 3.0+):
from abc import ABC, ABCMeta class Hero(ABC): . # или: class Hero(metaclass=ABCMeta): .
Любой из вариантов работает, первый современнее и короче. На данном этапе мы можем создавать объекты этих классов, потому что в них пока не абстрактных методов. Добавим:
from abc import ABC, abstractmethod class Hero(ABC): @abstractmethod def attack(self): pass
Hero() – выдаст ошибку «TypeError: Can’t instantiate abstract class Hero with abstract methods attack», которая говорит, что в классе Hero есть абстрактный метод attack . Мы вставили в него заглушку pass , но вообще там может быть какая-то реализация. Отнаследуем от героя Hero – конкретный подкласс лучника Archer :
class Archer(Hero): def attack(self): print('выстрел из лука') Archer().attack()
Вот объект Archer мы можем уже создать и использовать реализацию метода attack .
Кроме обычных методов, абстрактными можно обозначить и статические, классовые методы, а также свойства:
class C(ABC): @classmethod @abstractmethod def my_abstract_classmethod(cls): . @staticmethod @abstractmethod def my_abstract_staticmethod(): . @property @abstractmethod def my_abstract_property(self): . @my_abstract_property.setter @abstractmethod def my_abstract_property(self, val): .
Абстрактные классы широко фигурируют в ООП, часто всплывают в шаблонах проектирования. Они говорят, что общий интерфейс уже обозначен, но этот класс еще не предназначен для использования, кроме как для наследования от него конкретных потомков.
Формально говоря, абстрактные классы для Python не являются чем-то необходимым в силу динамичности языка. Если мы выкинем все упоминания абстрактности классов и методов из рабочего кода, он продолжит работать, как и ранее. Абстрактные классы нужны на этапе проектирования или расширения кода, чтобы обеспечивать «правильные» взаимодействия новых классов, защищая от создания экземпляров абстрактных классов. Важно помнить, что эта защита срабатывает на этапе выполнения программы, а не компиляции, как в языках Java, C++ или C#!
Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway