Классы в 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 скажет:

— Всему самому лучшему, что у меня есть, я обязан родителю! 
  Я всё унаследовал от него — и эти поля, и эти методы — а сам лишь кое-что улучшил и добавил немного от себя.

FrBrGeorge/ClassesInPython (last edited 2013-12-13 06:28:12 by FrBrGeorge)