Итоги курса «Объектно-ориентированное программирование на Python»

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

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

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

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

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

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

Преимущества ООП

Особенности объектно-ориентированного программирования наделяют его рядом преимуществ.

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

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

Недостатки ООП

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

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

Особенности ООП в Python

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

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

В Python нет просто типов данных. Все типы – это классы.

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

И напоследок

Python – это все-таки скриптовый интерпретируемый язык. Хотя на нем пишутся в том числе крупные проекты, часто он используется в веб-разработке, системном администрировании для создания небольших программ-сценариев. В этом случае обычно достаточно встроенных средств языка, «изобретать» собственные классы излишне.

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

Курс с примерами решений практических работ:
pdf-версия


ООП в Python — принципы, классы, объекты, аттрибуты

upd:

Александр Зайков

20.6K

2

Содержание:развернуть

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

Главным понятием ООП является понятие программного объекта. Вообще говоря, большинство сущностей на планете Земля — это некие объекты.

И с частью из них мы взаимодействуем при помощи программирования. Банковский счёт, персонаж компьютерной игры или анимированный виджет сайта — всё это легко представить в виде объектов. Можно сказать, что объектно-ориентированное программирование позволяет смоделировать реальный объект в виде программного.

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

💁‍♂️ Итак, мы — разработчики игр. Наша студия трудится над новым автосимулятором. В игре будут представлены разные виды транспорта: легковые автомобили, гоночные, грузовые и пассажирские. Все их можно описать одним словом — автотранспорт. Сделав это, мы абстрагировались от деталей и, таким образом, определили класс. Объектом этого класса может быть, как Бьюик 1968-го года, так и грузовой Freightliner Columbia желтого цвета.

У класса есть свойства и функции (в ООП их называют методами).

  • Свойства — это характеристики, присущие данному конкретному множеству объектов.
  • Методы — те действия, которые они могут совершать.

Свойствами класса «автотранспорт» могут быть, например: год выпуска, вид и цвет. На уровне объектов это будет выглядеть так: Бьюик Электра — это объект класса «Автотранспорт» со следующими свойствами:

  • вид — легковой автомобиль;
  • цвет — чёрный;
  • год выпуска — 1968.

Можно сказать, что объект — это вполне конкретный экземпляр класса

Помимо физических атрибутов, которые описывают внешний вид и характеристики транспортного средства, автомобили обладают между собой и другими фундаментальными сходствами. Например, все они могут ехать, тормозить, переключать скорости, поворачивать и сигналить. В нашем случае, всё это — методы класса «Автотранспорт». То есть действия, которые любые объекты данного класса могут выполнять.

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

В Питоне класс «Автотранспорт» может выглядеть так:

# класс автотранспорт class MotorTransport(object): def __init__(self, color, year, auto_type): self.color = color self.year = year self.auto_type = auto_type # тормозить def stop(self): print("Pressing the brake pedal") # ехать def drive(self): print('WRRRRRUM!')

Теперь никто не помешает нам получить собственную красную феррари. Пусть и в симуляторе.

# создадим объект класса Автотранспорт ferrari_testarossa = MotorTransport('Red', 1987, 'passenger car') # жмём на газ и вперёд! ferrari_testarossa.drive() > WRRRRRUM!

Принципы ООП

Абстракция

Абстракция — это выделение основных, наиболее значимых характеристик объекта и игнорирование второстепенных.

Любой составной объект реального мира — это абстракция. Говоря «ноутбук», вам не требуется дальнейших пояснений, вроде того, что это организованный набор пластика, металла, жидкокристаллического дисплея и микросхем. Абстракция позволяет игнорировать нерелевантные детали, поэтому для нашего сознания это один из главных способов справляться со сложностью реального мира. Если б, подходя к холодильнику, вы должны были иметь дело с отдельно металлом корпуса, пластиковыми фрагментами, лакокрасочным слоем и мотором, вы вряд ли смогли бы достать из морозилки замороженную клубнику.

Полиморфизм

Полиморфизм подразумевает возможность нескольких реализаций одной идеи. Простой пример: у вас есть класс «Персонаж», а у него есть метод «Атаковать». Для воина это будет означать удар мечом, для рейнджера — выстрел из лука, а для волшебника — чтение заклинания «Огненный Шар». В сущности, все эти три действия — атака, но в программном коде они будут реализованы совершенно по-разному.

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

