Наследование позволяет создавать новый класс на основе уже существующего класса. Наряду с инкапсуляцией наследование является одним из краеугольных камней объектно-ориентированного программирования.
Ключевыми понятиями наследования являются подкласс и суперкласс. Подкласс наследует от суперкласса все публичные атрибуты и методы. Суперкласс еще называется базовым (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: {self.__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: {self.__name} ")
def work(self):
print(f"{self.name} 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: {self.__name} ")
class Employee(Person):
def work(self):
print(f"{self.name} 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"{self.__name} 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"{self.name} works")
class Student:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
def study(self):
print(f"{self.name} studies")
class WorkingStudent(Employee, Student):
pass
tom = WorkingStudent("Tom")
tom.work() # Tom works
tom.study() # Tom studies
Множественное наследование может показаться удобным, тем не менее оно может привести к путанице, если оба наследуемых класса содержат методы/атрибуты с одинаковыми именами. Например:
class Employee:
def do(self):
print("Employee works")
class Student:
def do(self):
print("Student studies")
# class WorkingStudent(Student,Employee):
class WorkingStudent(Employee, Student):
pass
tom = WorkingStudent()
tom.do() # ?
Оба базовых класса - Employee и Worker определяют метод do, который выводит разную строку на консоль. Какую именно из этих реализаций будет использовать класс-наследник WorkingStudent? При определении класса первым в списке базовых классов идет класс Employee
class WorkingStudent(Employee, Student)
Поэтому реализация метода do будут браться из класса Employee.
Если бы мы поменяли очередность классов:
class WorkingStudent(Student,Employee)
то использовалась бы реализация класса Student
При необходимости мы можем программным образом посмотреть очередность применения функционала базовых классов. Для этого применяется атрибут __mro__, либо метод mro():
print(WorkingStudent.__mro__) print(WorkingStudent.mro())