Введение в объектно-ориентированное программирование (ООП) на Python

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


2020-09-27 18:01

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

Текст публикации представляет собой незначительно сокращенный перевод статьи Дэвида АмосаObject-Oriented Programming (OOP) in Python 3.

Интерактив

Текст адаптирован в виде блокнота Jupyter, который можно запустить в интерактивном виде онлайн в среде Colab. Другие адаптированные таким образом тексты доступны в GitHub-репозитории.

Что собой представляет объектно-ориентированное программирование в Python?

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

Например, объект может представлять человекасвойствами«имя», «возраст», «адрес» иметодами(поведением) «ходьба», «разговор», «дыхание» и «бег». Или электронное письмо описывается свойствами «список получателей», «тема» и «текст», а также методами «добавление вложений» и «отправка».

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

Другой распространенной парадигмой программирования являетсяпроцедурное программирование, которое структурирует программу подобно рецепту. Такая программа предоставляет набор шагов в виде функций и блоков кода, которые последовательно выполняются для выполнения задачи.

Определим класс в Python

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

Допустим, вы хотите отслеживать работу сотрудников. Необходимо хранить основную информацию о каждом сотруднике: Ф.И.О., возраст, должность, год начала работы. Один из способов это сделать – представить каждого сотрудника в виде списка:

         kirk = ["James Kirk", 34, "Captain", 2265] spock = ["Spock", 35, "Science Officer", 2254] mccoy = ["Leonard McCoy", "Chief Medical Officer", 2266]     

У этого подхода есть ряд проблем.

Во-первых, ухудшаетсячитаемость кода. Чтобы понять, чтоkirk[0]ссылается на имя сотрудника, нужно перемотать код к объявлению списка.

Во-вторых, возрастает вероятность ошибки. В приведенном коде в спискеmccoyне указан возраст, поэтомуmccoy[1]вместо возраста вернет"Chief Medical Officer".

Отличный способ сделать такой тип кода более удобным – использовать классы.

Классы и экземпляры

Итак, для создания пользовательских структур данных используются классы. Классы определяют функции, называемые методами класса. Методы описывают поведение – те действия, которые объект, созданный с помощью класса, может выполнять с данными.

В этом туториале в качестве примера мы создадим класс Dog, который будет хранить информацию о характеристиках собак.

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

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

Другая аналогия: класс – это бланк анкеты. Экземпляр – анкета, которую заполнили ?. Подобно тому как люди заполняют одну и ту же форму своей уникальной информацией, так из одного класса может быть создано множество экземпляров. Обычно бланк анкеты сам по себе не нужен, он используется лишь для удобства оформления информации.

Как определить класс

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

         class Dog:     pass     

Здесь тело класса Dog пока состоит из одного оператора – ключевого слова-заполнителя pass. Заполнитель позволяет запустить этот код без вызова исключений.

Примечание

Имена классов Python принято записывать в нотации CamelCase.

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

Свойства, которые должны иметь все объекты классаDog, определяются в специальном методе с именем__init__(). Каждый раз, когда создается новый объектDog,__init __()присваивает свойствам объекта значения. То есть__init__()инициализирует каждый новый экземпляр класса.

Методу__init__()можно передать любое количество параметров, но первым параметром всегда является автоматически создаваемая переменная с именемself. Переменнаяselfссылается на только что созданный экземпляр класса, за счет чего метод__init__()сразу может определить новые атрибуты.

Обновим классDogс помощью метода__init__(), который создает атрибутыnameиage:

         class Dog:     def __init__(self, name, age):         self.name = name         self.age = age     

В теле__init__()две инструкции, задействующие переменнуюself:

  • self.name = nameсоздает атрибут с именемnameи присваивает ему значение параметраname.
  • self.age=ageсоздает атрибутageи присваивает ему значение параметраage.

Атрибуты, созданные в__init__()называютсяатрибутами экземпляра. Значение атрибута экземпляра зависит от конкретного экземпляра класса. Все объектыDogимеют имя и возраст, но значения атрибутовnameиageбудут различаться в зависимости от экземпляра Dog.

С другой стороны, можно создатьатрибуты класса– атрибуты, которые имеют одинаковое значение для всех экземпляров класса. Вы можете определить атрибут класса, присвоив значение имени переменной вне__init__():

         class Dog:     # Атрибут класса     species = "Canis familiaris"      def __init__(self, name, age):         self.name = name         self.age = age     

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

Теперь, когда у нас есть классDog, создадим нескольких собак! ?

Создание экземпляра класса в Python

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

         class Dog:     pass     

Создание нового экземпляра класса похоже на вызов функции:

         >>> Dog() <__main__.Dog at 0x7f6854738150>     

В памяти компьютера по указанному послеatадресу был создан новый объект типа__main__.Dog.

