Как сделать полиморфизм в питоне

Обновлено: 01.07.2024

Популярный язык программирования Python также следует парадигме объектно-ориентированного программирования. Объявление классов и объектов в Python закладывают основу концепций ООП.

Что такое объектно-ориентированное программирование в Python?

Программа разбита на автономные объекты или несколько мини-программ. Каждый индивидуальный объект представляет собой отдельную часть приложения, имеющую собственную логику и данные для взаимодействия внутри себя.

Что такое концепции ООП?

Основные концепции ООП (объектно-ориентированного программирования) в Python включают класс, объект, метод, наследование, полиморфизм, абстракцию данных и инкапсуляцию.

Что такое классы и объекты?

Класс – это набор объектов или план объектов, определяющих общие атрибуты и поведение. Возникает вопрос, как это сделать?

Примечание. Python не чувствителен к регистру.

Объекты

Объекты – это экземпляр класса, у которого есть состояние и поведение. В двух словах, это экземпляр класса, который может получить доступ к данным.

Синтаксис: obj = class1()

Создание объекта и класса

Методологии объектно-ориентированного программирования

  • наследование;
  • полиморфизм;
  • инкапсуляция;
  • абстракция.

Давайте подробно разберемся с первой концепцией наследования.

Наследование

Типы наследования

Разберемся подробно с каждой из подтем.

Одиночное наследование

Одноуровневое наследование позволяет производному классу наследовать характеристики от одного родительского класса.

Многоуровневое наследование

Многоуровневое наследование позволяет производному классу наследовать свойства от непосредственного родительского класса, который, в свою очередь, наследует свойства от своего родительского класса.

Иерархическое наследование

Наследование на иерархическом уровне позволяет более чем одному производному классу наследовать свойства родительского класса.

Множественное наследование

Многоуровневое наследование позволяет производному классу наследовать свойства более чем одного базового класса.

Полиморфизм

  • Полиморфизм времени компиляции
  • Полиморфизм времени выполнения

Полиморфизм времени компиляции

Его зовут Харшит, 3000 – его зарплата, 22 – его возраст; его зовут Рахул, 4000 – его зарплата, 23 – его возраст.

Полиморфизм времени выполнения

Выход: денег нет, деньги есть.

Переходя к следующей методологии объектно-ориентированного программирования Python, я расскажу об инкапсуляции.

Инкапсуляция

В необработанном виде инкапсуляция означает связывание данных в одном классе. В Python, в отличие от Java, нет закрытых ключевых слов. Доступ к классу не должен осуществляться напрямую, он должен иметь префикс в виде подчеркивания.

Объяснение: Вы получите этот вопрос, что такое подчеркивание и ошибка? Класс python обрабатывает частные переменные, как (__ salary), к которым нельзя получить доступ напрямую.

Итак, в моем следующем примере я использовал метод установки, который обеспечивает косвенный доступ к ним.

Вывод: Заработок: 1000000, заработок: 1000000, заработок: 10000.

Объяснение: Использование метода установки обеспечивает косвенный доступ к методу частного класса. Здесь я определил класс сотрудника и использовал (__maxearn), который представляет собой метод установки, используемый здесь для хранения максимального заработка сотрудника, и функцию установки setmaxearn(), которая принимает цену в качестве параметра.

Это наглядный пример инкапсуляции, когда мы ограничиваем доступ к методу частного класса, а затем используем метод установки для предоставления доступа.

Далее в методологии объектно-ориентированного программирования Python рассказывается об одной из ключевых концепций, называемой абстракцией.

Абстракция

Невозможно создать экземпляр абстрактного класса, что просто означает, что вы не можете создавать объекты для этого типа класса. Его можно использовать только для наследования функциональных возможностей.

Вывод: emp_id – 12345.

Объяснение: Как вы можете видеть в приведенном выше примере, мы импортировали абстрактный метод, а остальная часть программы имеет родительский и производный классы. Создается экземпляр объекта для базового класса childemployee, и используются функциональные возможности абстрактного.

Недавно мы говорили об основах объектно-ориентированного программирования в python, теперь продолжим эту тему и поговорим о таких понятиях ООП, как инкапсуляция, наследование и полиморфизм.

Инкапсуляция

