July 13, 2018

Super функция в Python

Довольно частая ситуация в программировании, когда мы хотим переопределить какой-то метод родительского класса. И всё бы хорошо, но иногда требуется сохранить поведение родителя. Вот здесь возникает проблема: "Копировать код из родительского класса или же выносить в другую функцию?" Предположим, что у нас есть следующий класс:

class ParentClass:
	def __init__(self, arg1, arg2):
		self.arg1 = arg1
		self.arg2 = arg2

Допустим мы хотим сделать его потомка, у которого первые 2 аргумента конструктора такие же, но ещё есть третий.

class ChildClass(ParentClass):
	def __init__(self, arg1, arg2, arg3):
		self.arg3 = arg3

Мы обработали новый аргумент arg3. Но что делать с arg1 и arg2? Можно просто скопировать код из ParentClass, но что если мы там что-то потом изменим и эти аргументы надо будет обрабатывать по-другому?

В питоне есть функция super, которая призвана решить эту проблему. В общем виде, она используется так: super(subclass, obj), где obj - объект, а subclass - его класс. Возвращает эта функция промежуточный объект, который является тем же объектом, но в котором все свойства и методы заменены на "родительские". С тем приоритетом, который указан при объявлении класса(выше у того класса, который указан раньше).

Функция-то хорошая, а при чём тут наша задача?

Ответ: мы можем в переопределении метода воспользоваться ей, сохраняя родительское поведение, а потом дописать то, что добавляется. ChildClass из примера выше с применением функции super:

class ChildClass(ParentClass):
	def __init__(self, arg1, arg2, arg3):
		super(ChildClass, self).__init__(arg1, arg2)
		self.arg3 = arg3

Таким образом, мы на нашем объекте ChildClass явно вызываем родительский конструктор, сохраняя поведение ParentClass, а потом дополняем тем функционалом, который требуется.

Можем проверить, что это работает, как требуется:

c = ChildClass(1, 2, 3)
print(c.arg1)  # 1
print(c.arg2)  # 2
print(c.arg3)  # 3

Всё именно так, как и должно быть, верно? Напоминаю, что если родительских классов несколько, то будет взят метод того класса, в котором он определён раньше. Ещё с 3 версии питона, можно заменять super(subclass, obj) на просто super(), если действие происходит внутри класса. Наш код примет следующий финальный вид:

class ChildClass(ParentClass):
	def __init__(self, arg1, arg2, arg3):
		super().__init__(arg1, arg2)
		self.arg3 = arg3

Просто и красиво. Надеюсь, что статья была вам полезна.