Важно, что следующий экземплярDogбудет создан уже по другому адресу. Это совершенно новый экземпляр и он полностью уникален:

         >>> Dog() <__main__.Dog at 0x7f6854625cd0>     
         >>> a = Dog() >>> b = Dog() >>> a == b False     

Хотяaиbявляются экземплярами класса Dog, они представляют собой два разных объекта.

Атрибуты класса и экземпляра

Теперь возьмем последнюю рассмотренную нами структуру класса:

         class Dog:     species = "Canis familiaris"     def __init__(self, name, age):         self.name = name         self.age = age     

Для создания экземпляров объектов класса необходимо указать кличку и возраст собаки. Если мы этого не сделаем, то Python вызовет исключение TypeError:

         >>> Dog() [...]  TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'     

Чтобы передать аргументы, помещаем значения в скобки после имени класса:

         buddy = Dog("Buddy", 9) miles = Dog("Miles", 4)     

Но ведь в описании класса__init__()перечислены три параметра – почему в этом примере передаются только два аргумента?

При создании экземпляра Python сам передает новый экземпляр в виде параметраselfв метод__init__(). Так что нам нужно беспокоиться только об аргументахnameиage.

После того как экземпляры созданы, записанные данные доступны в виде атрибутов экземпляра:

         >>> buddy.name 'Buddy' >>> buddy.age 9 >>> miles.name 'Miles' >>> miles.age 4 >>> buddy.species 'Canis familiaris' >>> miles.species 'Canis familiaris'     

Одним из важных преимуществ использования классов для организации данных является то, что экземпляры гарантированно имеют ожидаемые атрибуты. У всех экземпляров Dog гарантировано есть атрибутыspecies,nameиage.

Значения атрибутов могут изменяться динамически:

         >>> buddy.age = 10 >>> buddy.age 10 >>> miles.species = "Felis silvestris" >>> miles.species 'Felis silvestris'     

Экземпляры не зависят друг от друга. Изменение атрибута класса у одного экземпляра не меняет его у остальных экземпляров:

         >>> buddy.species 'Canis familiaris'     

Методы экземпляра

Методы экземпляра – это определенные внутри класса функции, которые могут вызываться из экземпляра этого класса. Так же, как и у метода__init__(), первым параметром метода экземпляра всегда являетсяself:

         class Dog:     species = "Canis familiaris"      def __init__(self, name, age):         self.name = name         self.age = age      # Метод экземпляра     def description(self):         return f"{self.name} is {self.age} years old"      # Другой метод экземпляра     def speak(self, sound):         return f"{self.name} says {sound}"     

Мы добавили два метода экземпляра, возвращающих строковые значения. Метод description возвращает строку с описанием собаки, метод speak принимает аргумент sound:

         >>> miles = Dog("Miles", 4) >>> miles.description() 'Miles is 4 years old' >>> miles.speak("Woof Woof") 'Miles says Woof Woof' >>> miles.speak("Bow Wow") 'Miles says Bow Wow'     

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

К примеру, когда вы создаете объект списка, вы можете использовать для отображения функциюprint():

         >>> names = ["Fletcher", "David", "Dan"] >>> print(names) ['Fletcher', 'David', 'Dan']      

Посмотрим, что произойдет, когда мы попробуем применить print() к объекту miles:

         >>> print(miles) <__main__.Dog object at 0x7f6854623690>     

В большинстве практических приложений информация о расположении объекта в памяти не очень полезна. Поведение объекта при взаимодействии с функцией print() можно изменить, определив специальный метод __str__():

         class Dog:     species = "Canis familiaris"      def __init__(self, name, age):         self.name = name         self.age = age      def __str__(self):         return f"{self.name} is {self.age} years old"      def speak(self, sound):         return f"{self.name} says {sound}"     
         >>> miles = Dog("Miles", 4) >>> print(miles) Miles is 4 years old     

Двойные символы подчеркивания в таких методах, как __init__() и __str__() указывают на то, что они имеют предопределенное поведение. Есть множество более сложных методов, которые вы можете использовать для настройки классов в Python, но это тема отдельной публикации.

Наследование от других классов в Python

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

Пример: место для выгула собак

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

Мы можем изменить классDog, добавив атрибутbreed(англ. порода):

         class Dog:     species = "Canis familiaris"      def __init__(self, name, age, breed):         self.name = name         self.age = age         self.breed = breed      def __str__(self):         return f"{self.name} is {self.age} years old"      def speak(self, sound):         return f"{self.name} says {sound}"     

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

         miles = Dog("Miles", 4, "Jack Russell Terrier") buddy = Dog("Buddy", 9, "Dachshund") jack = Dog("Jack", 3, "Bulldog") jim = Dog("Jim", 5, "Bulldog")     

