Ооп в python 3: концепции, принципы и примеры реализации

Содержание

Почему в Python плохой ООП / Хабр

В Python ужасный ООП. Кликбейтный тезис, который имеет право на существование. Есть много языков, где ООП представлен не в самом классическом виде, ну или так принято считать. Прототипные JavaScript и Lua, Golang с прикреплением методов и прочие. Но «не такой как все» всегда ли синоним слова «неправильный»?  С чего мы вообще вязли, что ООП в Python не такой каким должен быть ООП? Что вообще является точкой отсчёта «правильности» ООП? Smalltalk или Simula 67? Вроде бы объектно-ориентированное программирование – это просто парадигма.. или уже догма?

В этом статье мы попробуем понять:

  • что не так с ООП в Python;

  • чем его ООП отличается от языков с эталонной по мнению многих реализацией ООП: Java, C# и С++;

  • почему разработчики языка Python решили сделать всё именно так.

Реализует этот текст автор YouTube-канала PyLounge Макс. Поехали!

Дисклеймер: В этой статье я хочу высказать свои «рассуждения на тему» и подчёркиваю, что не обладаю монополией на истину. Буду рад осудить альтернативное мнение в комментариях.

Для начала необходимо понять. Чем ООП в Python отличается от классической концепции и реализации в других ЯП.

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

Ключевыми особенностями ООП является понятия:

  • абстракция; 

  • инкапсуляция; 

  • наследование; 

  • полиморфизм.

Алан Кэй, создателя языка Smalltalk, одним из «отцов-основателей» ООП, говорил, что ООП подход заключается в следующем наборе принципов:

  1. Всё является объектом.

  2. Вычисления осуществляются путём взаимодействия (обмена данными) между объектами, при котором один объект требует, чтобы другой объект выполнил некоторое действие.

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

  4. Каждый объект является представителем класса, который выражает общие свойства объектов (таких, как целые числа или списки).

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

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

«ООП для меня означает лишь обмен сообщениями, локальное сохранение, и защита, и скрытие состояния, и крайне позднее связывание». (c) Алан Кэй

Другими словами, в соответствии с идеями Алана Кэя, самыми важными ингредиентами ООП является:

  1. Передача сообщений (то есть взаимодействие).

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

  3. Динамическое связывание.

Интересно, что указывается именно термин связывание, а терминов наследование и полиморфизм нет. Ведь полиморфизм бывает статический (раннее связывание) – это перегрузки и дженерики (шаблоны).  То есть Кэй, человек, который считается изобретателем термина «ООП» не считал важными частями ООП наследование и полиморфизм. Получается пропорции условны, а границы размыты.

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

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

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

Например, в Java можно определить поле как приватное, и тогда оно будет видно только членам этого класса. Также работает и С++, однако там есть концепция друзей (friend), которые могут видеть приватные поля других классов, что сильно критикуется.

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

Полиморфизм — это возможность обработки разных типов данных, т. е. принадлежащих к разным классам, с помощью «одной и той же» функции, или метода. На самом деле одинаковым является только имя метода, его исходный код зависит от класса. Поэтому в данном контексте под полиморфизмом понимается множество форм одного и того же слова – имени метода.

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

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

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

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

Современная идея ООП — это синтез всех их идей, а также идей Голдберга, Барбары Лисков, Дэвида Парнаса, Бертрана Мейера, Гюля Ага и других. Но никто из них не может утверждать, что же такое «ООП». Термины развиваются, как и задачи, которые изначально эти инструменты должны были решать.

А что же касаемо Python. Python полностью удовлетворяет всем перечисленным выше требования, а значит является «полностью объектно-ориентированным». ООП – просто стиль.

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

Отсутствие модификаторов доступа

В Python отсутствует деление на публичные, защищённые, приватные свойства и методы.   Многие вещи в Python основаны на соглашениях. Сокрытие данных реализуется чисто конвенционально. За счёт соглашения использовать подчёркивание у свойств и методов (защищённые члены). Да, можно использовать двойное подчёркивание, так называемый манглинг. Чисто технически это запрещает прямой доступ к данным и равносильно модификатору приват, но это скорее придуманный адептами классического ООП «грязный хак». Таким образом, в Python нет классического разделения на группы доступа, потому что Python доверяет разработчику. В этом плане Python ближе к С++.