Инкапсуляция — ограничение доступа к составляющим объект компонентам (методам и переменным). Инкапсуляция делает некоторые из компонент доступными только внутри класса.

Инкапсуляция в Python работает лишь на уровне соглашения между программистами о том, какие атрибуты являются общедоступными, а какие — внутренними.

Одиночное подчеркивание в начале имени атрибута говорит о том, что переменная или метод не предназначен для использования вне методов класса, однако атрибут доступен по этому имени.

Двойное подчеркивание в начале имени атрибута даёт большую защиту: атрибут становится недоступным по этому имени.

Однако полностью это не защищает, так как атрибут всё равно остаётся доступным под именем _ИмяКласса__ИмяАтрибута:

Наследование

Наследование подразумевает то, что дочерний класс содержит все атрибуты родительского класса, при этом некоторые из них могут быть переопределены или добавлены в дочернем. Например, мы можем создать свой класс, похожий на словарь:

Класс Mydict ведёт себя точно так же, как и словарь, за исключением того, что метод get по умолчанию возвращает не None, а 0.

Полиморфизм

Полиморфизм - разное поведение одного и того же метода в разных классах. Например, мы можем сложить два числа, и можем сложить две строки. При этом получим разный результат, так как числа и строки являются разными классами.

Данный урок посвящен объектно-ориентированному программированию в Python. Разобраны такие темы как создание объектов и классов, работа с конструктором, наследование и полиморфизм в Python.

Объектно-ориентированное программирование (ООП) является методологией разработки программного обеспечения, в основе которой лежит понятие класса и объекта, при этом сама программа создается как некоторая совокупность объектов, которые взаимодействую друг с другом и с внешним миром. Каждый объект является экземпляром некоторого класса. Классы образуют иерархии. Более подробно о понятии ООП можно прочитать на википедии.

Выделяют три основных “столпа” ООП- это инкапсуляция, наследование и полиморфизм.

Инкапсуляция

Наследование

Под наследованием понимается возможность создания нового класса на базе существующего. Наследование предполагает наличие отношения “является” между классом наследником и классом родителем. При этом класс потомок будет содержать те же атрибуты и методы, что и базовый класс, но при этом его можно (и нужно) расширять через добавление новых методов и атрибутов.

Примером базового класса, демонстрирующего наследование, можно определить класс “автомобиль”, имеющий атрибуты: масса, мощность двигателя, объем топливного бака и методы: завести и заглушить. У такого класса может быть потомок – “грузовой автомобиль”, он будет содержать те же атрибуты и методы, что и класс “автомобиль”, и дополнительные свойства: количество осей, мощность компрессора и т.п..

Полиморфизм

Полиморфизм позволяет одинаково обращаться с объектами, имеющими однотипный интерфейс, независимо от внутренней реализации объекта. Например, с объектом класса “грузовой автомобиль” можно производить те же операции, что и с объектом класса “автомобиль”, т.к. первый является наследником второго, при этом обратное утверждение неверно (во всяком случае не всегда). Другими словами полиморфизм предполагает разную реализацию методов с одинаковыми именами. Это очень полезно при наследовании, когда в классе наследнике можно переопределить методы класса родителя.

Создание классов и объектов

Создание класса в Python начинается с инструкции class. Вот так будет выглядеть минимальный класс.

Класс состоит из объявления (инструкция class), имени класса (нашем случае это имя C) и тела класса, которое содержит атрибуты и методы (в нашем минимальном классе есть только одна инструкция pass).

Для того чтобы создать объект класса необходимо воспользоваться следующим синтаксисом:

имя_объекта = имя_класса()

Статические и динамические атрибуты класса

Как уже было сказано выше, класс может содержать атрибуты и методы. Атрибут может быть статическим и динамическим (уровня объекта класса). Суть в том, что для работы со статическим атрибутом, вам не нужно создавать экземпляр класса, а для работы с динамическим – нужно. Пример:

В представленном выше классе, атрибут default_color – это статический атрибут, и доступ к нему, как было сказано выше, можно получить не создавая объект класса Rectangle.

width и height – это динамические атрибуты, при их создании было использовано ключевое слово self. Пока просто примите это как должное, более подробно про self будет рассказано ниже. Для доступа к width и height предварительно нужно создать объект класса Rectangle:

