-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmetaclass.py
More file actions
226 lines (178 loc) · 11.4 KB
/
metaclass.py
File metadata and controls
226 lines (178 loc) · 11.4 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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Copyright (c) 2015, 2016 Artur Eganyan
#
# This work is provided "AS IS", WITHOUT ANY WARRANTY, express or implied.
#-------------------------------------------------------------------------------
# Кратко:
# - метакласс - это класс класса
# - каждый класс является экземпляром своего метакласса (по умолчанию - type)
# - класс создается вызовом метакласс.__new__("имя класса", родители, переменные)
# - метакласс указывается в переменной __metaclass__
# Метакласс - это класс класса (тип класса). Каждый класс является экземпляром
# своего метакласса. По умолчанию метакласс - это type.
#
# После того, как python прочитал описание класса "class <имя>: ...", выполнив
# все выражения в нем, у него есть:
# 1. Имя класса.
# 2. Список родительских классов.
# 3. Переменные класса в виде словаря (пространство имен).
#
# Далее python создает класс как экземпляр метакласса, передавая в конструктор
# эти три объекта:
#
# класс = метакласс("класс", список родителей, переменные)
#
# Соответственно, конструктор __new__ метакласса должен возвращать созданный
# класс. Для создания класса он обычно вызывает type.__new__(метакласс, ...).
# Также, метакласс (тип) любого класса возвращается вызовом type(класс).
#
# Метакласс определяется в следующем порядке:
# 1. Переменная __metaclass__ в описании класса.
# 2. Метакласс базового класса.
# 3. Глобальная переменная __metaclass__.
# 4. Если ничего не найдено, используется types.ClassType
# (это old-style метакласс, называемый также "classobj").
#
# Замечание: В python 3.x метакласс указывается не в переменной __metaclass__,
# а как параметр после списка родительских классов: class A(B1, B2, metaclass=M).
# Глобальная переменная __metaclass__ там тоже не поддерживается.
#
# Замечание: Класс type особенный - он является метаклассом самому себе.
# Метакласс M будет регистрировать свои классы в списке M.classes
class M(type):
classes = []
def __new__(metaclass, name, bases, attributes):
print u"Сейчас создается класс", name
cls = type.__new__(metaclass, name, bases, attributes)
M.classes.append(cls)
return cls
class A(object):
__metaclass__ = M
# В этом месте будет выполнено A = M("A", (object), переменные)
class B(A):
pass
# В этом месте будет выполнено B = M("B", (A), переменные)
print M.classes # [<class '__main__.A'>, <class '__main__.B'>]
# Замечание: В примере выше для создания класса вызывался конструктор
# type.__new__(метакласс, ...). Если бы вместо этого вызывался просто
# type(...), то был бы создан класс типа type, а не M. Т.е. класс A имел бы
# метакласс type, и для создания B уже не вызывался бы M(...).
# При множественном наследовании у класса должен быть метакласс, являющийся
# потомком каждого метакласса родителей. В примере ниже это правило не
# выполнено, поэтому возникнет ошибка.
class MA(type):
pass
class MB(type):
pass
class A(object):
__metaclass__ = MA
class B(object):
__metaclass__ = MB
try:
class C(A, B):
print u"Пока все нормально, но когда python попробует создать"
print u"класс C, будет ошибка. То есть сразу после этого print."
# В этом месте будет ошибка TypeError: Error when calling the metaclass bases
# metaclass conflict: the metaclass of a derived class must be a (non-strict)
# subclass of the metaclasses of all its bases
except TypeError as e:
print e
# Это логично, т.к. python не может определить метакласс C - это
# MA или MB? Поэтому метакласс надо создавать вручную.
class MC(MA, MB):
pass
class C(A, B):
__metaclass__ = MC
print u"Теперь все в порядке"
# В посте "SOLVING THE METACLASS CONFLICT (Python recipe)" предлагается код,
# который позволяет создавать такие метаклассы автоматически (не проверял).
# Однако интересно, что если метаклассы родителей являются родственными (один
# наследует другой), то python выберет из них самого "глубокого" по уровню
# наследования в качестве нужного метакласса.
class MA(type):
def __new__(metaclass, *args):
print metaclass.__name__ # Чтобы видеть, какой метакласс используется
return type.__new__(metaclass, *args)
class MB(MA): # Теперь MB наследует MA
pass
class A(object):
__metaclass__ = MA
# Выведет "MA", т.к. сейчас создается A = MA(...)
class B(object):
__metaclass__ = MB
# Выведет "MB", т.к. сейчас создается B = MB(...)
class C(A, B):
pass
# Выведет "MA" и "MB". По всей видимости, это означает примерно следующее:
# 1. Сначала проверяется, что у C нет __metaclass__.
# 2. Берется метакласс первого родителя - type(A), и создается C = MA(...)
# 3. Берется метакласс следующего родителя - type(B). Т.к. MB наследует от
# метакласса текущего C (type(C) == MA), класс C создается заново:
# C = MB(...). Если бы MB был родителем MA, создавать C заново не было
# бы необходимости. А если бы MB не был ни потомком, ни родителем MA,
# произошла бы ошибка из-за несовместимости метаклассов.
# 4. Родительских классов больше нет, поэтому C успешно создан. Иначе снова
# выполнился бы шаг 3.
#
# Чтобы увидеть ошибку "Error when calling the metaclass bases" на шаге 3,
# можно заменить родителя MB на type - тогда при создании C сначала будет
# выведено "MA" (шаг 2), а потом ошибка (шаг 3).
#
# Замечание: Настоящим метаклассом является именно type(класс), а не
# __metaclass__. Например, если в A.__metaclass__ и B.__metaclass__ поместить
# функции, возвращающие MA(...) и MB(...), то будет видно, что при создании
# класса C эти функции не вызываются.
print type(C) # <class '__main__.MB'>
class C(B, A):
pass
# Теперь будет выведено только "MB". Потому что на шаге 3 окажется, что
# MA - родитель MB, поэтому созданный на этот момент C менять не надо.
print type(C) # <class '__main__.MB'>
# При обращении к атрибуту класса ("класс.атрибут"), его метакласс и
# родители метакласса добавляются в конец MRO.
class M1(type):
y = "M1"
class M2(M1):
x = "M2"
class A(object):
x = "A"
class B(A):
__metaclass__ = M2
print B.x # A, потому что при поиске A.x идет раньше M.x
print B.y # M1
# Если у класса нет родителей, это old-style класс, и для него метаклассом
# будет types.ClassType (при отсутствии глобальной переменной __metaclass__)
class A(object):
pass
class B:
pass
import types
print type(A), type(B) # <type 'type'>, <type 'classobj'>
print types.ClassType # <type 'classobj'>
# Интересно, что сам ClassType наследует от object, т.е. является new-style
# классом, и имеет тип type.
print types.ClassType.__bases__ # (<type 'object'>,)
print type(types.ClassType) # <type 'type'>
# Замечание: Переменная __metaclass__ может содержать ссылку на любой объект,
# который можно вызвать с тремя параметрами. Например, это может быть обычная
# функция, возвращающая класс. Но в этом случае метаклассом будет, как и
# всегда, тип возвращенного класса. Т.е., метакласс - это класс класса, его
# можно узнать через type(класс). Переменная __metaclass__ обычно совпадает
# с type(класс), но это не обязательно.
def M(name, bases, attributes):
print "M()"
return type(name, bases, attributes)
class A(object):
__metaclass__ = M
# M()
print type(A) # <type 'type'>. Метаклассом A будет тот класс, экземпляр
# которого вернет функция M() (тут это type). В этом случае
# __metaclass__ и реальный метакласс различаются.
# Замечание: В конечном счете класс создается через конструктор
# type.__new__(метакласс, "класс", список родителей, переменные). Поэтому
# типом класса будет первый параметр __new__. Кроме того, этот параметр должен
# наследовать от type, поэтому метаклассы обычно наследуют от type.
#
# Замечание: Конструктор __new__ метакласса может вернуть любой класс - не
# обязательно тот, который требовался.