13-й час

Специальные методы классов в Python

  В этой главе мы попытаемся свести вместе известные Вам факты. Мы рассмотрим специальные методы классов — эти забавные функции, имена которых начинаются и заканчиваются двумя символами подчёркивания. К концу главы Вы научитесь добавлять собственные специальные методы в написанные Вами классы.

Операторы и перегрузка операторов

  В главе 3 Вы узнали об операторах и операндах. Вы также узнали, что операторы, например суммирования (+), могут вести себя по-разному в зависимости от типа подставленных операндов. Например, выражения 1+1 и "Hello, "+"World!" будут выполняться совершенно по-разному. Если в качестве операндов используются числа, оператор + складывает их, если строки — то выполняет конкатенацию. Это типичный пример полиморфизма — два разных ответа на одно и то же сообщение "сложить", переданное разным объектам. В первом случае сообщение передаётся двум числовым объектам, во втором — двум строковым. Полиморфизм в данном случае был достигнут за счёт процедуры, называемой перегрузкой операторов. (Чтобы быть более точным, речь идёт о перегрузке имени оператора, т.е. допускается существование в одном пространстве имён нескольких функций с одинаковыми именами, но с разными наборами параметров и выполнениями.) Под этим термином понимается определение различного поведения операторов в зависимости от контекста. Поведение встроенных операторов при обработке базовых типов данных не может быть изменено в Python, но Вы вольны самостоятельно определять работу того или иного оператора при использовании созданных Вами объектов. Причем не только в тех случаях, когда оба операнда относятся к пользовательским типам данных, но и в случае выполнения операций над объектом и переменной базового типа. Перегрузка операторов, для выполнения которой используются специальные методы классов Python, является одним из самых мощных и эффективных средств программирования. В других современных языках также допускается перегрузка операторов, но нигде это не делается так просто, как в Python.

  *Прим. В. Шипкова: эта методика была явно позаимствована из других языков, в частности С++. ИМХО, Гвидо превзошёл Бъёрна! ;)))

  Очень полезно иметь возможность определять поведение объектов в своей программе в ответ на сообщения разных типов. В языке С нельзя перегружать операторы.

  Существует возможность только создавать пользовательские функции для выполнения специфических задач, но вызов функции в С всегда осуществляется только по имени без учёта типов операндов. Так, если Вам в программе нужно особым образом обрабатывать пару объектов, например сложить их, то следует написать специальную функцию, куда эти объекты передаются с аргументами. Такой подход усложняет код и делает его трудным для чтения и понимания. Например, когда я только увлёкся календарем индейцев Майя, для своей программы мне пришлось создать целую библиотеку функций, позволяющих выполнять операции с целыми числами произвольной длины. Я устал от необходимости постоянно контролировать тип переменной, так как в своих вычислениях мне приходилось оперировать цифрами, лежащими на границе допустимых значений для типа int. Чтобы решить проблему, мне пришлось разработать набор функций вроде тех, что используются в Python для обработки длинных целых значений. Поскольку моя библиотека и программы были написаны на языке С, приходилось явно вызывать каждую функцию, так как перегрузка операторов была недоступна. У меня возникла необходимость умножить переменные ram на tt, а затем разделить результат по модулю на 13. Но вместо простого однострочного выражения пришлось вводить целый блок определений и вызовов функций:

Hint * tmpn = mpIntToMint(0);

Mint * q = mpIntToMint(0);

Mint * r = mpIntToMint(0);

Mint * tx = mp!ntToMint(13);

Mint * mm = mp!ntToMint(20);

Mint * tt = mp!ntToMint(819);

mpMultiply(mm, tt, tmpn); /* Вывод заносится в tmpn */

mpDivide(tmpn, tx, q, r) ; /* Деление по модулю на 13, результат в г */

mpPtrFree (mm, modname) ; /* Очистка временных переменных */

mpPtrFree(tt, modname); mpPtrFree(q, modname);

mpPtrFree(tmpn, modname); mpPtrFree(tx, modname);

return(r);

  Позже, когда я стал использовать C++, я захотел переписать свою библиотеку с использованием появившейся возможности перегрузки операторов. Но, к счастью, в этот момент я узнал о Python и о том, что в нём весь этот блок можно просто заменить одним выражением:

r = (20L * 819L) % 13

return r

или даже

return (20L * 819L) % 13L

  Но и это не всё. Оказалось, что в Python я могу очень просто создать свой пользовательский класс, который избавит меня от проблем подобного рода. Достаточно будет просто импортировать мой класс, как в следующем примере:

from mayalib import *

