Когда вступать в наследство: как оформить, пошлина, документы и сроки оформления

Содержание

Принять наследство: инструкция — новости Право.ру

Главные вопросы

Пресс-служба Федеральной нотариальной палаты и нотариус города Москвы Татьяна Ништ по просьбе «Право.ru» рассказали про самые частые ошибки, которые встречаются при вступлении в наследство, несмотря на известные нормы (например, полугодовой срок для вступления в наследство). Также Ништ рассказала про первые месяцы работы законов блока так называемой наследственной реформы. 

Какая самая распространённая ошибка при вступлении в наследство?

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

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

Обзор практики ВС

Если наследник срок пропустил или вовсе не обратился к нотариусу, то ему нужно будет доказать, что он фактически принял наследство. То есть управлял наследством, защищал его от расхищения и содержал имущество за свой счёт (например, платил за квартиру). Иначе нужно будет через суд восстанавливать сроки для принятия наследства, чтобы доказать, что причина пропуска была уважительной.

Если наследник не подавал заявление и у нотариуса нет сведений о том, что наследство принято кем-то из наследников фактически, то имущество умершего можно посчитать выморочным. Так называют имущество, на которое не нашлось претендентов. Оно переходит в собственность государства (по правилам ст. 1151 ГК).


Куда направлять заявление?

Частая ошибка – это направление заявления в нотариальную палату. Заявление нужно отправлять именно по последнему месту жительства умершего (п. 1 ст. 1152 Гражданского кодекса). Если же этот документ подали в нотариальную палату, то он не будет учитываться.

Часто заявления направляют по почте, но при этом подлинность подписи не заверил нотариус. Такое заявление тоже не может рассматриваться. При этом в течение полугода наследник может устранить все недостатки и принять наследство.

То есть после смерти наследодателя в течение шести месяцев (а не позднее) нужно обратиться к нотариусу (не в нотариальную палату) и подать заявление о принятии наследства либо о выдаче свидетельства о праве на наследство. Поданное заявление – самое бесспорное подтверждение принятия наследства.  

Про обновления 

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

Один из супругов в любое время, в том числе после смерти другого супруга, вправе совершить последующее завещание, а также отменить совместное завещание супругов (по п. 4 ст. 1118 ГК). А согласно п. 10 ст. 1140.1 ГК, наследодатель вправе совершить в любое время односторонний отказ от наследственного договора, если уведомит всех причастных о таком решении. Но при этом он обязан возместить другим сторонам наследственного договора убытки, которые появились из-за исполнения наследственного договора. 

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


В чём же разница между наследственным договором и договором ренты?

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

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

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


Общий порядок

Необходимо получить свидетельство о смерти – его отдадут в ЗАГС в обмен на паспорт умершего и медицинское свидетельство о смерти. Если другие родственники получили его за вас, но вы тоже претендуете на наследство, воспользуйтесь ресурсом «Розыск наследников», там вы можете ввести Ф.И.О. умершего и увидеть, открыто ли наследственное дело. Там же указаны контакты нотариуса, который его ведёт. Такое дело на человека может быть только одно: завести другое не получится, дела будут объединены. 

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

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

Самым главным документом в наследовании является свидетельство о праве на наследство. С ним можно оформить собственность на недвижимость (в Росреестре) или зарегистрировать на себя автомобиль (в ГИБДД). 

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

Как правильно вступить в наследство и оформить на себя недвижимость

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

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

Нюансы открытия

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

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

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

Сбор документов

Несмотря на наличие или отсутствие завещания, действия наследников по большому счету одинаковые, говорит Корума. По ее словам, первый документ, который следует получить — свидетельство о смерти. Оно выдается в ЗАГСе по месту жительства наследодателя. Для этого нужно предоставить в ЗАГС справку о смерти, которая оформляется уполномоченным органом, чаще всего больницей или моргом, замечает эксперт.

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

Как вступить в наследство — «Жизнь… Продолжается»

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

Варианты вступления в наследство

Важно, прежде всего, помнить, что согласно законодательству Российской Федерации наследование может быть двух типов, а именно:

  1. Наследование по завещанию.
  2. Наследование по закону.

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

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

 

Сроки вступления в наследство

Давайте разберемся со сроками вступления в наследство. Важно знать, что общий срок вступления в наследство составляет ШЕСТЬ календарных месяцев. Следует отметить, что срок начинает исчисляться со дня, который следует за днем наступления событий, повлекших за собой открытие наследства. Среди таких событий могут быть:

  • Смерть гражданина.
  • Отказ наследника от принятия наследства.
  • Вступление в законную силу судебного решения о признании гражданина погибшим.
  • Рождение наследника после смерти наследодателя.
  • Другие события, оговоренные законом.

Важное уточнение: если преимущественный наследник отказывается от вступления в наследство (к примеру, наследник первой линии – сын), для последующего наследника (наследника второй очереди, например, брата или сестры усопшего), отведен меньший срок на вступление в наследство – он составляет ТРИ календарных месяца.

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

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

 

Вступление в наследство: что нужно знать о реформе наследственного права

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

С учётом международного опыта

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

Составлять их смогут супруги. В документе они пропишут, кому и какое имущество достанется после смерти обоих или одного из них. Если же муж и жена потом разведутся, то завещание будет аннулировано. То же самое произойдёт, если впоследствии один из супругов решит составить отдельное.

  • Наследственный договор

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

  • Наследственный фонд

С 1 сентября в России появится институт наследственных фондов. Они будут создаваться нотариусами после смерти учредителя для содержания людей, которых он укажет в договоре. В Госдуме предлагают дать возможность создавать такие фонды уже при жизни, чтобы граждане могли переводить в фонды свои активы и прописывать способы управления ими после своей смерти.

Если человек не оставит завещание или оно будет признано недействительным, тогда его имущество разделяется по закону. Фото: ИТАР-ТАСС/ Валерий Шарифулин

Вступление в наследство: главные вопросы

Что важнее — закон или завещание?

Есть два способа наследования имущества — по общему закону и по завещанию. Гражданский кодекс РФ определяет приоритетным завещание. В завещании человек может распорядиться своим имуществом как угодно. Закон ставит одно условие: право на обязательную долю имеют несовершеннолетние дети, дети-инвалиды и нетрудоспособные родители и супруги.

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

Кто считается наследниками первой очереди?         

Очерёдность наследования касается случаев, когда имущество делится по закону. Если представителей первой очереди нет, право наследства переходит ко второй очереди и так далее. Всего в ГК РФ установлено семь очередей:

  1. Дети (от всех браков), супруг и родители наследодателя. Наследство делится поровну между всеми. Внуки покойного и их потомки —  по праву представления*
  2. Братья и сёстры, дедушки и бабушки
  3. Дяди и тёти. Двоюродные братья и сёстры — по праву представления*
  4. Прадедушки и прабабушки
  5. Двоюродные внуки, дедушки и бабушки
  6. Двоюродные правнуки, племянники, дяди и тёти
  7. Пасынки, падчерицы, отчим и мачеха

* Если законный наследник умер до открытия наследства или одновременно с наследодателем, то его доля переходит по праву представления  к потомкам и делится между ними поровну. 

Гражданский брак и наследство

Гражданские жёны и мужья, равно как и бывшие, на имущество покойного по закону претендовать не могут: принимать наследство может тот супруг, отношения с которым были официально зарегистрированы на момент смерти наследодателя. Единственный способ позаботиться о неофициальном супруге — упомянуть его в завещании.  

ЧИТАЙТЕ ТАКЖЕ: В России появятся «прижизненные» наследственные фонды

Росреестр

Как зарегистрировать право на наследство? Куда для этого нужно пойти и какие документы подготовить? И в какие сроки важно успеть все это сделать?

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

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

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

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

Пятое правило: получите у нотариуса свидетельство о праве на наследство, оно выдается спустя полгода после смерти наследодателя. С этим свидетельством нужно обратиться туда, где наследство можно получить: если это деньги, – в банк, квартира – в Росреестр, машина – в ГИБДД, для переоформления.

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

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

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

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

Телесюжет по ссылке: https://www.m24.ru/videos/video/16082018/182263

Вступление в наследство без завещания после смерти – Как вступить в наследство – Финсовет

