-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsuper.py
More file actions
124 lines (106 loc) · 7.8 KB
/
super.py
File metadata and controls
124 lines (106 loc) · 7.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Copyright (c) 2015 Artur Eganyan
#
# This work is provided "AS IS", WITHOUT ANY WARRANTY, express or implied.
#-------------------------------------------------------------------------------
# Кратко:
# - super().метод() используется для вызова методов родительских классов
# - super().метод() вызывает метод в порядке MRO
# super - это объект, который передает обращение к атрибутам экземпляра или
# класса в его родительские классы, в порядке MRO (Method Resolution Order).
# По сути, super позволяет вызывать методы родительских классов из дочернего,
# учитывая этот порядок.
#
# При обычном обращении "объект.атрибут", атрибут тоже ищется в порядке MRO.
# Первым в MRO стоит сам объект (экземпляр или класс), затем идет иерархия
# классов и метаклассов. super позволяет выполнить этот же поиск, пропустив
# объект и, возможно, несколько классов.
# Поскольку super() в python - вещь не совсем привычная, далее подробно
# расписан пример. В нем такая иерархия классов:
#
# A
# / \
# B C
# \ /
# D
#
# У A есть метод event_handler(), который дочерние классы могут переопределить.
# Предположим, что он обрабатывает события, происходящие с экземпляром. Обычно
# такой метод обрабатывает событие, если оно интересно классу, и затем передает
# событие в родительский класс, вызывая "родитель.event_handler" - и т.д. по
# цепочке родителей. Тогда, если с экземпляром D произойдет событие, о нем
# узнают все классы, от которых D наследует.
#
# B.event_handler и C.event_handler могут просто вызвать A.event_handler. Но
# с D сложнее: если D.event_handler вызовет подряд обработчики B и C, то каждый
# из них вызовет A.event_handler, и в итоге он обработает событие дважды.
#
# В данной ситуации надо либо запрещать A "входить" в дочерний класс более
# одного раза (как это запрещено в Qt для базового класса QObject), либо
# отмечать в событии, какие классы его уже обработали, либо вызывать
# обработчики по-другому. super как раз позволяет вызывать их по-другому.
#
# super() вызывает метод родительского класса, делая поиск в том же порядке,
# в каком ищется любой атрибут объекта - в порядке MRO. Для экземпляра класса D
# порядок такой: экземпляр, D, B, C, A. super должен знать, для какого объекта
# делается вызов (чтобы знать MRO), и из какого класса (чтобы начать поиск с
# нужного места MRO). Поэтому параметры такие: super(текущий класс, объект),
# где объект - экземпляр или класс.
#
# В данном случае super(D, экземпляр).event_handler() вызовет обработчик B.
# В этом обработчике super(B, экземпляр).event_handler() вызовет обработчик C,
# потому что экземпляр будет тот же самый. И наконец
# super(C, экземпляр).event_handler() вызовет обработчик A.
class A(object):
def event_handler(self, e):
print "A"
class B(A):
def event_handler(self, e):
print "B"
super(B, self).event_handler(e)
class C(A):
def event_handler(self, e):
print "C"
super(C, self).event_handler(e)
class D(B, C):
def event_handler(self, e):
print "D"
super(D, self).event_handler(e)
d = D()
d.event_handler(u"Какое-то событие")
# Последовательность вызовов будет такой:
# D.event_handler(d, e)
# B.event_handler(d, e)
# C.event_handler(d, e)
# A.event_handler(d, e)
#
# Ключевой момент здесь в том, что каждый следующий вызов делается для того
# же самого экземпляра d (он же self), а вот текущий класс в super() меняется.
# Поэтому super() каждый раз знает MRO экземпляра (D, B, C, A), и начинает
# поиск метода с класса, стоящего за текущим. К примеру, если бы B не
# переопределял event_handler, то super(D, self).event_handler вызвал бы
# обработчик C, а тот - A.
# В статье "Python’s super() considered super!" даются советы, как использовать
# super():
# 1. Метод, вызываемый через super(), должен быть хотя бы в одном классе.
# 2. Этот метод во всех классах должен иметь одну сигнатуру (набор параметров).
# 3. Этот метод во всех классах должен вызывать super() (кроме корневого класса).
#
# Перечисленные пункты позволяют менять иерархию классов, не думая, что где-то
# что-то сломается, т.е. что будет вызван несуществующий или несовместимый
# метод. Создание классов с учетом этих советов называется "cooperative
# multiple inheritance".
# В статье "Things to Know About Python Super [2 of 3]" показано, зачем
# существует вариант super(текущий класс), без второго параметра. И там же
# показано, почему это плохой вариант, который лучше убрать (причем автор
# языка согласен, но убирать уже поздно, похоже).
# Замечание: super важен при множественном наследовании, потому что он
# позволяет сохранить порядок MRO, пропустив в нем один или несколько классов.
# Если бы было только одиночное наследование, или множественное, но без
# "умного" MRO, то super не играл бы особой роли - разве что позволял не
# указывать явно имя родительского класса.
#
# Замечание: В python 2.x в super() надо передавать текущий класс, из которого
# делается вызов метода, и объект, для которого он делается. В python 3.x это
# не обязательно.