«Да, я знаю, что ты можешь выстрелить себе в ногу, но я верю, что ты этого не сделаешь. Ведь не даром ты столько узнал, прежде чем приступить к написанию кода». (с) Python

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

Но почему разработчики языка не добавили такой привычный «предохранитель»? Ответ кроется в философии Python. Гвидо не любит что-то скрывать. Как он выразился в одном интервью: «мы все здесь взрослые по обоюдному согласию. Python доверяет вам. Он говорит: «Эй, если хочешь чтобы ковыряться в темных местах, я надеюсь, что у тебя есть уважительная причина, и вы не создаете проблем». Этого тезиса мы ещё коснёмся ниже. Пока просто запомните.

Вообще инкапсуляция – это не совсем про сокрытие. Инкапсуляция определяется как «процесс объединения элементов данных и функций в единое целое, называемое классом» или «отделение реализации от описания». Таким образом, номинально в Python всё соблюдается более чем верно.

Отсутствие интерфейсов

В языке Python нет как таковой конструкции как интерфейс (interface). К слову в С++ их тоже нет. Но что в Python, что в С++, есть механизмы, позволяющие так или иначе использовать интерфейсы.  Абстрактные классы ­– это хоть и немного другое, но функционалу отвечает и допускает некоторое упрощение концепции. На мой взгляд, отсутствие интерфейсов искусственный механизм избежания неоднозначности. Вот у тебя есть абстрактные классы, вот их и используй. С помощью абстрактных классов можно сделать всё тоже что и с интерфейсами, но не надо заморачиваться. Ведь Python идёт по пути простоты и убирает всё лишнее. Создатели языка даже конструкцию switch case выкинули, дабы «место не занимала».

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

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

Утиная типизация

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

Если что-то выглядит как утка, плавает как утка и крякает как утка, это наверняка и есть утка.

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

Тут во всей красе демонстрируется один из главных принципов Дзена Python«явное лучше, чем неявное». Если что-то выглядит как утка и крякает, то это утка, к чему погружаться в экзистенциальные копания и вопросы самоопределения? Будь проще и посмотри пример.

Поскольку Duck и Human это разные классы, Python повторно вызывает функцию fly_quack() для экземпляра класса Human. И хотя класс Human имеет похожие методы quack и fly , типы объектов были разными и поэтому все работает правильно и вызываются верные методы.

Константные методы

Нет способов предотвратить изменение состояния класса методами класса (константные методы), снова всё полагается на добрую волю программиста.

Вообще докопаться ещё можно много до чего. Например, не совсем стандартное описание статических методов и свойств, параметр self, MRO и многое многое другое.

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

The Zen of Python

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

По мнению многих Smalltalk — самый чистый ООП язык, но что даёт и какова цена этой чистоты? Можно написать очень хороший объектно-ориентированный код как на Smalltalk, так и на Python.

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

Есть языки, которые идут по одному из двух векторов развития: доверяют разработчику, дают средства и возможности, за что он может заплатить неправильностью своих решений. И языки, которые по максимуму всё запрещают, чтобы писать было просто и топорно. Все решения давно приняты за тебя, всем известно как делать правильно, например, Golang. С такой точки зрения «Почти все «фичи» — это сахарная кола, а программирование — это толстяк с диабетом»».

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

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

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

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

ООП в Python не лучше и не хуже, чем в других языка. Он другой. Такой каким концептуально его видел главный разработчик языка Гвидо ван Россум. ООП в Python это часть Дзена Python. Философии, для которой язык и был разработан.

Проблема в том, что люди пытаются перенять подходы из других языков, а не учатся использовать уникальные сильные стороны Python. У Python довольно надежная объектная модель, но это объектная модель Python, а не C++, Java или…кого-то другого.

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