Если обратиться через класс, то получим ошибку:

При этом, если вы обратитесь к статическому атрибуту через экземпляр класса, то все будет ОК, до тех пор, пока вы не попытаетесь его поменять.

Проверим ещё раз значение атрибута default_color:

Присвоим ему новое значение:

Создадим два объекта класса Rectangle и проверим, что default_color у них совпадает:

Если поменять значение default_color через имя класса Rectangle, то все будет ожидаемо: у объектов r1 и r2 это значение изменится, но если поменять его через экземпляр класса, то у экземпляра будет создан атрибут с таким же именем как статический, а доступ к последнему будет потерян:

Меняем default_color через r1:

При этом у r2 остается значение статического атрибута:

Вообще напрямую работать с атрибутами – не очень хорошая идея, лучше для этого использовать свойства.

Методы класса

Добавим к нашему классу метод. Метод – это функция, находящаяся внутри класса и выполняющая определенную работу.

Методы бывают статическими, классовыми (среднее между статическими и обычными) и уровня класса (будем их называть просто словом метод). Статический метод создается с декоратором @staticmethod, классовый – с декоратором @classmethod, первым аргументом в него передается cls, обычный метод создается без специального декоратора, ему первым аргументом передается self:

Статический и классовый метод можно вызвать, не создавая экземпляр класса, для вызова ex_method() нужен объект:

Конструктор класса и инициализация экземпляра класса

В Python разделяют конструктор класса и метод для инициализации экземпляра класса. Конструктор класса это метод __new__(cls, *args, **kwargs) для инициализации экземпляра класса используется метод __init__(self). При этом, как вы могли заметить __new__ – это классовый метод, а __init__ таким не является. Метод __new__ редко переопределяется, чаще используется реализация от базового класса object (см. раздел Наследование), __init__ же наоборот является очень удобным способом задать параметры объекта при его создании.

Создадим реализацию класса Rectangle с измененным конструктором и инициализатором, через который задается ширина и высота прямоугольника:

Что такое self?

В приведенной реализации метод area получает доступ к атрибутам width и height для расчета площади. Если бы в качестве первого параметра не было указано self, то при попытке вызвать area программа была бы остановлена с ошибкой.

Уровни доступа атрибута и метода

Внесем соответствующие изменения в класс Rectangle:

В приведенном примере для доступа к _width и _height используются специальные методы, но ничего не мешает вам обратиться к ним (атрибутам) напрямую.

Если же атрибут или метод начинается с двух подчеркиваний, то тут напрямую вы к нему уже не обратитесь (простым образом). Модифицируем наш класс Rectangle:

Попытка обратиться к __width напрямую вызовет ошибку, нужно работать только через get_width():

Но на самом деле это сделать можно, просто этот атрибут теперь для внешнего использования носит название: _Rectangle__width:

Свойства

Свойством называется такой метод класса, работа с которым подобна работе с атрибутом. Для объявления метода свойством необходимо использовать декоратор @property.

Важным преимуществом работы через свойства является то, что вы можете осуществлять проверку входных значений, перед тем как присвоить их атрибутам.

Сделаем реализацию класса Rectangle с использованием свойств:

Теперь работать с width и height можно так, как будто они являются атрибутами:

Можно не только читать, но и задавать новые значения свойствам:

Если вы обратили внимание: в setter’ах этих свойств осуществляется проверка входных значений, если значение меньше нуля, то будет выброшено исключение ValueError:

Наследование

В организации наследования участвуют как минимум два класса: класс родитель и класс потомок. При этом возможно множественное наследование, в этом случае у класса потомка может быть несколько родителей. Не все языки программирования поддерживают множественное наследование, но в Python можно его использовать. По умолчанию все классы в Python являются наследниками от object, явно этот факт указывать не нужно.

Синтаксически создание класса с указанием его родителя выглядит так:

class имя_класса(имя_родителя1, [имя_родителя2,…, имя_родителя_n])

Переработаем наш пример так, чтобы в нем присутствовало наследование:

