Наибольшим, пожалуй, недостатком стека, основанного на модуле, является поддержка только одного объекта стека. Все клиенты модуля stack фактически пользуются одним и тем же стеком. Иногда такая особенность нужна: стек может служить объектом памяти, совместно используемой несколькими модулями. Но для реализации стека как настоящего типа данных необходимо использовать классы.
Для иллюстрации определим полнофункциональный класс стека. Класс Stack, представленный в примере 18.2, определяет новый тип данных с разнообразным поведением. Как и модуль, для хранения помещаемых на стек объектов класс использует список Python. Но на этот раз каждый экземпляр имеет собственный список. В классе определены как «обычные» методы, так и специальные методы с особыми именами, реализующие стандартные операции над типом данных. Комментарии в программном коде описывают специальные методы.
Пример 18.2. PP4E\Dstruct\Basic\stack2.py
"класс стека, позволяющий создавать множество экземпляров"
class error(Exception): pass # при импортировании: локальное исключение
class Stack:
def __init__(self, start=[]): # self — объект экземпляра self.stack = [] # start — любая последовательность: stack…
for x in start: self.push(x) self.reverse() # переупорядочивает операции push
# в обратном порядке
def push(self, obj): # методы: подобно модулю + self
self.stack = [obj] + self.stack # вершина в начале списка
def pop(self):
if not self.stack: raise error(‘underflow’) top, *self.stack = self.stack return top
def top(self):
if not self.stack: raise error(‘underflow’) return self.stack[0]
def empty(self): return not self.stack # instance.empty()
# методы перегрузки операторов def __repr__(self):
return ‘[Stack:%s]’ % self.stack # print, repr(),..
def __eq__(self, other):
return self.stack == other.stack # ‘==’, ‘!=’?
def __len__(self):
return len(self.stack) # len(instance), not instance
def __add__(self, other):
return Stack(self.stack + other.stack) # instance1 + instance2
def __mul__(self, reps):
return Stack(self.stack * reps) # instance * reps
def __getitem__(self, offset): # смотрите также __iter__
return self.stack[offset] # instance[i], [i:j], in, for
def __getattr__(self, name):
return getattr(self.stack, name) # instance.sort()/reverse()/..
Теперь можно создавать отдельные экземпляры обращением к имени класса Stack как к функции. Во многих отношениях операции в классе Stack реализованы точно так же, как в модуле stack из примера 18.1. Но здесь доступ к стеку выполняется через аргумент self, объект экземпляра. Каждый экземпляр имеет свой атрибут stack, который ссылается на собственный список экземпляра. Кроме того, экземпляры стеков создаются и инициализируются в методе конструктора __init__, а не при импортировании модуля. Создадим несколько стеков и посмотрим, как все это действует на практике:
>>> from stack2 import Stack
>>> x = Stack() # создать объект стека, поместить
# в него данные
>>> x.push(‘spam’)
>>> x.push(123)
>>> x # __repr__ выведет содержимое стека
[Stack:[123, ‘spam‘]]
>>> y = Stack() # два независимых объекта стека
>>> y.push(3.1415) # они не используют совместно данные
>>> y.push(x.pop())
>>> x, y
([Stack:[‘spam‘]], [Stack:[123, 3.1415]])
>>> z = Stack() # третий независимый объект стека
>>> for c in ‘spam’: z.push(c)
…
> >> while z: # __len__ проверит истинность стека
… print(z.pop(), end=’ ‘)
m a p s
>>>
> >> z = x + y # __add__ реализует операцию + над стеком
> >> z # хранит три объекта разных типов
[Stack:[‘spam’, 123, 3.1415]]
> >> for item in z: # __getitem__ используется в итерациях
… print(item, end=’ ‘)
spam 123 3.1415
>>>
> >> z.reverse() # вызов __getattr__ передается списку
> >> z
[Stack:[3.1415, 123, ‘spam’]]
Подобно спискам и словарям класс Stack определяет методы и операторы для обработки операций выражений и обращения к атрибутам. Кроме того, он определяет специальный метод __getattr__ для перехвата обращений к атрибутам, не определенным в классе, и передачи их обернутому объекту списка (для поддержки методов списка: sort, append, reverse и так далее). Многие операции модуля превратились в операции в классе. В табл. 18.3 показаны эквивалентные операции модуля и класса (колонки 1 и 2) и приводится метод класса, который выполняет каждую из них (колонка 3).
Табли ца 18.3. Срав не ние опера ций мо дуля/клас са
Операции в модуле |
Операции в классе |
Метод класса |
module.empty() |
not instance |
_ len |
module.member(x) |
x in instance |
_ getitem |
module.item(i) |
instance[i] |
_ getitem |
module.length() |
len(instance) |
_ len |
module.dump() |
print(instance) |
_ repr |
range() счет чик циклов |
for x in instance |
_ getitem |
вып ол не ние ите ра ций вруч ную |
instance + instance |
_ add |
module.stack.reverse() |
instance.reverse() |
_ getattr |
module.push/pop/top |
instance.push/pop/top |
push/pop/top |
В сущности, классы позволяют расширять набор встроенных типов Python с помощью многократно используемых типов, реализуемых в модулях Python. Основанные на классах типы могут использоваться так же, как встроенные типы: в зависимости от того, какие методы операций в них определены, классы могут реализовывать числа, отображения и последовательности и быть изменяемыми или нет. Основанные на классах типы могут также занимать положение, промежуточное между этими категориями.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011