Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

[ Россум, Дрейк, Откидач ] Язык программирования Python

.pdf
Скачиваний:
285
Добавлен:
25.04.2014
Размер:
1.5 Mб
Скачать

9.6. Частные атрибуты

91

class РТПЙЪЧПДОЩК_ЛМБУУ(ВБЪПЧЩК_ЛМБУУ1, ВБЪПЧЩК_ЛМБУУ2, ВБЪПЧЩК_ЛМБУУ3):

ЙОУФТХЛГЙС1

.

.

.

ЙОУФТХЛГЙСN

Единственное правило, необходимое для объяснения семантики, — правило разрешения имен атрибутов. Поиск производится сначала в глубину, затем слева направо. Таким образом, если атрибут не найден в РТПЙЪЧПДОЩК_ЛМБУУ, то он ищется сначала в ВБЪПЧЩК_ЛМБУУ1, затем (рекурсивно) в базовых классах класса ВБЪПЧЩК_ЛМБУУ1 и только потом в ВБЪПЧЩК_ЛМБУУ2 и т. д. (Для некоторых людей кажется более естественным другой порядок разрешения имен атрибутов — сначала в классах ВБЪП- ЧЩК_ЛМБУУ1, ВБЪПЧЩК_ЛМБУУ2, ВБЪПЧЩК_ЛМБУУ3 и только потом в базовых классах класса ВБЪПЧЩК_ЛМБУУ1. Однако в этом случае возникает зависимость результата от реализации каждого из базовых классов. С принятым же правилом, нет разницы между прямыми и унаследованными атрибутами базовых классов.)

9.6 Частные атрибуты

Python предоставляет ограниченную поддержку частных атрибутов классов. Любой атрибут вида __БФТЙВХФ (имя которого содержит не менее двух символов подчеркивания в начале и не более одного в конце) заменяется на _ЛМБУУ__БФТЙВХФ, где ЛМБУУ — имя текущего класса с “обрезанными” символами подчеркивания в начале. Такая обработка производится независимо от синтаксического расположения идентификатора, то есть может использоваться для определения частных атрибутов, доступ к которым будет возможен только из методов этого класса и методов его экземпляров. (Имя может быть обрезано, если его длина превысит 255 символов.) Если Вы ссылаетесь на имя, находясь за пределами класса, или если имя класса состоит только из символов подчеркивания, то оно преобразованию не подлежит.

Преобразование имен обеспечивает классам возможность определить “частные” атрибуты, не беспокоясь о том, что производные классы могут переопределить их, а также не дают к ним доступа коду, находящемуся за пределами класса. Заметьте, что правила преобразования разработаны в основном для того, чтобы избежать случайного вмешательства. Иногда доступ к частным атрибутам необходим, например, для отладчиков — это одна из причин, почему оставлена лазейка.

Если Вы из класса вызываете код с помощью exec, execfile, eval() или evalfile(), то внутри этого кода класс не будет считаться текущим: ситуация аналогична использованию инструкции global — действие ограничивается единовременно байт-компилированным кодом. Это ограничение распространяется и на getattr(), setattr() и delattr(), а также на прямое использование __dict__.

92

Глава 9. Классы

9.7 Примеры использования классов

Иногда полезно иметь тип данных (record в Pascal или struct в C), объединяющий несколько именованных единиц данных. С этой задачей прекрасно справится пустой класс:

class Employee: pass

#уПЪДБЕН РХУФХА ЛБТФПЮЛХ ОБ УМХЦБЭЕЗП john = Employee()

#ъБРПМОСЕН РПМС ЛБТФПЮЛЙ:

john.name = ’John Doe’ john.dept = ’computer lab’ john.salary = 1000

Использование экземпляров классов в качестве исключений позволяет расположить их в виде “дерева” и обрабатывать ошибки находящиеся на определенной ветви.