Родительским классом является Figure, который при инициализации принимает цвет фигуры и предоставляет его через свойства. Rectangle – класс наследник от Figure. Обратите внимание на его метод __init__: в нем первым делом вызывается конструктор (хотя это не совсем верно, но будем говорить так) его родительского класса:

super – это ключевое слово, которое используется для обращения к родительскому классу.

Теперь у объекта класса Rectangle помимо уже знакомых свойств width и height появилось свойство color:

Полиморфизм

Как уже было сказано во введении в рамках ООП полиморфизм, как правило, используется с позиции переопределения методов базового класса в классе наследнике. Проще всего это рассмотреть на примере. Добавим в наш базовый класс метод info(), который печатает сводную информацию по объекту класса Figure и переопределим этот метод в классе Rectangle, добавим в него дополнительные данные:

Посмотрим, как это работает

Таким образом, класс наследник может расширять функционал класса родителя.

P.S.

Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas. На нашем сайте вы можете найти вводные уроки по этой теме. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.

Python. Урок 14. Классы и объекты : 18 комментариев

А вот если Вы добавите вот это
.entry-title a:last-child float:right;
>
в свой css будет намного удобнее, нежели вы будите использовать 2-ную табуляцию в HTML. Спасибо.

Класс, о методе super() вообще ни слова

Спасибо за замечание! Добавим!

Про self ничего не сказано. Похоже на ссылку на текущий обьект.

Да, это действительно ссылка на текущий объект. Нужно будет вообще этот урок переработать, в нем плохо раскрыты многие вопросы! Спасибо за комментарий!

О методе __new__(cls) тоже нет ни слова, а он так же участвует в конструировании экземпляра класса.

ОК, спасибо! Добавим!

Наконец-то всё стало понятно. Огромное спасибо за разъяснение на уровне 1 класса 2 четверти!

Определение инкапсуляции неверное. Приведенное определение скорее присуще самому понятию “класс”. А инкапсуляция – это сокрытие деталей реализации.

> Атрибут может быть статическим и не статическим (уровня объекта класса)

В других языках принято “не статические атрибуты” называть динамическими. Предлагаю использовать, чтобы язык не ломать 🙂

Пытаюсь разобраться с декораторами.
@property
def width(self):
return self.__width
@width.setter
def width(self, w):
if w > 0:
self.__width = w
else:
raise ValueError

Свойство @____.setter является зарезервированным “именем” ? Т.е. любой метод обозначенный декоратором как @property для изменения значения должен использовать именно декоратор @X.setter ? Декоратор @property и @___.setter работают только в паре ?
Бывает еще используют декоратор @X.getter, как его использовать ? Может быть бывают и другие декораторы ?

Понял назначение методов уровня Класс. Но не понятно назначение классовых и статических методов (@classmethod, @staticmethod)

Ссылки на предыдущие уроки не нашел, причем тут декораторы и вообще, что это (хотя бы ссылкой) тоже не нашел.

Работаю с питоном уже больше года. Долго пытался понять что такое @property и @setter, А тут автор за 10 строчек объяснил, браво!

Класно описано. Только вот про сеттеры ни слова объяснения, из кода приходится догадыватся.

Полиморфизм подразумевает возможность использовать один и тот же интерфейс для различных базовых элементов (таких как типы данных или классы). При этом функции могут использовать объекты разных классов.

В объектно-ориентированном программировании Python это значит, что конкретный объект, принадлежащий определенному классу, может быть использован таким же образом, как если бы он был другим объектом, принадлежащим другому классу.

Полиморфизм делает код гибким, такой код легко расширять и поддерживать.

Данное руководство научит вас применять полиморфизм в классах Python.

Что такое полиморфизм?

Полиморфизм – важная функция в определении классов Python. Она применяется тогда, когда у классов или подклассов есть общие методы. Таким образом, функции могут использовать объекты любого из этих полиморфных классов, не зная различий между классами.

Если несколько классов содержат методы с одинаковыми именами, но реализуют их по-разному, эти классы являются полиморфными. Функция сможет оценить эти полиморфные методы, не зная, какой класс она вызывает.

Создание полиморфных классов

Для примера создайте пару классов и пару объектов. Чтобы у классов был общий интерфейс, создайте одноименные объекты с разной функциональностью.