Это способность одного класса расширять понятие другого, и главный механизм повторного использования кода в ООП. Вернёмся к нашему автосимулятору. На уровне абстракции «Автотранспорт» мы не учитываем особенности каждого конкретного вида транспортного средства, а рассматриваем их «в целом». Если же более детализировано приглядеться, например, к грузовикам, то окажется, что у них есть такие свойства и возможности, которых нет ни у легковых, ни у пассажирских машин. Но, при этом, они всё ещё обладают всеми другими характеристиками, присущими автотранспорту.

Мы могли бы сделать отдельный класс «Грузовик», который является наследником «Автотранспорта». Объекты этого класса могли бы определять все прошлые атрибуты (цвет, год выпуска), но и получить новые. Для грузовиков это могли быть грузоподъёмность, снаряженная масса и наличие жилого отсека в кабине. А методом, который есть только у грузовиков, могла быть функция сцепления и отцепления прицепа.

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

Инкапсуляция — это ещё один принцип, который нужен для безопасности и управления сложностью кода. Инкапсуляция блокирует доступ к деталям сложной концепции. Абстракция подразумевает возможность рассмотреть объект с общей точки зрения, а инкапсуляция не позволяет рассматривать этот объект с какой-либо другой.

Вы разработали для муниципальных служб класс «Квартира». У неё есть свойства вроде адреса, метража и высоты потолков. И методы, такие как получение информации о каждом из этих свойств и, главное, метод, реализующий постановку на учёт в Росреестре. Это готовая концепция, и вам не нужно чтобы кто-то мог добавлять методы «открыть дверь» и «получить место хранения денег». Это А) Небезопасно и Б) Избыточно, а также, в рамках выбранной реализации, не нужно. Работникам Росреестра не требуется заходить к вам домой, чтобы узнать высоту потолков — они пользуются только теми документами, которые вы сами им предоставили.

Класс

— У тебя есть ключ? — Лучше! У меня есть рисунок ключа!

Классы, в некотором смысле, подобны чертежам: это не объекты сами по себе, а их схемы. Класс «банковских счетов» имеет строго определенные и одинаковые для всех атрибуты, но объекты в нём — сами счета — уникальны.

Как в Python создать класс

В Python классы и объекты по смыслу не отличаются от других языков. Нюансы в реализации. Для создания класса в Питоне необходимо написать инструкцию class, а затем выбрать имя. В простейшем случае, класс выглядит так:

class SimpleClass: pass

Для именования классов в Python обычно используют стиль «camel case», где первая буква — заглавная.

LikeThis

Конструктор

Метод, который вызывается при создании объектов, в ООП зовётся конструктором. Он нужен для объектов, которые изначально должны иметь какие-то значение. Например, пустые экземпляры класса «Студент» бессмысленны, и желательно иметь хотя бы минимальный обозначенный набор вроде имени, фамилии и группы.

В качестве Питоновского конструктора выступает метод __init__():

class Student: def __init__(self, name, surname, group): self.name = name self.surname = surname self.group = group alex = Student("Alex", "Ivanov", "admin")

Атрибуты класса

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

Поля могут быть статическими и динамическими:

  • Статические поля (поля класса) можно использовать без создания объекта. А значит, конструктор вам не нужен.
  • Динамические поля (поля объекта) задаются с помощью конструктора, и тут уже, как вы видели, экземпляр нужно создать, а полям присвоить значения.
class MightiestWeapon: # статический атрибут name = "Default name" def __init__(self, weapon_type): # динамический атрибут self.weapon_type = weapon_type

☝️ Обратите внимание — статический и динамический атрибут может иметь одно и то же имя:

class MightiestWeapon: # статический атрибут name = "Default name" def __init__(self, name): # динамический атрибут self.name = name weapon = MightiestWeapon("sword") print(MightiestWeapon.name) print(weapon.name)

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

Метод — это функция класса.

Например, у всех научно-фантастических космических кораблей есть бортовое оружие. И оно может стрелять.

class SpaceShip: def atack(self): print('Пиу!') star_destroyer = SpaceShip() star_destroyer.atack() > Пиу!

Что такое self?