Часто вместо ожидаемого типа данных в функции (методе) можно использовать экземпляр класса, эмулирующего методы этого типа. Например, если есть функция, считывающая данные из файла, Вы можете определить класс с методами read() и readline(), которые будут брать данные из буфера вместо файла, и передать его экземпляр функции в качестве аргумента. Используя же специальные методы (см. раздел 11.6.3), можно эмулировать поведение чисел, списков, словарей и даже полностью контролировать доступ к атрибутам.

В библиотеке стандартных модулей Вы найдете множество примеров классов, эмулирующих поведение строк, списков, словарей, файлов. Рекомендуем посмотреть на реализацию таких модулей, как UserString, UserList и UserDict, StringIO. Кроме того, в дистрибутив обычно входит несколько демонстрационных модулей, среди которых Вы найдете много интересных примеров, показывающих, как, например, можно реализовать рациональные числа.

9.7.1Экземпляры классов в качестве исключений

Исключения в языке Python могут быть строками (для совместимости со старыми версиями; поддержка строк в качестве исключений может быть удалена в будущих версиях) или экземплярами классов. Использование механизма наследования позволяет создавать легко расширяемые иерархии исключений.

Чаще всего инструкция raise употребляется в следующем виде:

raise ЬЛЪЕНРМСТ_ЛМБУУБ

9.7. Примеры использования классов

93

После ключевого слова except могут быть перечислены как строковые объекты, так и классы. Указанный класс считается “совместимым” с исключением, если исключение является экземпляром этого класса или класса, производного от него (но не наоборот — если в ветви except указан производный класс от того, экземпляром которого является исключение, то исключение не обрабатывается). Следующий пример выведет (именно в этом порядке) ‘B’, ‘C’, ‘D’:

class B: pass

class C(B): pass

class D(C): pass

for c in [B, C, D]: try:

raise c() except D:

print "D" except C:

print "C" except B:

print "B"

Обратите внимание, что если расположить ветви except в обратном порядке, то Вы получите ‘B’, ‘B’, ‘B’ — срабатывает первая ветвь except, совместимая с исключением.

Если исключение-экземпляр не обрабатывается, выводится сообщение об ошибке: имя класса, двоеточие, пробел и строка, полученная применением встроенной функции str() к исключению-экземпляру.

>>> class MyError:

...

def __init__(self, value):

...

self.value = value

...

def __str__(self):

...

return ‘self.value‘

...

>>> raise MyError(1) Traceback (innermost last):

File "<stdin>", line 1 __main__.MyError: 1

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

94

Глава 9. Классы

>>>class MyError(Exception): pass

...

>>>raise MyError(’Oops!’) Traceback (most recent call last):

File "<stdin>", line 1, in ? __main__.MyError: Oops!

9.7.2Классы-помощники

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

class parallel:

def __init__(self, *args): self.__args = args

def __getitem__(self, item):

return map(lambda s, i=item: s[i], self.__args)

С таким классом-помощником задача параллельного перебора элементов сильно упрощается:

>>>seq1 = xrange(10)

>>>seq2 = [1, 2, 3, 5, 7]

>>>for x, y in parallel(seq1, seq2):

... print x, y

...

0 1

1 2

2 3

3 5

4 7

Как же наша псевдопоследовательность “узнает” о том, что элементы в одной из последовательностей закончились? Дело в том, что индикатором конца последовательности при использовании цикла for или средств функционального программирования в языке Python служит исключение IndexError. Исключение генерируется при первом использовании индекса, выходящего за пределы любой из последовательностей (в нашем примере seq1 и seq2). Так что нам достаточно не обрабатывать его и инструкция for будет считать, что закончилась наша псевдопоследовательность.

9.7. Примеры использования классов

95

В версии 2.0 языка появилась новая встроенная функции zip(), предназначенная специально для параллельного перебора нескольких последовательностей. Ее поведение аналогично приведенному здесь классу parallel, с одним лишь отличием — функция zip() создает полноценный список, в который можно вносить изменения.