А Python просто сейчас очень популярен. Он своего рода фронтмен, а тот кто на передовой, того обычно и критикуют. И да, я понимаю, что Python стремится быть как можно более простым, как завещал Эйнштейн: «все должно быть настолько простым, насколько это возможно, но не проще». Однако иногда Python всё же попадает в это «проще» чем надо, что может выливаться в проблемы.

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

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

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

Если мы напишем язык, который смогут использовать идиоты, в конце концов, только идиоты и будут его использовать. И да, это цена, которую придётся заплатить.


Закончу мысль довольно известной фразой: «Есть всего два типа языков программирования: те, на которые люди всё время ругаются, и те, которые никто не использует».

Наследование | Python: Введение в ООП

Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

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

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

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

Когда один класс становится наследником другого, то все атрибуты класса-предка (надкласса, superclass) становятся доступны классу-потомку (подклассу, subclass) — наследуются (достаются в наследство).

Что дает наследование

Наследование позволяет выделить общее для нескольких классов поведение и вынести его в отдельную сущность. То есть наследование является средством переиспользования кода (code reuse) — использования существующего кода для решения новых задач!

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

Как обычно, рассмотрим пример:

# этот класс у нас уже был
class Counter:
    def __init__(self):
        self. value = 0
    def inc(self):
        self.value += 1
    def dec(self):
        self.value -= 1
# А этот класс - новый. Наследник Counter
class NonDecreasingCounter(Counter):  # в скобках указан класс-предок
    def dec(self):
        pass

Если мы выполним эти объявления классов и посмотрим на поведение экземпляра NonDecreasingCounter, то увидим, что он работает как Counter — имеет те же методы и атрибуты (правда, при вызове метода .dec новый счетчик не изменяет текущее значение):

n = NonDecreasingCounter()
n.inc()
n.inc()
n.value  # 2
n.dec()
n.value  # 2

В объявлении NonDecreasingCounter присутствует метод dec, а вот откуда взялись value и inc? Они были взяты от предка — класса Counter! Данный факт даже можно пронаблюдать:

n.dec
# <bound method NonDecreasingCounter.dec of <__main__.NonDecreasingCounter object at 0x7f361b29c940>>
n.inc
# <bound method Counter. inc of <__main__.NonDecreasingCounter object at 0x7f361b29c940>>

Метод dec — метод класса NonDecreasingCounter, связанный с конкретным экземпляром NonDecreasingCounter. А вот inc — метод класса Counter, хоть и связанный с все тем же экземпляром класса-потомка.

Здесь вы можете увидеть сходство с взаимоотношениями между классом и его экземпляром: если экземпляр получает свой собственный атрибут, то этот атрибут заменяет атрибут класса. Точно так же объявления в классе-потомке заменяют собой атрибуты класса-предка, если имя используется то же самое — говорят, переопределяют (override).

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

Все будет

super()

Представим, что нас в целом устраивает класс Counter из предыдущего примера, но мы хотим при вызове

inc увеличивать значение дважды. Мы могли бы заменить в потомке весь метод и прописать внутри нового метода self.value += 2. Но если бы позже что-то поменялось в исходном классе Counter, то эти изменения не коснулись бы нашего метода.

Получается, что нам внутри метода потомка нужно получить доступ к методу предка. Методу с тем же именем! Если мы просто обратимся к self.inc, то получим ссылку на новый метод, ведь мы его переопределили.

Тут нам на помощь приходит специальная функция super:

class DoubleCounter(Counter):
    def inc(self):
        super().inc()
        super().inc()

Вызов super здесь заменяет обращение к self. При этом вы фактически обращаетесь к «памяти предков»: получаете ссылку на атрибут предка. Более того, в данном случае,

super().inc — это связанный с текущим экземпляром метод, то есть полноценная «оригинальная версия» из класса-предка. Если бы вы вдруг решили вручную вызвать метод класса предка, то вам бы пришлось использовать его не связанную версию:

class DoubleCounter(Counter):
    def inc(self):
        Counter. inc(self)  # явно обращаемся к методу класса предка
        Counter.inc(self)  # и передаем ссылку на экземпляр