Исчерпывающая информация по этому поводу содержится в статье 1117 «Недостойные наследники» ГК РФ, раздел V, глава 61:

«Статья 1117. Недостойные наследники

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

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

2. По требованию заинтересованного лица суд отстраняет от наследования по закону граждан, злостно уклонявшихся от выполнения лежавших на них в силу закона обязанностей по содержанию наследодателя.

3. Лицо, не имеющее права наследовать или отстраненное от наследования на основании настоящей статьи (недостойный наследник), обязано возвратить в соответствии с правилами главы 60 настоящего Кодекса все имущество, неосновательно полученное им из состава наследства.

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

5. Правила настоящей статьи соответственно применяются к завещательному отказу * (статья 1137). В случае, когда предметом завещательного отказа было выполнение определенной работы для недостойного отказополучателя или оказание ему определенной услуги, последний обязан возместить наследнику, исполнившему завещательный отказ, стоимость выполненной для недостойного отказополучателя работы или оказанной ему услуги».

Вступление в наследство: где подавать документы?

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

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

Согласно законодательству, заведение наследственного дела (НД) должно осуществляться там, где открывалось наследство. Положения Гражданского кодекса Российской Федерации таким местом считает последний населенный пункт, в котором наследодатель проживал постоянно либо большую часть времени до момента своей кончины. В случаях, когда место, где проживал наследодатель, не установлено либо он проживал за границей, то НД заводится в том регионе России, где размещена собственность, которая должна отойти наследникам. НД может быть открыто лишь одно, и открывается оно у нотариуса палаты, юрисдикция которой распространяется на населенный пункт, признанный местом, где открыто наследство.

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

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

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

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

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

Законодательство допускает отправку заявления на электронную почту нотариуса в населенном пункте, где открыто НД. Если электронный документ отправляется через представителя наследника, другого полноправного наследника или курьера, то гражданин, претендующий на наследство, явившись лично, получает на руки цифровой носитель информации с текстом документа (флэш-карту, диск и пр.). Для передачи носителя информации нотариусу округа, где открыто НД, доверенность лицам, передающим носитель, не требуется. Указанные лица не имеют отношения к вопросам, напрямую касающихся НД.

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

Единой информационной системе нотариата (ЕИС). Через этот ресурс, с использованием всероссийского портала Государственных услуг, документы направляются в Федеральную нотариальную палату. Получить доступ ко всем возможностям ЕИС граждане (конечно же и претендующие на имущество умерших родственников) могут зарегистрировавшись и авторизовавшись на страницах нового ресурса.

Передача документации нотариусу по месту открытия НД через ЕИС действует с 29.12.2020 года. Именно с этой даты в обрел юридическую силу Федеральный закон от 27.12.2019 № 480-ФЗ, по сути, внедривший в России цифровой нотариат, и 232-й приказ Министерства юстиции РФ, регулирующий процедуру дистанционного совершения нотариальных действий. Тем же приказом регламентируется возможность перечислить средства за оказанные нотариусом услуги с банковской карты или иным дистанционным способом. все статьи

Состав vs. наследование: как выбрать?

В начале …

… не было наследования и композиции, только код.

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

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

Было темное время.

Затем мир озарил объектно-ориентированное программирование (ООП)… И мир в значительной степени игнорировал это в течение нескольких десятилетий. 1 . Вплоть до графических пользовательских интерфейсов 2 , которые оказались очень-очень нуждающимися в ООП. Когда вы нажимаете кнопку в окне, что может быть проще, чем отправить этой кнопке (или ее суррогату) сообщение Click 3 ?

После этого ООП взлетело. Было написано множество 4 книг и появилось бесчисленное количество статей 5 .Итак, к настоящему времени все подробно разбираются в объектно-ориентированном программировании, не так ли?

К сожалению, код (и Интернет) говорит «нет».

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

Мантры, считающиеся вредными

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

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

Начнем с основ.

Определения

Вот определение объектно-ориентированного программирования, которое я буду использовать до конца статьи: предположим, что у нас есть «классический» язык ООП, т.е.е., который поддерживает классы с полями, методами и одиночным наследованием. Без интерфейсов, без миксинов, без аспектов, без множественного наследования, без делегатов, без замыканий, без лямбда-выражений, только основы:
  • Класс: именованное понятие в доменном пространстве с необязательным суперклассом, определенным как набор полей и методов.
  • Поле: именованное свойство некоторого типа, которое может ссылаться на другой объект (см. Состав)
  • Метод: именованная функция или процедура с параметрами или без них, реализующая некоторое поведение класса.
  • Наследование: класс может наследовать — использовать по умолчанию — поля и методы своего суперкласса. Наследование является транзитивным, поэтому класс может наследовать от другого класса, который наследуется от другого класса, и так далее, вплоть до базового класса (обычно Object, возможно, неявного / отсутствующего). Подклассы могут переопределять некоторые методы и / или поля, чтобы изменить поведение по умолчанию.
  • Состав: когда тип поля является классом, поле будет содержать ссылку на другой объект, тем самым создавая связь между ними.Не вдаваясь в нюансы разницы между простой ассоциацией, агрегацией и композицией, давайте интуитивно определим композицию, как когда класс использует другой объект для обеспечения некоторых или всех своих функций.
  • Инкапсуляция: взаимодействуя с объектами, а не напрямую с реализацией методов и полей, мы скрываем и защищаем реализацию класса. Если потребитель ничего не знает об объекте, кроме его общедоступного интерфейса, он не может полагаться на какие-либо внутренние детали реализации.

Наследование является фундаментальным

Наследование фундаментально объектно-ориентированного программирования. Язык программирования может иметь объекты и сообщения, но без наследования он не является объектно-ориентированным (просто «объектно-ориентированным», но все же полиморфным).

… и композиция

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

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

Так в чем суета?

Композиция и наследование имеют фундаментальное значение, так что в этом такого?

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

Композицию понять довольно просто — композицию мы видим в повседневной жизни: у стула есть ножки, стена из кирпича и раствора и так далее. Хотя определение наследования простое, оно может стать сложным и запутанным, если его использовать неразумно. Наследование — это скорее абстракция, о которой мы можем только говорить, а не касаться напрямую. Хотя во многих ситуациях можно имитировать наследование с помощью композиции, часто это бывает громоздко. Цель композиции очевидна: сделать из частей целое.Цель наследования немного сложнее, потому что наследование служит двум целям: семантике и механике.

Семантика наследования

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

Механика наследования

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

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

Как злоупотребить наследованием — Пример 1

Давайте начнем с простого и чрезвычайно распространенного примера неправильного использования наследования:
 class Stack extends ArrayList {
    public void push (значение объекта) {…}
    public Object pop () {…}
}
 

Этот класс будет функционировать как стек, но его интерфейс фатально раздут. Открытый интерфейс этого класса — это не просто push и pop, как можно было бы ожидать от класса с именем Stack, он также включает в себя получение, установку, добавление, удаление, очистку и множество других сообщений, унаследованных от ArrayList, которые не подходят для Куча.

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

  1. Семантически утверждение «Стек является списком массивов» неверно; Стек не является правильным подтипом ArrayList. Предполагается, что стек должен обеспечивать выполнение последним вошел — первым вышел, ограничение легко удовлетворяется интерфейсом push / pop, но не обеспечивается интерфейсом ArrayList.
  2. Механически наследование от ArrayList нарушает инкапсуляцию; использование ArrayList для хранения коллекции объектов стека — это вариант реализации, который следует скрыть от потребителей.
  3. Наконец, реализация стека путем наследования от ArrayList является междоменной связью: ArrayList — это коллекция с произвольным доступом; Стек — это концепция организации очередей со специально ограниченным (неслучайным) доступом 8 . Это разные области моделирования.
Последний вопрос важен, но немного тонок, поэтому давайте рассмотрим его на другом примере.

Как злоупотребить наследованием — Пример 2

Создание класса концепции предметной области путем наследования от класса реализации является распространенным злоупотреблением наследованием. Например, предположим, что мы хотим что-то сделать с определенным сегментом наших клиентов. Легко и очевидно, что нужно создать подкласс ArrayList, назвать его CustomerGroup и начать кодирование, верно?

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

  1. ArrayList уже является подклассом списка, коллекция утилит — реализация класса .
  2. CustomerGroup — это еще один подкласс — домен , класс .
  3. Классы домена должны использовать классы реализации , а не наследовать от них.