У каждой породы собак поведение несколько отличаются. Например, разные породы по-разному лают: одни говорят «гав», другие делают «вуф». Используя только класс Dog, мы были бы должны указывать строку для аргумента sound метода speak() каждый раз, когда вызываем его в экземпляре Dog:

         >>> buddy.speak("Yap") 'Buddy says Yap' >>> jim.speak("Woof") 'Jim says Woof' >>> jack.speak("Woof") 'Jack says Woof'     

Передавать строку в каждый вызов методspeak()неудобно. Более того, строка, соответствующая звуку, который издает экземпляр, в идеале должна определяться атрибутомbreed.

Один из вариантов упростить взаимодействие с классомDog– создать дочерний класс для каждой породы. Это позволит расширить функциональные возможности наследующих дочерних классов. В том числе можно будет указать аргумент по умолчанию дляspeak.

Создаём дочерние классы

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

         class Dog:     species = "Canis familiaris"      def __init__(self, name, age):         self.name = name         self.age = age      def __str__(self):         return f"{self.name} is {self.age} years old"      def speak(self, sound):         return f"{self.name} says {sound}"     

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

         class JackRussellTerrier(Dog):     pass  class Dachshund(Dog):     pass  class Bulldog(Dog):     pass     

Дочерние классы действуют так же, как родительский класс:

         miles = JackRussellTerrier("Miles", 4) buddy = Dachshund("Buddy", 9) jack = Bulldog("Jack", 3) jim = Bulldog("Jim", 5)     

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

         >>> miles.species 'Canis familiaris' >>> buddy.name 'Buddy' >>> print(jack) Jack is 3 years old >>> jim.speak("Woof") 'Jim says Woof'     

Чтобы определить, к какому классу принадлежит определенный объект, используйте встроенную функцию type():

         >>> type(miles) __main__.JackRussellTerrier     

Чтобы определить, является ли miles экземпляром класса Dog, используем встроенную функцию isinstance():

         >>> isinstance(miles, Dog) True     

Объекты miles, buddy, jack и jim являются экземплярами Dog, но miles не является экземпляром Bulldog, а jack не является экземпляром Dachshund:

         >>> isinstance(miles, Bulldog) False >>> isinstance(jack, Dachshund) False      

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

Теперь дадим нашим собакам немного полаять.

Расширяем функциональность родительского класса

Что мы хотим сделать: переопределить в дочерних классах пород методspeak(). Чтобы переопределить метод, определенный в родительском классе, достаточно создать метод с тем же названием в дочернем классе:

         class JackRussellTerrier(Dog):     def speak(self, sound="Arf"):         return f"{self.name} says {sound}"     

Мы переопределили метод speak, добавив для породы JackRussellTerrier значение по умолчанию.

         >>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles says Arf'     

Мы по-прежнему можем передать какой-то иной звук:

         >>> miles.speak("Grrr") 'Miles says Grrr'     

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

         class Dog:     species = "Canis familiaris"      def __init__(self, name, age):         self.name = name         self.age = age      def __str__(self):         return f"{self.name} is {self.age} years old"      def speak(self, sound):         return f"{self.name} barks {sound}"     
         >>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles says Arf'     

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

         class JackRussellTerrier(Dog):     def speak(self, sound="Arf"):         return super().speak(sound)     
         >>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles barks Arf'     

Здесь при вызовеsuper().speak(sound)внутри классаJackRussellTerrier, Python ищет родительский классDog(на это указывает функцияsuper()), и вызывает его методspeak()с переданной переменнойsound. Именно поэтому выводится глаголbarks, а неsays, но с нужным нам звукомArf, который определен в дочернем классе.

Обратите внимание

В приведенных примерах иерархия классов очень проста. КлассJackRussellTerrierимеет единственный родительский классDog. В реальных примерах иерархия классов может быть довольно сложной.Функцияsuper()делает гораздо больше, чем просто ищет в родительском классе метод или атрибут. В поисках искомого метода или атрибута функция проходит по всей иерархии классов. Поэтому без должной осторожности использованиеsuper()может привести к неожиданным результатам.

Заключение

Итак, в этом руководстве мы разобрали базовые понятия объектно-ориентированного программирования (ООП) в Python. Мы узнали:

  • в чём отличия классов и экземпляров;
  • как определить класс;
  • как инициализировать экземпляр класса;
  • как определить методы класса и методы экземпляра;
  • как одни классы наследуются от других.

***

Данный материал мы подготовили при поддержке компании GeekBrains — нашего партнёра, предоставляющего помощь в освоении Python. Если вы хотите получит навыки, необходимые для разработки на Python, не тратя лишнее время и силы на поиск знаний, инструментов и привыкание к разному стилю чтения курсов, обратите внимание на факультет Python-разработки. Программа и преподаватели имеют высокие оценки учащихся, а при успешном прохождении курса онлайн-университет гарантирует не только диплом, но и трудоустройство.

Интересно, хочу посмотреть

Источники


Источник: proglib.io

Комментарии: