Как вызвать функцию из функции, которая в классе?
У методов по умолчанию задается параметр self , который незаметно перекладывается при вызове метода объектом:
# В test будет передана ссылка на launch, т.е. self это launch launch.test()
Поэтому, для функции test1 нужно вручную передавать параметр, раз его объявили:
def test1(self): print(self.name) test1(self)
Либо, не указывайте в test1 ничего и обращайтесь к self , т.к. область действия происходит внутри метода и self будет доступен:
def test1(): print(self.name) test1()
Как вызвать метод класса python?
Метод класса можно вызвать как функцию, указав его название через точку после названия класса:
class MyClass: def my_method(a, b): return a + b MyClass.my_method # MyClass.my_method(3, 5) # 8
Как вызвать метод из родительского класса python?
Для вызова метода из родительского класса в Python есть два способа:
- Явное обращение к методу предка
- Функция super()
class Counter: def __init__(self): self.value = 0 def inc(self): self.value += 1 def dec(self): self.value -= 1 # Создаем класс-потомок, при вызове inc увеличивающих значение дважды # Вариант 1 - с прямым обращением к предку: class DoubleCounter(Counter): def inc(self): Counter.inc(self) # явно обращаемся к методу класса предка Counter.inc(self) # и передаем ссылку на экземпляр # Вариант 2 - с применением функции super(): class DoubleCounter(Counter): def inc(self): super().inc() super().inc() num = DoubleCounter() num.value # 0 num.inc() # В обоих случаях, наследованный метод inc() будет работать одинаково num.value # 2
Функция super() названа в честь названия класса-предка: «superclass». Потому что благодаря ей мы получаем ссылку на атрибут предка и заменяем обращение self , создавая таким образом связанный с текущим классом метод, который будет полноценной «оригинальной версией» из класса-предка. При чем если предок сменится, то super в описании класса учтет изменения, и мы получим доступ к поведению нового предка.
Вызов методов базового класса
Надо вызвать метод базового класса из метода, который переопределен в производном классе.
Из конструктора дочернего класса нужно явно вызывать конструктор родительского класса.
Обращение к базовому классу происходит с помощью super()
Нужно явно вызывать конструктор базового класса
class A(object): def __init__(self, x=5): print('A.__init__') self.x = x class B(A): def __init__(self, y=2): print('B.__init__') self.y = y k = B(7) # B.__init__ print('k.y =', k.y) # k.y = 7 #print('k.x =', k.x) # AttributeError: 'B' object has no attribute 'x'
Видно, что без явного вызова конструктора класса А не вызывается A.__init__ и не создается поле x класса А.
Вызовем конструктор явно.
Конструктор базового класса стоит вызывать раньше, чем иницилизировать поля класса-наследника, потому что поля наследника могут зависеть (быть сделаны из) полей экземпляра базового класса.
class A(object): def __init__(self, x=5): print('A.__init__') self.x = x class B(A): def __init__(self, y=2): print('B.__init__') super().__init__(y/2) self.y = y k = B(7) # B.__init__ # A.__init__ print('k.y =', k.y) # k.y = 7 print('k.x =', k.x) # k.x = 3.5
super() или прямое обращение к классу?
Метод класса можно вызвать, используя синтаксис вызова через имя класса:
class Base(object): def __init__(self): print('Base.__init__') class A(Base): def __init__(self): Base.__init__(self) print('A.__init__') k = A() # Base.__init__ # A.__init__
Все работает. Но при дальнейшем развитии классов могут начаться проблемы:
class Base(object): def __init__(self): print('Base.__init__') class A(Base): def __init__(self): Base.__init__(self) print('A.__init__') class B(Base): def __init__(self): Base.__init__(self) print('B.__init__') class C(A, B): def __init__(self): A.__init__(self) B.__init__(self) print('C.__init__') x = C() # Base.__init__ # A.__init__ # Base.__init__ - второй вызов # B.__init__ # C.__init__
Видно, что конструктор Base.__init__ вызывается дважды. Иногда это недопустимо (считаем количество созданных экземпляров класса, увеличивая в конструкторе счетчик на 1; выдаем очередное auto id какому-то нашему объекту, например, номер пропуска или паспорта или номер заказа).
То же самое через super():
- вызов конструктора Base.__init__ происходит только 1 раз.
- вызваны конструкторы всех базовых классов.
- порядок вызова конструкторов для классов А и В не определен.
class Base(object): def __init__(self): print('Base.__init__') class A(Base): def __init__(self): super().__init__() print('A.__init__') class B(Base): def __init__(self): super().__init__() print('B.__init__') class C(A, B): def __init__(self): super().__init__() print('C.__init__') x = C() # Base.__init__ # B.__init__ - вызваны конструкторы обоих базовых классов # A.__init__ - порядок вызова # C.__init__
Как это работает?
Для реализации наследования питон ищет вызванный атрибут начиная с первого класса до последнего. Этот список создается слиянием (merge sort) списков базовых классов:
- дети проверяются раньше родителей.
- если родителей несколько, то проверяем в том порядке, в котором они перечислены.
- если подходят несколько классов, то выбираем первого родителя.
При вызове super() продолжается поиск, начиная со следующего имени в MRO. Пока каждый переопределенный метод вызывает super() и вызывает его только один раз, будет перебран весь список MRO и каждый метод будет вызван только один раз.
Не забываем вызывать метод суперкласса
А если где-то не вызван метод суперкласса?
class Base(object): def __init__(self): print('Base.__init__') class A(Base): def __init__(self): #super().__init__() - НЕ вызываем super() print('A.__init__') class B(Base): def __init__(self): super().__init__() print('B.__init__') class C(A, B): def __init__(self): super().__init__() print('C.__init__') x = C() # A.__init__ # C.__init__ print(C.__mro__) # (, , , , )
Заметим, что хотя в B.__init__ есть вызов super(), то до вызова B.__init__ не доходит.
- Вызываем у объекта класса С метод __init__.
- Ищем его в mro и находим С.__init__. Выполняем его.
- В этом методе вызов super() — ищем метод __init__ далее по списку от найденного.
- Находим A.__init__. Выполняем его. В нем нет никаких super() — дальнейший поиск по mro прекращается.
Нет метода в своем базовом классе, есть у родителя моего сиблинга
Определим класс, который пытается вызвать метод, которого нет в базовом классе:
class A(object): def spam(self): print('A.spam') super().spam() x = A() x.spam()
получим, как и ожидалось:
A.spam Traceback (most recent call last): File "examples/oop_super_3.py", line 7, in x.spam() File "examples/oop_super_3.py", line 4, in spam super().spam() AttributeError: 'super' object has no attribute 'spam'
Определим метод spam в классе В. Класс С, наследник А и В, вызывает метод A.spam(), который вызывает B.spam — класс В не связан с классом А.
class A(object): def spam(self): print('A.spam') super().spam() class B(object): def spam(self): print('B.spam') class C(A, B): pass y = C() y.spam() print(C.__mro__)
A.spam B.spam (, , , )
Для объекта класса С вызвали метод spam(). Ищем его в MRO. Находим A.spam() и вызываем. Далее для super() из A.spam() идем дальше от найденного по списку mro и находим B.spam().
Отметим, что при другом порядке описания родителей class C(B, A) , вызывается метод B.spam() у которого нет super():
B.spam (, , , )
Вызываем метод spam для объекта класса С. В С его нет, ищем дальше в В. Находим. Вызваем. Далее super() нет и дальнейший поиск не производится.
Чтобы не было таких сюрпризов при переопределении методов придерживайтесь правил:
- все методы в иерархии с одинаковым именем имеют одинаковую сигнатуру вызова (количество аргументов и их имена для именованных аргументов).
- реализуйте метод в самом базовом классе, чтобы цепочка вызовов закончилась хоть каким
Обращение к дедушке
Игнорируем родителя
Если у нас есть 3 одинаковых метода foo(self) в наследуемых классах А, В(А), C(B), и нужно из C.foo() вызвать сразу A.foo() минуя B.foo(), то наши классы неправильно сконструированы (почему нужно игнорировать В? может, нужно было наследовать С от А, а не от В?). Нужен рефакторинг.
Но можно всегда вызвать метод по имени класса:
class A(object): def spam(self): print('A.spam') class B(A): def spam(self): print('B.spam') class C(B): def spam(self): A.spam(self) print('C.spam') y = C() y.spam() print(C.__mro__)
A.spam C.spam (, , , )
Метод определен только у дедушки
Если в В такого метода нет, и из C.foo() нужно вызвать A.foo() (или в базовом классе выше по иерархии), вызываем super().foo() и больше не думаем, у какого пра-пра-пра-дедушки реализован этот метод.
Просто воспользуйтесь super() для поиска по mro.
class A(object): def spam(self): print('A.spam') class B(A): pass class C(B): def spam(self): super().spam() print('C.spam') y = C() y.spam() print(C.__mro__)
A.spam C.spam (, , , )
super().super() не работает
Или мы ищем какого-то родителя в mro, или точно указываем из какого класса нужно вызвать метод.
Литература
- Документация по python
- Python’s super() considered super!
- Python Cookbook, chapter 8.7 Calling a Method on a Parent Class