Аналог этого ключевого слова в других языках — слово this. self — это всего лишь ссылка на текущий экземпляр класса.

🐈 Отличный пример с котофеями:

  1. Все котики умеют мурлыкать;
  2. Эта способность реализована в классе Кот, как метод Мурчать;
  3. Вы хотите, чтобы ваш кот по имени Пушок помурчал;
  4. Если сделать так: Кот.Мурчать, то мурлыкать начнут все коты во Вселенной;
  5. Но так как вам нужен один конкретный кот, то нужно вызвать метод иначе: self.Мурчать;
  6. Сделано. Пушок мурлыкает.

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

В Питоне не существует квалификаторов доступа к полям класса. Отсутствие аналогов связки public/private/protected можно рассматривать как упущение со стороны принципа инкапсуляции.

Декораторы

Декоратор — это функция-обёртка. В неё можно завернуть другой метод, и, тем самым, изменить его функциональность, не меняя код.

Объекты или экземпляры класса

Чем объекты отличаются от классов

Как уже было сказано, объект — это конкретный экземпляр класса. Все мы относимся к классу людей, но каждый из нас — уникальный объект этого класса.

Как создать объект класса в Python

Если у нас есть реализация класса, то его экземпляр создать очень просто:

class AirConditioner: def __init__(self, model, capacity): self.model = model self.capacity = capacity def turn_on(self): print('Now in the room will be cool') # создадим объект класса Кондиционер ballu = AirConditioner('BPAC-07', 785) ballu.turn_on() > Now in the room will be cool

Атрибуты объекта

Атрибуты класса могут быть динамическими и статическими. На уровне объекта они инициализируются так:

class MightiestWeapon: name = "Default name" def __init__(self, weapon_type): self. weapon_type = weapon_type # атрибут name можно переопределить и не создавая объекта MightiestWeapon.name = 'Steel Sword' print(MightiestWeapon.name) > Steal Sword # создаём объект и сразу же инициализируем динамический атрибут с помощью конструктора hero_sword = MightiestWeapon('sword') # и теперь, уже для конкретного объекта, можно задать имя hero_sword.name = 'Excalibur' # новое статическое имя по умолчанию для всего класса не изменится print(MightiestWeapon.name) > Steal Sword print(hero_sword.name) > Excalibur

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

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

# класс "Животное". Это достаточно абстрактный класс всего с одним методом "Издать звук". class Animal: def make_a_sound(self): print("Издаёт животный звук")

Мы все прекрасно знаем, что котики, к примеру, любят всё ронять, а собакены — рыть землю. Создадим два соответствующих класса-наследника:

# факт наследования в Python указывается при объявлении класса-наследника. # в скобках, после имени класса, указывается класс-родитель class Cat(Animal): def drop_everything(self): print('Вставай скорее, я всё уронил!') class Dog(Animal): def dig_the_ground(self): print('Однажды я докопаюсь до ядра планеты!')

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

Tom = Cat() Tom.make_a_sound() > Издаёт животный звук Tom.drop_everything() > Вставай скорее, я всё уронил!

Переопределение

Сейчас у нас и кошка, и собака просто «издают животные звуки», а хотелось бы, конечно, слышать звуки, свойственные именно этим животным. Для этого существует механика переопределения. Достаточно объявить в классе-наследнике метод с тем же названием, что и в базовом классе:

class Dog(Animal): def dig_the_ground(self): print('Однажды я докопаюсь до ядра планеты!') # отныне для объектов класса "Собака" будет выполняться именно эта реализация метода def make_a_sound(self): print('Гав-гав!') Balto = Dog() Balto. make_a_sound() > Гав-гав!

Документирование классов

Весь код нужно комментировать и документировать. Классы — не исключение. Стоит помнить, что код вы пишите не для себя, и вполне вероятно, что написанное вами придётся поддерживать другим людям. Комментарии повышают читаемость и увеличивают легкость восприятие кода в разы, тем самым экономя время и деньги.


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

Python OOP Basics — Python Cheatsheet

Объектно-ориентированное программирование

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

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

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