х = mayanum()

x()

x = x + "13.0.0.0.0"

x()

print x.gregorian()

  В языке С для выполнения этой же задачи пришлось бы написать множество строк кода, затем ещё потратить время на их отладку, а программа в результате получилась бы громоздкой и трудночитаемой. (Эта программа, между прочим, рассчитывает дату конца света в соответствии с верованиями индейцев Майя и преобразовывает её в дату григорианского календаря. Вы можете загрузить эту программу с Web-страницы по адресу http://www.pauahtun.org/TYPython/.)

  Часто вполне очевидно, что данный класс должен делать в ответ на опредёленное сообщение или применение оператора. Например, в случае с датами календаря Майя смыл применения оператора + вполне понятен. Он используется для суммирования одной даты в формате календаря Майя с другой датой в этом же формате или другом, понятном для функции оператора. Но не всегда все так просто. В главе 15 мы займёмся разработкой программы для автоматизации записи на видеомагнитофон выбранных телепередач. В этой программе не так-то просто будет разобраться, что именно выполняет оператор + с пользовательскими объектами, когда суммирует телепередачу с видеокассетой. Но если задуматься, как происходит запись передачи в реальной жизни, то сами собой появятся идеи, что должен делать оператор суммирования, хотя выполнение этих задач функцией оператора может быть реализовано по-разному. Давайте попробуем найти одно из логических решений. Создание программы всегда следует начинать с анализа логики её выполнения, что существенно сократит число проб и ошибок во время написания кода программы.

  Магнитная лента — это контейнер. Контейнер может быть пустым, может содержать одну или несколько записанных программ, а может даже случиться так, что в кассете уже не останется места для записи новой программы. Иногда магнитофон может "зажевать" ленту, и тогда нужно прервать запись и звонить мастеру. Таким образом, нам потребуется класс cassette (кассета), который может быть пустым, а может содержать экземпляры класса program. Что мы делаем с обычной видеокассетой? Мы записываем на неё передачу, а когда она надоест, можем стереть её. Значит, нам нужен класс, который знает, что делать с объектами других классов, т.е. может добавлять их в состав своего объекта и обрабатывать по командам пользователя. Пришло время заняться планированием класса — важнейшим этапом объектно-ориентированного программирования на любом языке. Конструирование объекта с опредёленными свойствами, содержащего другие объекты, свойства которых, в свою очередь, являются предметом такого же анализа, — это фундамент программирования классов. Но если в других языках под планированием понимают чисто умозрительный процесс с вычерчиванием блок-схем на листке бумаги, то в Python, учитывая предельную простоту программирования, планирование удобно сочетать с экспериментированием разных вариантов выполнения класса, сидя прямо у экрана компьютера. Такой подход позволяет сразу оценить на практике, насколько точно в данном варианте класса реализованы ваши идеи. Представления о том, каким класс должен быть, проще всего выкристаллизовать путём многократного изменения и настройки его функциональности. Такой диалог между программистом и машиной может быть чрезвычайно продуктивен, главное — не бояться пробовать и экспериментировать. Отсутствие необходимости в Python перекомпилировать и связывать программные модули после внесения каждого изменения даёт возможность программисту вволю поупражняться с кодом программы, не рискуя при этом потерять много времени и выпасть из графика сдачи проекта.

  Мы ещё вернёмся к классам кассеты и программ чуть позже, в главе 16. Но чтобы в полной мере раскрыть эту тему, сейчас нам нужно ближе познакомиться со специальными методами классов.

Использование специальных методов

  В приложении В Вы найдёте отсортированный в алфавитном порядке полный список всех специальных методов классов, представленных в Python 1.5.2. Все эти методы могут быть использованы в пользовательских классах. Если у программиста возникнет такая необходимость, он может разработать особое выполнение данного метода, чтобы обогатить им функциональность своего класса. Обратите внимание, что к этому средству следует прибегать только в случае необходимости. Стремление молодых программистов переписать все специальные методы на свой лад часто выливается в пустую трату времени. Кроме того, новые функциональные возможности класса должны быть логичными. Например, в предыдущем разделе мы показали, что для класса cassette было бы логично иметь перегруженный оператор суммирования (+) для добавления в контейнер новых записей программ (объектов класса program). Но какой смысл в этом классе может иметь оператор умножения (*)? Вы вольны приписать этому оператору любую функцию, например очистку контейнера от всех записей программ. Программа будет работать, но вашим пользователям будет непонятно, каким образом оператор умножения связан с удалением записей, что сделает интерфейс вашей программы недружественным для пользователя. Видимо, в классе cassette следует ограничиться перегрузкой операторов + и -, поскольку их применение в большей степени соответствует представлениям пользователей о происходящих процессах.

  Большинство специальных методов классов требуют передачи им определённых аргументов и в опредёленном порядке, а в ответ они возвращают значения опредёленного типа. Некоторые другие методы значительно более гибкие в применении, хотя общим требованием для всех специальных методов будет передача аргумента self первым в списке параметрам. Как Вы помните, self — это простой и явный метод указания методу текущего типа вызывающего объекта, на основании чего происходит выбор корректной версии метода для этого типа данных. Вы уже встречались с примерами реализации полиморфизма специальных методов, например при вызове метода __init__(). Единственный аргумент, требуемый этим методом, является self, а сам метод не возвращает никаких значений. Впрочем, при написании кода класса Вы можете потребовать передачу дополнительных аргументов методу __init__() для создания экземпляра, своего класса. При запуске программы Python проследит за тем, чтобы пользователь ввёл все аргументы, которые Вы сделали обязательными для своего класса, хотя встроенному методу __init__() достаточно одного аргумента self.

  Это означает, что Вы можете оставить метод __init__() таким, какой он есть. В этом случае он ничего не будет делать в Вашем классе, а для настройки состояния и функциональности класса можно будет разработать свой методы. Чтобы метод __init__() ничего не делал в Вашем классе, достаточно написать следующее:

class egg:

def __init__(self):

  pass

  Инструкция pass в определении класса несёт тот же смысл, что и при её употреблении в конструкциях с инструкциями if или try-except, т.е. она просто сообщает Python, что лучше в этом месте ничего не делать.

  Другие специальные методы классов, особенно те из них, что используются для обслуживания операторов действий с числовыми значениями, гораздо более требовательны к числу и типу аргументов. Для примера рассмотрим метод __add__(), который может принимать только два аргумента, self и other, и возвращает новый объект, который представляет собой результат суммирования или конкатенации значений self и other. Впрочем, Вы можете определить для этого метода выполнение какой-либо другой операции над этими двумя переданными объектами. Желательно только, чтобы эта операция логически как-то соотносилась с процессом суммирования. Например, не разумно заставлять метод __add__() выполнять умножение двух чисел! Но для нашего предыдущего примера с добавлением объекта телепередачи к объекту кассеты этот метод вполне подойдёт, хотя реальное суммирование значений здесь не происходит.

  В качестве примера приведём ещё одну область программирования, где перегрузка операторов будет весьма полезной. Так, в теории множеств используются понятия соединения, объединения и пересечения, для которых в математике существуют специальные операторы. Но эти операторы, к сожалению, отсутствуют в Python. В таком случае для Вас будет вполне оправдана перегрузка операторов + — для выполнения соединения множеств, | — для объединения и & — для пересечения. Любой, кто хоть немного разбирается в теории множеств, быстро запомнит смысл использования операторов в вашей программе.

  Метод __coerce__() в месте с другими специальными методами операторов, такими как __add__(), будет весьма эффективен для решения задач, связанных с преобразованием объектов разных типов, как базовых, так и пользовательских, в экземпляр Вашего класса. У этого метода также достаточно жёсткие требования к аргументам и возвращаемому значению. Он принимает два аргумента, self и other, а возвращает их же в виде набора (self, other). В качестве аргумента other может выступать значение базового типа, экземпляр того же класса, что и self, или объект любого другого типа. Только нужно продумать, какие типы данных будут допустимы для Вашего конкретного класса, и разработать соответствующие выполнения метода __coerce__() и структуру возвращаемого им набора для разных типов. В возвращённом наборе элемент other должен быть представлен новым экземпляром класса, заданного аргументом self. (Хотя в принципе допустимо приводить с помощью этого метода аргумент self к типу other, такое решение будет выглядеть довольно странно. Нужно иметь достаточно веское основание, чтобы так поступить, я даже не могу придумать, какое именно.)

  Прежде чем перейти к детальному рассмотрению применения на практике наиболее важных специальных методов, давайте остановимся на особенностях ещё одного встроенного метода - __del__(). Во многих объектно-ориентированных языках программирования поддерживается техника сбора мусора, под которой понимают способность интерпретатора время от времени просматривать все когда-либо созданные объекты и избавляться от тех из них, которые больше не используются. Python также имеет встроенный механизм избавления от мусора, но в основе его лежит несколько иной принцип, называемый счётчиком ссылок. Он состоит в том, что каждый раз при создании объекта Python добавляет в него скрытый атрибут, представляющий собой счётчик числа функций, классов и методов, использующих этот объект. Если никто больше не использует этот объект в программе, Python удаляет его из памяти компьютера, предоставляя освободившиеся ячейки памяти другим объектам. В следующем примере показано определение функции с локальной переменной i:

def spam(y):

  i = у * 400

  ... другие выражения ...

  При создании переменой i к небольшому объекту, представляющему данную переменную, добавляется скрытый счётчик с присвоенным значением 1. Это число указывает, сколько пользователей в данный момент используют переменную i. По завершении выполнения функции, когда переменная i выходит из поля зрения, очевидно, что никто больше не будет использовать эту переменную, поэтому интерпретатор отнимает от значения счётчика единицу. При этом он обнаруживает, что значение счётчика стало равным 0, поэтому этот объект удаляется. Хотя существует много других алгоритмов сбора мусора, вариант со счётчиком ссылок отличается стабильностью, ошибкоустойчивостью и простотой. С точки зрения Гуидо, наиболее важной особенностью данного алгоритма является его переносимость, тогда как другие более сложные алгоритмы сбора мусора, как правило, зависимы от платформы и системы компьютера. Кроме того, многие алгоритмы периодически прерывают работу интерпретатора на время их выполнения, чего не происходит с алгоритмом счётчика ссылок. Такие задержки в ответе программы на команды могут раздражать пользователя или даже восприниматься как ошибки программы. Недостатком использования алгоритма счётчика ссылок в таком динамичном языке программирования, как Python, является то, что не всегда удаётся добиться надёжности в определении текущего состояния счётчика. (Тут много причин, почему могут происходить сбои, но эта тема выходит за рамки данной книги.) В случае возникновения неопредёленности Python предпочитает не удалять подозрительные объекты. Сохраняя объект, Python рискует, что после завершения программы в памяти компьютера могут остаться заблокированные ничейные ячейки памяти, к которым никто уже не сможет обратиться. Впрочем, ничего страшного с компьютером не происходит, если не считать того, что часть памяти временно становится недоступной для использования другими программами. Для большинства современных компьютеров небольшая утечка памяти не будет такой уж серьёзной проблемой.

  Метод __del__() автоматически вызывается Python для удаляемых объектов. Существует также встроенная функция del(), но в действительности она сама не удаляет объект, а уменьшает на единицу значение его счётчика ссылок. Если после этого значение счётчика становится равным 0, то остальное делает метод __del__(). Пример удаления объекта показан в листинге 13.1.

Листинг 13.1. Удаление объекта, программа del.py

#!c:\python\python.exe

class spam:
def __init__(self):
pass

  def __del__(self):
print "I'm about to be deleted!"

a = spam()

def eggspam():
z = spam()

if __name__ == "__main__":
print "Calling eggspam()"
b = eggspam()
t = []
for i in range(10):
t.append(a)

for i in range(9,-1,-1):
print "Calling del() on number", i
del(t[i])

x = spam()

Результат выполнения программы del.py показан на рис. 13.1.

Рис. 13.1. Результат выполнения программы del.py

  Функция eggspam() создаёт объект z класса spam, который тут же выходит из поля видимости с завершением функции. Список t содержит 10 ссылок на объект а класса spam. По мере удаления элементов списка t в обратном порядке в действительности происходит только уменьшение значения счётчика ссылок объекта а. И только когда значение счётчика становится равным нулю, действительно удаляется объект. Наконец, мы создаём ещё один объект х класса spam, который явно в программе не удаляется. Но Python при завершении программы автоматически удаляет все созданные в ней объекты, сообщение о чём и появилось на экране.

  Всегда, когда Вам нужно удалить все элементы списка, делайте это в обратном порядке, как это происходит во втором цикле for в листинге 13.1. Дело в том, что при удалении элемента списка автоматически уменьшается его размер. Так, в нашем примере при удалении элементов списка от 0-го до 9-го в момент удаления 5-го элемента в списке не существовало элементов с индексом больше 5. Чтобы убедиться в этом, измените второй цикл for следующим образом:

for i in range(10):

  Теперь вновь запустите программу del.py и внимательно прочтите появившееся сообщение об ошибке: IndexError: list assignment index out of range (индекс указывает за пределы списка).

Описание специальных методов

Числовые специальные методы классов

  В этом разделе мы используем уже знакомый Вам класс spam (фарш) и на его примере продемонстрируем использование основных числовых специальных методов классов. Специальные методы классов разделены на группы по тому признаку, на работу с какими типами данных они ориентированы. Выделяют числовые методы, методы для работы с пользовательскими классами, методы для обработки последовательностей и методы доступа. Первый из числовых специальных методов классов, к которому мы обратимся, — это метод __add__().

  Как Вы помните, метод __add__() требует наличия двух аргументов, self и other, a должен возвратить объект типа self. (Это не совсем верно говорить, что метод __add__() обязательно должен возвращать объекты типа self. В действительности допускается прямо в теле метода перед возвращением изменить тип объекта на другой, но это не считается хорошей практикой программирования. Давайте снабдим наш класс spam дополнительной функциональностью, как показано в листинге 13.2.

Листинг 13.2. Выполнение метода add() в программе spam.py

#!с:\python\python.exe

class spam:

  def __init__(self):

    self.eggs = 1

 

  def __del__(self):

    pass

 

  def __add__(self,other):

    rt= spam()

    rtreggs = self.eggs + other.eggs

    return rt

 

if __name__ == "__main__":

  a = spam()

  b = spam()

  a = a + b

  print "a now has", a.eggs, "eggs"

  Если Вы сейчас запустите spam.py, то программа сообщит Вам: a now has 2 eggs (в фарш 'а' добавлено 2 яйца). Таким образом, с помощью метода __add__() мы сложили наши виртуальные яйца. Точно так же мы можем определить метод __sub__(), только вместо сложения применить вычитание. Также не должно возникнуть проблем с определением для класса spam методов __raul__() и __div__(), которые соответственно будут умножать и делить объекты. Определения большинства числовых методов вполне очевидны. Полный список специальных числовых методов классов Вы найдёте в приложении Г.

  Теперь предположим, что Вам нужно выполнить сложение объекта spam и строки символов, например следующие выражения:

а=spam()

а=а+"24"

а ="24"+а

print a.eggs

  Чтобы снабдить класс такой функциональностью, нужно добавить выполнение ещё двух специальных методов: __coerce__() и __radd__(). Для упрощения кода мы будем добавлять типы в метод __coerce__(), только по мере возникновения потребности в них. Так что на этом этапе добавим только функцию обработки строковых переменных, как показано в листинге 13.3.

Листинг 13.3. Добавим в фарш ещё яиц

#!c:\python\python.exe

import string

 

class spam:

  def __init__(self):

    self.eggs = 1

 

  def __del__(self):

    pass

 

  def __add__(self,other):

    rt = spamf)

    rt.eggs = self.eggs + other.eggs

    return rt

 

  def __coerce__(self,other):

    rt = spam()

    if type(other) == type(rt):

      return (self,other)

    elif type(other) == type(""):

      e = string.atoi(other)

      rt.eggs = e

      return (self,rt)

    else:

      return None

 

  def __radd__(self,other):

    return self+other

 

if __name__ == "__main__":

  a=spam()

  b=spam()

  a=a+b

  print "a no"w has", a.eggs, "eggs"

  a=a+"24"

  print "a now has", a.eggs, "eggs"

  -a="24"+a

  print "a now has", a.eggs, "eggs"

  На рис. 13.2 показан результат выполнения измененной программы spam.py.

Рис. 13.2. Фарш и яйца

  Обратите внимание, что в методе __radd__() нам не пришлось делать ничего особенного. Python лишь требует, чтобы последовательность аргументов в этом методе сохранялась такой же, как и в методе __add__(). Поскольку у нас уже есть в программе выполнение метода __add__(), то метод __radd__() просто вызывает его из своего тела.

  Примите это как общее правило. Если в определении класса указано выполнение нормального числового метода и метода __coerce__(), то любой вспомогательный метод __r*__() выполняет вызов соответствующего нормального метода, благодаря чему Вам не нужно переписывать код дважды. Это лишь один из примеров встроенной возможности Python принимать правильные решения без лишних указаний.

  Поскольку класс spam оперирует числовыми значениями, можно добавить в него дополнительные специальные числовые методы классов, такие как __abs__(). Полный вариант определения класса spam, содержащего выполнение большинства специальных числовых методов классов, показан в листинге 13.4.

Листинг 13.4. Специальные числовые методы классов

#!с:\python\python.exe

import string

 

class spam:

  def __init__(self):

    sfcLf.eggs = 1

 

  def __Jel__(self):

    pass

 

  def __add__(self,other):

    rt = spam()

    rt.eggs=self.eggs+other.eggs

    return rt

 

  def __coerce__(self,other):

    rt=spam()

    if type(other) == type(rt):

      return (self,other)

    elif type(other) == type(""):

      e = string.atoi(other)

      rt.eggs = e

      return (self,rt)

    elif type(other) == type(0):

      rt.eggs = other

      return(self,rt)

    else:

      return None

 

  def __radd__(self,other):

    return self+other

 

  def __absj__(self):

    return abs(self.eggs)

 

  def __long__(self):

    return longfself.eggs)

 

  def __float__(self):

    return float(self.eggs)

 

  def __int__(self):

    return int(self.eggs)

 

  def __complex__(self):

    return complex(self.eggs)

 

  def __divmod__(self,other):

    return divmod(self.eggs,other.eggs)

 

  def __and__(self,other):

    return self.eggs & other.eggs 42:

 

