Что такое наследование в python
Наследование позволяет создавать новый класс на основе уже существующего класса. Наряду с инкапсуляцией наследование является одним из краеугольных камней объектно-ориентированного программирования.
Ключевыми понятиями наследования являются подкласс и суперкласс . Подкласс наследует от суперкласса все публичные атрибуты и методы. Суперкласс еще называется базовым (base class) или родительским (parent class), а подкласс — производным (derived class) или дочерним (child class).
Синтаксис для наследования классов выглядит следующим образом:
class подкласс (суперкласс): методы_подкласса
Например, у нас есть класс Person, который представляет человека:
class Person: def __init__(self, name): self.__name = name # имя человека @property def name(self): return self.__name def display_info(self): print(f"Name: ")
Предположим, нам необходим класс работника, который работает на некотором предприятии. Мы могли бы создать с нуля новый класс, к примеру, класс Employee:
class Employee: def __init__(self, name): self.__name = name # имя работника @property def name(self): return self.__name def display_info(self): print(f"Name: ") def work(self): print(f" works")
Однако класс Employee может иметь те же атрибуты и методы, что и класс Person, так как работник — это человек. Так, в выше в классе Employee только добавляется метод works , весь остальной код повторяет функционал класса Person. Но чтобы не дублировать функционал одного класса в другом, в данном случае лучше применить наследование.
Итак, унаследуем класс Employee от класса Person:
class Person: def __init__(self, name): self.__name = name # имя человека @property def name(self): return self.__name def display_info(self): print(f"Name: ") class Employee(Person): def work(self): print(f" works") tom = Employee("Tom") print(tom.name) # Tom tom.display_info() # Name: Tom tom.work() # Tom works
Класс Employee полностью перенимает функционал класса Person, лишь добавляя метод work() . Соответственно при создании объекта Employee мы можем использовать унаследованный от Person конструктор:
tom = Employee("Tom")
И также можно обращаться к унаследованным атрибутам/свойствам и методам:
print(tom.name) # Tom tom.display_info() # Name: Tom
Однако, стоит обратить внимание, что для Employee НЕ доступны закрытые атрибуты типа __name. Например, мы НЕ можем в методе work обратиться к приватному атрибуту self.__name :
def work(self): print(f" works") # ! Ошибка
Множественное наследование
Одной из отличительных особенностей языка Python является поддержка множественного наследования, то есть один класс можно унаследовать от нескольких классов:
# класс работника class Employee: def work(self): print("Employee works") # класс студента class Student: def study(self): print("Student studies") class WorkingStudent(Employee, Student): # Наследование от классов Employee и Student pass # класс работающего студента tom = WorkingStudent() tom.work() # Employee works tom.study() # Student studies
Здесь определен класс Employee, который представляет сотрудника фирмы, и класс Student, который представляет учащегося студента. Класс WorkingStudent, который представляет работающего студента, не определяет никакого функционала, поэтому в нем определен оператор pass . Класс WorkingStudent просто наследует функционал от двух классов Employee и Student. Соответственно у объекта этого класса мы можем вызвать методы обоих классов.
При этом наследуемые классы могут более сложными по функциональности, например:
class Employee: def __init__(self, name): self.__name = name @property def name(self): return self.__name def work(self): print(f" works") class Student: def __init__(self, name): self.__name = name @property def name(self): return self.__name def study(self): print(f" studies") class WorkingStudent(Employee, Student): pass tom = WorkingStudent("Tom") tom.work() # Tom works tom.study() # Tom studies
Наследование классов в Python
Классы в языке Python поддерживают наследование классов, что позволяет создавать новые классы с расширенным и/или измененным функционалом базового класса. Новый класс, созданный на основе базового класса — называется производный класс (derived class) или просто подкласс.
Подкласс наследует атрибуты и методы из родительского класса. Он так же может переопределять (override) методы родительского класса. Если подкласс не определяет свой конструктор __init__ , то он наследует конструктор родительского класса по умолчанию.
Синтаксис определения производного (дочернего) класса выглядит следующим образом:
class DerivedClassName(BaseClassName): statement-1> . statement-N>
Имя BaseClassName должно быть определено в области, содержащей определение производного класса. Вместо имени базового класса допускаются и другие произвольные выражения. Это может быть полезно, например, когда базовый класс определен в другом модуле:
class DerivedClassName(modname.BaseClassName): statement-1> . statement-N>
Выполнение определения производного класса DerivedClassName происходит так же, как и для базового класса BaseClassName . Когда объект класса создан, базовый класс BaseClassName запоминается. Это используется для разрешения ссылок на атрибуты. Если запрошенный атрибут не найден в классе DerivedClassName , поиск переходит к поиску в базовом классе BaseClassName . Это правило применяется рекурсивно, если сам базовый класс является производным от какого-либо другого класса.
В создании экземпляров производных классов нет ничего особенного. Выражение a = DerivedClassName() создает новый экземпляр класса. Ссылки на методы разрешаются следующим образом: поиск соответствующего атрибута данных класса осуществляется по цепочке базовых классов, если это необходимо. Ссылка на метод класса будет действительна, если поиск обнаружил функциональный объект.
Производные классы DerivedClassName могут переопределять методы своих базовых классов BaseClassName . Поскольку методы не имеют особых привилегий при вызове других методов того же объекта, метод базового класса, который вызывает другой метод, определенный в том же базовом классе, может в конечном итоге вызвать метод производного класса, который переопределяет его. Для программистов C++ — все методы в Python фактически являются виртуальными.
Переопределяющий метод в производном классе может фактически расширить, а не просто заменить метод базового класса с тем же именем. Существует простой способ вызвать метод базового класса напрямую: просто вызовите BaseClassName.methodname(self, arguments) . Это иногда полезно и для «клиентов». Обратите внимание, что это работает только в том случае, если базовый класс доступен как имя базового класса BaseClassName в глобальной области видимости.
Python имеет две встроенные функции, которые работают с наследованием:
- Используйте isinstance() для проверки типа экземпляра класса: isinstance(obj, int) будет истинным True только в том случае, если obj.__class__ равен int или класс является производным от класса int .
- Используйте issubclass() для проверки наследования классов: issubclass(bool, int) является истинным, так как bool является подклассом int() . Однако issubclass(float, int) является ложным False , так как float не является подклассом int .
Примеры использования переопределения методов.
class One: def __init__(self, name): self.name = name def talk(self): return f'Меня зовут self.name>' def say(self): return f'Привет self.name>' class Two(One): # декорируем метод def say(self): x = One.say(self) return f'x> . ' class Three(One): # переопределяем метод def say(self, word): return f'word> self.name>. ' one = One('Андрей') two = Two('Юра') three = Three('Аня')
print(f'class One.__name__>:') print(one.talk()) print(one.say()) # class One: # Меня зовут Андрей # Привет Андрей print(f'class Two.__name__>:') print(two.talk()) print(two.say()) print('Two is subclass One:', issubclass(Two, One)) # class Two: # Меня зовут Юра # Привет Юра . # Two is subclass One: True print(f'class Three.__name__>:') print(three.talk()) print(three.say('Пока')) print('Three is subclass One:', issubclass(Three, One)) # class Three: # Меня зовут Аня # Пока Аня. # Three is subclass One: True print('Three is subclass Two:', issubclass(Three, Two)) # Three is subclass Two: False
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Пространство имен и область видимости в классах
- Определение классов
- Объект класса и конструктор класса
- Создание экземпляра класса
- Метод экземпляра класса
- Что такое метод класса и зачем нужен
- Что такое статический метод в классах Python и зачем нужен
- Атрибуты класса и переменные экземпляра класса
- Кэширование методов экземпляра декоратором lru_cache
- Закрытые/приватные методы и переменные класса Python
- Наследование классов
- Множественное наследование классов
- Абстрактные классы
- Перегрузка методов в классе Python
- Что такое миксины и как их использовать
- Класс Python как структура данных, подобная языку C
- Создание пользовательских типов данных
- Специальные (магические) методы класса Python
- Базовая настройка классов Python магическими методами
- Настройка доступа к атрибутам класса Python
- Дескриптор класса для чайников
- Протокол дескриптора класса
- Практический пример дескриптора
- Использование метода .__new__() в классах Python
- Специальный атрибут __slots__ класса Python
- Специальный метод __init_subclass__ класса Python
- Определение метаклассов metaclass
- Эмуляция контейнерных типов в классах Python
- Другие специальные методы класса
- Как Python ищет специальные методы в классах
- Шаблон проектирования Фабрика и его реализация
Наследование
Наследование – важная составляющая объектно-ориентированного программирования. Так или иначе мы уже сталкивались с ним, ведь объекты наследуют атрибуты своих классов. Однако обычно под наследованием в ООП понимается наличие классов и подклассов. Также их называют супер- или надклассами и классами, а также родительскими и дочерними классами.
Суть наследования здесь схожа с наследованием объектами от классов. Дочерние классы наследуют атрибуты родительских, а также могут переопределять атрибуты и добавлять свои.
—>
Простое наследование методов родительского класса
В качестве примера рассмотрим два класса столов. Класс Table – родительский по отношению к DeskTable (письменные столы). Независимо от своего типа все столы имеют длину, ширину и высоту. Пусть для письменных столов также важна площадь поверхности. Общее вынесем в класс, частное – в подкласс.
Наследственная связь между классами устанавливается через подкласс. При определении дочернего после его имени в скобках указывается родительский.
class Table: def __init__(self, l, w, h): self.length = l self.width = w self.height = h class DeskTable(Table): def square(self): return self.width * self.length t1 = Table(1.5, 1.8, 0.75) t2 = DeskTable(0.8, 0.6, 0.7) print(t2.square()) # вывод: 0.48
В данном случае у класса DeskTable нет своего конструктора, поэтому он наследует его от родителя. При создании объектов передавать аргументы необходимо в обоих случаях. Попытка вызова DeskTable с пустыми скобками приведет к ошибке.
С другой стороны, экземпляры надкласса Table , согласно неким родственным связям, не наследуют метод square своего подкласса.
В этом смысле терминология «родительский и дочерний класс» не совсем верна. Наследование в ООП – это скорее аналог систематизации и классификации наподобие той, что есть в живой природе. Все млекопитающие имеют четырехкамерное сердце, но только носороги – рог.
Полное переопределение метода надкласса
Рассмотрим вариант программы с «цепочкой наследования». Пусть дочерний по отношению к Table класс DeskTable в свою очередь выступит родительским по отношению к ComputerTable (компьютерные столы):
class Table: def __init__(self, l, w, h): self.length = l self.width = w self.height = h class DeskTable(Table): def square(self): return self.width * self.length class ComputerTable(DeskTable): def square(self, monitor=0.0): return self.width * self.length - monitor t3 = ComputerTable(0.8, 0.6, 0.7) print(t3.square(0.3)) # вывод: 0.18
Допустим, по задумке разработчиков рабочая поверхность компьютерного стола может вычисляться за вычетом площади, которую занимает монитор. В результате метод square в ComputerTable имеет отличия.
Определив в дочернем классе метод, одноименный методу родительского, мы тем самым переопределяем метод родительского класса. При вызове square на экземпляры ComputerTable будет вызываться метод из этого класса, а не из родительского класса DeskTable .
В то же время ComputerTable наследует конструктор класса от своей «бабушки» – класса Table .
Дополнение, оно же расширение, метода
Часто требуется не столько заменить, то есть полностью переопределить, метод родительского класса в дочернем, сколько дополнить, то есть расширить, код метода родительского класса в дочернем. В таких случаях решением является вызов метода надкласса в теле соответствующего метода подкласса. Обычно после этого в теле метода подкласса пишется дополнительный код.
Пусть имеется подкласс кухонных столов, для которых необходимо еще одно поле – количество посадочных мест. Из-за этой детали мы вынуждены в дочернем классе переопределить конструктор родительского.
class Table: def __init__(self, l, w, h): self.length = l self.width = w self.height = h class KitchenTable(Table): def __init__(self, l, w, h, p): self.length = l self.width = w self.height = h self.places = p t4 = KitchenTable(1.5, 2, 0.75, 6)
Поскольку существенная часть кода конструктора подкласса является такой же как в надклассе, правильнее будет вызвать метод другого класса, а не дублировать код:
class Table: def __init__(self, l, w, h): self.length = l self.width = w self.height = h class KitchenTable(Table): def __init__(self, l, w, h, p): Table.__init__(self, l, w, h) self.places = p t4 = KitchenTable(1.5, 2, 0.75, 6)
Здесь в теле конструктора KitchenTable мы вызываем метод __init__ через объект-класс Table , а не через объект-экземпляр. Вспомним, что в таких случаях метод вызывается как обычная функция (объект, к которому применяется метод, не передается в качестве первого аргумента). Поэтому в конструктор надкласса мы «вручную» передаем текущий экземпляр ( self ), записывая его перед остальными аргументами.
У кода выше есть небольшой недостаток. Нам ничего не мешает (при условии совпадения количества параметров) вызвать конструктор другого класса, а не только родительского, указав его имя вместо Table . Кроме того, имя надкласса может измениться, и тогда есть риск неправильных обращений к нему из дочерних классов.
В Python с целью улучшения так называемой обслуживаемости кода можно использовать встроенную в язык функцию super . Наиболее распространенным вариантом ее применения является вызов метода родительского класса из метода подкласса:
class KitchenTable(Table): def __init__(self, l, w, h, p): super().__init__(l, w, h) self.places = p
В данном случае аргумент self в скобках вызываемого родительского метода указывать явно не требуется.
Параметры со значениями по умолчанию у родительского класса
Рассмотрим случай, когда родительский класс имеет параметры со значениями по умолчанию, а дочерний – нет:
class Table: def __init__(self, l=1, w=1, h=1): self.length = l self.width = w self.height = h class KitchenTable(Table): def __init__(self, p, l, w, h): Table.__init__(self, l, w, h) self.places = p
При таком определении классов можно создать экземпляр от Table без передачи аргументов для конструктора:
t = Table()
Можем ли мы создать экземпляр от KitchenTable , передав значение только для параметра p ? Например, вот так:
k = KitchenTable(10)
Возможно ли, что p будет присвоено число 10, а l , w и h получат по единице от родительского класса? Невозможно, будет выброшено исключение по причине несоответствия количества переданных аргументов количеству требуемых конструктором:
. k = KitchenTable(10) TypeError: __init__() missing 3 required positional arguments: 'l', 'w', and 'h'
Когда создается объект от дочернего класса, сначала вызывается его конструктор, если он есть. Интерпретатор еще не знает, что в теле этого конструктора будет вызван конструктор родительского класса. Ведь это не обязательно. Значит, если все параметры дочернего конструктора не имеют значений по умолчанию, при построении объекта все значения должны передаваться.
Поэтому, если требуется допустить создание объектов от дочернего класса без передачи аргументов, придется назначить значения по умолчанию также в конструкторе дочернего класса.
class Table: def __init__(self, l=1, w=1, h=1): self.length = l self.width = w self.height = h class KitchenTable(Table): def __init__(self, l=1, w=1, h=0.7, p=4): Table.__init__(self, l, w, h) self.places = p
Параметр p , которого нет у родительского класса, мы делаем последним не просто так. Бывает, объекты разных родственных классов создаются или обрабатываются в одном цикле, то есть по одному алгоритму. При этом у них должны быть одинаковые «интерфейсы», то есть одинаковое количество передаваемых в конструктор аргументов.
Поэтому лучше, когда методы родственных классов принимают одинаковое число параметров. А если разное, то у «лишних» должны быть значения по-умолчанию, чтобы при вызове конструктора их можно было бы не передавать. Если такие параметры находятся еще и в конце, передачу аргументов для предстоящих параметров можно выполнять без ключей.
Другой вариант – отказаться от конструктора в дочернем классе, а значение для поля places устанавливать отдельным вызовом метода:
class Table: def __init__(self, l=1, w=1, h=1): self.length = l self.width = w self.height = h class KitchenTable(Table): places = 4 def set_places(self, p): self.places = p
Здесь у всех кухонных столов по-умолчанию будет 4 места. Если мы хотим изменить значение поля places , можем вызвать метод set_places . Хотя в случае Python можем сделать это напрямую, присвоив полю. При этом у экземпляра появится собственное поле places .
k = KitchenTable() k.places = 6
Поэтому метод set_places в общем-то не нужен.
В любом случае произвольное количество мест будет устанавливаться не в конструкторе, а отдельно. Если все же требуется указывать места при создании объекта, это можно сделать и в конструкторе родителя:
class Table: def __init__(self, l=1, w=1, h=1): self.length = l self.width = w self.height = h if isinstance(self, KitchenTable): p = int(input("Сколько мест: ")) self.places = p
С помощью функции isinstance проверяется, что создаваемый объект имеет тип KitchenTable . Если это так, то у него появляется поле places .
Мы не используем параметр p со значением по умолчанию в заголовке конструктора потому, что, если объектам других родственных классов он не нужен, не происходило бы путаницы и сложностей с документированием кода.
Практическая работа
Разработайте программу по следующему описанию.
В некой игре-стратегии есть солдаты и герои. У всех есть свойство, содержащее уникальный номер объекта, и свойство, в котором хранится принадлежность команде. У солдат есть метод «иду за героем», который в качестве аргумента принимает объект типа «герой». У героев есть метод увеличения собственного уровня.
В основной ветке программы создается по одному герою для каждой команды. В цикле генерируются объекты-солдаты. Их принадлежность команде определяется случайно. Солдаты разных команд добавляются в разные списки.
Измеряется длина списков солдат противоборствующих команд и выводится на экран. У героя, принадлежащего команде с более длинным списком, увеличивается уровень.
Отправьте одного из солдат первого героя следовать за ним. Выведите на экран идентификационные номера этих двух юнитов.
Курс с примерами решений практических работ:
pdf-версия
X Скрыть Наверх
Объектно-ориентированное программирование на Python
Python Наследование
Родительский класс это наследуемый класс, также называемый базовым классом.
Дочерний класс это класс, который наследуется от другого класса, также называемого производным классом.
Создать родительский класс
Любой класс может быть родительским классом, поэтому синтаксис аналогичен созданию любого другого класса:
Пример
Создайте класс с именем Person , со свойствами firstname и lastname и методом, printname:
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self.firstname, self.lastname)
#Используйте класс Person для создания объекта, а затем выполните метод printname:
x = Person(«Щипунов», «Андрей»)
x.printname()
Создать дочерний класс
Чтобы создать класс, наследующий функциональность от другого класса, отправьте родительский класс в качестве параметра при создании дочернего класса:
Пример
Создайте класс Student , который унаследует свойства и методы класса Person:
class Student(Person):
pass
Примечание: Используйте ключевое pass если вы не хотите добавлять в класс какие-либо другие свойства или методы.
Теперь класс Student имеет те же свойства и методы, что и класс Person.
Пример
Используйте класс Student для создания объекта, а затем выполните метод printname:
x = Student(«Щипунова», «Татьяна»)
x.printname()
Добавьте функцию __init__()
Итак, мы создали дочерний класс, который наследует свойства и методы своего родителя.
Мы хотим добавить функцию __init__() в дочерний класс (вместо ключевого слова pass ).
Примечание: Функция __init__() вызывается автоматически каждый раз, когда класс используется для создания нового объекта.
Пример
Добавьте функцию __init__() в класс Student:
class Student(Person):
def __init__(self, fname, lname):
#добавление свойств и т.д.
Когда вы добавляете функцию __init__() function, дочерний класс больше не будет наследовать функцию __init__() родителя.
Примечание: Дочерняя функция __init__() переопределяет наследование родительской функции __init__().
Чтобы сохранить наследование родительской функции __init__() , добавьте вызов родительской функции __init__() :
Пример
class Student(Person):
def __init__(self, fname, lname):
Person.__init__(self, fname, lname)
Теперь мы успешно добавили функцию __init__() и сохранили наследование родительского класса, и мы готовы добавить функциональность в функцию __init__() .
Используйте функцию super()
Python также имеет функцию super() которая заставляет дочерний класс наследовать все методы и свойства своего родителя:
Пример
class Student(Person):
def __init__(self, fname, lname):
super().__init__(fname, lname)
При использовании функции super() вам не нужно использовать имя родительского элемента, он автоматически унаследует методы и свойства от своего родителя.
Добавить свойства
Пример
Добавьте в класс Student свойство под названием graduationyear
class Student(Person):
def __init__(self, fname, lname):
super().__init__(fname, lname)
self.graduationyear = 2019
В приведенном ниже примере 2019 год должен быть переменной и передаваться в класс Student при создании объектов Student. Для этого добавьте еще один параметр в функцию __init__():
Пример
Добавьте параметр year и укажите правильный год при создании объектов:
class Student(Person):
def __init__(self, fname, lname, year):
super().__init__(fname, lname)
self.graduationyear = year
x = Student(«Щипунова», «Татьяна», 2019)
Добавить методы
Пример
Добавьте метод welcome в класс Student :
class Student(Person):
def __init__(self, fname, lname, year):
super().__init__(fname, lname)
self.graduationyear = year
def welcome(self):
print(«Добро пожаловать», self.firstname, self.lastname, «в класс», self.graduationyear)
Если вы добавите в дочерний класс метод с тем же именем, что и у функции в родительском классе, наследование родительского метода будет переопределено.
Мы только что запустили
SchoolsW3 видео
ВЫБОР ЦВЕТА
курс сегодня!
Сообщить об ошибке
Если вы хотите сообщить об ошибке или внести предложение, не стесняйтесь отправлять на электронное письмо:
Ваше предложение:
Спасибо Вам за то, что помогаете!
Ваше сообщение было отправлено в SchoolsW3.
ТОП Учебники
ТОП Справочники
ТОП Примеры
Получить сертификат
SchoolsW3 оптимизирован для бесплатного обучения, проверки и подготовки знаний. Примеры в редакторе упрощают и улучшают чтение и базовое понимание. Учебники, ссылки, примеры постоянно пересматриваются, чтобы избежать ошибок, но не возможно гарантировать полную правильность всего содержания. Некоторые страницы сайта могут быть не переведены на РУССКИЙ язык, можно отправить страницу как ошибку, так же можете самостоятельно заняться переводом. Используя данный сайт, вы соглашаетесь прочитать и принять Условия к использованию, Cookies и политика конфиденциальности.