Наследование позволяет создавать новый класс на основе уже существующего класса. Наряду с инкапсуляцией наследование является одним из краеугольных камней объектно-ориентированного программирования.
Ключевыми понятиями наследования являются подкласс и суперкласс. Подкласс наследует от суперкласса все публичные атрибуты и методы. Суперкласс еще называется базовым (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())