Вызов super вместо явного вызова предка хорош не только тем, что автоматически связывает методы. При смене предка (такое бывает) в описании класса super учтет изменения, и вы получите доступ к поведению нового предка. Удобно!

super работает не только с методами, но и с атрибутами классов:

class A:
    x = 'A'
class B(A):
    x = 'B'
    def super_x(self):
        return super().x
B().x  # 'B'
B().super_x()  # 'A'

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

Функция super так названа в честь названия класса-предка: «superclass».

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

object

В прошлом мы не указывали предка в объявлениях классов, то есть писали так:

class Foo:
    pass

В Python3 такая запись равнозначна записи class Foo(object):. То есть, если класс-предок не указан, то таковым считается object — самый базовый класс в Python. Сейчас, в эпоху повсеместного использования Python3, указывать или не указывать наследование от object — дело вкуса.

ООП-упражнение Python — упражнения с классами и объектами

Это упражнение по объектно-ориентированному программированию (ООП) призвано помочь вам изучить и применить на практике концепции ООП. Все вопросы тестируются на Python 3.

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

Что включено в это упражнение Python OOP?

Это упражнение по классам и объектам ООП включает в себя 8 различных программ, вопросов и задач. Все решения протестированы на Python 3.

В этом упражнении ООП рассматриваются вопросы по следующим темам :

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

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

Используйте онлайн-редактор кода для решения упражнений.

  • ООП Упражнение 1. Создание класса с атрибутами экземпляра
  • ООП Упражнение 2: Создайте класс Vehicle без каких-либо переменных и методов
  • ООП Упражнение 3: Создайте дочерний класс Bus, который наследует все переменные и методы класса Vehicle
  • ООП Упражнение 4: Наследование классов
  • ООП Упражнение 5: Определите свойство, которое должно иметь одинаковое значение для каждого экземпляра класса (объекта)
  • ООП Упражнение 6: Наследование класса
  • ООП Упражнение 7: Проверка типа объекта
  • ООП Упражнение 8: Определите, является ли School_bus также экземпляром класса ТС

ООП Упражнение 1.

Создание класса с атрибутами экземпляра

Напишите программу на Python для создания класса транспортного средства с атрибутами экземпляра

max_speed и mileage .

См. :

  • Классы и объекты в Python
  • Переменные экземпляра в Python
Показать решение
 класс Транспортное средство:
    def __init__(я, максимальная_скорость, пробег):
        self.max_speed = максимальная_скорость
        self.mileage = пробег
модельX = Транспортное средство (240, 18)
print(modelX.max_speed, modelX.mileage) 

ООП Упражнение 2: Создайте класс Vehicle без каких-либо переменных и методов

Показать решение
 class Vehicle:
    pass 

ООП Упражнение 3: Создайте дочерний класс Bus, который унаследует все переменные и методы класса Vehicle

Учитывая :

 класс Vehicle:
    def __init__(self, name, max_speed, mileage):
        self.name = имя
        self. max_speed = максимальная_скорость
        self.mileage = mileage 

Создайте объект Bus, который унаследует все переменные и методы родительского класса Vehicle и отобразит его.

Ожидаемый результат:

 Название автомобиля: School Volvo Скорость: 180 Пробег: 12 

Обратитесь : Наследование в Python def __init__(self, name, max_speed, mileage): self.name = имя self.max_speed = максимальная_скорость self.mileage = пробег Класс Автобус(Автомобиль): проходить School_bus = Bus(«Школьный Volvo», 180, 12) print(«Название транспортного средства:», School_bus.name, «Скорость:», School_bus.max_speed, «Пробег:», School_bus.mileage)

ООП Упражнение 4: Наследование классов

Дано :

Создайте класс Bus , который наследуется от класса Vehicle . Присвойте аргументу емкости Bus.seating_capacity() значение по умолчанию , равное 50.

