Классы в Python — это очень просто
Примечание: достаточно сделать все примеры, и наступит Просветление! Если оно не наступило, прочитать комментарии к примерам. Если всё равно непонятно, пишите письма автору.
Поля классов и объектов
Стандартные объекты — например, кортежи — рождаются с полями:
>>> l=() >>> dir(l) ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] >>> l.new="QQ" Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'tuple' object has no attribute 'new'
Впихивать в них новые поля нельзя.
Но можно самому создать объект. Для этого надо с помощью оператора class описать конструктор этого объекта, а потом вызвать этот конструктор. Он вернёт объект — ровно такой, как заказывали. Например, пустой:
>>> class C: ... pass ... >>> dir(C) ['__doc__', '__module__'] >>> c=C() >>> dir(c) ['__doc__', '__module__'] >>> c <__main__.C instance at 0x7f5f384f6b48> >>> C <class __main__.C at 0x7f5f384d9258>
Постой-постой, он не совсем пустой! Есть пара каких-то служебных имён, ну да это мелочи. Итак, C — конструктор объекта, а c — сконструированный им объект (или «экземпляр», instance).
И вот в такой объект можно самому напихать какие хочешь поля:
>>> c.new="QQ" >>> dir(c) ['__doc__', '__module__', 'new'] >>> c.new 'QQ'
Поля, добавленные в объект, в самом классе не появляются. Но и в сам класс (конструктор) тоже можно напихать какие хочешь поля:
>>> C.strange="NN" >>> dir(C) ['__doc__', '__module__', 'strange'] >>> dir(c) ['__doc__', '__module__', 'new', 'strange'] >>> c.strange 'NN'
Более того, эти поля тут же становятся видны видны во всех экземплярах. Если в самом экземпляре поля нет, Python смотрит, а нет ли его в классе.
А если такое поле в объекте создать, оно заслоняет собой поле класса (как локальная переменная заслоняет глобальную):
>>> d=C() >>> dir(d) ['__doc__', '__module__', 'strange'] >>> d.strange='YY' >>> c.strange 'NN' >>> C.strange 'NN' >>> d.strange 'YY'
Задание полей в конструкторе, часть первая
Если мы хотим, чтобы при создании объекта в нём уже были какие-то поля, эти поля надо добавить в класс. Только вот делать это в виде класс.поле = значение там и сям в коде программы как-то… неструктурно. Можно сделать так:
>>> class D: ... w=10 ... h=20 ... >>> d=D() >>> d.w, d.h (10, 20)
Теперь при обращении к полям w и h любого объекта типа D («экземпляра класса D») мы получим какое-нибудь значение — либо поля из класса, если мы нисего не добавляли в объект, либо поля объекта, если добавляли:
>>> e=D() >>> e.w, e.h = 18, 11 >>> d.w*d.h-e.w*e.h 2
Тут есть одна неприятность. Если поле класса — не константа (например, список), то пока мы его не переопределили в объекте, мы изменяем поле класса:
>>> class E: ... l=[] ... >>> e=E() >>> e.l, E.l ([], []) >>> e.l.append("QQ") >>> e.l, E.l (['QQ'], ['QQ'])
А как только мы зададим поле объекта, мы начинаем изменять поле объекта:
>>> f=E() >>> f.l=[] >>> e.l, f.l, E.l (['QQ'], [], ['QQ']) >>> f.l.append("Q-K-R-Q") >>> e.l, f.l, E.l (['QQ'], ['Q-K-R-Q'], ['QQ'])
Та же история, что с глобальными и локальными переменными. Не было присваивания — видно поле класса, присвоили — трах! — появилось поле объекта. И за этим надо следить. Запомним на будущее.
Методы
Разумеется, поля объекта могут быть абсолютно любого типа. Числами. Строками. Функциями. Экземплярами класса. Классами. Любого типа.
Функциями — это хорошо! Вот, например, функция, которая показывает значение поля класса:
>>> class F: ... data = "dada" ... >>> def fun(): ... print F.data ... >>> f=F() >>> f.pri=fun >>> f.pri() dada >>> f.data = "nono" >>> f.pri() dada
Поле класса — это не так интересно, как поле объекта. Нам же их в основном менять. А для того, чтобы добыть поле объекта, нужно знать сам объект. Выходит, чтобы добавлять функцию в объект, нужно сначала создать объект, запоминать и никогда не трогать ссылку на этот объект, потом каждый раз создавать новую функцию, в которой использовать эту неприкосновенную ссылку? Ужас.
А нельзя ли прямо в конструктор функцию записать? В класс? Чтобы она всегда там уже была? А ссылку на объект — что же, передадим и ссылку, жалко, что ли? Вот так как-нибудь:
>>> class G: ... d = 1 ... def fun(obj): ... print obj.d ... >>> g=G() >>> g.fun(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: fun() takes exactly 1 argument (2 given)
Час от часу не легче! Как это так «задано два параметра»?! Один же!
Посмотрим. Передадим один параметр, а в функции напишем, что их два, раз уж оно так думает:
>>> class H: ... def test(p1, p2): ... print p1, p2 ... >>> h=H() >>> h.test(100500) <__main__.H instance at 0x7f5d95a32a70> 100500 >>> h <__main__.H instance at 0x7f5d95a32a70> >>> H.test(h,"I've got it!") <__main__.H instance at 0x7f5d95a32a70> I've got it!
Ах во-о-о-т как. Функции, определённой внутри класса (она называется метод) Python передаёт на один параметр больше! При вызове вида объект.метод(параметр1, …) на самом деле вызывается функция класс.метод(объект, параметр1, …).
Ну-ка, ну-ка:
>>> class I: ... t = 42 ... def answer(obj): ... print obj.t ... >>> i=I() >>> i.answer() 42 >>> i.t='forty two' >>> i.answer() forty two
Вуаля. Осталось только заметить, чтоб для удобства чтения этот первый параметр принято называть self.
Инициализация
Вернёмся теперь к чуду превращения полей класса в поля объекта. Это плохое, негодное чудо: за ним надо следить. Например, создать объект и тут же заполнить все его поля. Тогда они все будут полями объекта, и следить больше будет не за чем.
Ну так вот, если задать специальный метод __init__(), конструктор его вызовет в процессе создания объекта (точнее — сразу после создания, но перед тем, как отдавать в программу результат). Там-то можно все поля свежесозданного, но незаполненного ещё объекта и заполнить.
Более того, если передать какие-то параметры конструктору, он их передаст в __init__() (разумеется, вместе со ссылкой на объект в качестве первого параметра):
>>> class J: ... def __init__(self, w, h): ... self.w, self.h = w, h ... def square(self): ... return self.w*self.h ... >>> r=J(10,20) >>> dir(r) ['__doc__', '__init__', '__module__', 'h', 'square', 'w'] >>> r.square() 200
Вот.
До встречи в прямом эфире! Не пропустите следующую серию, в которой класс G скажет:
— Всему самому лучшему, что у меня есть, я обязан родителю! Я всё унаследовал от него — и эти поля, и эти методы — а сам лишь кое-что улучшил и добавил немного от себя.