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

Последнее обновление: 03.02.2024

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

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