Используйте следующий код для родительского класса транспортного средства.

 класс Транспортное средство:
    def __init__(self, name, max_speed, mileage):
        self.name = имя
        self.max_speed = максимальная_скорость
        self.mileage = пробег
    def seat_capacity (я, вместимость):
        return f"Вместимость {self.name} равна {вместимости} пассажиров" 

Ожидаемый результат :

 Вместимость автобуса 50 пассажиров 

Refer :

  • Наследование в Python
  • Полиморфизм в Python 9 0018
Показать подсказку
  • Сначала используйте переопределение метода.
  • Затем используйте аргумент метода по умолчанию в определении метода seating_capacity() класса шины.
Показать решение класса
 Транспортное средство:
    def __init__(self, name, max_speed, mileage):
        self.name = имя
        self.max_speed = максимальная_скорость
        self.
mileage = пробег def seat_capacity (я, вместимость): return f"Вместимость {self.name} равна {вместимости} пассажиров" Класс Автобус(Автомобиль): # присвоить значение емкости по умолчанию def seat_capacity (я, вместимость = 50): вернуть super().seating_capacity(capacity=50) School_bus = Bus("Школьный Volvo", 180, 12) печать (School_bus.посадочные места ())

ООП Упражнение 5. Определите свойство, которое должно иметь одинаковое значение для каждого экземпляра класса (объекта). То есть, каждое транспортное средство должно быть белым.

В этом упражнении используйте следующий код.

 класс Транспортное средство:
    def __init__(self, name, max_speed, mileage):
        self.name = имя
        self.max_speed = максимальная_скорость
        self.mileage = пробег
Класс Автобус(Автомобиль):
    проходить
Класс Автомобиль(Автомобиль):
    пройти 

Ожидаемый результат :

 Цвет: Белый, Название автомобиля: School Volvo, Скорость: 180, Пробег: 12
Цвет: Белый, Название транспортного средства: Audi Q5, Скорость: 240, Пробег: 18 

Обратитесь : Переменная класса в Python

Показать подсказку

Определите цвет как переменную класса в классе транспортного средства

Показать решение

Созданные переменные в . __init__() называются переменными экземпляра. Значение переменной экземпляра зависит от конкретного экземпляра класса. Например, в решении все объекты Vehicle имеют имя и max_speed, но значения переменных name и max_speed будут различаться в зависимости от экземпляра Vehicle.

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

 класс Транспортное средство:
    # Атрибут класса
    цвет = "Белый"
    def __init__(self, name, max_speed, mileage):
        self.name = имя
        self.max_speed = максимальная_скорость
        self.mileage = пробег
Класс Автобус(Автомобиль):
    проходить
Класс Автомобиль(Автомобиль):
    проходить
School_bus = Bus("Школьный Volvo", 180, 12)
print(School_bus.color, School_bus.name, "Скорость:", School_bus.max_speed, "Пробег:", School_bus.mileage)
автомобиль = Автомобиль ("Ауди Q5", 240, 18)
print(car.
color, car.name, "Скорость:", car.max_speed, "Пробег:", car.mileage)

ООП Упражнение 6: Наследование классов

Дано :

Создайте дочерний класс Bus , который наследуется от класса Vehicle. Стоимость проезда по умолчанию для любого транспортного средства составляет мест * 100 . Если транспортное средство является экземпляром автобуса , нам нужно добавить дополнительные 10% к полной стоимости проезда в качестве платы за обслуживание. Таким образом, общая стоимость проезда для экземпляра автобуса станет окончательной суммой = общая стоимость проезда + 10% от общей стоимости проезда.

Примечание: Автобус рассчитан на 9 мест.0007 50 . поэтому окончательная сумма тарифа должна быть 5500. Вам необходимо переопределить метод fare() класса транспортного средства в классе автобуса.

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

 класс Транспортное средство:
    def __init__(я, имя, пробег, вместимость):
        self.name = имя
        self.mileage = пробег
        собственная мощность = мощность
    деф тариф (сам):
        возврат собственной емкости * 100
Класс Автобус(Автомобиль):
    проходить
School_bus = Автобус("Школьный Volvo", 12, 50)
print("Общая стоимость проезда на автобусе:", School_bus.fare()) 