В Python инкапсуляция может быть достигнута с помощью модификаторов доступа. Модификаторы доступа — это ключевые слова, определяющие доступность атрибутов и методов в классе. В Python доступны три модификатора доступа: public, private и protected. Однако в Python нет явного способа определения модификаторов доступа, как в некоторых других языках программирования, таких как Java и C++. Вместо этого он использует соглашение об использовании префиксов подчеркивания для обозначения уровня доступа.

В данном примере кода класс MyClass имеет два атрибута: _protected_var и __private_var. _protected_var помечен как защищенный с помощью одного префикса подчеркивания. Это означает, что доступ к атрибуту возможен внутри класса и его подклассов, но не за пределами класса. __private_var помечен как частный с помощью двух префиксов подчеркивания. Это означает, что доступ к атрибуту возможен только внутри класса, а не за его пределами, даже в его подклассах.

Когда мы создаем объект класса MyClass, мы можем получить доступ к атрибуту _protected_var, используя имя объекта с одним префиксом подчеркивания. Однако мы не можем получить доступ к атрибуту __private_var по имени объекта, так как он скрыт от внешнего мира. Если мы попытаемся получить доступ к атрибуту __private_var, мы получим AttributeError, как показано в коде.

Таким образом, инкапсуляция — важная концепция объектно-ориентированного программирования, помогающая защитить детали реализации объекта. В Python мы можем добиться инкапсуляции, используя модификаторы доступа и используя префиксы подчеркивания для указания уровня доступа.

 # Определить класс с именем MyClass
класс MyClass:
    # Метод конструктора, который инициализирует объект класса
    защита __init__(сам):
        # Определяем защищенную переменную с начальным значением 10
        # Имя переменной начинается с символа подчеркивания, что указывает на защищенный доступ
        self. _protected_var = 10
        # Определите приватную переменную с начальным значением 20
        # Имя переменной начинается с двух символов подчеркивания, что указывает на закрытый доступ
        self.__private_var = 20
# Создаем объект класса MyClass
объект = МойКласс()
# Доступ к защищенной переменной по имени объекта и вывод ее значения
# Защищенная переменная доступна вне класса, но
# он предназначен для использования внутри класса или его подклассов
print(obj._protected_var) # вывод: 10
# Попробуйте получить доступ к приватной переменной, используя имя объекта, и вывести ее значение
# Доступ к закрытой переменной вне класса невозможен даже для его подклассов
# Это вызовет AttributeError, поскольку переменная недоступна вне класса
print(obj.__private_var) # AttributeError: объект 'MyClass' не имеет атрибута '__private_var'
 

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

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

В коде мы определяем класс с именем Animal, который имеет метод конструктора, который инициализирует объект класса с атрибутом имени, и метод с именем speak. Метод speak определен в классе Animal, но не имеет тела.

Затем мы определяем два подкласса с именами Dog и Cat, которые наследуются от класса Animal. Эти подклассы переопределяют метод speak класса Animal.

Мы создаем объект Dog с атрибутом имени «Rover» и объект Cat с атрибутом имени «Whiskers». Мы вызываем метод speak объекта Dog с помощью dog.speak(), и он печатает «Гав!» потому что метод говорить класса Dog переопределяет метод говорить класса Animal. Точно так же мы вызываем метод speak объекта Cat, используя cat.speak(), и он печатает «Мяу!» потому что метод говорить класса Cat переопределяет метод говорить класса Animal.

 # Определить класс с именем Animal
класс Животное:
    # Метод конструктора, который инициализирует объект класса атрибутом имени
    def __init__(я, имя):
        self.name = имя
    # Метод, определенный в классе Animal, но не имеющий тела
    # Этот метод будет переопределен в подклассах Animal
    Def говорить (сам):
        Распечатать("")
# Определить подкласс с именем Dog, который наследуется от класса Animal
класс Собака(Животное):
    # Переопределить метод speak класса Animal
    Def говорить (сам):
        печать("Гав!")
# Определить подкласс с именем Cat, который наследуется от класса Animal
класс Кошка(Животное):
    # Переопределить метод speak класса Animal
    Def говорить (сам):
        печать("Мяу!")