Создайте файл polymorphic_fish.py и добавьте в него классы Shark и Clownfish с методами swim(), swim_backwards() и skeleton().

class Shark():
def swim(self):
print("The shark is swimming.")
def swim_backwards(self):
print("The shark cannot swim backwards, but can sink backwards.")
def skeleton(self):
print("The shark's skeleton is made of cartilage.")
class Clownfish():
def swim(self):
print("The clownfish is swimming.")
def swim_backwards(self):
print("The clownfish can swim backwards.")
def skeleton(self):
print("The clownfish's skeleton is made of bone.")

Классы Shark и Clownfish используют три одноименных метода с разной функциональностью.

На основе классов создайте объекты:

.
wally = Shark()
wally.skeleton()
casey = Clownfish()
casey.skeleton()

python polymorphic_fish.py
The shark's skeleton is made of cartilage.
The clownfish's skeleton is made of bone.

Итак, теперь у вас есть два объекта с общим интерфейсом.

Полиморфизм в методах классов

Чтобы посмотреть, как Python использует каждый из этих классов, создайте цикл for, который итерирует корсаж объектов. Затем можно вызвать методы, не указывая, к какому классу относится каждый из них. Достаточно просто указать, что такой метод существует.

.
wally = Shark()
casey = Clownfish()
for fish in (wally, casey):
fish.swim()
fish.swim_backwards()
fish.skeleton()

Теперь у вас есть два объекта, wally из класса Shark и casey из класса Clownfish. Цикл for итерирует методы swim(), swim_backwards() и skeleton() каждого объекта.

Запустите программу. Она выведет:

The shark is swimming.
The shark cannot swim backwards, but can sink backwards.
The shark's skeleton is made of cartilage.
The clownfish is swimming.
The clownfish can swim backwards.
The clownfish's skeleton is made of bone.

Цикл for сначала итерировал объект класса Shark, а затем объект класса Clownfish.

Как видите, Python использует эти методы, не зная точно, к какому классу относится каждый из них.

Полиморфизм в функциях

Также можно создать функцию для работы с объектами.

Создайте функцию in_the_pacific(), которая принимает объект fish. Несмотря на то, что объект называется fish, функция сможет принять любой существующий объект.

Вызовите методы swim(), каждый из которых определён в отдельном классе.

def in_the_pacific(fish):
fish.swim()

Создайте экземпляры классов Shark и Clownfish, если вы не сделали этого ранее. Так вы сможете вызывать их действия с помощью функции in_the_pacific().

.
def in_the_pacific(fish):
fish.swim()
wally = Shark()
casey = Clownfish()
in_the_pacific(wally)
in_the_pacific(casey)

При запуске программы вы получите такой вывод:

The shark is swimming.
The clownfish is swimming.

Несмотря на то, что в функции in_the_pacific() указан случайный объект fish, функция смогла правильно обработать классы.

Язык python полноценно поддерживает объектно-ориентированную разработку. Минимальный класс в python можно создать следующим образом:

Любой объект в python является объектом какого-либо класса:

Эти примеры показывают, что типы данных сами являются объектами класса type . Встроенная функция dir позволяет получить все атрибуты (поля и методы) объекта. Выведем для примера атрибуты объекта False класса bool :

Довольно много для такого простого объекта. Попробуем вызвать метод __or__ :

Вызов этого метода эквивалентен использованию оператора or . Мы обнаружили способ перегрузки операторов в python. Она выполняется с помощью определения "магических" методов, некоторые из которых мы рассмотрим ниже.

Поля и методы

Вспомним класс LorentzVector , с которым мы работали в разделе про классы в C++ и начнем писать его аналог на python:

Метод __init__ является конструктором. Объект self ссылается на сам объект класса (аналог this в C++) и является обязательным первым аргументом всех нестатических методов, включая конструктор. В конструкторе мы определили два поля класса: self.x и self.y . Также мы определили два метода: r2 возвращает квадрат пространственной компоненты вектора, и inv возвращает релятивистский инвариант, соответствующий вектору.

Все поля и методы класса в python являются публичными. Разделение интерфейса и деталей реализации происходит на уровне соглашения об именах полей и методов: если атрибут не является частью интерфейса, то его имя должно начинаться с двух подчеркиваний, например: __internal_variable .