Ожидаемый результат :

 Общая стоимость проезда на автобусе: 5500,0 
Показать решение
 класс Транспортное средство:
    def __init__(я, имя, пробег, вместимость):
        self.name = имя
        self.mileage = пробег
        собственная мощность = мощность
    деф тариф (сам):
        возврат собственной емкости * 100
Класс Автобус(Автомобиль):
    деф тариф (сам):
        сумма = супер().тариф()
        сумма += сумма * 10/100
        сумма возврата
School_bus = Автобус("Школьный Volvo", 12, 50)
print("Общая стоимость проезда на автобусе:", School_bus. fare())
 

ООП Упражнение 7. Проверка типа объекта

Напишите программу для определения, к какому классу принадлежит данный объект Bus.

Дано :

 класс Транспортное средство:
    def __init__(я, имя, пробег, вместимость):
        self.name = имя
        self.mileage = пробег
        собственная мощность = мощность
Класс Автобус(Автомобиль):
    проходить
School_bus = Bus("School Volvo", 12, 50) 
Показать подсказку

Использовать встроенную функцию Python type() .

Показать решение
 класс Транспортное средство:
    def __init__(я, имя, пробег, вместимость):
        self.name = имя
        self.mileage = пробег
        собственная мощность = мощность
Класс Автобус(Автомобиль):
    проходить
School_bus = Автобус("Школьный Volvo", 12, 50)
# Встроенный тип Python()
print(type(School_bus)) 

ООП Упражнение 8. Определите, является ли School_bus также экземпляром класса Vehicle

Учитывая :

 class Vehicle:
    def __init__(я, имя, пробег, вместимость):
        self. name = имя
        self.mileage = пробег
        собственная мощность = мощность
Класс Автобус(Автомобиль):
    проходить
School_bus = Bus("Школьный Volvo", 12, 50) 
Show Solution
 класс Транспортное средство:
    def __init__(я, имя, пробег, вместимость):
        self.name = имя
        self.mileage = пробег
        собственная мощность = мощность
Класс Автобус(Автомобиль):
    проходить
School_bus = Автобус("Школьный Volvo", 12, 50)
# Встроенная функция Python isinstance()
print(isinstance(School_bus, Vehicle)) 

Цифровой кот — объектно-ориентированное программирование на Python 3

Этот пост доступен как блокнот IPython здесь

Об этой серии

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

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

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

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

Print

Одним из наиболее заметных изменений, внесенных в Python 3, является преобразование ключевого слова print в функцию print() . Это действительно очень небольшое изменение по сравнению с другими изменениями, внесенными во внутренние структуры, но оно является наиболее ярким и будет источником 80% ваших синтаксических ошибок, когда вы начнете писать код Python 3.

Помните, что print теперь является функцией, поэтому пишите print(a) , а не напечатать .

Вернуться к объекту

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

Таким образом, если данные — это ингредиенты, а процедуры — это рецепты, кажется (и может быть) разумным хранить их отдельно.

Давайте займемся процедурным программированием на Python

 # Это некоторые данные
данные = (13, 63, 5, 378, 58, 40)
# Это процедура, которая вычисляет среднее
среднее значение (d):
    возвращаемая сумма (d)/len (d)
печать (среднее (данные))
 

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

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

Вы по-прежнему можете управлять им, разделяя данные и процедуры, например

 # Это две пронумерованные двери, изначально закрытые
дверь1 = [1, 'закрыта']
дверь2 = [2, 'закрыто']
# Эта процедура открывает дверь
защита open_door (дверь):
    дверь[1] = 'открыто'
open_door (дверь1)
печать (дверь1)
 

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

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

# Это две стандартные двери, изначально закрытые
дверь1 = [1, 'закрыта']
дверь2 = [2, 'закрыто']
# Это запираемая дверь, изначально закрытая и разблокированная
ldoor1 = [1, 'закрыто', 'разблокировано']
# Эта процедура открывает стандартную дверь
защита open_door (дверь):
    дверь[1] = 'открыто'