# Создайте объект Dog с атрибутом имени "Rover"
собака = Собака ("Ровер")
# Создайте объект Cat с атрибутом имени "Whiskers"
кошка = кошка ("Усы")
# Вызовите метод speak класса Dog и распечатайте вывод
# Метод speak класса Dog переопределяет метод speak класса Animal
# Таким образом, когда мы вызываем метод speak объекта Dog, он напечатает «Гав!»
dog. speak() # вывод: Гав!
# Вызовите метод speak класса Cat и распечатайте вывод
# Метод speak класса Cat переопределяет метод speak класса Animal
# Таким образом, когда мы вызываем метод speak объекта Cat, он напечатает «Мяу!»
cat.speak() # вывод: Мяу!
 

Полиморфизм

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

Переопределение метода — это когда подкласс предоставляет собственную реализацию метода, который уже определен в его родительском классе. Это позволяет подклассу изменять поведение метода без изменения его имени или подписи.

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

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

 #Класс Shape определен с помощью метода абстрактной области, который предназначен для переопределения подклассами.
Форма класса:
    область защиты (я):
        проходить
класс Прямоугольник (Форма):
    # Класс Rectangle определен с помощью метода __init__, который инициализирует
    # переменные экземпляра ширины и высоты.
    # Он также определяет метод площади, который вычисляет и возвращает
    # площадь прямоугольника с использованием переменных экземпляра ширины и высоты.
    def __init__(я, ширина, высота):
        self.width = ширина # Инициализировать переменную экземпляра ширины
        self.height = height # Инициализировать переменную экземпляра высоты
    область защиты (я):
        return self.width * self.height # Возвращает площадь прямоугольника
 # Класс Circle определен с помощью метода __init__
 # который инициализирует переменную экземпляра радиуса. 2
# Список фигур создается с одним объектом Rectangle и одним объектом Circle. Для
# цикл перебирает каждый объект в списке и вызывает метод области каждого объекта
# Результатом будет площадь прямоугольника (20) и площадь круга (153,86).
shape = [Rectangle(4, 5), Circle(7)] # Создать список объектов Shape
для формы в формах:
    print(shape.area()) # Вывод площади каждого объекта Shape
 

Абстракция

Абстракция — важная концепция объектно-ориентированного программирования (ООП), поскольку она позволяет сосредоточиться на основных характеристиках объекта или системы, игнорируя детали, не относящиеся к текущему контексту. Снижая сложность и скрывая ненужные детали, абстракция может сделать код более модульным, более легким для чтения и обслуживания.

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

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

Python не имеет встроенной поддержки абстрактных классов или интерфейсов, но их можно реализовать с помощью модуля abc (абстрактный базовый класс). Этот модуль предоставляет класс ABC и декоратор abstractmethod, которые можно использовать для определения абстрактных классов и методов.

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

 # Импорт модуля abc для определения абстрактных классов и методов
из abc импортировать ABC, abstractmethod
# Определите абстрактный класс с именем Shape, который имеет абстрактный метод с именем area
Форма класса (ABC):
    @абстрактный метод
    область защиты (я):
        проходить
# Определите класс Rectangle, который наследуется от Shape
класс Прямоугольник (Форма):
    def __init__(я, ширина, высота):
        собственная ширина = ширина
        self.height = высота
    # Реализовать метод площади для прямоугольников
    область защиты (я):
        вернуть self.width * self.height
# Определите класс Circle, который также наследуется от Shape
класс Круг (Форма):
    def __init__(я, радиус):
        self.radius = радиус
    # Реализовать метод площади для кругов
    область защиты (я):
        возврат 3,14 * собственный радиус ** 2
# Создайте список фигур, который включает в себя как прямоугольники, так и круги
формы = [прямоугольник (4, 5), круг (7)]
# Перебираем все фигуры в списке и печатаем их площадь
для формы в формах:
    печать (форма. область())
 

Это некоторые из основных принципов ООП в Python. Эта страница в настоящее время находится в разработке, и вскоре появятся более подробные примеры и пояснения.

Объектно-ориентированное программирование · HonKit

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

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

Примечание для программистов на статических языках

Обратите внимание, что четные целые числа рассматриваются как объекты (класса int ). Это отличается от C++ и Java (до версии 1.5), где целые числа являются примитивными родными типами.

См. help(int) для более подробной информации о классе.

Программисты C# и Java 1.5 найдут это похожим на бокс и распаковка концепт.

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

Поля бывают двух типов — они могут принадлежать каждому экземпляру/объекту класса или могут принадлежать самому классу. Они называются переменными экземпляра и переменными класса соответственно.

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