Пространство реализации должно быть невидимым на уровне домена. Когда мы думаем о том, что делает наше программное обеспечение, мы работаем на уровне домена; мы не хотим отвлекаться на подробности о том, как он работает. Если мы сосредоточимся только на «повторном использовании кода» через наследование, мы будем неоднократно попадать в эту ловушку.

Проблема не в одиночном наследовании

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

. Отношения наследования не должны пересекать границы домена (домен реализации или домен приложения).Заставляя CustomerGroup наследовать от ArrayList , но также и от (скажем) DemographicSegment, эти два поддомена запутываются, что сбивает с толку таксономию.

Предпочтительное (по крайней мере, для меня!) Решение — унаследовать от служебных классов столько, сколько необходимо для реализации ваших механических структур, а затем использовать эти структуры в доменных классах посредством композиции, а не наследования. Позвольте мне повторить это:

Если вы не создаете класс реализации, вы не должны наследовать от класса реализации.

Это одна из самых распространенных проблем новичков — ведь это так удобно! — и причины, по которым это неправильно, не часто обсуждаются в литературе по программированию, поэтому я повторю это еще раз: классы области приложения должны использовать классы реализации , а не быть одними из них. Храните эти таксономии / домены отдельно.

Итак, когда и как нам использовать наследование?

Использование колодца наследования

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

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

Как определиться: состав или наследование?

Если у вас есть ситуация, когда сработает либо композиция, либо наследование, подумайте о разделении обсуждения дизайна на две части:
  1. Представление / реализация концепций вашей предметной области — одно измерение
  2. Семантика концепций вашей предметной области и их отношения друг к другу — это второе измерение
В общем, наследование в пределах одного из этих параметров допустимо.Проблема возникает, когда мы забываем разделить два измерения и начинаем наследование через межпространственные границы.

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

Ничто не заменит объектное моделирование и критическое дизайнерское мышление.Но если у вас есть какие-то рекомендации, учтите их —

Наследование следует использовать только в следующих случаях:

  1. Оба класса находятся в одном логическом домене
  2. Подкласс — это правильный подтип суперкласса
  3. Реализация суперкласса необходима или подходит для подкласса
  4. Усовершенствования, внесенные подклассом, в основном аддитивны.
Бывают моменты, когда все эти вещи сходятся:
  • Моделирование предметной области более высокого уровня
  • Каркасы и расширения каркасов
  • Дифференциальное программирование
Если вы не делаете ничего из этого, вероятно, вам не понадобится наследование классов очень часто.«Предпочтение» композиции — это не вопрос «лучше», это вопрос «наиболее подходящего» для ваших нужд в конкретном контексте.

Надеюсь, эти рекомендации помогут вам заметить разницу.

Удачного кодирования!

Приложение

Особая благодарность за ценный вклад и комментарии следующим работникам мысли: Питу Ходжсону, Тиму Брауну, Скотту Робинсону, Мартину Фаулеру, Минди Ор, Шону Ньюхэму, Сэму Гибсону и Махендре Кария.
1. Первый официально объектно-ориентированный язык, SIMULA 67, родился в 1967 году.Объектно-ориентированному программированию 48 лет!
2. Программисты систем и приложений приняли C ++ в середине 1980-х, но повсеместное распространение ООП пришлось ждать еще десять лет.
3. да, я слишком упрощаю, игнорируя слушателей / делегатов событий и т. Д .; стараюсь сделать эту статью короткой!
4. Amazon заявляет о 24 777 книгах по теме объектно-ориентированного программирования на момент написания этой статьи
5. Поиск в Google заявляет ~ 8 миллионов результатов для точной фразы «объектно-ориентированное программирование» на момент написания этой статьи
6. Поиск в Google дает ориентировочные результаты 37 600 результатов для точной фразы «наследство — зло» на момент написания этой статьи
7.Семантика (интерфейсы) и механика (представление) могут быть разделены за счет дополнительной сложности языка; см., например, спецификацию языка D К. Дж. Дэйта и Хью Дарвена.
8. С некоторой грустью обратите внимание на то, что класс Java Stack наследуется от Vector.
9. Проектирование классов для повторного использования посредством наследования выходит за рамки данной статьи. Просто имейте в виду, что потребители экземпляров и подклассов имеют разные потребности, и обе они должны быть удовлетворены дизайном вашего класса.

Как Python super () работает с множественным наследованием?

Это подробно подробно описано самим Гвидо в своем блоге «Порядок разрешения методов» (включая две более ранние попытки).

В вашем примере Third () вызовет First .__ init__ . Python ищет каждый атрибут в родительских классах, поскольку они перечислены слева направо. В этом случае мы ищем __init__ . Итак, если вы определите

  класс Третий (Первый, Второй):
    ...
  

Python начнет с рассмотрения First , и, если First не имеет атрибута, тогда он будет смотреть на Second .

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

Так, например, если у вас было:

  класс Первый (объект):
    def __init __ (сам):
        печать "первый"

класс Второй (Первый):
    def __init __ (сам):
        печать "второй"

класс Третий (Первый):
    def __init __ (сам):
        печать "третья"

класс Четвертый (Второй, Третий):
    def __init __ (сам):
        super (Четвертый, сам) .__ init __ ()
        напечатать "вот и все"
  

MRO будет [четвертый, второй, третий, первый].

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

Пример неоднозначного ТОиР:

  класс Первый (объект):
    def __init __ (сам):
        печать "первый"
        
класс Второй (Первый):
    def __init __ (сам):
        печать "второй"

класс Третий (Первый, Второй):
    def __init __ (сам):
        печать "третья"
  

Должен ли MRO третьего быть [Первый, Второй] или [Второй, Первый] ? Нет очевидного ожидания, и Python выдаст ошибку:

  TypeError: Ошибка при вызове баз метакласса
    Невозможно создать согласованный порядок разрешения методов (MRO) для баз Второй, Первый
  

Почему в приведенных выше примерах не хватает вызовов super () ? Смысл примеров — показать, как строится ТОиР.Они , а не , предназначены для печати "первая \ nсекунда \ третья", или что-то еще. Вы можете — и, конечно же, должны поэкспериментировать с примером, добавить вызовов super () , посмотреть, что происходит, и получить более глубокое понимание модели наследования Python. Но моя цель здесь — упростить и показать, как строится ТОиР. И он построен, как я объяснил:

  >>> Четвертый .__ mro__
(,
 <класс '__main __. Второй'>, <класс '__main__.Третий '>,
 ,
 <тип 'объект'>)
  

c # — Почему бы не унаследовать от списка?

Дизайн> Реализация

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

Объект — это набор данных и поведения.

Итак, ваши первые вопросы должны быть:

  • Какие данные содержит этот объект в создаваемой мной модели?
  • Какое поведение демонстрирует этот объект в этой модели?
  • Как это может измениться в будущем?

Имейте в виду, что наследование подразумевает отношение «isa» (является), тогда как композиция подразумевает отношение «имеет» (hasa).Выберите наиболее подходящий для вашей ситуации, учитывая, что может происходить по мере развития вашего приложения.

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

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

Учитывать особенности дизайна

Взгляните на List и IList в MSDN или Visual Studio.Посмотрите, какие методы и свойства они предоставляют. По вашему мнению, все эти методы похожи на то, что кто-то хотел бы сделать с командой FootballTeam ?

Имеет ли для вас смысл footballTeam.Reverse () ? Выглядит ли footballTeam.ConvertAll () как то, что вам нужно?

Это не вопрос с подвохом; ответ действительно может быть «да». Если вы реализуете / наследуете List или IList , вы застряли с ними; если это идеально подходит для вашей модели, сделайте это.

Если вы решите да, это имеет смысл, и вы хотите, чтобы ваш объект обрабатывался как коллекция / список игроков (поведение), и поэтому вы хотите реализовать ICollection или IList , путем все средства делают так. Условно:

  класс FootballTeam: ... ICollection 
{
    ...
}
  

Если вы хотите, чтобы ваш объект содержал коллекцию / список игроков (данных), и, следовательно, вы хотите, чтобы коллекция или список были свойством или членом, обязательно сделайте это.Условно:

  класс Футбольная команда ...
{
    public ICollection  Players {get {...}}
}
  

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

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

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

Перейти к реализации

После того, как вы определились с данными и поведением, вы можете принять решение о реализации. Сюда входят конкретные классы, от которых вы зависите посредством наследования или композиции.

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

Мысленный эксперимент

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

  класс FootballTeam: ... ICollection ,
                         ICollection ,
                         ICollection 
{
    ....
}
  
Несмотря на дизайн

, на данном этапе в C # вы все равно не сможете реализовать все это путем наследования от List , поскольку C # «только» поддерживает одиночное наследование. (Если вы пробовали эту злобу в C ++, вы можете считать это хорошей вещью.) Реализация одной коллекции через наследование и одной через композицию, вероятно, приведет к тому, что покажется грязным. А такие свойства, как Count , сбивают пользователей с толку, если вы не реализуете ILIst .Подсчитайте и IList .Count и т. Д. Явно, и тогда они будут просто болезненными, а не запутанными. Вы можете видеть, к чему это идет; Инстинктивное чувство, когда вы размышляете об этом направлении, вполне может подсказать вам, что вы считаете неправильным идти в этом направлении (и ваши коллеги, правильно или неправильно, могут также, если бы вы реализовали это таким образом!)

Короткий ответ (слишком поздно)

Рекомендация об отказе от наследования от классов коллекций не зависит от C #, вы найдете ее во многих языках программирования.Мудрость получена, а не закон. Одна из причин заключается в том, что на практике считается, что композиция часто побеждает наследование с точки зрения понятности, реализуемости и ремонтопригодности. С объектами реального мира / предметной области чаще встречаются полезные и согласованные отношения «hasa», чем полезные и согласованные отношения «isa», если только вы не углубляетесь в абстрактность, особенно по мере прохождения времени и точных данных и поведения объектов в коде. изменения. Это не должно заставлять вас всегда исключать наследование от классов коллекции; но это может показаться наводящим на размышления.

Наследование и состав. А что использовать? | by Brian

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

Иерархию классов очень, очень трудно изменить после развертывания.Один из вариантов — переписать все это целиком.

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

 git pull master 

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

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

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

Например: B наследуется от A. Используйте подстановку Лискова, чтобы обосновать связь между B и A, затем измените связь и подтвердите ее снова. Если кажется, что отношения имеют смысл в обоих направлениях, лучше не использовать наследование.

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

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

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

Учебник по Python в ООП: наследование

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

Введение и определения

Ни один объектно-ориентированный язык программирования не был бы достоин внимания или использования, если бы он не поддерживал наследование. Наследование было изобретено в 1969 году для Simula.Python поддерживает не только наследование, но и множественное наследование. Вообще говоря, наследование — это механизм наследования новых классов из существующих. Делая это, мы получаем иерархию классов. В большинстве объектно-ориентированных языков на основе классов объект, созданный посредством наследования («дочерний объект»), получает все — хотя в некоторых языках программирования есть исключения — свойств и поведения родительского объекта.

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

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

Синтаксис наследования в Python

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

  класс DerivedClassName (BaseClassName):
    перевал  

Руководство по ООП Python — Настоящий Python

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

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

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

Объект Суперкласс

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

>>>
  >>> класс MyClass:
...     проходить
...
  

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

>>>
  >>> c = MyClass ()
>>> dir (c)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__']
  

dir () возвращает список всех членов в указанном объекте.Вы не указали участников в MyClass , так откуда берется список? Вы можете узнать с помощью интерактивного переводчика:

>>>
  >>> o = object ()
>>> dir (o)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__']
  

Как видите, два списка почти идентичны.В MyClass есть несколько дополнительных членов, например __dict__ и __weakref__ , но каждый отдельный член класса объекта также присутствует в MyClass .

Это связано с тем, что каждый класс, который вы создаете в Python, неявно является производным от объекта . Вы могли бы быть более явным и написать class MyClass (object): , но это избыточно и ненужно.

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

Исключения являются исключением

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

Вы можете увидеть проблему с помощью интерактивного интерпретатора Python:

>>>
  >>> класс MyError:
...     проходить
...
>>> поднять MyError ()

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
TypeError: исключения должны быть производными от BaseException
  

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

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

Правильный способ определить тип ошибки следующий:

>>>
  >>> класс MyError (Исключение):
...     проходить
...
>>> поднять MyError ()

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
__main __. MyError
  

Как видите, когда вы вызываете MyError , в выходных данных правильно указывается тип возникшей ошибки.

Создание иерархии классов

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

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

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

Вы начинаете с реализации класса PayrollSystem , который обрабатывает расчет заработной платы:

  # В часах в год

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
            Распечатать('')
  

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

Теперь вы реализуете базовый класс Employee , который обрабатывает общий интерфейс для каждого типа сотрудника:

  # В часах в год

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя
  

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

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

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

  # В час.ру

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        super () .__ init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary
  

Вы создаете производный класс SalaryEmployee , который наследует Employee . Класс инициализируется с идентификатором и именем , требуемым базовым классом, и вы используете super () для инициализации членов базового класса.Вы можете прочитать все о super () в Supercharge Your Classes With Python super ().

SalaryEmployee также требуется параметр инициализации weekly_salary , который представляет сумму, которую сотрудник зарабатывает в неделю.

Класс предоставляет требуемый метод .calculate_payroll () , используемый системой HR. Реализация просто возвращает сумму, хранящуюся в weekly_salary .

Компания также нанимает рабочих, получающих почасовую оплату, поэтому вы добавляете HourlyEmployee в систему управления персоналом:

  # В час.ру

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        self.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate
  

Класс HourlyEmployee инициализируется идентификатором и именем , как и базовый класс, плюс hours_worked и hour_rate , необходимые для расчета заработной платы.Метод .calculate_payroll () реализуется путем возврата количества отработанных часов, умноженного на почасовую ставку.

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

  # В часах в год

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        super () .__ init __ (идентификатор, имя, недельная_ зарплата)
        себя.комиссия = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

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

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

Поскольку CommissionEmployee является производным от SalaryEmployee , у вас есть доступ напрямую к свойству weekly_salary , и вы могли бы реализовать .calculate_payroll () , используя значение этого свойства.

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

Вы создали свою иерархию первого класса для системы. UML-диаграмма классов выглядит так:

На схеме показана иерархия наследования классов. Производные классы реализуют интерфейс IPayrollCalculator , который требуется для PayrollSystem . Реализация PayrollSystem.calculate_payroll () требует, чтобы переданные объекты employee содержали id , name и реализацию calculate_payroll () .

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

Приложение создает своих сотрудников и передает их в систему расчета заработной платы для обработки расчета заработной платы:

  # В program.py

импортные часы

salary_employee = hr.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = hr.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = hr.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
payroll_system = час.Система начисления заработной платы()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee
])
  

Вы можете запустить программу в командной строке и посмотреть результат:

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250
  

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

Обратите внимание, что базовый класс Employee не определяет метод .calculate_payroll () . Это означает, что если вы создадите простой объект Employee и передадите его в PayrollSystem , то получите сообщение об ошибке. Вы можете попробовать это в интерактивном интерпретаторе Python:

>>>
  >>> импорт ч.
>>> Сотрудник = час.Сотрудник (1, «Недействительный»)
>>> payroll_system = hr.PayrollSystem ()
>>> payroll_system.calculate_payroll ([сотрудник])

Заработная плата для: 1 - недействительна
Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
  Файл "/hr.py", строка 39, в файле calculate_payroll
    print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
AttributeError: объект "Сотрудник" не имеет атрибута "calculate_payroll"
  

Хотя вы можете создать экземпляр объекта Employee , этот объект не может использоваться системой PayrollSystem .Почему? Потому что не может .calculate_payroll () для Сотрудника . Чтобы соответствовать требованиям PayrollSystem , вам нужно преобразовать класс Employee , который в настоящее время является конкретным классом, в абстрактный класс. Таким образом, ни один сотрудник не будет просто сотрудником , а будет реализован .calculate_payroll () .

Абстрактные базовые классы в Python

Класс Employee в приведенном выше примере называется абстрактным базовым классом.Абстрактные базовые классы существуют для наследования, но никогда не создаются. Python предоставляет модуль abc для определения абстрактных базовых классов.

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

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

Вы можете изменить реализацию класса Employee , чтобы исключить возможность его создания:

  # В часах в год

from abc import ABC, abstractmethod

класс Сотрудник (ABC):
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

    @abstractmethod
    def calculate_payroll (самостоятельно):
        проходить
  

Вы производите Employee из ABC , что делает его абстрактным базовым классом. Затем вы украшаете .Calcul_payroll () с декоратором @abstractmethod .

У этого изменения есть два хороших побочных эффекта:

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

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

>>>
  >>> импорт ч.
>>> Сотрудник = час.Сотрудник (1, 'аннотация')

Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
TypeError: невозможно создать экземпляр абстрактного класса Employee с абстрактными методами
Calcul_payroll
  

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

Наследование реализации против наследования интерфейса

Когда вы производите один класс от другого, производный класс наследует оба:

  1. Интерфейс базового класса: Производный класс наследует все методы, свойства и атрибуты базового класса.

  2. Реализация базового класса: Производный класс наследует код, реализующий интерфейс класса.

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

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

В Python нет необходимости явно объявлять интерфейс. Любой объект, реализующий желаемый интерфейс, может использоваться вместо другого объекта. Это известно как утка , набирающая . Утиный набор текста обычно объясняется так: «Если он ведет себя как утка, значит, это утка».

Чтобы проиллюстрировать это, теперь вы добавите класс DisgruntledEmployee в приведенный выше пример, который не является производным от Employee :

.
  # В disgruntled.py

класс DisgruntledEmployee:
    def __init __ (я, идентификатор, имя):
        себя.id = id
        self.name = имя

    def calculate_payroll (самостоятельно):
        возврат 1000000
  

Класс DisgruntledEmployee не является производным от Employee , но предоставляет тот же интерфейс, который требуется для PayrollSystem . PayrollSystem.calculate_payroll () требует список объектов, реализующих следующий интерфейс:

  • Свойство или атрибут id , который возвращает идентификатор сотрудника
  • A name свойство или атрибут, представляющий имя сотрудника
  • А .Calcul_payroll () метод, который не принимает никаких параметров и возвращает сумму заработной платы для обработки

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

Вы можете изменить программу, чтобы использовать класс DisgruntledEmployee :

  # В program.py

импортные часы
недовольный импорт

salary_employee = hr.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = час.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = hr.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
disgruntled_employee = disgruntled.DisgruntledEmployee (20000, 'Анонимный')
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee,
    disgruntled_employee
])
  

Программа создает объект DisgruntledEmployee и добавляет его в список, обрабатываемый системой PayrollSystem .Теперь вы можете запустить программу и увидеть ее результат:

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Заработная плата для: 20000 - Аноним
- Сумма чека: 1000000
  

Как видите, PayrollSystem все еще может обрабатывать новый объект, потому что он соответствует желаемому интерфейсу.

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

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

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

Теперь вы можете очистить приведенный выше пример, чтобы перейти к следующей теме. Вы можете удалить файл disgruntled.py , а затем изменить модуль hr в исходное состояние:

  # В часах в год

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {сотрудник.Calcul_payroll ()} ')
            Распечатать('')

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        super () .__ init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        себя.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        super () .__ init __ (идентификатор, имя, недельная_ зарплата)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Вы удалили импорт модуля abc , поскольку класс Employee не обязательно должен быть абстрактным.Вы также удалили из него абстрактный метод calculate_payroll () , поскольку он не предоставляет никакой реализации.

По сути, вы наследуете реализацию атрибутов id и name класса Employee в производных классах. Поскольку .calculate_payroll () — это просто интерфейс для метода PayrollSystem.calculate_payroll () , вам не нужно реализовывать его в базовом классе Employee .

Обратите внимание, как класс CommissionEmployee является производным от SalaryEmployee . Это означает, что CommissionEmployee наследует реализацию и интерфейс SalaryEmployee . Вы можете увидеть, как метод CommissionEmployee.calculate_payroll () использует реализацию базового класса, поскольку он полагается на результат от super (). Calculate_payroll () для реализации своей собственной версии.

Проблема взрыва класса

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

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

Система ProductivitySystem отслеживает производительность в зависимости от ролей сотрудников. Существуют разные роли сотрудников:

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

С этими требованиями вы начинаете видеть, что Employee и его производные классы могут принадлежать не к модулю hr , а где-то еще, потому что теперь они также используются ProductivitySystem .

Вы создаете модуль сотрудников и перемещаете туда классы:

  # В employee.py

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        self.id = id
        self.name = имя

class SalaryEmployee (Сотрудник):
    def __init __ (self, id, name, weekly_salary):
        супер().__init __ (идентификатор, имя)
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

class HourlyEmployee (Сотрудник):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя)
        self.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

class CommissionEmployee (SalaryEmployee):
    def __init __ (self, id, name, weekly_salary, Commission):
        супер().__init __ (id, name, weekly_salary)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Реализация остается той же, но вы перемещаете классы в модуль employee . Теперь вы измените свою программу, чтобы поддержать изменение:

  # В program.py

импортные часы
импортные сотрудники

salary_employee = сотрудники.SalaryEmployee (1, 'Джон Смит', 1500)
hourly_employee = сотрудники.HourlyEmployee (2, 'Джейн Доу', 40, 15)
Commission_employee = employee.CommissionEmployee (3, 'Кевин Бэкон', 1000, 250)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll ([
    salary_employee,
    hourly_employee,
    Commission_employee
])
  

Вы запускаете программу и проверяете, что она по-прежнему работает:

  $ python program.py

Расчет заработной платы
===================
Заработная плата для: 1 - Джон Смит
- Сумма чека: 1500

Зарплата для: 2 - Джейн Доу
- Сумма чека: 600

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250
  

Когда все готово, вы начинаете добавлять новые классы:

  # У сотрудников.ру

Менеджер класса (SalaryEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} кричит и кричит в течение {hours} часов.')

классный секретарь (SalaryEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} тратит {hours} часов на оформление офисных документов.')

class SalesPerson (CommissionEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} тратит на телефон {hours} часов.')

класс FactoryWorker (HourlyEmployee):
    def работа (самостоятельно, часы):
        print (f '{self.name} производит гаджеты в течение {часов} часов.')
  

Сначала вы добавляете класс Manager , производный от SalaryEmployee . Класс предоставляет метод work () , который будет использоваться системой повышения производительности. Метод занимает часа, отработанных сотрудником.

Затем вы добавляете Secret , SalesPerson и FactoryWorker , а затем реализуете интерфейс work () , чтобы их можно было использовать в системе повышения производительности.

Теперь вы можете добавить класс ProductivitySytem :

  # По производительности.ру

class ProductivitySystem:
    def track (я, сотрудники, часы):
        print ('Отслеживание производительности сотрудников')
        print ('==============================')
        для сотрудника в составе сотрудников:
            employee.work (часы)
        Распечатать('')
  

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

  # В program.py

импортные часы
импортные сотрудники
производительность импорта

менеджер = сотрудники.Менеджер (1, 'Мэри Поппинс', 3000)
secretary = сотрудники.Secretary (2, 'Джон Смит', 1500)
sales_guy = сотрудники.SalesPerson (3, 'Кевин Бэкон', 1000, 250)
factory_worker = employee.FactoryWorker (2, 'Джейн Доу', 40, 15)
сотрудники = [
    менеджер,
    секретарь,
    sales_guy,
    рабочий,
]
performance_system = продуктивность.ProductivitySystem ()
performance_system.track (сотрудников, 40)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll (сотрудники)
  

Программа формирует список сотрудников разного типа.Список сотрудников отправляется в систему продуктивности для отслеживания их работы в течение 40 часов. Затем тот же список сотрудников отправляется в систему расчета заработной платы для расчета их заработной платы.

Вы можете запустить программу, чтобы увидеть результат:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.
Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600
  

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

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

На следующей диаграмме показана новая иерархия классов:

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

Наследование нескольких классов

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

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

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

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

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

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

  1. Унаследовать от Секретарь : Вы можете унаследовать от Секретарь , чтобы унаследовать .work () для роли, а затем переопределите метод .calculate_payroll () , чтобы реализовать его как HourlyEmployee .

  2. Производный от HourlyEmployee : Вы можете наследовать от HourlyEmployee метод .calculate_payroll () , а затем переопределить метод .work () , чтобы реализовать его как секретарь .

Затем вы помните, что Python поддерживает множественное наследование, поэтому вы решили наследовать как от Secret , так и от HourlyEmployee :

  # У сотрудников.ру

class TemporarySecretary (Секретарь, Почасовой сотрудник):
    проходить
  

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

Теперь вы измените свою программу, добавив нового временного секретаря:

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

manager = employee.Manager (1, 'Мэри Поппинс', 3000)
secretary = сотрудники.Secretary (2, 'Джон Смит', 1500)
sales_guy = сотрудники.SalesPerson (3, 'Кевин Бэкон', 1000, 250)
factory_worker = сотрудники.FactoryWorker (4, 'Джейн Доу', 40, 15)
временный_секретарий = сотрудники.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
company_employees = [
    менеджер,
    секретарь,
    sales_guy,
    рабочий,
    временный_секретарь,
]
performance_system = продуктивность.ProductivitySystem ()
performance_system.track (company_employees, 40)
payroll_system = hr.PayrollSystem ()
payroll_system.calculate_payroll (company_employees)
  

Вы запускаете программу для проверки:

  $ программа на Python.ру

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  Временный_секретари = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
TypeError: __init __ () принимает 4 позиционных аргумента, но было дано 5
  

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

Это связано с тем, что вы получили TemporarySecretary сначала из секретарь , а затем из HourlyEmployee , поэтому переводчик пытается использовать секретарь.__init __ () для инициализации объекта.

Ладно, перевернем:

  класс Временный секретарь (почасовая, секретарь):
    проходить
  

Теперь запустите программу еще раз и посмотрите, что произойдет:

  $ python program.py

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  Временный_секретари = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
 Файл "employee.py", строка 16, в __init__
  super () .__ init __ (идентификатор, имя)
TypeError: __init __ () отсутствует 1 обязательный позиционный аргумент: 'weekly_salary'
  

Теперь кажется, что вам не хватает параметра weekly_salary , который необходим для инициализации секретаря , но этот параметр не имеет смысла в контексте TemporarySecretary , потому что это HourlyEmployee .

Может быть, реализация TemporarySecretary .__ init __ () поможет:

  # В employee.py

class TemporarySecretary (Почасовой сотрудник, секретарь):
    def __init __ (self, id, name, hours_worked, hour_rate):
        super () .__ init __ (идентификатор, имя, часы работы, скорость_часа)
  

Попробуйте:

  $ python program.py

Отслеживание (последний вызов последний):
 Файл ". \ Program.py", строка 9, в 
  Временный_секретари = сотрудник.Темверинсекретарий (5, 'Робин Уильямс', 40, 9)
 Файл "Сотрудник".py ", строка 54, в __init__
  super () .__ init __ (идентификатор, имя, часы работы, скорость_часа)
 Файл "employee.py", строка 16, в __init__
  super () .__ init __ (идентификатор, имя)
TypeError: __init __ () отсутствует 1 обязательный позиционный аргумент: 'weekly_salary'
  

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

Когда осуществляется доступ к методу или атрибуту класса, Python использует класс MRO, чтобы найти его. MRO также используется super () , чтобы определить, какой метод или атрибут вызывать.Вы можете узнать больше о super () в Supercharge Your Classes With Python super ().

Вы можете оценить MRO класса TemporarySecretary с помощью интерактивного интерпретатора:

>>>
  >>> из сотрудников ввозят временного секретаря
>>> Временный секретарь .__ mro__

(<класс 'employee.TemporarySecretary'>,
 <класс 'employee.HourlyEmployee'>,
 <класс 'сотрудники. Секретарь'>,
 <класс 'employee.SalaryEmployee'>,
 <класс сотрудников.Сотрудник '>,
 <класс 'объект'>
)
  

MRO показывает порядок, в котором Python будет искать соответствующий атрибут или метод. В этом примере это происходит, когда мы создаем объект TemporarySecretary :

  1. Вызывается метод TemporarySecretary .__ init __ (self, id, name, hours_worked, hour_rate) .

  2. Вызов super () .__ init __ (id, name, hours_worked, hour_rate) соответствует HourlyEmployee.__init __ (self, id, name, hour_worked, hour_rate) .

  3. HourlyEmployee вызывает super () .__ init __ (id, name) , который MRO собирается сопоставить с Secret .__ init __ () , который унаследован от SalaryEmployee .__ init __ (self, id, name, weekly_salary ) .

Поскольку параметры не совпадают, возникает исключение TypeError .

Вы можете обойти MRO, изменив порядок наследования и напрямую позвонив по номеру HourlyEmployee.__init __ () следующим образом:

  класс TemporarySecretary (секретарь, почасовая оплата):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyEmployee .__ init __ (self, id, name, hours_worked, hour_rate)
  

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

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.
Робин Уильямс тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
Отслеживание (последний вызов последний):
  Файл ".\ program.py ", строка 20, в 
    payroll_system.calculate_payroll (сотрудники)
  Файл "hr.py", строка 7, в файле calculate_payroll
    print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
  Файл employee.py, строка 12, в файле calculate_payroll
    return self.weekly_salary
AttributeError: объект TemporarySecretary не имеет атрибута weekly_salary
  

Проблема в том, что, поскольку вы изменили порядок наследования, MRO находит метод .calculate_payroll () для SalariedEmployee перед методом в HourlyEmployee .Вам нужно переопределить .calculate_payroll () в TemporarySecretary и вызвать из него правильную реализацию:

  класс TemporarySecretary (секретарь, почасовая оплата):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyEmployee .__ init __ (self, id, name, hours_worked, hour_rate)

    def calculate_payroll (самостоятельно):
        return HourlyEmployee.calculate_payroll (self)
  

Метод calculate_payroll () напрямую вызывает HourlyEmployee.calculate_payroll () , чтобы убедиться, что вы получите правильный результат. Вы можете снова запустить программу, чтобы убедиться, что она работает:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс кричит и кричит 40 часов.
Джон Смит тратит 40 часов на оформление офисных документов.
Кевин Бэкон проводит по телефону 40 часов.
Джейн Доу производит гаджеты 40 часов.
Робин Уильямс тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
- Сумма чека: 360
  

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

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

На следующей диаграмме показана проблема ромба в иерархии классов:

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

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

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

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

Производные классы Employee используются двумя разными системами:

  1. Система продуктивности , отслеживающая продуктивность сотрудников.

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

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

  # По производительности.ру

class ProductivitySystem:
    def track (я, сотрудники, часы):
        print ('Отслеживание производительности сотрудников')
        print ('==============================')
        для сотрудника в составе сотрудников:
            result = employee.work (часы)
            print (f '{имя сотрудника}: {результат}')
        Распечатать('')

класс ManagerRole:
    def работа (самостоятельно, часы):
        ответь f'scream и кричит в течение {hours} часов ».

класс Секретарь
    def работа (самостоятельно, часы):
        return f'expends {hours} часов на оформление офисных документов.'

class SalesRole:
    def работа (самостоятельно, часы):
        return f 'тратит {hours} часов на телефон.'

класс FactoryRole:
    def работа (самостоятельно, часы):
        вернуть гаджеты на {hours} часов ».
  

Модуль продуктивности реализует класс ProductivitySystem , а также связанные роли, которые он поддерживает. Классы реализуют интерфейс work () , необходимый для системы, но они не являются производными от Employee .

То же самое можно сделать и с модулем hr :

  # В час.ру

класс PayrollSystem:
    def calculate_payroll (самостоятельно, сотрудники):
        print ('Расчет заработной платы')
        print ('===================')
        для сотрудника в составе сотрудников:
            print (f'Payroll for: {employee.id} - {employee.name} ')
            print (f'- Проверить сумму: {employee.calculate_payroll ()} ')
            Распечатать('')

класс SalaryPolicy:
    def __init __ (self, weekly_salary):
        self.weekly_salary = weekly_salary

    def calculate_payroll (самостоятельно):
        return self.weekly_salary

класс HourlyPolicy:
    def __init __ (self, hours_worked, hour_rate):
        себя.hours_worked = hours_worked
        self.hour_rate = часовой_рейт

    def calculate_payroll (самостоятельно):
        вернуть self.hours_worked * self.hour_rate

класс CommissionPolicy (SalaryPolicy):
    def __init __ (self, weekly_salary, Commission):
        super () .__ init __ (недельная_ зарплата)
        self.commission = комиссия

    def calculate_payroll (самостоятельно):
        fixed = super (). calculate_payroll ()
        возврат фиксированный + self.commission
  

Модуль hr реализует систему PayrollSystem , которая рассчитывает заработную плату для сотрудников.Он также реализует классы политики для расчета заработной платы. Как видите, классы политик больше не являются производными от Employee .

Теперь вы можете добавить необходимые классы в модуль сотрудник :

  # В employee.py

из часов импорта (
    SalaryPolicy,
    CommissionPolicy,
    Почасовая политика
)
из импорта производительности (
    ManagerRole,
    Секретарь роль,
    SalesRole,
    FactoryRole
)

класс Сотрудник:
    def __init __ (я, идентификатор, имя):
        себя.id = id
        self.name = имя

Менеджер класса (Сотрудник, ManagerRole, SalaryPolicy):
    def __init __ (self, id, name, weekly_salary):
        SalaryPolicy .__ init __ (self, weekly_salary)
        super () .__ init __ (идентификатор, имя)

класс Секретарь (Employee, SecretRole, SalaryPolicy):
    def __init __ (self, id, name, weekly_salary):
        SalaryPolicy .__ init __ (self, weekly_salary)
        super () .__ init __ (идентификатор, имя)

class SalesPerson (Сотрудник, SalesRole, CommissionPolicy):
    def __init __ (self, id, name, weekly_salary, Commission):
        CommissionPolicy.__init __ (самостоятельно, недельная_ зарплата, комиссия)
        super () .__ init __ (идентификатор, имя)

класс FactoryWorker (Сотрудник, FactoryRole, HourlyPolicy):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyPolicy .__ init __ (self, hours_worked, hour_rate)
        super () .__ init __ (идентификатор, имя)

класс TemporarySecretary (Сотрудник, SecretRole, HourlyPolicy):
    def __init __ (self, id, name, hours_worked, hour_rate):
        HourlyPolicy .__ init __ (self, hours_worked, hour_rate)
        super () .__ init __ (идентификатор, имя)
  

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

Обратите внимание, что вам по-прежнему необходимо явно инициализировать политики заработной платы в конструкторах. Вы, наверное, видели, что инициализации Manager и Secret идентичны. Кроме того, инициализации FactoryWorker и TemporarySecretary одинаковы.

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

Вот диаграмма UML для нового дизайна:

На схеме показаны отношения для определения секретарей и TemporarySecretary с использованием множественного наследования, но без проблемы ромба.

Вы можете запустить программу и посмотреть, как она работает:

  $ python program.py

Отслеживание производительности сотрудников
==============================
Мэри Поппинс: кричит и кричит 40 часов.Джон Смит: тратит 40 часов на оформление офисных документов.
Кевин Бэкон: 40 часов разговаривает по телефону.
Джейн Доу: производит гаджеты 40 часов.
Робин Уильямс: тратит 40 часов на оформление офисных документов.

Расчет заработной платы
===================
Заработная плата для: 1 - Мэри Поппинс
- Сумма чека: 3000

Зарплата для: 2 - Джон Смит
- Сумма чека: 1500

Зарплата для: 3 - Кевин Бэкон
- Сумма чека: 1250

Зарплата для: 4 - Джейн Доу
- Сумма чека: 600

Зарплата для: 5 - Робин Уильямс
- Сумма чека: 360
  

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

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

Обзор

  • Узнайте о наследовании в объектно-ориентированном программировании и различных формах наследования
  • Общие сведения о переопределении методов и функции super () в мире объектно-ориентированного программирования

Введение

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

Это, как вы понимаете, экономит массу времени. А в науке о данных время — деньги!

ООП — это объекты реального мира, а наследование — это способ представления отношений в реальном мире. Вот пример — автомобиль, автобус, велосипед — все они подпадают под более широкую категорию под названием Автомобиль . Это означает, что они унаследовали свойства автомобилей i-го класса.Все они используются для транспортировки.

Мы можем представить эти отношения в коде с помощью наследования.

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

Это вторая статья из серии статей, связанных с объектно-ориентированным программированием. Просмотрите также первую статью:

Содержание

  1. Что такое наследование в объектно-ориентированном программировании?
  2. Различные формы наследования в объектно-ориентированном программировании
    • Одиночное наследование
    • Множественное наследование
    • Многоуровневое наследование
    • Иерархическое наследование
    • Гибридное наследование
  3. Переопределение метода
  4. Функция super ()

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

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

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

Вы можете использовать следующий синтаксис: \ для реализации наследования в Python:

 класс parent_class:
тело родительского класса

класс child_class (родительский_класс):
тело дочернего класса 

Давайте посмотрим на реализацию:

 class Car: #parent class

 def __init __ (я, имя, пробег):
 себя.name = имя
собственный пробег = пробег

 def описание (self):
return f "Автомобиль {self.name} показывает пробег {self.m900} км / л"

class BMW (Автомобиль): #child class
    проходить

class Audi (Автомобиль): #child class
    def audi_desc (сам):
        return "Это метод описания класса Audi." 
 obj1 = BMW ("BMW 7-й серии", 39,53)
печать (obj1.description ())

obj2 = Audi («Audi A8 L», 14)
печать (obj2.описание())
печать (obj2.audi_desc ()) 

Выход:

Мы создали два дочерних класса, а именно «BMW» и «Audi», которые унаследовали методы и свойства родительского класса «Car». Мы не предоставили никаких дополнительных функций и методов в классе BMW. А внутри класса Audi есть еще один метод.

Обратите внимание, что описание метода экземпляра () родительского класса доступно объектам дочерних классов с помощью obj1.description () и obj2.description (). И отдельный метод класса Audi также доступен с помощью obj2.audi_desc ().

Мы можем проверить базовый или родительский класс любого класса с помощью встроенного атрибута класса __bases__

 принт (BMW .__ base__, Audi .__ base__) 

Как мы видим, базовым классом обоих подклассов является Автомобиль. Теперь давайте посмотрим, что происходит при использовании __base__ с родительским классом Car:

 принт (Автомобиль .__ баз__) 

Каждый раз, когда мы создаем новый класс в Python 3.x он унаследован от встроенного базового класса Object. Другими словами, класс Object является корнем всех классов.

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

Существует пять основных форм наследования, основанных на участии родительского и дочернего классов.

1. Единоличное наследство

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

 class Parent:
  def f1 (себя):
    print ("Функция родительского класса.")

class Child (Родитель):
  def f2 (себя):
    print ("Функция дочернего класса.")

object1 = Ребенок ()
object1.f1 ()
объект1.f2 () 

Здесь класс Child наследует только один класс Parent, следовательно, это пример наследования Single.

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

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

 класс Parent_1:
  def f1 (себя):
    print ("Функция класса parent_1.")

класс Parent_2:
  def f2 (себя):
    print ("Функция класса parent_2.")

класс Parent_3:
  def f3 (себя):
    print ("функция класса parent_3.")

класс Child (Родитель_1, Родитель_2, Родитель_3):
  def f4 (себя):
    print ("Функция дочернего класса.")

object_1 = Дочерний ()
object_1.f1 ()
object_1.f2 ()
object_1.f3 ()
объект_1.f4 () 

Здесь у нас есть один дочерний класс, который наследует свойства трех родительских классов Parent_1, Parent_2 и Parent_3. Все классы имеют разные функции, и все функции вызываются с использованием объекта класса Child.

Но предположим, что дочерний класс наследует два класса, выполняющих одну и ту же функцию:

 класс Parent_1:
  def f1 (себя):
    print ("Функция класса parent_1.")

класс Parent_2:
  def f1 (себя):
    print ("Функция класса parent_2.")