# Эта процедура открывает запираемую дверь
def open_ldoor (дверь):
    если дверь[2] == 'разблокирована':
        дверь[1] = 'открыто'
open_door (дверь1)
печать (дверь1)
open_ldoor(ldoor1)
печать (ldoor1)
 

Все работает, никаких сюрпризов в этом коде. Однако, как видите, мне пришлось искать другое название для процедуры, открывающей запертую дверь, так как ее реализация отличается от процедуры, открывающей стандартную дверь. Но, подождите… Я все еще открываю дверь, действие то же самое, и оно просто меняет статус самой двери. Так почему я должен помнить, что запертая дверь должна быть открыта с помощью open_ldoor() вместо open_door() , если глагол тот же?

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

Именно это приводит к понятию объекта : объект в контексте ООП представляет собой структуру, содержащую данные 9Процедуры 0263 и , действующие на них.

Что насчет типа?

Когда вы говорите о данных сразу нужно ввести понятие типа . Это понятие может иметь два значения, которые стоит упомянуть в информатике: поведенческое и структурное .

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

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

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

Классовые игры

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

В Python тип объекта представлен классом, используемым для создания объекта: то есть в Python слово тип имеет то же значение, что и слово class .

Например, один из встроенных классов Python — int , представляющее целое число

 >>> a = 6
>>> напечатать(а)
6
>>> печать (тип (а))
<класс 'целое'>
>>> печать (a. __class__)
<класс 'целое'>
 

Как видите, встроенная функция type() возвращает содержимое атрибута magic __class__ (магия здесь означает, что его значением управляет сам Python вне сцены). Тип переменной a или ее класс: int . (Это очень неточное описание этой довольно сложной темы, так что помните, что на данный момент мы только царапаем поверхность).

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

 >>> b = int()
>>> тип(б)
<класс 'целое'>
 

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

 >>> б = целое()
>>> печатать(б)
0
>>> с = целое (7)
>>> печатать(с)
7
 

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

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

 класс Дверь:
    def __init__(я, номер, статус):
        self.number = число
        self.status = статус
    деф открыть (самостоятельно):
        self.status = 'открыто'
    защита близко (я):
        self.status = 'закрыто'
 

Ключевое слово class определяет новый класс с именем Door ; все, что находится под классом , является частью класса. Функции, которые вы пишете внутри объекта, называются методами и ничем не отличаются от стандартных функций; номенклатура меняется только для того, чтобы подчеркнуть тот факт, что эти функции теперь являются частью объекта.

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

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

Переменные self.number и self.status называются атрибутами объекта. В Python методы и атрибуты являются 90 263 членами 90 264 объекта и доступны с точечным синтаксисом; разница между атрибутами и методами заключается в том, что последние могут быть вызваны (на жаргоне Python вы говорите, что метод — это callable ).

Как видите, метод __init__() должен создавать и инициализировать атрибуты, поскольку они нигде больше не объявлены. Это очень важно в Python и строго связано с тем, как язык обрабатывает типы переменных. Я подробно расскажу об этих концепциях при работе с полиморфизмом в следующем посте.

Класс можно использовать для создания конкретного объекта

 >>> door1 = Door(1, 'closed')
>>> тип(дверь1)
<класс '__main__.Дверь'>
>>> напечатать(дверь1.номер)
1
>>> print(door1.status)
закрыто
 

Теперь door1 является экземпляром класса Door ; type() возвращает класс как __main__. Door , поскольку класс был определен непосредственно в интерактивной оболочке, то есть в текущем основном модуле.

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

 >>> door1.open()
>>> напечатать(дверь1.номер)
1
>>> print(door1.status)
открыть
 

В данном случае был вызван метод open() экземпляра door1 . В метод open() не были переданы никакие аргументы, но если вы просмотрите объявление класса, вы увидите, что он был объявлен для приема аргумента ( сам ). Когда вы вызываете метод экземпляра, Python автоматически передает сам экземпляр методу в качестве первого аргумента.

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

Оставить комментарий

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *