НОЧУ ДПО "Национальный открытый университет "ИНТУИТ"
Опубликован: 24.01.2021 | Доступ: свободный | Студентов: 2489 / 106 | Длительность: 03:57:00
Лекция 24:

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

< Лекция 1 || Лекция 24: 123

Смотреть на youtube

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

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

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

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

Построение класса CreatingStr

Практически все классы определяют метод __str__. В большинстве случаев этот метод строится по стандартной схеме. Результатом работы метода является строка, содержащая для каждого атрибута метода имя атрибута и его значение. Можно было бы код, реализующий эту стратегию, встраивать в каждый метод, следуя разработанному шаблону. Но намного полезнее избежать дублирования кода, а использовать возможности наследования. Наш класс, который мы будем строить, содержит единственный метод __str__, который будут наследовать все потомки этого класса. Для построения метода класс будет вызывать метод потомка, которого он знать не знает, но уверен, что все потомки имеют вызываемый метод __ dict__. Этот встроенный метод, имеющийся у всех классов, представляет словарь, содержащий для каждого атрибута метода пару, где ключом является имя атрибута, а вторым элементом пары является значение атрибута, что и позволяет построить стандартную реализацию метода __str__. Заметьте, эта реализация основана на специфическом полиморфизме Python. Потомок вызывает метод родителя, родитель в свою очередь вызывает метод потомка. В отличие от классического полиморфизма, где все потомки принадлежат некоторой иерархии классов, связанных отношением наследования, в данной ситуации все потомки могут быть классами, не связанными никакими отношениями. Для успеха работы требуется лишь, чтобы не возникали синтаксические проблемы при вызове метода потомка. Метод __dict__ имеется у всех классов, так что проблемы не возникают. Приведу достаточно простую и понятную реализацию этого класса:

class CreatingStr(object):
    """
    Реализует наследуемый метод __str__, отображающий
    атрибуты класса потомка в виде пар имя = значение. 
    Может добавляться в качестве родителя в любые классы.
    """
    def __str__(self):
        attributs = []
        for key in sorted(self.__dict__):
            attributs.append('%s = %s' % (key, self.__dict__[key]))
            #attributs.append('%s = %s' % (key, getattr(self, key)))
        s = ', '.join(attributs)
        return '[%s:  %s]' % ("Атрибуты объекта: ", s)

Метод __str__ реализуется в несколько строчек кода. Вызываемый метод потомка __dict__ возвращает словарь, содержащий атрибуты потомка. Метод sorted сортирует ключи атрибутов в алфавитном порядке. В цикле по отсортированным ключам формируется список атрибутов, содержащий пары из ключа и значения атрибутов. Метод join преобразует список в строку, которая и возвращается в качестве результата с предшествующим текстом "Атрибуты объекта:". Закомментированная строка кода показывает другой возможный вариант получения значения атрибута, используя вызов метода getattr.

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

Проектирование иерархии классов с множественным наследованием

Создадим вначале два класса - Pa и Ma, представляющие родителей их потомка - класса Child.

Вот код класса Pa:

from CreatingStr import CreatingStr
class Pa(CreatingStr):
    """один из родителей - отец"""
    #конструктор 
    #поля класса
    #fam: string - фамилия
    #sex: string - пол
    #height: string - рост
    #persistance: string - настойчивость 
    def __init__(self, fam, sex, height = 'высокий', 
                 persistance = 'настойчив в достижении цели'):
        self.fam = fam 
        self.sex = sex
        self.height = height
        self.persistance = persistance
    #Методы 
    def smart(self, task):
        return "оригинальное решение задачи: " + task
    def common(self):
        return 'Работает Pa'

У класса Pa четыре поля, задающие свойства объектов этого класса. Два атрибута класса заданы позиционными параметрами конструктора класса, два - именованными параметрами, имеющими значение по умолчанию. Класс также определяет уникальный метод smart, которая в данном модельном проекте представляет строку текста. У класса есть также метод common. Метод с таким же именем имеется и у класса Ma, но реализации этого метода в обоих классах различны.

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

Приведем теперь код класса Ma, схожий во многом с кодом класса Pa:

from CreatingStr import CreatingStr
class Ma(CreatingStr):
    """один из родителей - мать"""
    #конструктор 
    #поля класса
    #fam: string - фамилия
    #sex: string - пол
    #height: string - рост
    #goodness: string - доброта, отзывчивость 
    def __init__(self, fam, sex, height = 'средний', 
                 goodness = 'Готовность помогать'):
        self.fam = fam 
        self.sex = sex
        self.height = height
        self.goodness = goodness
    #Методы 
    def beauty(self, task):
        return "красивое решение задачи: " + task
    def common(self):
        return 'Работает Ma'

У класса Ma, также как и у класса Pa, четыре поля и два метода. Этот класс также является потомком класса CreatingStr. Заметьте, первые три поля у обоих классов совпадают, но значение по умолчанию поля height - разные.

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

Давайте, первым делом, приведем код класса Child, имеющего двух прямых родителей и общего предка:

from CreatingStr import CreatingStr
from Pa import Pa
from Ma import Ma
class Child(Pa, Ma):
    """потомок служебного класса и двух родителей """
    def __init__(self, fam, sex, age, 
       height = 'соответствует возрасту', 
       persistence = 'как у Ра', goodness = 'как у Ма' ):        
        Pa.__init__(self,fam, sex)
        Ma.__init__(self, fam, sex)
        self.age = age
    
    def skill(self):
        return("Умелые ручки!")

Заметьте, первым делом, что в списке родителей нет класса CreatingStr. Его нельзя включать в этот список, поскольку он наследуется классами Pa и Ma, а, следовательно, и их потомком - классом Child. Попытка включить этот класс в список непосредственных родителей класса Child приведет к появлению ошибки.

Класс Child помимо тех свойств и методов, которые он наследует от своих прямых родителей и предков, имеет собственное свойство - age и собственный метод - skill. Давайте на примере этого класса разберемся как устроено множественное наследование. Мы понимаем, что множественное наследование позволяет строить сложную иерархию классов. Граф, задающий структуру множественного наследования для нашего класса потомка, представляет собой сеть с одним источником - классом object и одним стоком - классом потомка.

< Лекция 1 || Лекция 24: 123
Алексей Авилов
Алексей Авилов

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

Елена Лаптева
Елена Лаптева

Думаю. что не смогу его закончить. Хотелось предупредить других - не тратьте зря время, ищите другой курс.