Сам

Методы класса имеют только одно специфическое отличие от обычных функций — они должны иметь дополнительное имя, которое должно быть добавлено в начало списка параметров, но вы не давайте значение для этого параметра при вызове метода, его предоставит Python. Эта конкретная переменная относится к самому объекту , и по соглашению ей дается имя self .

Хотя вы можете дать любое имя для этого параметра, настоятельно рекомендуется использовать имя самого себя — любое другое имя определенно осуждается. Есть много преимуществ в использовании стандартного имени — любой читатель вашей программы сразу узнает его, и даже специализированные IDE (интегрированные среды разработки) могут помочь вам, если вы используете сам .

Примечание для программистов на C++/Java/C#

self в Python эквивалентен указателю this в C++ и ссылке this в Java и C#.

Вам должно быть интересно, как Python дает значение для self и почему вам не нужно указывать для него значение. Пример прояснит это. Скажем, у вас есть класс с именем MyClass и экземпляр этого класса с именем 9. 0077 мой объект . Когда вы вызываете метод этого объекта как myobject.method(arg1, arg2) , он автоматически преобразуется Python в MyClass.method(myobject, arg1, arg2) — это все, что касается специального self . .

Это также означает, что если у вас есть метод, не принимающий аргументов, то вам все равно нужно иметь один аргумент — self .

Классы

Самый простой класс показан в следующем примере (сохранить как oop_simplestclass.py ).

 класс Лицо:
    pass # Пустой блок
р = человек ()
печать (р)
 

Выход:

 $ питон oop_simplestclass.py
Экземпляр <__main__.Person по адресу 0x10171f518>
 

Как это работает

Мы создаем новый класс, используя оператор class и имя класса. Далее следует блок операторов с отступом, формирующий тело класса. В этом случае у нас есть пустой блок, который обозначается цифрой 9. 0077 передать выписку.

Далее мы создаем объект/экземпляр этого класса, используя имя класса, за которым следует пара круглых скобок. (Мы узнаем больше о инстанцировании в следующем разделе). Для нашей проверки мы подтверждаем тип переменной, просто печатая ее. Он говорит нам, что у нас есть экземпляр класса Person в модуле __main__ .

Обратите внимание, что адрес памяти компьютера, где хранится ваш объект, также печатается. Адрес будет иметь другое значение на вашем компьютере, так как Python может хранить объект везде, где найдет место.

Методы

Мы уже обсуждали, что классы/объекты могут иметь методы точно так же, как и функции, за исключением того, что у нас есть дополнительная переменная self . Теперь мы увидим пример (сохраните как oop_method.py ).

 класс Лицо:
    def say_hi (я):
        print('Привет, как дела?')
р = человек ()
p.say_hi ()
# Предыдущие 2 строки также можно записать как
# Человек(). say_hi()
 

Выход:

 $ питон oop_method.py
Привет, как дела?
 

Как это работает

Здесь мы видим self в действии. Обратите внимание, что метод say_hi не принимает параметров, но по-прежнему имеет self в определении функции.

Метод

__init__

Многие имена методов имеют особое значение в классах Python. Теперь мы увидим значение метода __init__ .

Метод __init__ запускается, как только создается экземпляр объекта класса (т. е. создается). Метод полезен для выполнения любых инициализация (т.е. передача начальных значений вашему объекту), которую вы хотите сделать с вашим объектом. Обратите внимание на двойное подчеркивание в начале и в конце имени.

Пример (сохранить как oop_init.py ):

 класс Лицо:
    def __init__(я, имя):
        self.name = имя
    def say_hi (я):
        print('Привет, меня зовут', self. name)
p = Человек('Swaroop')
p.say_hi ()
# Предыдущие 2 строки также можно записать как
# Человек('Сваруп').say_hi()
 

Выход:

 $ питон oop_init.py
Привет, меня зовут Сваруп.
 

Как это работает

Здесь мы определяем метод __init__ как принимающий параметр name (наряду с обычным self ). Здесь мы просто создаем новое поле, также называемое name . Обратите внимание, что это две разные переменные, несмотря на то, что обе они называются «имя». Это не проблема, потому что обозначение с точками self.name означает, что есть что-то, называемое «имя», которое является частью объекта, называемого «я», а остальные имя — это локальная переменная. Поскольку мы явно указываем, какое имя имеем в виду, путаницы не возникает.

