Язык программирования Python
Шрифт:
Класс, получаемый при множественном наследовании, объединяет поведение своих надклассов, комбинируя стоящие за ними абстракции.
Использовать множественное наследование следует очень осторожно, а необходимость в нем возникает реже одиночного.
Множественное наследование можно применить для получения класса с заданными общедоступными методами, причем методы задает один родительский класс, а реализуются они на основе методов второго класса. Первый класс может быть полностью абстрактным.
Множественное наследование применяется для добавления примесей (mixins). Примесь — специально сконструированный
Изредка множественное наследование применяется в своем основном смысле, когда объекты класса, получающегося в результате множественного наследования, предназначаются для использования в качестве объектов всех родительских классов.
В случае с Python наследование можно считать одним из способов собрать нужные комбинации методов в серии классов:
Листинг
class A:
def a(self): return 'a'
class B:
def b(self): return 'b'
class C:
def c(self): return 'c'
class AB(A, B):
pass
class BC(B, C):
pass
class ABC(A, B, C):
pass
Впрочем, собрать нужные методы можно и по–другому, без использования наследования:
Листинг
def ma(self): return 'a'
def mb(self): return 'b'
def mc(self): return 'c'
class AB:
a = ma
b = mb
class BC:
b = mb
c = mc
class ABC:
a = ma
b = mb
c = mc
Порядок разрешения методов
В случае, когда надклассы имеют одинаковые методы, использование того или иного метода определяется порядком разрешения методов (method resolution order). Для «новых» классов узнать этот порядок очень просто с помощью атрибута __mro__:
Листинг
>>> str.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>)
Это означает, что сначала методы ищутся в классе str, затем в basestring, а уже потом — в object.
Для «классических» классов порядок несколько отличается от порядка разрешения методов в «новых» классах. Нужно стараться избегать множественного наследования или применять его очень аккуратно.
Агрегация
Контейнеры
Под контейнером обычно понимают объект, основным назначением которого является хранение и обеспечение доступа к другим объектам. Контейнеры реализуют отношение «HAS–A» («ИМЕЕТ») между объектами. Встроенные типы, список и словарь — яркие примеры контейнеров. Можно построить собственные типы контейнеров, которые будут иметь свою логику доступа к хранимым объектам. В контейнере хранятся не сами объекты, а ссылки на них.
Для практических нужд в Python обычно хватает встроенных контейнеров (словаря и списка), но если это необходимо, можно создать и другие. Ниже приведен класс Стек, реализованный на базе списка:
Листинг
class Stack:
def __init__(self):
«"«Инициализация стека»""
self._stack = []
def top(self):
«"«Возвратить вершину стека (не снимая)»""
return self._stack[-1]
def pop(self):
«"«Снять со стека элемент»""
return self._stack.pop
def push(self, x):
«"«Поместить
элемент на стек»""self._stack.append(x)
def __len__(self):
«"«Количество элементов в стеке»""
return len(self._stack)
def __str__(self):
«"«Представление в виде строки»""
return " : ".join(["%s» % e for e in self._stack])
Использование:
Листинг
>>> s = Stack
>>> s.push(1)
>>> s.push(2)
>>> s.push(«abc»)
>>> print s.pop
abc
>>> print len(s)
2
>>> print s
1 : 2
Таким образом, контейнеры позволяют управлять набором (любых) других объектов в соответствии со структурой их хранения, не вмешиваясь во внутренние дела объектов. Узнав интерфейс класса Stack, можно и не догадаться, что он реализован на основе списка, и каким именно образом он реализован с помощью него. Но для использования стека это не важно.
Примечание:
В данном примере для краткости изложения не учтено, что в результате некоторых действий могут возбуждаться исключения. Например, при попытке снять элемент с вершины пустого стека.
Итераторы
Итераторы — это объекты, которые предоставляют последовательный доступ к элементам контейнера (или генерируемым «на лету» объектам). Итератор позволяет перебирать элементы, абстрагируясь от реализации того контейнера, откуда он их берет (если этот контейнер вообще есть).
В следующем примере приведен итератор, выдающий значения из списка по принципу «считалочки» по N:
Листинг
class Zahlreim:
def __init__(self, lst, n):
self.n = n
self.lst = lst
self.current = 0
def __iter__(self):
return self
def next(self):
if self.lst:
self.current = (self.current + self.n — 1) % len(self.lst)
return self.lst.pop(self.current)
else:
raise StopIteration
print range(1, 11)
for i in Zahlreim(range(1, 11), 5):
print i,
Программа выдаст
Листинг
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
5 10 6 2 9 8 1 4 7 3
В этой программе делегировано управление доступом к элементам списка (или любого другого контейнера, имеющего метод pop(n) для взятия и удаления n–го элемента) классу–итератору. Итератор должен иметь метод next и возбуждать исключение StopIteration по завершении итераций. Кроме того, метод __iter__ должен выдавать итератор по экземпляру класса (в данном случае итератор — он сам (self)).
В настоящее время итераторы приобретают все большее значение, и о них много говорилось в лекции по функциональному программированию.
Ассоциация
Если в случае агрегации имеется довольно четкое отношение «ИМЕЕТ» (HAS–A) или «СОДЕРЖИТСЯ–В», которое даже отражено в синтаксисе Python:
Листинг
lst = [1, 2, 3]
if 1 in lst:
…
то в случае ассоциации ссылка на экземпляр другого класса используется без отношения включения одного в другой или принадлежности. О таком отношении между классами говорят как об отношении USE–A («ИСПОЛЬЗУЕТ»). Это достаточно общее отношение зависимости между классами.