9.7.3Множества

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

class set:

def __init__(self, seq = ()):

#бФТЙВХФ ’_data’ УПДЕТЦЙФ УМПЧБТШ, ЛМАЮБНЙ

#ЛПФПТПЗП СЧМСАФУС ЬМЕНЕОФЩ НОПЦЕУФЧБ. дЕМБФШ

#БФТЙВХФ ЮБУФОЩН (’__data’) ОЕЦЕМБФЕМШОП, ФБЛ

#ЛБЛ Ч ЬФПН УМХЮБЕ ВХДЕФ УМПЦОП ТБВПФБФШ У

#РТПЙЪЧПДОЩНЙ ПФ set ЛМБУУБНЙ.

if isinstance(seq, set):

#еУМЙ seq СЧМСЕФУС ЬЛЪЕНРМСТПН set ЙМЙ

#РТПЙЪЧПДОПЗП ПФ ОЕЗП ЛМБУУБ, НПЦОП

#ЧПУРПМШЪПЧБФШУС "УЕЛТЕФБНЙ" ТЕБМЙЪБГЙЙ self._data = seq._data.copy()

else:

#йОБЮЕ НЩ УЮЙФБЕН seq РТПЙЪЧПМШОПК

#РПУМЕДПЧБФЕМШОПУФША

self._data = {} for item in seq:

self._data[item] = None

def items(self):

#ьФПФ НЕФПД РПЪЧПМЙФ РЕТЕВЙТБФШ ЬМЕНЕОФЩ

#НОПЦЕУФЧБ:

#for item in myset.items():

#...

return self._data.keys()

def tuple_key(self):

#уБНЙ НОПЦЕУФЧБ ЙЪНЕОСЕНЩЕ Й ОЕ НПЗХФ

#ЙУРПМШЪПЧБФШУС Ч ЛБЮЕУФЧЕ ЛМАЮБ Ч УМПЧБТЕ ЙМЙ

#ЬМЕНЕОФБ Ч ДТХЗПН НОПЦЕУФЧЕ. дМС ЬФПЗП ЙИ

#ОХЦОП РТЕПВТБЪПЧБФШ Ч ЛПТФЕЦ.

96

Глава 9. Классы

items = self._data.keys()

#уПТФЙТХС ЬМЕНЕОФЩ, НЩ НПЦЕН ЗБТБОФЙТПЧБФШ,

#ЮФП РПТСДПЛ УМЕДПЧБОЙС ЬМЕНЕОФПЧ Ч ДЧХИ

#НОПЦЕУФЧБИ ЧУЕЗДБ ВХДЕФ ПДЙОБЛПЧЩН. items.sort()

return tuple(items)

def add(self, item):

#дПВБЧМЕОЙЕ ЬМЕНЕОФБ ТЕБМЙЪХЕФУС ДПВБЧМЕОЙЕН Ч

#УМПЧБТШ РХУФПК ЪБРЙУЙ У УППФЧЕФУФЧХАЭЙН

#ЛМАЮЕН.

self._data[item] = None

def remove(self, item):

if self._data.has_key(item): del self._data[item]

else:

# нОПЦЕУФЧП ОЕ УПДЕТЦЙФ ФБЛПЗП ЬМЕНЕОФБ raise ValueError("item doesn’t exist")

def copy(self): return set(self)

def __repr__(self):

return ’set(’+‘self.items()‘+’)’

def __len__(self):

#лПМЙЮЕУФЧП ЬМЕНЕОФПЧ Ч НОПЦЕУФЧЕ, ЧЩЪЩЧБЕФУС

#ЖХОЛГЙЕК len(). фБЛЦЕ ПРТЕДЕМСЕФ ЙУФЙООПУФШ

#НОПЦЕУФЧБ.

return len(self._data)

def __contains__(self, item):

# пРЕТБФПТЩ ’in’ Й ’not in’. return self._data.has_key(item)

