Меню сайта
Статистика
Онлайн всего: 1 Гостей: 1 Пользователей: 0 |
Лекция 4.СодержаниеPython проектировался как объектно-ориентированный язык программирования. Это означает (по Алану Кэю, автору объектно-ориентированного языка Smalltalk), что он построен с учетом следующих принципов:
Основные понятияПри процедурном программировании программа разбивается на части в соответствии с алгоритмом: каждая часть (подпрограмма, функция, процедура) является составной частью алгоритма. При объектно-ориентированном программировании программа строится как совокупность взаимодействующих объектов. С точки зрения объектно-ориентированного подхода, объект - это нечто, обладающее значением (состоянием), типом (поведением) и индивидуальностью. Когда программист выделяет объекты в предметной области, он обычно абстрагируется (отвлекается) от большинства их свойств, концентрируясь на существенных для задачи свойствах. Над объектами можно производить операции (посылая им сообщения). В языке Python все данные представлены в виде объектов. Взаимодействие объектов заключается в вызове методов одних объектов другими. Иногда говорят, что объекты посылают друг другу сообщения. Сообщения - это запросы к объекту выполнить некоторые действия. (Сообщения, методы, операции, функции-члены являются синонимами). Каждый объект хранит свое состояние (для этого у него есть атрибуты) и имеет определенный набор методов. (Синонимы: атрибут, поле, слот, объект-член, переменная экземпляра). Методы определяют поведение объекта. Объекты класса имеют общее поведение. Объекты описываются не индивидуально, а с помощью классов. Класс - объект, являющийся шаблоном объекта. Объект, созданный на основе некоторого класса, называется экземпляром класса. Все объекты определенных пользователем классов являются экземплярами класса. Тем не менее, объекты даже с одним и тем же состоянием могут быть разными объектами. Говорят, что они имеют разную индивидуальность. В языке Python для определения класса используется оператор class: class имя_класса(класс1, класс2, ...): # определения методов Класс определяет тип объекта, то есть его возможные состояния и набор операций. Абстракция и декомпозицияАбстракция в ООП позволяет составить из данных и алгоритмов обработки этих данных объекты, отвлекаясь от несущественных (на некотором уровне) с точки зрения составленной информационной модели деталей. Таким образом, программа подвергается декомпозиции на части "дозированной" сложности. Отдельный объект, даже вместе с совокупностью его связей с другими объектами, человеком воспринимается легче (именно так он привык оперировать в реальном мире), чем что-то неструктурированное и монотонное. Перед тем как начать написание даже самой простенькой объектно-ориентированной программы, необходимо провести анализ предметной области, для того чтобы выявить в ней классы объектов. При выделении объектов необходимо абстрагироваться (отвлечься) от большинства присущих им свойств и сконцентрироваться на свойствах, значимых для задачи.. Выделяемые объекты необязательно должны походить на физические объекты - ведь это абстракции, за которыми скрываются процессы, взаимодействия, отношения. Удачная декомпозиция стоит многого. От нее зависят не только количественные характеристики кода (быстродействие, занимаемая память), но и трудоемкость дальнейшего развития и сопровождения. При отсутствии соответствующего опыта лучше не загадывать будущих путей развития программы, а делать ее как можно проще, под конкретную задачу. Даже если просто перечислить все существительные, встретившиеся в описании задачи (явно или неявно), получится неплохой список кандидатов в классы. При процедурном подходе тоже используется декомпозиция, но при объектно-ориентированном подходе производится декомпозиция не самого алгоритма на более мелкие части, а предметной области на классы объектов. ОбъектыДо этой лекции объекты Python встречались много раз: ведь каждое число, строка, функция, модуль и т.п. - это объекты. Некоторые встроенные объекты имеют в 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 всегда может сказать, к какому типу относится объект. Однако с точки зрения применимости объекта в операции его принадлежность к классу не играет решающей роли: гораздо важнее, какие методы поддерживает объект.
Экземпляры классов могут появляться в программе не только из литералов или в результате операций. Обычно для получения объекта класса достаточно вызвать конструктор этого класса с некоторыми параметрами. Объект-класс, как и объект-функция, может быть вызван. Это и будет вызовом конструктора: >>> 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) ИнкапсуляцияОбычно считается, что без инкапсуляции невозможно представить себе ООП, что это ключевое понятие. История развития методологий программирования движима борьбой со сложностью разработки программного обеспечения. Сложность больших программных систем, в создании которых участвует сразу большое количество разработчиков, уменьшается, если на верхнем уровне не видно деталей реализации нижних уровней. Собственно, процедурный подход был первым шагом на этом пути. Под инкапсуляцией (encapsulation, что можно перевести по-разному, но на нужные ассоциации хорошо наводит слово "обволакивание") понимается сокрытие информации о внутреннем устройстве объекта, при котором работа с объектом может вестись только через его общедоступный (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(). Следует отметить, что в экземпляре класса в Python можно организовать доступ к любым (даже несуществующим) атрибутам, обрабатывая запрос на доступ к атрибуту группой специальных методов:
Следующий небольшой пример демонстрирует все перечисленные моменты. В этом примере из словаря создается объект, именами атрибутов которого будут ключи словаря, а значениями - значения из словаря по заданным ключам: class AttDict(object): def __init__(self, dict=None): object.__setattr__(self, '_selfdict', dict or {}) def __getattr__(self, name): if self._selfdict.has_key(name): return self._selfdict[name] else: raise AttributeError def __setattr__(self, name, value): if name[0] != '_': self._selfdict[name] = value else: raise AttributeError def __delattr__(self, name): if name[0] != '_' and self._selfdict.has_key(name): del self._selfdict[name] ad = AttDict({'a': 1, 'b': 10, 'c': '123'}) print ad.a, ad.b, ad.c ad.d = 512 print ad.d Сокрытие данныхПодчеркивание ("_") в начале имени атрибута указывает на то, что он не входит в общедоступный интерфейс. Обычно применяется одиночное подчеркивание, которое в языке не играет особой роли, но как бы говорит программисту: "этот метод только для внутреннего использования". Двойное подчеркивание работает как указание на то, что атрибут - приватный. При этом атрибут все же доступен, но уже под другим именем, что и иллюстрируется ниже: >>> class X: ... x = 0 ... _x = 0 ... __x = 0 ... >>> dir(X) ['_X__x', '__doc__', '__module__', '_x', 'x'] |
Поиск
Друзья сайта
|
|||||||||