Язык программирования Python - Роман Сузи
Шрифт:
Интервал:
Закладка:
Теперь следует посмотреть на них в свете только что приведенных определений. Пример:
a = 3
b = 4.0
c = a + b
Здесь происходит следующее. Сначала имя «a» связывается в локальном пространстве имен с объектом–числом 3 (целое число). Затем «b» связывается с объектом–числом 4.0 (число с плавающей точкой). После этого над объектами 3 и 4.0 выполняется операция сложения, и имя «c» связывается с получившимся объектом. Кстати, операциями, в основном, будут называться методы, которые имеют в Python синтаксическую поддержку, в данном случае — инфиксную запись. То же самое можно записать как:
c = a.__add__(b)
Здесь __add__() — метод объекта a, который реализует операцию + между этим объектом и другим объектом.
Узнать набор методов некоторого объекта можно с помощью встроенной функции dir():
>>> dir(a)
['__abs__', '__add__', '__and__', '__class__', '__cmp__',
'__coerce__', '__delattr__', '__div__', '__divmod__',
'__doc__', '__float__', '__floordiv__', '__getattribute__',
'__getnewargs__', '__hash__', '__hex__', '__init__',
'__int__', '__invert__', '__long__', '__lshift__',
'__mod__', '__mul__', '__neg__', '__new__', '__nonzero__',
'__oct__', '__or__', '__pos__', '__pow__', '__radd__',
'__rand__', '__rdiv__', '__rdivmod__', '__reduce__',
'__reduce_ex__', '__repr__', '__rfloordiv__',
'__rlshift__', '__rmod__', '__rmul__', '__ror__',
'__rpow__', '__rrshift__', '__rshift__', '__rsub__',
'__rtruediv__', '__rxor__', '__setattr__', '__str__',
'__sub__', '__truediv__', '__xor__']
Здесь стоит указать на еще одну особенность Python. Не только инфиксные операции, но и встроенные функции ожидают наличия некоторых методов у объекта. Например, можно записать:
abs(c)
А функция abs() на самом деле использует метод переданного ей объекта:
c.__abs__()
Объекты появляются в результате вызова функций–фабрик или конструкторов классов (об этом ниже), а заканчивают свое существование при удалении последней ссылки на объект. Оператор del удаляет имя (а значит, и одну ссылку на объект) из пространства имен:
a = 1
# ...
del a
# имени a больше нет
Типы и классы
Тип определяет область допустимых значений объекта и набор операций над ним. В ООП тип тесно связан с поведением — действиями объекта, состоящими в изменении внутреннего состояния и вызовами методов других объектов.
Ранее в языке Python встроенные типы данных не являлись экземплярами класса, поэтому считалось, что это были просто объекты определенного типа. Теперь ситуация изменилась, и объекты встроенных типов имеют классы, к которым они принадлежат. Таким образом, тип и класс в Python становятся синонимами.
Интерпретатор языка Python всегда может сказать, к какому типу относится объект. Однако с точки зрения применимости объекта в операции его принадлежность к классу не играет решающей роли: гораздо важнее, какие методы поддерживает объект.
Примечание:Пока что в Python есть «классические» и «новые» классы. Первые классы определяются сами по себе, а вторые обязательно ведут свою родословную от класса object. Для целей данного изложения разница между этими видами классов не имеет значения.
Экземпляры классов могут появляться в программе не только из литералов или в результате операций. Обычно для получения объекта класса достаточно вызвать конструктор этого класса с некоторыми параметрами. Объект–класс, как и объект–функция, может быть вызван. Это и будет вызовом конструктора:
>>> import sets
>>> s = sets.Set([1, 2, 3])
В этом примере модуль sets содержит определение класса Set. Вызывается конструктор этого класса с параметром [1, 2, 3]. В результате с именем s будет связан объект–множество из трех элементов 1, 2, 3.
Следует заметить, что, кроме конструктора, определенные классы имеют и деструктор — метод, который вызывается при уничтожении объекта. В языке Python объект уничтожается в случае удаления последней ссылки на него либо в результате сборки мусора, если объект оказался в неиспользуемом цикле ссылок. Так как Python сам управляет распределением памяти, деструкторы в нем нужны очень редко. Обычно в том случае, когда объект управляет ресурсом, который нужно корректно вернуть в определенное состояние.
Еще один способ получить объект некоторого типа — использование функций–фабрик. По синтаксису вызов функции–фабрики не отличается от вызова конструктора класса.
Определение класса
Пусть в ходе анализа данной предметной области необходимо определить класс Граф. Граф — это множество вершин и набор ребер, попарно соединяющий эти вершины. Над графом можно проделывать операции, такие как добавление вершины, ребра, проверка наличия ребра в графе и т.п. На языке Python определение класса может выглядеть так:
from sets import Set as set # тип для множества
class G:
def __init__(self, V, E):
self.vertices = set(V)
self.edges = set(E)
def add_vertex(self, v):
self.vertices.add(v)
def add_edge(self, (v1, v2)):
self.vertices.add(v1)
self.vertices.add(v2)
self.edges.add((v1, v2))
def has_edge(self, (v1, v2)):
return (v1, v2) in self.edges
def __str__(self):
return "%s; %s" % (self.vertices, self.edges)
Использовать класс можно следующим образом:
g = G([1, 2, 3, 4], [(1, 2), (2, 3), (2, 4)])
print g
g.add_vertex(5)
g.add_edge((5,6))
print g.has_edge((1,6))
print g
что даст в результате
Set([1, 2, 3, 4]); Set([(2, 4), (1, 2), (2, 3)])
False
Set([1, 2, 3, 4, 5, 6]); Set([(2, 4), (1, 2), (5, 6), (2, 3)])
Как видно из предыдущего примера, определить класс не так уж сложно. Конструктор класса имеет специальное имя __init__. (Деструктор здесь не нужен, но он бы имел имя __del__.) Методы класса определяются в пространстве имен класса. В качестве первого формального аргумента метода принято использовать self. Кроме методов в объекте класса имеются два атрибута: vertices (вершины) и edges (ребра). Для представления объекта G в виде строки используется специальный метод __str__().
Принадлежность классу можно выяснить с помощью встроенной функции isinstance():
print isinstance(g, G)
Инкапсуляция
Обычно считается, что без инкапсуляции невозможно представить себе ООП, что это ключевое понятие. История развития методологий программирования движима борьбой со сложностью разработки программного обеспечения. Сложность больших программных систем, в создании которых участвует сразу большое количество разработчиков, уменьшается, если на верхнем уровне не видно деталей реализации нижних уровней. Собственно, процедурный подход был первым шагом на этом пути. Под инкапсуляцией (incapsulation, что можно перевести по–разному, но на нужные ассоциации хорошо наводит слово «обволакивание») понимается сокрытие информации о внутреннем устройстве объекта, при котором работа с объектом может вестись только через его общедоступный (public) интерфейс. Таким образом, другие объекты не должны вмешиваться в «дела» объекта, кроме как используя вызовы методов.
В языке Python инкапсуляции не придается принципиального значения: ее соблюдение зависит от дисциплинированности программиста. В других языках программирования имеются определенные градации доступности методов объекта.
Доступ к свойствам
В языке Python не считается зазорным получить доступ к некоторому атрибуту (не методу) напрямую, если, конечно, этот атрибут описан в документации как часть интерфейса класса. Такие атрибуты называются свойствами (properties). В других языках программирования принято для доступа к свойствам создавать специальные методы (вместо того чтобы напрямую обращаться к общедоступным членам–данным). В Python достаточно использовать ссылку на атрибут, если свойство ни на что в объекте не влияет (то есть другие объекты могут его произвольно менять). Если же свойство менее тривиально и требует каких–то действий в самом объекте, его можно описать как свойство (пример взят из документации к Python):
class C(object):
def getx(self): return self.__x
def setx(self, value): self.__x = value
def delx(self): del self.__x
x = property(getx, setx, delx, "I'm the 'x' property.")
Синтаксически доступ к свойству x будет обычной ссылкой на атрибут:
>>> c = C()
>>> c.x = 1
>>> print c.x
1
>>> del c.x
А на самом деле будут вызываться соответствующие методы: setx(), getx(), delx().