def __cmp__(self, other):

# чУЕ ПРЕТБГЙЙ УТБЧОЕОЙС.

return cmp(self._data, other._data)

def __or__(self, other):

# пРЕТБФПТ ’|’ (ПВЯЕДЙОЕОЙЕ). res = set(self) res._data.update(other._data) return res

def __ior__(self, other):

# пРЕТБФПТ ’|=’.

9.7. Примеры использования классов

97

self._data.update(other._data) return self

def __and__(self, other):

#пРЕТБФПТ ’&’ (РЕТЕУЕЮЕОЙЕ).

#вХДЕН РЕТЕВЙТБФШ ЬМЕНЕОФЩ ФПЗП НОПЦЕУФЧБ,

#ЛПФПТПЕ УПДЕТЦЙФ НЕОШЫЕ ЬМЕНЕОФПЧ.

if len(other._data) < len(self._data): self, other = other, self

res = set()

for item in self._data.keys(): if other._data.has_key(item):

res._data[item] = None return res

def __iand__(self, other):

# пРЕТБФПТ ’&=’.

for item in self._data.keys():

if not other._data.has_key(item): del self._data[item]

return self

def __sub__(self, other):

#пРЕТБФПТ ’-’ (ЬМЕНЕОФЩ, ЛПФПТЩЕ УПДЕТЦБФУС Ч

#РЕТЧПН НОПЦЕУФЧЕ Й ОЕ УПДЕТЦБФУС ЧП ЧФПТПН). res = set()

for item in self._data.keys():

if not other._data.has_key(item): res._data[item] = None

return res

def __isub__(self, other):

# пРЕТБФПТ ’-=’.

for item in other._data.keys(): if self._data.has_key(item):

del self._data[item] return self

#еУМЙ НЩ ТЕБМЙЪХЕН ЧЩЮЙФБОЙЕ, ФП ДМС УЙННЕФТЙЙ

#УМЕДХЕФ ФБЛЦЕ ТЕБМЙЪПЧБФШ Й УМПЦЕОЙЕ

#(ПВЯЕДЙОЕОЙЕ).

__add__ = __or__ __iadd__ = __ior__

def __xor__(self, other):

#пРЕТБФПТ ’^’ (ЬМЕНЕОФЩ, УПДЕТЦБЭЙЕУС ФПМШЛП Ч

#ПДОПН ЙЪ НОПЦЕУФЧ).

98 Глава 9. Классы

if len(other._data) < len(self._data): self, other = other, self

res = set(other)

for item in self._data.keys(): if res._data.has_key(item):

del res._data[item] else:

res._data[item] = None return res

def __ixor__(self, other):

# пРЕТБФПТ ’^=’

for item in other._data.keys(): if self._data.has_key(item):

del self._data[item] else:

self._data[item] = None return self

9.7.4Контроль доступа к атрибутам

С помощью специальных методов __getattr__(), __setattr__() и __delattr__() Вы можете контролировать все обращения к атрибутам экземпляра. Приведем пример класса, реализующего собственные методы __getattr__() и __setattr__() и сохраняющего все атрибуты в частной переменной:

class VirtualAttributes: __vdict = None

#фБЛЙН ПВТБЪПН НЩ НПЦЕН РПМХЮЙФШ РТЕПВТБЪПЧБООПЕ

#ЙНС БФТЙВХФБ __vdict:

__vdict_name = locals().keys()[0]

def __init__(self):

#НЩ ОЕ НПЦЕН ЪБРЙУБФШ ’self.__vdict = {}’,

#Ф.Л. РТЙ ЬФПН РТПЙЪПКДЕФ ПВТБЭЕОЙЕ Л НЕФПДХ

#__setattr__

self.__dict__[self.__vdict_name] = {}

def __getattr__(self, name): return self.__vdict[name]

def __setattr__(self, name, value): self.__vdict[name] = value

Часть II

Встроенные возможности языка