В случае с 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__
(
Это означает, что сначала методы ищутся в классе 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,
Программа выдаст