Все классы в питоне наследуются от базового object во 2 версии
это делалось явно, начиная с 3 версии это происходит автоматически.
Есть класс class Prop чтобы наследоваться от него, указываем его в
дочернем классе, в скобках class Line(Prop)
Когда в классе Point создается метод конструктор:
class Prop:
def __init__(self, start, end, color, width):
print("Метод __init__ класса Prop")
self._start = start
self._end = end
self._color = color
self._width = widthЕсли мы наследуемся от него классом Line, и в самом классе Line
мы не создаем метода конструктора, то по дефолту будет использоваться
метод конструктор родительского класса:
class Line(Prop):
def drawLine(self):
print(f"Рисуем линию: {self._start}, {self._end}," \
f" {self._color}, {self._width}")Если мы хотим переопределить метод конструктор в Line, или хотим
добавить дополнительный функционал, то мы можем сделать
это 2 способами:
Point.__init__(self, *args)Этот метод не правильный, его можно использовать, но указание конкретного класса родителя приводит к путанице в случае множественного наследования.
Обратим внимание, что данный способ вызова требует передачу self
super().__init__(*args)Этот метод правильный, он сам извлекает из стека наследуемых классов правильный.
Обратим внимание, что данный способ вызова не требует передачу self
Этот механизм я встречал только в 2 языках, C++ и Python.
Суть в том что мы можем наследоваться сразу от 2 классов.
Указание происходит также в скобках, первым вызывается методы класса
который указан первым, есть в данном случае первый будет вызван One:
class My(One, Two):
При вызове конструктора, будет вызван конструктор класса One если
конечно нет конструктора в классе My
Для корректной работы множественного наследования используется функция
super() суть этой функции заключается в том, чтобы обходить
родительские классы только по одному разу и не повторяться.
class Poss:
def __init__(self, x, y, *args):
self._x = x
self._y = y
Style.__init__(self, *args)
class Style:
def __init__(self, name, style):
self._name = name
self._style = style
class Point(Poss, Style):
def draw(self):
return f"x={self._x} y={self._y} name={self._name} style={self._style}"
pt = Point(10, 20, 'Название', 'Стили')
print(pt.draw())
# Вывод
# x=10 y=20 name=Название style=СтилиТут проблема заключается в том что указывая в явном виде эту строчку
Style.__init__(self, *args) мы жестко задаем класс конструктор
которого будет вызван.
А если в классе Style указать что то типа
Poss.__init__(self, *args) то это приведет нас к рекурсии,
в общем лучше так не делать.
Мы могли бы сделать такую реализацию:
class Styles:
def __init__(self, color="red", width=1, *args):
print("Конструктор Styles")
self._color = color
self._width = width
super().__init__(*args)
class Pos:
def __init__(self, sp:Point, ep:Point, *args):
print("Конструктор Pos")
self._sp = sp
self._ep = ep
super().__init__(*args)
class Line(Pos, Styles):
def draw(self):
print(f"Рисование линии: {self._sp}, {self._ep}, {self._color}, {self._width}")То есть просто определять в каждом конструкторе super().__init__(*args)
который бы устанавливал нужные атрибуты, а другие передавал бы далее по
цепочке родителей.
Но в таком случае есть другая проблема, нам бы потребовалось четко контролировать порядок передачи аргументов, и мы бы не могли менять порядок наследования.
Правильная реализация выглядит следующим образом:
class Styles:
def __init__(self):
print("Конструктор Styles")
super().__init__()
class Pos:
def __init__(self):
print("Конструктор Pos")
super().__init__()
class Line(Pos, Styles):
# class Line(Styles, Pos):
def __init__(self, sp:Point, ep:Point,color="red", width=1,):
self._sp = sp
self._ep = ep
self._color = color
self._width = width
def draw(self):
print(f"Рисование линии: {self._sp}, {self._ep}, {self._color}, {self._width}")
ln = Line(Point(10, 10), Point(100, 100), "green", 5)
ln.draw()Как можно увидеть тут вся инициализация происходит только в
непосредственном родительском классе, а не в его родителях,
родитель только вызывают super().__init__() более верхнего класса.
При такой реализации нпм уже не важно какой порядок наследования, он может быть любым.
Порядок наследования называется MRO он указывает порядок
наследования классов с начала Line -> Pos -> Style -> object
Это можно увидеть при помощи спец атрибута класса __mro__
print(Line.__mro__)
# Вывод
# (
# <class '__main__.func5.<locals>.Line'>,
# <class '__main__.func5.<locals>.Pos'>,
# <class '__main__.func5.<locals>.Styles'>,
# <class 'object'>
# )Представим что нам требуется сделать наследование, и каждый и конструкторов классов принимает и устанавливает свои атрибуты, таким образом нам требуется последовательно разделять получаемые параметры на части, скармливать эти части своим методам конструкторам.
Сделать это можно путем обь единения и разделения параметров с
помощью символа *
Пример работы:
"""Пример разделения аргументов на части"""
class One:
def __init__(self, x, y):
print('One = ', x, ' ', y)
self._x = x
self._y = y
class Two(One):
def __init__(self, string, number, *args):
print('Two = ', args)
self._string = string
self._number = number
super().__init__(*args)
def show(self):
return f"x = {self._x} y = {self._y} str = '{self._string}' num = {self._number}"
my = Two('some string', 55, 10, 20)
print(my.show())
# Вывод
# Two = (10, 20)
# One = 10 20
# x = 10 y = 20 str = 'some string' num = 55Видим что используя оператор * мы можем отделять сати параметров.