Абстракция в Python – простое объяснение
Абстракция используется, чтобы скрыть внутренние характеристики функции от пользователей. Пользователи взаимодействуют только с базовой реализацией функции, но внутренняя работа скрыта. Пользователь знаком с тем, «что делает функция», но не знает, «как она работает».
Проще говоря, мы все пользуемся смартфоном и хорошо знакомы с его функциями, такими как камера, диктофон, набор номера и т. д., но мы не знаем, как эти операции выполняются в фоновом режиме. Возьмем другой пример – когда мы используем пульт от телевизора для увеличения громкости. Мы не знаем, как нажатие клавиши увеличивает громкость телевизора. Известно только, что нужно нажимать кнопку «+», чтобы увеличить громкость.
Это именно та абстракция, которая работает в объектно-ориентированной концепции.
Почему важна абстракция?
В Python абстракция используется, чтобы скрыть незначимые данные или класс, чтобы уменьшить сложность. Это также повышает эффективность приложения. Далее мы узнаем, как добиться абстракции в Python.
Классы абстракции в Python
Абстракция в Python может быть достигнута с помощью абстрактных классов и интерфейсов.
Класс, состоящий из одного или нескольких абстрактных методов, называется абстрактным классом. Абстрактные методы не содержат своей реализации. Абстрактный класс может быть унаследован подклассом, а абстрактный метод получает свое определение в подклассе. Классы абстракции должны быть прототипом другого класса. Абстрактный класс может быть полезен при разработке больших функций, а также для предоставления стандартного интерфейса для различных реализаций компонентов. Python предоставляет модуль abc для использования абстракции в программе Python. Рассмотрим следующий синтаксис.
from abc import ABC class ClassName(ABC):
Мы импортируем класс ABC из модуля abc.
Абстрактные базовые классы
Абстрактный базовый класс – это общая прикладная программа интерфейса для набора подклассов. Его может использовать сторонний поставщик, который предоставит такие реализации, как плагины. Это также полезно, когда мы работаем с большой базой кода, трудно запомнить все классы.
Работа абстрактных классов
В отличие от других языков высокого уровня, Python не предоставляет самого абстрактного класса. Нам нужно импортировать модуль abc, который обеспечивает основу для определения абстрактных базовых классов (ABC). ABC работает, декорируя методы базового класса как абстрактные. Он регистрирует конкретные классы как реализацию абстрактной базы. Мы используем @abstractmethod декоратор для определения абстрактного метода или, если мы не предоставляем определение методу, он автоматически становится абстрактным методом. Давайте разберемся в следующем примере.
# Python program demonstrate # abstract base class work from abc import ABC, abstractmethod class Car(ABC): def mileage(self): pass class Tesla(Car): def mileage(self): print("The mileage is 30kmph") class Suzuki(Car): def mileage(self): print("The mileage is 25kmph ") class Duster(Car): def mileage(self): print("The mileage is 24kmph ") class Renault(Car): def mileage(self): print("The mileage is 27kmph ") # Driver code t= Tesla () t.mileage() r = Renault() r.mileage() s = Suzuki() s.mileage() d = Duster() d.mileage()
The mileage is 30kmph The mileage is 27kmph The mileage is 25kmph The mileage is 24kmph
В приведенном выше коде мы импортировали модуль abc для создания абстрактного базового класса. Мы создали класс Car, который унаследовал класс ABC, и определили абстрактный метод с именем mileage(). Затем мы унаследовали базовый класс от трех разных подклассов и по-разному реализовали абстрактный метод. Мы создали объекты для вызова абстрактного метода.
Разберемся еще на одном примере.
# Python program to define # abstract class from abc import ABC class Polygon(ABC): # abstract method def sides(self): pass class Triangle(Polygon): def sides(self): print("Triangle has 3 sides") class Pentagon(Polygon): def sides(self): print("Pentagon has 5 sides") class Hexagon(Polygon): def sides(self): print("Hexagon has 6 sides") class square(Polygon): def sides(self): print("I have 4 sides") # Driver code t = Triangle() t.sides() s = square() s.sides() p = Pentagon() p.sides() k = Hexagon() K.sides()
Triangle has 3 sides Square has 4 sides Pentagon has 5 sides Hexagon has 6 sides
В приведенном выше коде мы определили абстрактный базовый класс с именем Polygon, а также определили абстрактный метод. Этот базовый класс наследуется различными подклассами. Мы реализовали абстрактный метод в каждом подклассе. Мы создали объект подклассов и вызываем метод side(). В игру вступают скрытые реализации метода side() внутри каждого подкласса. Абстрактный метод side(), определенный в абстрактном классе, никогда не вызывается.
Что следует помнить
Ниже приведены моменты, которые мы должны помнить об абстрактном базовом классе в Python:
- Абстрактный класс может содержать как обычный метод, так и абстрактный метод.
- Аннотация не может быть создана; мы не можем создавать объекты для абстрактного класса.
Абстракция необходима для сокрытия подробных функций от пользователей. Мы рассмотрели все основные концепции абстракции в Python.
Абстрактные классы и интерфейсы в Питоне
Абстрактные базовые классы и интерфейсы — близкие по назначению и смыслу сущности. Как первые, так и вторые представляют собой своеобразный способ документирования кода и помогают ограничить (decouple) взаимодействие отдельных абстракций в программе (классов).
Питон — очень гибкий язык. Одна из граней этой гибкости — возможности, предоставляемые метапрограммированием. И хотя в ядре языка абстрактные классы и интерфейсы не представлены, первые были реализованы в стандартном модуле abc, вторые — в проекте Zope (модуль zope.interfaces).
Нет смысла одновременно использовать и то и другое, и поэтому каждый программист должен определить для себя, какой инструмент использовать при проектировании приложений.
2 Абстрактные базовые классы (abс)
Начиная с версии языка 2.6 в стандартную библиотеку включается модуль abc, добавляющий в язык абстрактные базовые классы (далее АБК).
АБК позволяют определить класс, указав при этом, какие методы или свойства обязательно переопределить в классах-наследниках:
from abc import ABCMeta, abstractmethod, abstractproperty class Movable(): __metaclass__=ABCMeta @abstractmethod def move(): """Переместить объект""" @abstractproperty def speed(): """Скорость объекта"""
Таким образом, если мы хотим использовать в коде объект, обладающий возможностью перемещения и определенной скоростью, то следует использовать класс Movable в качестве одного из базовых классов.
Наличие необходимых методов и атрибутов объекта теперь гарантируется наличием АБК среди предков класса:
class Car(Movable): def __init__: self.speed = 10 self.x = 0 def move(self): self.c += self.speed def speed(self): return self.speed assert issubclass(Car, Movable) assert ininstance(Car(), Movable)
Видно, что понятие АБК хорошо вписывается в иерархию наследования классов, использовать их легко, а реализация, если заглянуть в исходный код модуля abc, очень проста. Абстрактные классы используются в стандартных модулях collections и number, задавая необходимые для определения методы пользовательских
классов-наследников.
Подробности и соображения по поводу использования АБК можно найти в PEP 3119
(http://www.python.org/dev/peps/pep-3119/).
3 Интерфейсы (zope.interfaces)
Реализация проекта Zope в работе над Zope3 решила сделать акцент на компонентной архитектуре; фреймворк превратился в набор практически независимых компонент. Клей, соединяющий компоненты — интерфейсы и основывающиеся на них адаптеры.
Модуль zope.interfaces — результат этой работы.
В простейшем случае использвание интерфейсов напоминает примерение АБК:
import zope.interface class IVehicle(zope.interface.Interface): """Any moving thing""" speed = zope.interface.Attribute("""Movement speed""") def move(): """Make a single step""" class Car(object): zope.interface.implements(IVehicle) def __init__: self.speed = 1 self.location = 1 def move(self): self.location = self.speed*1 print "moved!" assert IVehicle.implementedBy(Car) assert IVehicle.providedBy(Car())
В интерфейсе декларативно показывается, какие атрибуты и методы должны быть у объекта. Причем класс реализует (implements) интерфейс, а объект класса — предоставляет (provides). Следует обратить внимание на разницу между этими понятиями!
«Реализация» чем-либо интерфейса означает, что только «производимая» сущность будет обладать необходимыми свойствами; а «предоставление» интерфейса говорит о конкретных возможностях оцениваемой сущности. Соответственно, в Питоне классы, кстати, могут как реализовывать, так и предоставлять интерфейс.
На самом деле декларация implement(IVehicle) — условность; просто обещание, что данный класс и его объекты ведут себя именно таким образом. Никаких реальных проверок проводиться не будет
class IVehicle(zope.interface.Interface): """Any moving thing""" speed = zope.interface.Attribute("""Movement speed""") def move(): """Make a single step""" class Car(object): zope.interface.implements(IVehicle) assert IVehicle.implementedBy(Car) assert IVehicle.providedBy(Car())
Видно, что в простейших случаях интерфейсы только усложняют код, как, впрочем, и АБК
Компонентная архитектура Zope включает еще одно важное понятие — адаптеры. Вообще говоря, это простой шаблон проектирования, корректирующий один класс для использования где-то, где требуется иной комплект методов и атрибутов. Итак,
4 Адаптеры
Предположим, что имеется пара классов, Guest и Desk. Определим интерфейсы к ним, плюс класс, реализующий интерфейс Guest:
import zope.interface from zope.interface import implements from zope.component import adapts, getGlobalSiteManager class IDesk(zope.interface.Interface): def register(): "Register a person" class IGuest(zope.interface.Interface): name = zope.interface.Attribute("""Person`s name""") class Guest(object): implements(IGuest) def __init__(self, name): self.name=name
Адаптер должен учесть анонимного гостя, зарегистрировав в списке имен:
class GuestToDeskAdapter(object): adapts(IGuest) implements(IDesk) def __init__(self, guest): self.guest=guest def register(self): guest_name_db.append(self.guest.name)
Существует реестр, который ведет учет адаптеров по интерфейсам. Благодаря ему можно получить адаптер, передав в вызов класса-интерфейса адаптируемый объект. Если адаптер не зарегистрирован, то вернется второй аргумент интерфейса:
guest = Guest("Ivan") adapter = IDesk(guest, alternate=None) print adapter >>>>None found gsm = getGlobalSiteManager() gsm.registerAdapter(GuestToDeskAdapter) adapter = IDesk(guest, alternate="None found") print adapter >>>>__main__.GuestToDeskAdapter object at 0xb7beb64c>
Такую инфраструктуру удобно использовать для разделения кода на компоненты и их связывания.
Один из ярчайших примеров использования такого подхода помимо самого Zope — сетевой фреймворк Twisted, где изрядная часть архитектуры опирается на интерфейсы из zope.interfaces.
5 Вывод
При ближайшем рассмотрении оказывается, что интерфейсы и абстрактные базовые классы — разные вещи.
Абстрактные классы в основном жестко задают обязательную интерфейсную часть. Проверка объекта на соответствие интерфейсу абстрактного класса проверяется при помощи встроенной функции isinstance; класса — issubclass. Абстрактный базовый класс должен включаться в иерархию в виде базового класса либо mixin`а.
Минусом можно считать семантику проверок issubclass, isinstance, которые пересекаются с обычными классами (их иерархией наследования). На АБК не выстраивается никаких допонительных абстракций.
Интерфейсы — сущность декларативная, они не ставят никаких рамок; просто утверждается, что класс реализует, а его объект предоставляет интерфейс. Семантически утверждения implementedBy, providedBy являются более корректными. На такой простой базе удобно выстраивать компонентную архитектуру при помощи адапетров и других производных сущностей, что и делают крупные фреймворки Zope и Twisted.
Надо понимать, что использование обоих инструментов имеет смысл только при построении и использовании сравнительно крупных ООП-систем — фреймворков и библиотек, в малых программах они могут только запутать и усложнить код код лишними абстракциями.