Проверим работу класса LorentzVector :

Атрибуты могут определяться не только в конструкторе, но и в любом другом методе класса. Более того, атрибуты можно определять прямо в пользовательском коде:

Атрибуты класса (статические атрибуты) определяются сразу после названия класса:

Для задания статического метода необходимо использовать декоратор staticmethod :

Как и следовало ожидать, статический метод не имеет аргумента self . Декораторы — это инструмент python, позволяющий менять поведение функций. Технически — это функция, которая принимает на вход некоторую функцию, и возвращает новую функцию с тем же набором аргументов.

С помощью декоратора property можно делать вызов методов, не имеющих аргументов, похожим на обращение к полю класса:

Магические методы

Интеграция пользовательских классов в среду языка выполняется посредством определения специальных методов. Начнём с рассмотрения примера перегрузки арифметического оператора:

Аналогично выполняется перегрузка операторов вычитания ( __sub__ ), умножения ( __mul__ ), деления ( __div__ ), круглых скобок ( __call__ ), квадратных скобок ( __getitem__ ) и т.д.

Методы __str__ и __repr__ отвечают за текстовое представление объекта. Метод __str__ вызывается, когда объект передается в функцию print или в форматированную строку, и служит для "неформального" представления объекта. Метод __repr__ должен возвращать строку, которая содержит всю информацию о состоянии объекта и по которой объект может быть восстановлен. Если определен только метод __repr__ , то он будет вызываться в функции print вместо метода __str__ .

Если передать объект LorentzVector в функцию print , не определяя специальных методов, то мы получим что-то подобное:

Функция print вывела тип объекта и адрес, по которому он расположен в памяти.

Определим метод __repr__ :

Мы рассмотрели лишь некоторые из доступных специальных методов. Рекомендуем ознакомиться с полным списком в документации.

Наследование

Язык python позволяет выполнять наследование классов. Класс-потомок имеет доступ ко всем полям и методам класса-предка. Все классы в python являются наследниками класса object . Об класса object наш класс LorentzVector наследует большинство своих атрибутов:

Используем механизм наследования, чтобы реализовать тип релятивистского вектора иначе. Новый тип будет наследником именованного кортежа NamedTuple :

Новая реализация яснее показывает структуру нашего типа данных. Для создания объекта FourVector необходимо задать значения полям t и r :

При создании объекта fv4 мы нарушили соглашение и передали в поле r объект float вместо list . Это привело к ошибке при вызове метода r2 . При определении класса FourVector мы использовали аннотацию типов ( t: float ).

Больше информации о наследовании в python можно найти в документации.

Полиморфизм в python

Реализация полиморфизма в python сильно отличается от его реализации в C++. Полиморфизм в C++ реализуется с помощью инструментов наследования и шаблонов. Динамическая типизация python позволяет использовать гораздо более гибкие инструменты полиморфизма. Переменные, аргументы функций и атрибуты классов в python могут в разных контекстах иметь разные типы и даже менять тип со временем. Таким образом, все объекты в python изначально полиморфны.

В такой ситуации возникает вопрос о том как описывать ограничения на допустимые типы объектов. Здесь на помощь приходит принцип утиной типизации (duck typing), дословно состоящий в том, что "если что-то выглядит как утка и крякает как утка, значит это утка". Иными словами, если объект предоставляет необходимый интерфейс, то мы можем с ним работать вне зависимости от его типа. Для поддержки этого подхода в python реализованы инструменты для проверки свойств объектов:

Столь гибкая типизация приводит к необходимости качественной документации кода. Хорошим стилем является описание всех контрактов функции или метода в его строке комментария. Значительно улучшает читаемость кода и аннотация типов.

Резюме

В этом разделе мы выполнили краткий обзор инструментов python, реализующих парадигму объектно-ориентированного программирования. Обсудили создание классов; определение полей и методов; статических полей и методов; определение специальных методов, позволяющих интегрировать тип данных в среду языка; кратко рассмотрели наследование классов и принципы реализации полиморфизма в python.

Концепция ООП в python не является основной, как в C++, однако средства ООП составляют важную часть языка, и их понимание необходимо для грамотной разработки на python, поскольку все типы объектов в python являются классами.

Читайте также: