-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclasses.py
More file actions
300 lines (248 loc) · 17.9 KB
/
classes.py
File metadata and controls
300 lines (248 loc) · 17.9 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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Copyright (c) 2015, 2016 Artur Eganyan
#
# This work is provided "AS IS", WITHOUT ANY WARRANTY, express or implied.
#-------------------------------------------------------------------------------
# Класс описывается блоком:
#
# class <имя>(<родительский класс 1>, ...):
# <выражение 1>
# ...
# <выражение N>
#
# Когда python читает описание класса, он выполняет все выражения. В частности,
# выражением является описание функции "def <функция>". После выполнения
# последнего выражения создается объект-класс, хранящий переменные, созданные
# выражениями (в том числе - функции). Переменные хранятся в словаре
# класс.__dict__.
#
# Экземпляр класса создается вызовом "класс(параметры)". В свою очередь, это
# приводит к вызову двух функций:
# 1. экземпляр = класс.__new__(класс, параметры)
# 2. класс.__init__(экземпляр, параметры)
# После чего возвращается созданный экземпляр. __new__ создает и возвращает
# пустой экземпляр класса, __init__ может его проинициализировать - например,
# добавить ему какие-то атрибуты. Обе функции можно определить по-своему, но
# обычно определяют только __init__ (по сути это конструктор).
#
# Любой объект (экземпляр или класс) поддерживает операцию обращения к
# атрибуту: "объект.атрибут". Обычно атрибут - это переменная в словаре
# __dict__ объекта или одного из его классов. Атрибут ищется в порядке MRO -
# Method Resolution Order (см. заметку "mro"). В общем случае, MRO выглядит
# так: экземпляр, его класс, родительские классы, метаклассы. Подробнее
# про чтение и запись атрибутов есть в конце этой заметки.
#
# Если атрибут класса является функцией, то при обращении к такому атрибуту
# создается и возвращается специальный объект - метод. Если обращение сделано
# через экземпляр ("экземпляр.функция"), создается связанный метод (bound
# method), хранящий ссылки на экземпляр, функцию и класс. Если обращение
# сделано через класс ("класс.функция"), создается несвязанный метод (unbound
# method), хранящий ссылки на функцию и класс. В любом случае, методы
# создаются для функций, найденных в классе (а не в экземпляре).
#
# Метод можно вызывать так же, как функцию. При вызове связанного метода к
# его параметрам автоматически добавляется ссылка на экземпляр:
# экземпляр.функция(параметры) == функция(экземпляр, параметры).
# При вызове несвязанного метода все параметры надо передавать явно:
# класс.функция(все параметры).
#
# Замечание: Переменные класса недоступны из методов класса (см. пример).
# Все выражения в описании класса будут выполнены
class A:
print 1 # 1
x = 2
def f(self):
print A.x # 2
#print x # Ошибка - x не видна из метода класса
print x # 2
# В этом месте будет создан объект-класс A в текущем модуле (__main__).
# Если бы описание класса было внутри функции, объект был бы создан там,
# как локальная переменная.
print A # __main__.A. Если бы A наследовал от object (new-style class),
# было бы выведено <class '__main__.A'>.
class B:
def __init__(self, x, y):
self.x = x
self.y = y
def f(self):
print self.x, self.y
b = B(1, 2) # Вызовет B.__new__(B, 1, 2), получит пустой экземпляра класса B
# и вызовет B.__init__(экземпляр, 1, 2)
print b.f # <bound method B.f of <__main__.B instance at адрес>>.
# Обращение к функции класса через экземпляр приводит к созданию
# связанного метода.
b.f() # 1, 2
print B.f # <unbound method B.f>. Обращение к функции класса через
# сам класс приводит к созданию несвязанного метода.
B.f(b) # 1, 2
#B.f() # TypeError: unbound method f() must be called with B
# instance as first argument (got nothing instead)
# Класс может быть "new-style" или "old-style". Если класс наследует от класса
# object (прямо или косвенно), то это new-style, иначе old-style. New-style
# класс является полноценным типом данных, наравне со встроенными типами
# вроде int и string. Для него type(экземпляр класса) возвращает сам класс.
# Old-style класс имеет меньше возможностей, и для него type(экземпляр класса)
# возвращает тип 'instance', независимо от класса. В python 3.x оставлены
# только new-style классы.
#
# New-style классы имеют следующие отличия:
# - Новый способ поиска атрибутов (MRO - method resolution order)
# - Наличие дескрипторов, свойств, статических методов, методов класса, слотов
# - Наличие декораторов
# - Наличие конструктора __new__
# - Возможность наследовать от встроенных типов данных (int, list, и т.д.)
# Разница между new-style и old-style классами и их экземплярами
class A1:
pass
class A2(object):
pass
a1 = A1()
a2 = A2()
print A1, A2 # __main__.A1, <class '__main__.A2'>
print a1, a2 # <__main__.A1 instance at адрес>, <__main__.A2 object at адрес>
print type(a1), type(a2) # <type 'instance'> <class '__main__.A2'>
print type(A1), type(A2) # <type 'classobj'> <type 'type'>
# Статический метод - это функция в классе, которая вызывается без каких-либо
# неявных параметров (т.е. точно так же, как обычная функция). Для создания
# статического метода есть функция-декоратор staticmethod().
class A(object):
def f():
print "A.f()"
f = staticmethod(f) # Теперь f - статический метод
# Ровно то же самое можно записать проще
@staticmethod
def f():
print "A.f()"
A.f() # При обращении к A.f будет возвращена исходная функция f,
# а не обычный в таких случаях объект "несвязанный метод"
# Метод класса - это функция в классе, при вызове которой первым параметром
# передается ссылка на класс. Для создания метода класса есть функция-декоратор
# classmethod().
class A(object):
def f(cls):
print cls
f = classmethod(f) # Теперь f - метод класса
# Ровно то же самое можно записать проще
@classmethod
def f(cls):
print cls
A.f() # При обращении к A.f будет возвращена функция-обертка,
# которая передает в исходную f ссылку на класс A
# Замечание: Если записать в экземпляр ссылку на обычную функцию, это не
# сделает ее методом.
class A:
pass
def f(self):
print "f()"
a = A()
a.f = f # В экземпляр записывается ссылка на внешнюю функцию f
#a.f() # TypeError: f() takes exactly 1 argument (0 given)
# Здесь правильно было бы вызвать a.f(a), потому что a.f
# не ведет себя как метод. Только функции, находящиеся в
# классе, преобразуются в связанные методы при обращении
# к ним через "экземпляр.функция".
A.f = f # Та же функция, помещенная в класс, будет преобразовываться
# в связанный метод при обращении через "экземпляр.функция"
del a.f # Удаляем атрибут "f" из экземпляра a, чтобы он не перекрывал
# атрибут "f" класса A
a.f() # f()
# На этом примере видно, что все методы по сути виртуальные. Т.е. методы
# можно переопределять в дочерних классах, и при вызове "экземпляр.метод()"
# будет вызываться "последний" переопределенный метод.
class A:
def f(self):
self.g()
def g(self):
print "A.g()"
class B(A):
def g(self):
print "B.g()"
b = B()
b.f() # B.g(). A.f вызывает self.g(), что приводит к поиску функции g
# для объекта self - т.е. для экземпляра класса B, поэтому
# вызывается B.g.
# Name mangling (class-local references)
# Для всех атрибутов класса, начинающихся не менее чем с двух "_" ("__атрибут"),
# и заканчивающихся не более одной "_", делается автоматическая текстовая
# замена на "_класс__атрибут" в коде класса (где "класс" - имя класса, из
# которого спереди убраны все "_").
class H:
__x = 1 # Имя этого атрибута будет заменено на _H__x во всех
# местах внутри класса
__y__ = 2 # Имя атрибута содержит более одной "_" в конце, поэтому
# оно не будет изменено
def f( self ):
print H.__x # Здесь тоже будет сделана замена (H._H__x)
#print H.__x # AttributeError: class H has no attribute '__x'.
# Такого атрибута просто не существует, есть _H__x.
print H._H__x # 1
print H.__y__ # 2
h = H()
h.f() # 1
# Просто так: если имя класса состоит только из "_", name mangling не
# применяется (указано в Language Reference/Expressions/Identifiers).
class ___:
__x = 1
print ___.__x # 1
# Чтение и запись атрибутов:
#
# В официальной документации не удалось найти четкого описания того, как
# делаются чтение и запись атрибута объекта. Путем экспериментов и объединения
# того, что нашлось в документации, был получен приведенный ниже алгоритм.
# В нем используются следующие обозначения:
# - объект - это экземпляр или класс
# - класс объекта - это класс экземпляра или метакласс класса
# - поиск в классе - поиск в классе и родительских классах, в порядке MRO
# - MRO (Method Resolution Order) - порядок поиска атрибута
#
# Чтение атрибута (объект.атрибут):
# 1. Если атрибут является специальным методом ("___метод___"), он ищется
# в классе объекта, и возвращается.
# 2. Вызывается метод объект.__getattribute__("атрибут"). По умолчанию, он
# ищет атрибут в порядке MRO. Если атрибут найден в классе и имеет метод
# __get__, возвращается атрибут.__get__(). Иначе возвращается сам атрибут.
# Исключение: Если атрибут есть в классе объекта и имеет методы __get__()
# + __set__() или __del__(), возвращается результат __get__() именно для
# него - независимо от того, есть у объекта одноименный атрибут или нет.
# 3. Если атрибут не найден, вызывается метод объект.__getattr__("атрибут"),
# который по умолчанию генерирует исключение AttributeError.
#
# Запись атрибута (объект.атрибут = значение):
# 1. Вызывается метод объект.__setattr__("атрибут", значение). Сначала он
# ищет атрибут в классе объекта. Если атрибут найден и имеет метод
# __set__, вызывается атрибут.__set__(). Иначе выполняется
# объект.__dict__["атрибут"] = значение.
#
# MRO экземпляра имеет вид: экземпляр, иерархия классов. MRO класса:
# иерархия классов, иерархия метаклассов. Например, если есть иерархия
# M1 -> M2 -> A -> B, где M1 и M2 - метаклассы (M2 наследует M1), A и B -
# классы (B наследует A), и у A метакласс M2, то для экземпляра B MRO будет
# таким: экземпляр, B, A. Для класса B - таким: B, A, M2, M1.
#
# Атрибуты с методами __get__/__set__/__del__ называются дескрипторами -
# см. заметку "descriptor".
# Замечание: Похоже, создание объекта-метода при каждом обращении
# "экземпляр.функция" или "класс.функция" влияет на производительность.
# Потому что в Language Reference/Data model/User-defined methods написано,
# что в некоторых случаях хорошей оптимизацией может быть сохранение
# ссылки на метод в какой-нибудь локальной переменной, и затем вызов
# метода через нее:
# of = object.f
# of()
#
# Замечание: Класс любого объекта хранится в атрибуте __class__. Например,
# print (1).__class__ выведет <type 'int'>. __class__ ссылается на класс
# объекта как для old-style, так и для new-style объектов (если только в
# этот атрибут не записать что-нибудь другое).
#
# Замечание: Статический метод и метод класса реализованы в виде дескрипторов.
# Для статического метода дескриптор при обращении возвращает исходную функцию,
# без изменений. Для метода класса дескриптор при обращении возвращает
# специальную обертку, передающую ссылку на класс в исходную функцию.
#
# Замечание: В разделе Tutorial документации атрибуты экземпляра почему-то
# называют данными, а атрибуты-функции класса называют методами. Возможно,
# так хотят подчеркнуть, что только при обращении к функции класса создаются
# объекты-методы, а при обращении к атрибутам экземпляра не создается
# никаких специальных объектов.