При создании нового экземпляра p класса Person мы делаем это, используя имя класса, за которым следуют аргументы в скобках: p = Person(‘Swaroop’).

Мы не вызываем явно метод __init__ . В этом особое значение этого метода.

Теперь мы можем использовать self.name в наших методах, что демонстрируется в методе say_hi .

Переменные класса и объекта

Мы уже обсудили функциональную часть классов и объектов (то есть методы), теперь давайте узнаем о части данных. Часть данных, то есть поля, представляют собой не что иное, как обычные переменные, которые привязаны к пространствам имен классов и объектов. Это означает, что эти имена допустимы только в контексте этих классов и объектов. Вот почему они называются пространства имен .

Существует два типа полей — переменные класса и переменные объекта, которые классифицируются в зависимости от того, принадлежат ли классу или объекту переменные соответственно.

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

Переменные объекта принадлежат каждому отдельному объекту/экземпляру класса. В этом случае каждый объект имеет свою собственную копию поля, т.е. они не являются общими и никак не связаны с одноименным полем в другом экземпляре. Пример облегчит понимание (сохраните как oop_objvar.py ):

Робот класса
:
    """Обозначает робота с именем."""
    # Переменная класса, подсчитывающая количество роботов
    население = 0
    def __init__(я, имя):
        """Инициализирует данные."""
        self.name = имя
        print("(Инициализация {})".format(self.name))
        # Когда этот человек создан, робот
        # добавляет в популяцию
        Робот.население += 1
    деф умереть (я):
        """Я умираю."""
        print("{} уничтожается!".format(self.name))
        Робот.население -= 1
        если Robot. population == 0:
            print("{} был последним.".format(self.name))
        еще:
            print("Работают еще {:d} роботы.".format(
                Робот.популяция))
    def say_hi (я):
        """Приветствие робота.
        Да, они могут это сделать."""
        print("Здравствуйте, мои хозяева зовут меня {}.".format(self.name))
    @классметод
    определение сколько_много (cls):
        """Выводит текущую численность населения."""
        print("У нас {:d} роботов.".format(cls.population))
droid1 = Робот ("R2-D2")
droid1.say_hi()
Robot.how_many()
droid2 = Робот ("C-3PO")
droid2.say_hi()
Robot.how_many()
print("\nЗдесь могут работать роботы.\n")
print("Роботы закончили свою работу. Так что давайте их уничтожим.")
droid1.die()
droid2.die()
Robot.how_many()
 

Выход:

 $ питон oop_objvar.py
(Инициализация R2-D2)
Приветствую, мои мастера зовут меня R2-D2.
У нас есть 1 робот.
(Инициализация C-3PO)
Приветствую, мои мастера зовут меня C-3PO.
У нас есть 2 робота.
Здесь роботы могут работать. 
Роботы закончили свою работу. Так давайте уничтожим их.
R2-D2 уничтожается!
Работает еще 1 робот.
C-3PO уничтожается!
C-3PO был последним.
У нас 0 роботов.
 

Как это работает

Это длинный пример, но он помогает продемонстрировать природу переменных класса и объекта. Здесь, 9Популяция 0077 принадлежит к классу Робот и, следовательно, является переменной класса. Переменная name принадлежит объекту (она назначается с помощью self ) и, следовательно, является объектной переменной.

Таким образом, мы ссылаемся на переменную класса населения как Robot.population , а не как self.population . Мы ссылаемся на переменную объекта имя , используя нотацию self.name в методах этого объекта. Помните об этой простой разнице между переменными класса и объекта. Также обратите внимание, что переменная объекта с тем же именем, что и переменная класса, скроет переменную класса!

Вместо Robot. population мы могли бы также использовать self.__class__.population , потому что каждый объект ссылается на свой класс через атрибут self.__class__ .

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

Мы пометили метод how_many как метод класса с помощью декоратора.

Декораторы

можно представить как ярлык для вызова функции-оболочки (то есть функции, которая «оборачивает» другую функцию, чтобы она могла что-то делать до или после внутренней функции), поэтому применение декоратора @classmethod — это то же самое. по телефону:

 сколько_сколько = метод класса (сколько_сколько)
 

