При написании функции в Python обычно не проверяется, к какому типу (классу) относится тот или иной аргумент: некоторые методы просто применяются к переданному объекту. Тем самым функции получаются максимально обобщенными: они не требуют от объектов–параметров большего, чем наличие методов с определенным именем, набором аргументов и семантикой.
Следующий пример показывает полиморфизм в том виде, в котором он свойственен Python:
Листинг
def get_last(x):
return x[-1]
print get_last([1, 2, 3])
print get_last(«abcd»)
Описанной функции будет подходить в качестве аргумента все, от чего можно взять индекс–1 (последний элемент). Однако семантика «взятие последнего элемента» выполняется только для последовательностей. Функция будет работать и для словарей, но смысл при этом будет немного другой.
Имитация типов
Для иллюстрации понятия полиморфизма можно построить собственный тип, похожий на встроенный тип «функция». Построить класс, объекты которого вызываются подобно методам или функциям, можно так:
Листинг
class CountArgs(object):
def __call__(self, *args, **kwargs):
return len(args) + len(kwargs)
cc = CountArgs
print cc(1, 3, 4)
Как видно из этого примера, экземпляры класса CountArgs можно вызывать подобно функциям (в результате будет возвращено количество переданных параметров). При попытке вызова экземпляра на самом деле будет вызван метод __call__ со всеми аргументами.
Следующий пример показывает, что сравнением экземпляров класса тоже можно управлять:
Листинг
class Point:
def __init__(self, x, y):
self.coord = (x, y)
def __nonzero__(self):
return self.coord[0] != 0 or self.coord[1] != 0
def __cmp__(self, p):
return cmp(self.coord, p.coord)
for x in range(-3, 4):
for y in range(-3, 4):
if Point(x, y) < Point(y, x):
print "*",
elif Point(x, y):
print ".»,
else:
print «o»,
Программа выведет:
Листинг
. * * * * * *
. . * * * * *
… * * * *
… o * * *
… . . * *
… … *
… … .
В данной программе класс Point (Точка) имеет метод __nonzero__, который определяет истинностное значение объекта класса. Истину будут давать только точки, отличные от (0, 0). Другой метод - __cmp__ - вызывается при необходимости сравнить точку и другой объект (имеющий как и точка атрибут coord, который содержит кортеж как минимум из двух элементов). Нужно заметить, что вместо __cmp__ можно определить отдельные методы для операций сравнения: __lt__, __le__, __ne__, __eq__, __ge__, __gt__ (для <, <=, !=, ==, >=, > соответственно).
Достаточно легко имитировать и числовые типы. Класс, который пользуется удобством синтаксиса инфиксного +, можно определить так:
Листинг
class Plussable:
def __add__(self, x):
…
def __radd__(self, x):
…
def __iadd__(self, x):
…
Здесь метод __add__ вызывается, когда экземпляр класса Plussable стоит слева от сложения, __radd__ - если справа от сложения и метод слева от него не имеет метода __add__. Метод __iadd__ нужен для реализации +=.
Отношения между классами
Наследование
На практике часто возникает ситуация, когда в предметной области выделены очень близкие, но вместе с тем неодинаковые классы. Одним из способов сокращения описания классов за счет использования их сходства является выстраивание классов в иерархию. В корне этой иерархии стоит базовый класс, от которого нижележащие классы иерархии наследуют свои атрибуты, уточняя и расширяя поведение вышележащего класса. Обычно принципом построения классификации является отношение «IS–A» («ЕСТЬ»). Например, класс Окружность в программе — графическом редакторе может быть унаследован от класса Геометрическая Фигура. При этом Окружность будет являться подклассом (или субклассом) для класса Геометрическая Фигура, а Геометрическая Фигура — надклассом (или суперклассом) для класса Окружность.
В языке Python во главе иерархии («новых») классов стоит класс object. Для ориентации в иерархии существуют некоторые встроенные функции, которые будут рассмотрены ниже. Функция issubclass(x, y) может сказать, является ли класс x подклассом класса y:
Листинг
>>> class A(object): pass
…
>>> class B(A): pass
…
>>> issubclass(A, object)
True
>>> issubclass(B, A)
True
>>> issubclass(B, object)
True
>>> issubclass(A, str)
False
>>> issubclass(A, A) # класс является подклассом самого себя
True
В основе построения классификации всегда стоит принцип, играющий наиболее важную роль в анализируемой и моделируемой системе. Следует заметить, что одним из «перегибов» при использовании ОО методологии является искусственное выстраивание иерархии классов. Например, не стоит наследовать класс Машина от класса Колесо (внимательные заметят, что здесь отношение другое: колесо является частью машины).
Класс называется абстрактным, если он предназначен только для наследования. Экземпляры абстрактного класса обычно не имеют большого смысла. Классы с рабочими экземплярами называются конкретными.
В Python примером абстрактного класса является встроенный тип basestring, у которого есть конкретные подклассы str и unicode.
Множественное наследование