класс Child (Родитель_1, Родитель_2):
  def f2 (себя):
    print ("Функция дочернего класса.")
 

Здесь классы Parent_1 и Parent_2 имеют одинаковую функцию f1 (). Теперь, когда объект дочернего класса вызывает f1 (), поскольку дочерний класс наследует оба родительских класса, что, по вашему мнению, должно произойти?

 obj = Ребенок ()
obj.f1 () 

Но почему не унаследована функция f1 () класса Parent_2?

При множественном наследовании дочерний класс сначала ищет метод в своем собственном классе.Если не найден, поиск выполняется в родительских классах depth_first и в порядке слева направо. Поскольку это был простой пример с двумя родительскими классами, мы ясно видим, что класс Parent_1 был унаследован первым, поэтому дочерний класс будет искать метод в классе Parent_1 перед поиском в классе Parent_2.

Но для сложных проблем наследования трудно определить порядок. Фактический способ сделать это в Python называется Порядок разрешения методов (MRO) . Мы можем найти ТОиР любого класса с помощью атрибута __mro__

 Ребенок.__mro__ 

Это говорит о том, что класс Child сначала посетил класс Parent_1, а затем Parent_2, поэтому будет вызван метод f1 () класса Parent_1.

Давайте рассмотрим немного сложный пример на Python:

 класс Parent_1:
проходить

класс Parent_2:
проходить

класс Parent_3:
проходить

класс Child_1 (Родитель_1, Родитель_2):
проходить

класс Child_2 (Родитель_2, Родитель_3):
проходить

класс Child_3 (Ребенок_1, Ребенок_2, Родитель_3):
пройти 

Здесь класс Child_1 наследует два класса — Parent_1 и Parent_2.Класс Child_2 также наследует два класса — Parent_2 и Parent_3. Другой класс Child_3 наследует три класса — Child_1, Child_2 и Parent_3.

Теперь, просто взглянув на это наследование, довольно сложно определить порядок разрешения методов для класса Child_3. Итак, вот фактическое использование __mro __-

Мы видим, что сначала интерпретатор ищет Child_3, затем Child_1, а затем Parent_1, Child_2, Parent_2 и Parent_3 соответственно.

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

Например, class_1 наследуется class_2, и этот class_2 также наследуется class_3, и этот процесс продолжается. Это называется многоуровневым наследованием. Давайте разберемся на примере:

 class Parent:
  def f1 (себя):
    print ("Функция родительского класса.")

класс Child_1 (Родитель):
  def f2 (себя):
    print ("Функция класса child_1.")

класс Child_2 (Ребенок_1):
  def f3 (себя):
    print ("Функция класса child_2.")

obj_1 = Ребенок_1 ()
obj_2 = Ребенок_2 ()

obj_1.f1 ()
obj_1.f2 ()

печать ("\ п")
obj_2.f1 ()
obj_2.f2 ()
obj_2.f3 () 

Здесь класс Child_1 наследует класс Parent, а класс Child_2 наследует класс Child_1. В этом случае Child_1 имеет доступ к функциям f1 () и f2 (), тогда как Child_2 имеет доступ к функциям f1 (), f2 () и f3 (). Если мы попытаемся получить доступ к функции f3 () с помощью объекта класса Class_1, то возникнет ошибка:

Объект «Ребенок_1» не имеет атрибута «f3»

 obj_1.f3 () 

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

В этом случае различные дочерние классы наследуют один родительский класс. Пример, приведенный во введении наследования, является примером иерархического наследования, поскольку классы BMW и Audi наследуют класс Car.

Для простоты рассмотрим другой пример:

 class Parent:
deff1 (сам):
print ("Функция родительского класса.")

класс Child_1 (Родитель):
deff2 (сам):
print ("Функция класса child_1.")

класс Child_2 (Родитель):
deff3 (сам):
print ("Функция класса child_2.")

obj_1 = Ребенок_1 ()
obj_2 = Ребенок_2 ()

obj_1.f1 ()
obj_1.f2 ()

печать ('\ п')
obj_2.f1 ()
obj_2.f3 () 

Здесь два дочерних класса наследуют один и тот же родительский класс. Класс Child_1 имеет доступ к функциям f1 () родительского класса и самой функции f2 (). В то время как класс Child_2 имеет доступ к функциям f1 () родительского класса и самой функции f3 ().

5- Гибридное наследование

Когда существует комбинация более чем одной формы наследования, это называется гибридным наследованием. Будет более понятно после этого примера:

 class Parent:
  def f1 (себя):
    print ("Функция родительского класса.")

класс Child_1 (Родитель):
  def f2 (себя):
    print ("Функция класса child_1.")

класс Child_2 (Родитель):
  def f3 (себя):
    print ("Функция класса child_2.")

класс Child_3 (Ребенок_1, Ребенок_2):
  def f4 (себя):
    print ("Функция класса child_3.")

obj = Child_3 ()
obj.f1 ()
obj.f2 ()
obj.f3 ()
obj.f4 () 

В этом примере два класса «Child_1» и «Child_2» являются производными от базового класса «Parent» с использованием иерархического наследования. Другой класс «Child_3» является производным от классов «Child_1» и «Child_2» с использованием множественного наследования. Класс «Child_3» теперь является производным с использованием гибридного наследования.

Переопределение метода

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

 class Parent:
  def f1 (себя):
    print ("Функция родительского класса.")

class Child (Родитель):
  def f1 (себя):
    print ("Функция дочернего класса.")

obj = Ребенок ()
obj.f1 () 

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

 obj_2 = Родитель ()
obj_2.f1 () 

Функция super ()

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

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

    Например —

     class Parent:
      def f1 (себя):
        print ("Функция родительского класса.")
    
    class Child (Родитель):
      def f1 (себя):
        супер (). f1 ()
        print ("Функция дочернего класса.")
    
    obj = Ребенок ()
    obj.f1 () 

    Здесь с помощью super ().f1 () метод f1 () суперкласса дочернего класса, то есть родительский класс, был вызван без явного его имени.

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

     class Parent:
      def f1 (себя):
        print ("Функция родительского класса.")
    
    class Child (Родитель):
      def f1 (себя):
        super (Ребенок, я) .f1 ()
        print ("Функция дочернего класса.")
    
    obj = Ребенок ()
    obj.f1 () 

    Первый параметр относится к подклассу Child , а второй параметр относится к объекту Child, которым в данном случае является self . Вы можете увидеть результат после использования super () и super (Child, self) одинаковым, потому что в Python 3 super (Child, self) эквивалентен self ().

    Теперь рассмотрим еще один пример с использованием функции __init__.

     класс Родитель (объект):
      def__init __ (self, ParentName):
        print (ParentName, 'является производным от другого класса.')
    
    class Child (Родитель):
      def__init __ (я, ChildName):
        print (name, 'это подкласс.')
        super () .__ init __ (Имя ребенка)
    
    obj = Ребенок ('Ребенок') 

    Здесь мы вызвали функцию __init__ родительского класса (внутри дочернего класса), используя super () .__ init __ (ChildName) . И поскольку для метода __init__ родительского класса требуется один аргумент, он был передан как «ChildName». Итак, после создания объекта класса Child сначала выполнялась функция __init__ класса Child, а после этого — функция __init__ класса Parent.

  2. Второй вариант использования — поддержка совместного множественного наследования в динамической среде выполнения.
     класс Первый ():
      def __init __ (сам):
        print ("первый")
        супер () .__ init __ ()
    
    класс Второй ():
      def __init __ (сам):
        print ("второй")
        супер () .__ init __ ()
    
    класс Третий (Второй, Первый):
      def __init __ (сам):
        print ("третий")
        супер () .__ init __ ()
    
    obj = Третий () 


    Вызов super () находит следующий метод в MRO на каждом шаге, поэтому первый и второй тоже должны иметь его, в противном случае выполнение останавливается в конце first ().__init__ .

    Обратите внимание, что суперкласс Первого и Второго — это Объект.

    Давайте также найдем MRO Третьего () —

     Третий .__ mro__ 

    Порядок: Третий> Второй> Первый, и такой же порядок наших выходных данных.

Конечные ноты

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

alexxlab

*

*

Top