Обратите внимание, что __init__ 9Метод 0078 используется для инициализации экземпляра Robot с именем. В этом методе мы увеличиваем число популяции на 1, так как у нас добавляется еще один робот. Также обратите внимание, что значения self.name специфичны для каждого объекта, что указывает на природу переменных объекта.

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

В этой программе мы также видим использование строк документации для классов и методов. Мы можем получить доступ к строке документации класса во время выполнения, используя Robot.__doc__ и строку документации метода как Robot.say_hi.__doc__

.

В методе die мы просто уменьшаем количество Robot.population на 1.

Все члены класса открыты. Одно исключение: если вы используете элементы данных с именами, использующими префикс с двойным подчеркиванием , например __privatevar , Python использует изменение имени, чтобы сделать его частной переменной.

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

Примечание для программистов на C++/Java/C#

Все члены класса (включая элементы данных) являются общедоступными , а все методы виртуальными в Python.

Наследство

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

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

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

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

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

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

Класс SchoolMember в этой ситуации известен как базовый класс или суперкласс 9.0070 . Классы Учитель и Студент называются производными классами или подклассами .

Теперь мы увидим этот пример как программу (сохраните как oop_subclass.py ):

 классШкольник:
    '''Представляет любого члена школы'''.
    def __init__(я, имя, возраст):
        self.name = имя
        возраст = возраст
        print('(Инициализированный SchoolMember: {})'.format(self.name))
    деф скажи (себя):
        '''Расскажи мои данные'''.
        print('Имя:"{}" Возраст:"{}"'.format(self.name, self.age), end=" ")
Классный руководитель (член школы):
    '''Представляет учителя'''.
    def __init__(я, имя, возраст, зарплата):
        SchoolMember.__init__(я, имя, возраст)
        собственная зарплата = зарплата
        print('(Инициализированный учитель: {})'.format(self.name))
    деф скажи (себя):
        SchoolMember.tell (сам)
        print('Зарплата: "{:d}"'.format(self.salary))
Студент класса (член школы):
    '''Представляет студента'''.
    def __init__(я, имя, возраст, оценки):
        SchoolMember.__init__(я, имя, возраст)
        self. marks = отметки
        print('(Инициализированный ученик: {})'.format(self.name))
    деф скажи (себя):
        SchoolMember.tell (сам)
        print('Отметки: "{:d}"'.format(self.marks))
t = Учитель («Миссис Шривидья», 40, 30000)
s = Студент('Swaroop', 25, 75)
# печатает пустую строку
Распечатать()
члены = [т, с]
для члена в членах:
    # Работает как для учителей, так и для студентов
    член.расскажи()
 

Выход:

 $ питон oop_subclass.py
(Инициализированный член школы: миссис Шривидья)
(Инициализированный Учитель: миссис Шривидья)
(Инициализированный член школы: Сваруп)
(Инициализированный ученик: Сваруп)
Имя: "Миссис Шривидья" Возраст: "40" Зарплата: "30000"
Имя: "Swaroop" Возраст: "25" Оценка: "75"
 

Как это работает

Чтобы использовать наследование, мы указываем имена базовых классов в кортеже, следующем за именем класса в определении класса (например, class Teacher(SchoolMember) ). Далее мы наблюдаем, что метод __init__ базового класса вызывается явно с использованием переменной self , чтобы мы могли инициализировать часть базового класса экземпляра в подклассе. Это очень важно помнить. Поскольку мы определяем метод __init__ в подклассах Teacher и Student , Python не вызывает автоматически конструктор базового класса SchoolMember , вы должны явно вызвать его самостоятельно.

Напротив, если мы не определили метод __init__ в подклассе, Python автоматически вызовет конструктор базового класса.

Хотя мы могли бы обрабатывать экземпляры Teacher или Student так же, как экземпляр SchoolMember , и получить доступ к методу tell SchoolMember , просто набрав Teacher.tell или . Студент.скажи , мы вместо этого определите другой метод tell в каждом подклассе (используя сказать метод SchoolMember частично), чтобы адаптировать его для этого подкласса. Поскольку мы сделали это, когда мы пишем Teacher.tell , Python использует метод tell для этого подкласса по сравнению с суперклассом.