if __name__ == "__main__":

  a = spam()

  b = spam()

  a = a + b

  print "a now has", a.eggs, "eggs"

  a = a + "24"

  print "a now has", a.eggs, "eggs"

  a = "24" + a

  print "a now has", a.eggs, "eggs"

  b = b + "13"

  print divmod(a,b)

  print a & b

  В качестве домашнего задания найдите и добавьте в класс spam выполнение оставшихся специальных числовых методов классов. Это не очень сложное задание.

Специальные методы классов для обработки последовательностей и установки отношений

  В этом разделе мы создадим образец класса изменяемых строк, применение которых иногда бывает весьма эффективным. В листинге 13.5 показан первый вариант класса parrots (попугаи).

Листинг 13.5. Класс parrots

#!c: \python\python . exe

import string

import sys

 

class parrot:

  def _init_(self,s=""):

    self.s = list(s)

 

  def repr (self):

    t = ""

    t=string. join(self . s, ' ')

    return t

 

  def _str__(self):

    return parrot, repr (self)

 

  def _add_( self, other):

    rt = parrot( 'self )

    rt.s = rt.s + other. s

    return rt

 

  def radd (self , other) :

    return self + other

 

  def coerce (self , other):

    if type (other) == type (self):

      return (self, other)

    elif type (other) == type(""):

      rt = par rot (other)

      return (self, rt)

    elif type (other) == type([]):

      print "type list"

      rt = parrot( )

      for i in other:

        if len(i) > 1:

          for j in i:

            rt.s.append(j)

        else:

          rt.s.append(i)

          return (self, rt)

        elif type(other) == type({}):

          return None

        else:

          rt = parrot(str(other))

          return (self, rt)

 

  def __getitem__(self, key):

    if type(key) == type(O):

      return self.s[key]

    else:

      raise TypeError

 

  def _getslice__(self,i,j):

    s = self.s[i:j]

    t = ""

    for k in s:

      t = t + k

    return t

 

  def __setitem__(self, key, value):

    if type(key) == type(0):

      self.s[key] = value

    else:

      raise TypeError

 

  def __setslice__(self,i,j,value):

    self.s[i:j] = list(value)

 

  def __len_(self):

    return len(self.s)

 

if __nаmе__ == "__main__":

  if len(sys.argv) > 1:

    s = sys.argv[l]

  else :

    s = "I'm a little teapot!"

    p = parrot(s)

  print p

  q = parrot (" I'm not schizophrenic!")

  print "Add:", p + q

  x = ['N', 'o', ", that bird's not dead", "!"]

  print "Add:", p + ' ' + x

  print "Index[9]: ", p[9]

  print "Index[-9]: ", p[-9]

  p[ll], p[13] = p[13], p[ll]

  p[14], p.[16] = p[16], p[14]

  print p

  pa = p[ll:17]

  print pa

  print "Add:", p + ' ' + q

  p[ll:17] = "parrot"

  print p, len(p)

  В классе parrot используется лишь несколько специальных мeтодов классов. При желании можно было бы добавить ещё много дополнительных методов. Например, полезным может оказаться выполнение метода __str__(), с помощью которого можно сортировать объекты в списке.

  Особое внимание обратите на приём, использованный в строке 76: р[11], р[13]=р[13], р[11]. Аналогичное выражение содержит строка 77. Такой подход можно использовать каждый раз, когда нужно выполнить обмен значениями между двумя переменными. В других языках для этого потребовалось бы создание временной переменной для сохранения промежуточных значений, как в следующем примере:

  *Прим. В. Шипкова: приведённый пример, ИМХО, весьма стильное решение.

 

tmp = a;

а = b;

b = tmp;

Но в Python эту операцию можно выполнить в одной строке:

а, b = b, a

  Если добавить ещё специальные методы, у Вас получится великолепный класс с изменяемыми строковыми значениями, который можно использовать в самых разных программах. В следующей главе Вы подробнее познакомитесь с теxникой многократного использования в своих программах стандартных блоков, таких как класс parrot.

Специальные методы классов и вопросы управления доступом

  Ещё один аспект использования специальных методов классов, который осталось рассмотреть, — это управление с их помощью доступом к членам класса. Методы, которые используются для того, чтобы пользователь мог возвращать или устанавливать значения переменных-членов класса, называются методами доступа. К ним можно отнести такие специальные методы, как __call__(), __del__(), и некоторые другие. Методы доступа определяют способ использования объекта в программе как единого целого. Эти методы также важны для организации взаимоотношений между объектами. Например, метод __сmр__() используется при сравнении в выражениях, подобных следующему:

if а < b:

  Назначение большинства специальных методов классов самоочевидно, за исключением разве что метода __init__(), который мы уже рассматривали, и метода __hash__(), применяемого в тех случаях, когда неизменяемый объект используется в качестве ключа словаря. Этот метод используется довольно редко, поскольку в программах чаще нужны изменяемые объекты. Но если у Вас возникнет необходимость в таких словарях и неизменяемых объектах, информацию о них, конечно же, можно найти на уже хорошо известной Вам домашней странице Python (http://www.python.org/).

  Для изменяемых объектов правильное использование методов доступа может оказаться крайне важным. С их помощью можно контролировать взаимодействие пользователей класса с его членами, что позволяет предупредить возможное повреждение класса пользователем или неправильное его использования.

  *Прим. В. Шипкова: это замечание более чем справедливо. Действия профессионала предсказуемы. Но в этом мире полно любителей.

  К важным специальным методам доступа можно отнести __delattr__(), __getattr__() и __setattr__().

  Первый метод используется при удалении атрибута класса с помощью функции del(). Чтобы предупредить удаление, в методе __delattr__() можно установить вызов какого-нибудь подходящего исключения с предварительным выводом сообщения для пользователя. Если удаление атрибута допускается, то эта процедура должна выполняться на уровне словаря объектов. Другими словами, метод __delattr_() нельзя выполнять подобным образом:

def __delattr__(self, имя):

  ...определение удаляемых атрибутов...

  del(self .имя)

  От такого выполнения ваша программа сойдет с ума, так как метод __delattr__() автоматически выполняется каждый раз при вызове функции del(), в результате чего получится бесконечный цикл. Правильно будет выполнить удаление так:

def __delattr__(self, имя):

  ...определение удаляемых атрибутов...

  del (self.__diet__[имя])

В результате будете удовлетворены и Вы, и Python.

  Для ситуации, описанной выше, в программировании применяется специальный термин — рекурсия. Под рекурсией понимают вызов функцией самой себя. Бесконечная рекурсия возникает тогда, когда функция вызывает себя снова и снова и нет никакого способа остановить этот процесс. С другой стороны, рекурсивные функции могут оказаться весьма полезными, но обязательно нужно предусмотреть способ их остановки. В нашем примере с методом __delattr__() и функцией del() проблема возникнет именно из-за того, что это бесконечная рекурсия.

  Второй метод __getattr__() вызывается каждый раз, когда пользователь пытается возвратить несуществующий атрибут объекта. Например, в рассмотренном выше классе parrot представлен единственный атрибут состояния — список s. С помощью метода __getattr__() можно, например, определить возвращение строки списка s каждый раз, когда пользователь пытается возвратить любой другой атрибут:

def __getattr__(self, имя):

  return str(self.__diet__["s"])

  Наконец, третий метод, __setattr__(), вызывается при попытке пользователя добавить в класс новый атрибут, т. е. создать новую переменную-член класса. Как и в предыдущих двух случаях, эта процедура доступа к атрибутам должна выполняться на уровне словаря класса. Но выполнение этого метода может оказаться более сложной задачей. Не забудьте также, что этот метод вызывается при создании всех атрибутов, даже тех, чьё создание было предопределено в коде класса. В листинге 13.6 показаны примеры использования всех трёх методов доступа. Особенно внимательно проанализируйте выполнение метода __setattr__().

Листинг 13.6. Класс egg

#!с:\python\python.exe

import string

import sys

 

class egg:

  def __init__(self):

    self.yolks = 1

    self.white = 1

    self.brains = 0

    self.spam = 0

 

  def __delattr__(self, name):

    if name == "spam":

      print "All eggs .mast have spam!"

      raise AttributeError

    print "deleting", name

    del(self._dict_[name])

 

  def __setattr__(self, name, value):

    try:

      s = self.__diet__[name]

      except KeyError:

      if name not in ["yolks", "white", "brains", \

        "spam", "salt","pepper"]:

        print "you're not allowed to add",\

        name, "to eggs"

     else:

       s = self.__diet__[name] = value

     else:

       s = self.__diet__[name] = value

 

  def __getattr__(self, name):

    if name != "yolks":

      return self.__diet__["spam"]

    else:

      raise AttributeError

 

 x = egg()

 print x.yolks

 x.spam = 1

 X.snot = 1

 try:

   del x.spam

 except AttributeError:

   print "I couldn't get rid of the spam!"

   print x.spam

   x.spam = 1000

   print x.spam

   del x.yolks

 try:

   print x.yolks

 except AttributeError:

   print "No Yolks!"

   x.white = 29

   print "Snot is", x.snot, "white is", x.white

  Обратите внимание, как для упрощения кода в выполнении метода __setattr__() используется конструкция try...except...else (строки 17—26).

Резюме

  В этой главе Вы узнали о специальных методах классов. Теперь Вы умеете использовать специальные методы классов для перегрузки операторов; выполнять инициализацию и настройку состояния объекта класса; распознавать различные категории специальных методов классов и разбираться в особенностях их использования; управлять добавлением, возвращением и удалением атрибутов класса.

  В следующей главе Вы познакомитесь с многократно используемыми компонентами программ: как их использовать, создавать и находить, чтобы не создавать самостоятельно.

Практикум

Вопросы и ответы

Зачем нам использовать инструкцию pass?

  Возможно, было бы гораздо проще просто оставлять пустыми те строки в методах классов, циклах и конструкциях с инструкциями if, где программа не должна выполнять никаких действий. Но использование инструкции pass позволяет явно указывать необходимость ничего не делать в этом месте программы. По крайней мере Вы будете уверены, что другой программист, работающий с вашей программой, или Вы сами по прошествии времени не решите, что код в данном месте был пропущен по ошибке.

  Можно ли создать в программе классы, которые ссылаются друг на друга? Например, сделать так, чтобы класс spam включал объекты класса egg, а класс egg мог содержать объекты spam.

  Возможно, но в этом случае возникают цикличные ссылки, a Python не умеет корректно поддерживать их работу. В результате может возникнуть существенная утечка памяти. Тем не менее в некоторых случаях создания цикличных ссылок просто невозможно избежать.

Контрольные вопросы

  1. Назовите четыре категории специальных методов классов.

    а) Числовые, строчные, для обработки пользовательских классов и доступа.

    б) Числовые, для обработки последовательностей, для работы с пользовательскими классами и методы доступа.

    в) Гомерические, ионические, дорические и коринфские.

    г) Существенные, полусущественные, несущественные, абстрактные.

  2. Выберите три специальных метода доступа.

    а) __del__(), __init__() и __сmр__().

    б) __add__(), __radd__() и __mul__().

    в) __delattr__(), __getattr__() и __setattr__().

    г) __and__(),__or__() и __xor__().

  3. Каким способом в Python проще всего выполнить обмен значениями двух переменных?

    а) tmp = а, а = b, b = tmp

    б) а ^ b

    в) a, b = b, a

    г) swap(a, b)

Ответы

  1. б. Выделяют числовые специальные методы, методы для обработки последовательностей, методы для работы с пользовательскими классами и методы доступа. Вполне возможно, что в следующих версиях Python появятся новые категории специальных методов.

  2. в. К специальным методам доступа относятся __delattr__(), __getattr__() и __setattr__().

  3. в. В Python проще всего произвести обмен значениями между двумя переменными с помощью выражения a, b = b, а, так как в этом случае удаётся избежать создания временных переменных.

Примеры и задания

  Добавьте выполнение оставшихся числовых методов в класс spam, представленный в листинге 13.4.

  Добавьте специальные методы в класс parrot из листинга 13.5. Хотя в классе уже представлены два метода обработки последовательностей, можно ещё добавить, например, метод __sub__(), который будет просматривать строку self и отнимать от неё строку other, если таковая будет найдена. Полезным в этом классе может также оказаться метод __сmр__().

  Посмотрите, можно ли разработать ещё более простой способ выполнения метода __setattr__() в классе egg (см. листинг 13.6), заменив чем-либо конструкцию try...except...else.

  Вы можете загрузить библиотеку mayalib с классами и функциями для манипуляций с датами и числами календаря Майя с Web-страницы, находящейся по адресу http://www.pauahtun.org/TYPython/. Интересные данные о календаре Майя Вы также найдёте на домашней странице по адресу http://www.pauahtun.org/.