Множественное наследование в Python
Многие языки программирования, в том числе Python, поддерживают множественное наследование. Это значит, что класс может иметь несколько суперклассов. Несмотря на то, что множественное наследование расширяет возможности программирования и находит применение в больших программах, оно порождает ряд проблем, связанных с разрешением имен. То есть пониманием того, метод какого надкласса вызывается.
Рассмотрим три класса, два из которых наследуются третьим:
class A: def __init__(self, field): self.field1 = field def half(self): if type(self.field1) is int: return self.field1 / 2 elif type(self.field1) is str: return self.field1[:len(self.field1)//2] else: return None class B: def __init__(self, a, b): self.field2 = a self.field3 = b def total(self): return self.field2 + self.field3 class C(A, B): def view(self): for i in self.__dict__: print(i)
Класс C, не считая своего метода view, получает в распоряжение методы half и total от своих классов-родителей. Однако он не может наследовать сразу оба конструктора класса. В данном случае он позаимствует его от класса A, потому что он указан первым в перечне суперклассов в скобках.
Мы можем убедиться в этом уже на этапе создания объекта, а не только вызывая view. Конструктор класса C способен принимать только один аргумент:
c = C('red') c.view() # field1
Поскольку в методе total задействованы поля, которых нет у экземпляра C, то его вызов будет приводить к выбросу исключения. Хотя сам метод у экземпляра будет. С half таких проблем нет.
Чтобы класс C наследовал все поля суперклассов и, возможно, имел свои, придется добавить в него собственный конструктор, из которого уже вызывать конструкторы родительских классов:
class C(A, B): def __init__(self, field, a, b): A.__init__(self, field) B.__init__(self, a, b) self.field4 = [] def view(self): for i in self.__dict__: print(i) c = C(134, 5, 8) c.view() print(c.half()) print(c.total())
Результат:
field1 field2 field3 field4 67.0 13
Рассмотрим более сложный с точки зрения дерева наследования случай. Пусть класс D наследует от B и C. При этом B наследует от A:
class A: def __str__(self): return 'It is an object of [A]' class B(A): def __init__(self, field): self.field1 = field class C: def __init__(self, field): self.field1 = field def __str__(self): return 'It is an object of [C]' class D(B, C): pass d = D(134) print(d)
От кого класс D получит метод __str__
? От второго родителя, то есть класса C, или от "бабушки" со стороны первого, то есть от A? Это может показаться нелогичным, но получит он его от A. При выполнении print(d)
будет выведено "It is an object of [A]"
.
Интерпретатор Питона при поиске атрибута сначала берет первый суперкласс и поднимается по его дереву наследования. И только закончив там, начинает поиск во следующем суперклассе. Однако все не всегда так просто, так как классы могут быть взаимосвязаны разными путями наследования. Для таких случаев в языке есть сложные правила разрешения имен.
Практическая работа
Напишите классы по следующему описанию. В конструкторе класса Unit для экземпляра создается поле id, в котором хранится его уникальный идентификатор. Классы Walking и Flying имеют методы move, которые возвращают строки f'{self.id}-й идет'
и f'{self.id}-й летит'
соответственно. Подкласс WalkingUnit наследует от Unit и Walking. Подкласс FlyingUnit наследует от Unit и Flying. Код подклассов не важен (можно заполнить с помощью pass).
В основной ветке программы создайте группу-список, включающую экземпляры WalkingUnit и FlyingUnit. Переберите список в цикле for
, вызывая для каждого его элемента метод move()
.
С какой трудностью вы столкнетесь при реализации описанного наследования классов? Как ее можно разрешить?
PDF-версия курса с примерами решений практических работ