Часть II. Объекты, отраженные в зеркале, ближе, чем кажутся

 

Часть II

Объекты, отраженные в зеркале, ближе, чем кажутся

Темы занятий

  1. Объекты как они есть
  2. Определение объектов
  3. Концепции объектно-ориентированного программирования
  4. ещё о концепциях объектно-ориентированного программирования
  5. Специальные методы классов в Python
  6. Лаборатория доктора Франкенштейна
  7. Лаборатория доктора Франкенштейна (продолжение)
  8. Объекты в деле

9-й час

Объекты как они есть

  С этой главы мы начинаем захватывающее путешествие в мир объектов. Этой теме отведена почётная центральная часть книги. Мы начнём с подготовки и возведения фундамента для восприятия информации, изложенной в последующих главах, но к концу данной главы Вы будете в состоянии определять объекты; представлять переменные как ссылки на объекты; понимать основы объектно-ориентированного программирования.

  Изучая материал этой главы, Вы мало времени проведёте за компьютером. Вместо этого мы познакомитесь с объектами, а подробности Вы оставим до следующих глав.

Объекты - это всё вокруг

  В Python всё является объектами. Объекты можно представить себе в виде особых блоков или ящичков, в которых может храниться всякая всячина. Содержимое встроенных в Python объектов, каковыми являются базовые типы данных, довольно ограничено. Числовые типы, например, не содержат никаких методов, только значение. Списки являются объектами и содержат как методы, так и значения, но нет никакой возможности добавить к ним другие методы. Наборы вообще не имеют методов, но могут содержать значения любого типа. Словари имеют методы и содержат значения, но опять же, отсутствует возможность добавить к ним хотя бы несколько своих методов. Поэтому, хотя в Python всё является объектами, между объектами базовых типов данных и объектами, определяемыми пользователем, есть много отличий. Наиболее яркое отличие состоит в том, что определяемые пользователем объекты могут содержать любые понравившиеся Вам члены. Под членами объекта подразумевается всё, что угодно. Пользовательские объекты, которые ещё называют классами, могут содержать неограниченное количество необходимых Вам данных базовых типов. Кроме того, они могут содержать другие объекты. Наконец, они могут содержать функции. Функции, определённые внутри объектов, называются методами. Модуль также является особым видом объекта, так как может вмещать переменные базовых типов, другие объекты и методы.

  В сущности, когда Вы пишете программу на языке Python, то создаёте модуль. Модуль можно импортировать в свою программу с помощью инструкции import, указав имя файла, в котором он хранится. Все это Вам уже знакомо. Так, мы использовали модуль строк string, который находится в файле string.py. Все члены этого модуля определены внутри этого файла, т.е. имеют свои имена и к ним можно обратиться с помощью идентификатоpa string.имя члена. Таким образом, раньше Вам уже много раз приходилось оперировать объектами и их методами, например, когда Вы использовали функцию string.atoi(). Модуль, в котором выполняется ваша программа, всегда носит имя "__main__". Как Вы помните, проверяя текущее имя модуля, мы можем выяснить, является ли он в данный момент текущим или импортированным в основную программу:

if __name__ == "__main__":

  выполнение программы модуля или самотестирование

else:

  определение членов модуля

  В Python даже числа являются объектами. Все объекты должны нести с собой некие атрибуты, чтобы Python мог распознать, к какому типу относится тот или иной объект. Так, все числа имеют идентификатор, который сообщает, например: "Я — комплексное число" (или какой-либо другой числовой тип). Всякий раз, когда Вы вводите какое-нибудь число в код программы, а затем выполняете её с помощью интерпретатора, Python распознает тип числа и ассоциирует (проще — устанавливает связь) атрибут типа с фактическим значением. Но, как Вам уже известно, числа так же не имеют методов, как строки и наборы. Только списки и словари (называемые ещё ассоциативными типами) имеют методы, например list.append() или dict.keys(). Поэтому ещё раз усвоим, в Python объект объекту рознь. Этот факт не очень нравится Гуидо со товарищами, и они планируют в будущих версиях Python нивелировать эти различия, обеспечив методами строки и наборы. Станет возможным даже такое выражение, как '23.atoi()'. Но это оставим для будущего. Тем более, зная стиль работы Гуидо, я уверен, что все программы, которые мы создадим на языке Python сегодня, будут также хорошо выполняться в будущих версиях, несмотря на все нововведения. Даже если строки будут снабжены методами, модуль string всё равно можно будет использовать параллельно.

  То, что работа Python полностью основана на использовании объектов, позволяет ему быть языком с динамическим присвоением типа данных. Если бы значение переменной и атрибут типа не были связаны вместе в один объект, то определение типа переменной (а тем более изменение типа) было бы длительной процедурой. Таким образом, если бы числа в Python не были объектами, то это был бы ещё один язык со статическим распределением типов, каковых уже предостаточно. А медленных языков программирования также предостаточно.

  Объекты представляют собой совокупность членов. О любой подобной совокупности, содержащей переменные-члены, говорят, что она обладает структурой. Переменные-члены также определяют состояние объекта. Чтобы понять смысл этого термина, представим себе обычный выключатель света. Он может быть либо во включенном состоянии, либо в выключенном.

  *Прим. В. Шипкова: на самом деле я установил, что ещё есть третье состояние - "сломан".

  Большинство переменных характеризуется значительно большим количеством состояний, а не двумя, как выключатель, но это ничего не меняет, а только делает их более удобными в применении.

  О любом объекте, который содержит методы, говорят, что он функционален или характеризуется особым поведением. Второй термин звучит не официально, но зато лучше позволяет понять смысл использования объектов. Давайте сравним объект с котом, милым Васькой, который, быть может, сейчас трётся у ваших ног. Вы его погладили, и Васька замурлыкал. Стимулирующее воздействие вызывает ответную реакцию, что и является поведением. Поведение бывает разным и во многом зависит от Вашего обращения. Можно, например, дернуть кота за хвост. Так что если объект разрушит Вашу программу, то виноваты в этом будете Вы сами — не надо было дергать за хвост. Скажите ещё спасибо, что он Вас не поцарапал.

  У объектов, конечно, хвоста нет, но предостаточно альтернативных методов стимулировать их работу. В соответствии со сложившейся терминологией объектно-ориентированного программирования такая стимуляция называется направлением сообщения объекту. Это абстрактный термин, который на практике в Python сводится к простому вызову методов объекта.

  В Python некоторые объекты имеют состояние, но не имеют функциональности. Все числовые типы данных характеризуются только состоянием. Строки символов и наборы также обладают состоянием и, поскольку у них отсутствуют какие бы то ни было методы, являются объектами без поведения. Списки и словари характеризуются и состоянием, и функциональностью: list, append(item) и dict.keys() являются примерами методов, определяющих функциональность данных объектов. Наконец, модули, которые, как правило, не имеют никаких переменных-членов, являются примерами объектов с функциональностью, но без состояния. Кот с состоянием, но без поведения, был бы мертвым котом, но для объектов это допускается. Поэтому аналогию между объектом и котом можно проводить лишь до некоторой степени. В заключение можно лишь сказать, что наиболее интересными и яркими (как живые!) объектами являются те, которые характеризуются как состоянием, так и поведением.

  Мы возвратимся к свойствам объектов немного позже, после того как рассмотрим уже знакомые Вам переменные с другой стороны.

Переменные как ссылки на объекты

  Во многих языках программирования переменные представляют собой имена, ассоциированные с опредёленными ячейками памяти компьютера, в которых хранятся значения переменных. В Python всё реализовано иначе. Поскольку в Python переменные являются объектами, имя переменной ссылается на объект, а не на физическую ячейку памяти. Python берет на себя всю ответственность за управление распределением памяти в соответствии с его внутренними алгоритмами. Это очень важный момент, поскольку допускается иметь множество ссылок на один и тот же объект. Если объект изменится, то каждая переменная, которая ссылается на него, также изменится соответствующим образом. Не забывайте, что даже целые числа являются объектами. Python заранее резервирует опредёленный объём памяти, чтобы уменьшить время, необходимое для создания нового объекта. Каждый раз, когда создаётся новая целочисленная переменная, что в Python всегда сопровождается инициализацией, т.е. присвоением значения, то для первой сотни целых чисел Python ассоциирует новую переменную с соответствующим ей уже существующим объектом. Любая переменная, которой присваивается, например, значение 42, ссылается на один и тот же целочисленный объект. Создание нового целочисленного объекта происходит только в случае присвоения значения, выходящего за сотню, например 1 000 000. Никаких проблем с тем, что разные переменные ссылаются на один целочисленный объект, как правило, не возникает, поскольку невозможно изменить объект 1 на что-нибудь другое.

  Но другие объекты не отличаются таким постоянством. Помните, в предыдущих главах мы уже обсуждали отличия между изменяемыми и неизменяемыми (константными) объектами? Числа, строки символов и наборы являются константами, тогда как списки, словари и определяемые пользователем объекты можно изменять. При изменении целочисленной переменной в действительности изменяется её ссылка на объект, но не сам объект. Комплексные числа, хотя и имеют два компонента, каждый из которых может быть считан в отдельности, так же ссылаются на неизменяемые объекты, как и любая другая числовая переменная. Попробуйте выполнить с помощью своего интерпретатора следующие строки кода:

>>> i = 3 + 4j

>>> i.real 3.0

>>> i.imag 4.0

>>> i.real = 2.0 Traceback (innermost last):

File "<pyshell#4>", line 1, in ?

i.real = 2.0

TypeError: object has read-only attributes

>>>

  Вы получили сообщение об ошибке, поскольку попытались изменить константный объект, на который ссылается один из компонентов комплексного числа. Благодаря константности объектов, на которые ссылаются все числовые переменные, с их использованием в Python редко возникают какие-либо проблемы. Но этого нельзя сказать о списках, словарях и пользовательских объектах, непостоянство которых может приводить к неожиданным результатам. Рассмотрим один из примеров.

  В главе 7 Вы узнали, что функции могут иметь заданные по умолчанию значения параметров, которые вступают в силу в тех случаях, когда в вызове функции не представлены соответствующие аргументы. В качестве параметра функция может принимать список, и если никакой список не будет передан ей с аргументом, то, возможно, необходимо будет предусмотреть создание в функции пустого списка. В таком случае можно попытаться использовать следующее определение функции: def myfunc(l=[ ]):

  Это не самая лучшая идея, в чем мы убедимся, внимательно проанализировав выполнение следующей коротенькой программы, показанной в листинге 9.1.

Листинг 9.1. Программа spam.py

#! С : \PYTHON\PYTHON . ЕХЕ

def spam(n, l=[]):

  l.append(n)

 

х = spam(42)return 1

print x

у = spam(39)

print у

z = spam(9999, y)

print х, у, z

 

 

  После выполнения этой программы Вы увидите, что все три переменные, х, у, и z, ссылаются на один и тот же объект — список 1, который первоначально был пустым. Вероятно, Вы не ожидали такого результата, поскольку для каждой из переменных вызывали функцию spam() с разными аргументами. Предполагалось, что каждый раз будет создаваться новый список. Чтобы так и было, измените определение функции spam() так, как показано в листинге 9.2.

Листинг 9.2. Измененное определение функции spam()

def spam(n,il=[]):

  flist = il[:]

  flist.append(n)

  return flist

  Теперь программа будет работать правильно и возвратит три разных списка. Примененный нами оператор извлечения ([:]) создаёт копию пустого списка в случае, если список не будет передан с аргументом в вызове функции. Копия объекта по определению не является тем же самым объектом, что и оригинал. Вы можете непосредственно убедиться в этом с помощью оператора is, который возвращает значение 0, если два объекта не совпадают друг с другом, и 1, если операнды ссылаются на один и тот же объект. При использовании нашей первой версии функции spam() выражение х is у возвратит 1, а после изменения функции — 0. Проверьте это либо с помощью интерпретатора, либо редактора IDLE. Или можно просто изменить программу spam.py таким образом, чтобы она выводила на экран результат выражения х is у.

  С другой стороны, если в программе или модуле вне функции создаётся пустой список, а затем другая переменная, которая также содержит пустой список, как показано в следующем примере:

list1 = []

list2 = []

то в этом случае два списка будут ссылаться на разные объекты. Так происходит из-за того, что операторы присвоения в коде программы выполняются в Python по мере их обнаружения. Всякий раз, когда встречается определение функции, Python преобразовывает все инструкции функции в специальную форму, в так называемые байт-коды, которые хранятся в памяти для дальнейшего использования. Заданные по умолчанию параметры, перечисленные в списках параметров, обрабатываются таким же образом, как и обычные операторы присвоения, но выполнение этих операторов происходит только однажды при обнаружении определения функции, но не при её вызове. Таким образом, любой объект, требуемый параметром по умолчанию, создаётся только однажды, а при всех последующих вызовах функции происходит обращение только к этому объекту.

  В большинстве случаев особенность трактовки переменных в Python как объектов останется для Вас прозрачной и не повлияет на стиль программирования. Гораздо чаще ошибки возникают из-за неправильного применения переменных. Например, если Вы создадите переменную i, присвоив ей целочисленное значение, затем в программе присвоите ей строковое значение, а потом попытаетесь использовать переменную i в контексте, требующем только целое число, то возникнет ошибка. Но объектность переменных Python в данном случае не имеет к этому никакого отношения.

Объектно-ориентированное программирование

  Объектно-ориентированное программирование, или ООП, является стилем программирования с применением объектов. Мы уже видели, что объекты характеризуются состоянием и владеют функциональностью, но прежде чем приступить к применению их на практике, следует рассмотреть ещё одно свойство объектов — тождественность. Под этим термином подразумевается возможность отличить отдельный экземпляр объекта от всех остальных. Возьмём, к примеру, объекты чисел. Одна единица совершенно ничем не отличается от любой другой единицы. Именно по этой причине во всех местах, где в вашей программе необходимо значение 1, Python использует один и тот же объект, который создаётся при запуске интерпретатора. Но единица легко отличается от двойки, поскольку единица не является двойкой. Таким образом, базовую формулировку тождества можно выразить следующим образом: 1 -это 1, но 1 — это не 2.

  Хотя всегда можно отличить два несовпадающих объекта, многие из них могут характеризоваться одинаковым состоянием, или одинаковой функциональностью, или и тем и другим. Объекты, которые имеют общую функциональность и отличаются только состоянием, можно создавать с помощью единого шаблона, называемого классом. Классы можно представить себе в виде форм, используемых хозяйками для выпечки тортов или печенья. Представьте, что Вы раскатали тесто и собрались вырезать из него формочкой заготовки для печенья. При этом можно сказать, что Вы создаёте экземпляры печенья, каждый из которых представляет собой реальный объект. Классы сами по себе не являются реальными объектами, а только их образцами. Объекты, созданные с помощью класса, называются экземплярами класса, а процесс создания объекта называется реализацией. Из этого следует, что когда Вы даёте задание Python присвоить переменной какое-нибудь значение, что выполняется с помощью выражения типа а=42, то под этим подразумевается реализация объекта 42 в переменную под именем а. В данном примере переменную а можно представить себе в виде печенья, посыпанного 42 стружками шоколада, или в виде омлета из 42 яиц (кому что больше нравится). Печенье не имеет поведения, кроме того что оно съедается. Напротив, наш первый условный объект — кот — отличается довольно сложным поведением, благодаря чему может приносить радость или огорчать нас. Работать с объектами в компьютерных программах гораздо проще, чем убедить своего кота не лазить на стол.

  Давайте вернёмся к нашим формочкам для печенья. Каждый раз, когда Вы хотите изменить форму печенья, приходится начинать с изготовления новой формы. Вы облегчите свой труд, если удастся видоизменить уже имеющуюся у Вас форму (где-то вытянуть её, где-то подогнуть, закруглить), и можно приступать к выпечке нового печенья. Если изменить форму не удаётся, то приходится с нуля изготавливать новую, что сложнее, но тоже возможно. То же самое справедливо и для объектов. Вы можете определить класс, который характеризуется поведением и состоянием, и использовать его для создания необходимого количества экземпляров объектов. Если в дальнейшем Вы решите, что нужны другие объекты, в основном подобные предыдущим, но имеющие незначительные отличия в состоянии или функциональности, то для повышения эффективности своего труда можно начать с первоначального класса и изменить его структуру, добавив или удалив некоторые члены. Когда один класс получает часть своих членов, определяющих его функциональность и состояние, от другого класса, говорят, что новый класс наследует исходный. Наследование является ещё одним важным свойством объектов, которое обеспечивает возможность повторного использования ранее определённых классов, независимо от того, были ли они написаны лично Вами или другим программистом. Так, импортирование модулей в программы позволяет использовать функциональность, разработанную другим программистом и сохранённую в импортированном модуле. Хотя это действенная форма повторного использования программных блоков, избавляющая Вас от необходимости повторно переписывать ранее созданные коды функций, с формальной точки зрения этот процесс ещё не является наследованием. Чтобы разобраться в наследовании объектов, этому вопросу следует уделить больше времени. Поэтому давайте обсудим этот вопрос в следующих главах. Для общей информации сообщу Вам только, что в некоторых языках программирования наследование является единственным методом создания классов. Одним из таких языков является Java. В Java каждый определяемый пользователем класс должен наследоваться от некоторого другого класса. В Python, как и в C++, классы можно создавать с нуля, не наследуя их ни от каких других классов.

  Прежде чем мы завершим данную главу, имеет смысл рассмотреть ещё два важных свойства объектов — инкапсуляцию и полиморфизм. Названия путающие, но понять их не составит особого труда.

  Инкапсуляция является просто процессом определения интерфейса объекта и способа его выполнения. Раньше Вы уже использовали интерфейсы объектов, например функций. Интерфейсом функции являются способ её вызова и ожидаемый результат. Например, функция atoi() модуля string требует, чтобы при вызове ей передавали два аргумента: обязательно одну строку и при необходимости один целочисленный аргумент. Если поменять местами эти два параметра в вызове функции atoi(), интерпретатор выведет сообщение об ошибке. Таким образом, интерфейс функции atoi() опредёлен этими двумя параметрами и возвращаемым значением, которое всегда является целым числом. Объекты также имеют интерфейсы, хотя часто они значительно сложнее, чем у функций.

  Вторая часть инкапсуляции состоит в выполнении объекта. Когда функция string.atoi() вызывается в полном соответствии с её интерфейсом, то всё, что происходит между вызовом и возвращением значения, называется выполнением функции (или объекта). Другими словами, это весь тот набор инструкций, с помощью которых достигается требуемый результат. Как пользователя Вас совершенно не должно волновать, как именно функция string.atoi() выполняет свою работу. Вас интересует только то, как правильно запустить её и какой результат она возвратит.

  Однако не так все просто с инкапсуляцией. Любая функция, как правило, имеет только один интерфейс, видимый для пользователя. Определяемые пользователем объекты ведут себя несколько сложнее. Они содержат переменные-члены, определяющие их состояние, и методы, лежащие в основе их функциональности. Большинство языков программирования, поддерживающих классы, обладают средствами управления доступом (видимостью) к различным членам класса для пользователей. Это позволяет программистам скрывать некоторые члены класса от несанкционированного, как правило, ошибочного вмешательства пользователей, тогда как другие члены служат открытым интерфейсом класса. В таких языках, к которым Python не относится, инкапсуляция также включает открытие и закрытие доступа к членам класса. Обычно интерфейс класса делается открытым, а его выполнение — закрытым. Для новичков часто возникает проблема, что отнести к интерфейсу, а что к выполнению. Как показывает мой личный опыт, очень часто в классах, даже тех, которые были созданы профессиональными программистами, оказываются закрытыми именно те члены класса, которые как раз тебе сейчас и нужны.

  *Прим. В. Шипкова: на самом деле, защищённые члены класса могут пригодиться только в том случае, если Вы собираетесь заработать на программировании, т. е. пожить за счёт своих ближних. ;) [целевые заказы организаций я сюда не отношу, хотя всё ещё остаётся вопрос, сколько программист попросил]. На самом деле, на сопровождении программных продуктов можно заработать раза в 3 больше (не верите? почитайте про проект Зопи.)

  В Python ко всем членам класса, будь то переменные или методы, можно обращаться извне без всяких ограничений, лишь бы эти члены действительно присутствовали в классе. В Python не существует ни единого способа предотвратить "несанкционированное проникновение" в класс, чем он сильно отличается от C++, где программист может запретить даже просмотр имеющихся членов класса, не то что их использование и изменение. В языке Python используется совершенно иной подход к инкапсуляции, чем в других языках объектно-ориентированного программирования. В Python, чтобы открыть интерфейс, нужно сообщить пользователям, какие методы и переменные они могут вызывать или изменять, а чтобы закрыть некоторые члены, опять-таки необходимо сообщить в документации класса, что данные члены использовать и изменять крайне нежелательно для Вашего же блага. Python предполагает, что пользователи программных продуктов на этом языке будут людьми сознательными и грамотными, способными самостоятельно разобраться в нюансах кода и достаточно ответственными, чтобы не нарушать соглашения документации без объективных причин. Такой подход предоставляет ряд преимуществ, в частности даёт возможность пользователю более широкого использования приобретённых классов, а не только для решения узких задач, на которые их нацеливали авторы. В других языках использование закрытых членов класса невозможно без перепрограммирования кода источника с повторной компиляцией.

  В следующих нескольких главах Вы сможете разобраться на практических примерах, как правильно создавать объекты. Вероятно, тогда смысл инкапсуляции станет для Вас более понятным. Чтобы спроектировать эффективный объект, необходимо хорошо разобраться в контексте его использования. Раскрытие смысла термина контекст мы также оставим на следующие главы.

  С ключевой идеей, лежащей в основе полиморфизма, Вы также уже знакомы и неоднократно сталкивались с ней ранее. В Python каждый раз, когда нужно возвратить строковое представление переменной (например, для вывода на экран), можно воспользоваться функцией str(), символами обратного ударения `` или применить спецификатор форматирования 4s. Все указанные способы посылают идентичное сообщение объекту переменной, в ответ на что этот объект реагирует соответствующим образом. В результате всегда возвращается строка символов. В этом состоит смысл полиморфизма: в ответ на одно и то же сообщение объекты разных типов возвращают разные результаты. При создании собственного класса необходим самостоятельно разработать методы, ответственные за возвращение ответа на родовые (т.е. характерные для данного класса) типы сообщений. В последующих главах, по мере того как будет возрастать уровень Вашего понимания объектов и его членов, мы рассмотрим разные способы решения этой задачи.

  Я полагаю, что будет весьма поучительно сравнить следующие два листинга, в одном из которых определяется простой класс на языке C++ (листинг 9.3), а в другом — тот же класс на языке Python (листинг 9.4).

Листинг 9.3. Определение класса now (now.cpp) на языке C++

#include <stdio.h>

#include <time.h>

class now

{

 public:

   time_t t;

   int year;

   int month;

   int day;

   int hour;

   int minute;

   int second;

   int dow;

   int doy;

 

   now()

   {

     time(St);

     struct tm * ttime;

     ttime = localtime(St);

     year = 1900 + ttime->tm_year;

     month = ttime->tm_mon;

     day = ttime->tm_mday;

     hour = ttime->tm_hour;

     minute = ttime->tm_min;

     second = ttime->tm_sec;

     dow = ttime->tm_wday;

     doy = ttime->tm_yday;

   }

 };

 

main (int argc, char **argv)

{

  now x ;

  fprintf ( stdout, "The year is %d\n", x.year );

}

 

Листинг 9.4. Определение класса now (now.py) на языке Python

#!C:\PYTHON\PYTHON.EXE

import time

class now:

  def __init__(self):

    self.t = time.time()

    self.storetime()

 

  def storetime(self):

    self.year, \

    self.month, \

    self.day, \

    self.hour, \

    self.minute, \

    self.second, \

    self.dow, \

    self.doy, \

    self.dst = time.localtime(self.t)

 

  def __str__(self):

    return time.ctime(self.t)

 

n = now()

print "The year is", n.year

print n

S='n'

print S

 

  *Прим. В. Шипкова: просьба не обижаться любителям С++, но приведённые тексты программ - наглядно показывают, "ху из ху".

  В следующей главе мы подробно, буквально "по косточкам", разберем всё, что происходит в программе now.py, но в данный момент нам достаточно только подчеркнуть наиболее характерные отличия.

  Чтобы получить доступ из внешней программы к переменной year, в языке C++ её необходимо открыть с помощью ключевого слова public. В Python все члены класса являются общедоступными. (Эван Симпсон (Evan Simpson) no этому поводу сказал: "Python основан на доверии. В нём отсутствует механизм принудительного закрытия доступа для тех пользователей, которым в других языках программирования приходится тратить уйму своего времени, чтобы обойти эти запреты".) Другие ключевые слова C++ — private и protected — устанавливают соответственно закрытый и защищённый доступ к членам класса.

  Ещё одно отличие между опредёлениями класса now в C++ и Python, которое, несомненно, первым бросилось Вам в глаза, — это то, что в Python классы определяются значительно проще. Примите к сведению также следующий факт: после того как Вы ввели текст программы now.py, Вы можете сразу же выполнить eё. В C++ прежде чем выполнить программу now.cpp, необходимо предварительно скомпилировать её. Если же у Вас отсутствует компилятор C++, то Вам придётся либо приобретать его коммерческую версию, либо загрузить достаточно массивный пакет файлов бесплатной версии компилятора C++ из Internet. Как говорит в таких случаях Франк Стаяно (Frank Stajano): "Python всегда поставляется в комплекте с заряженными батарейками".

  *Прим. В. Шипкова: По всему Франк Стаяно - наш парень. :)

Резюме

  В этой главе Вы узнали, что такое объекты, что переменные в Python являются всего лишь только ссылками на объекты, какие базовые концепции лежат в основе объектно-ориентированного программирования.

  В следующей главе мы создадим простой класс, детально рассмотрим его работу, а также научимся "общаться" с объектами этого класса.

Практикум

Вопросы и ответы

  Приведите, пожалуйста, наглядный пример, раскрывающий суть полиморфизма.

  Вы можете относиться к полиморфным объектам так же, как к стае котов. На ласку каждая особь может реагировать по-разному, но если швырнуть в них палку, реакция будет однозначной.

  Наборы являются константными объектами и их нельзя изменять после создания. В то же время допускается, чтобы наборы содержали элементы разных типов, в том числе изменяемые объекты, например списки. Допускается ли в Python изменение списка, являющегося элементом набора?

  Да. Объекты, входящие в состав наборов, полностью сохраняют свои первоначальные свойства. Набор не может быть изменен после того, как был создан, но Вы можете вносить любые изменения в список, помещённый в набор. Не допускается только удалять полностью весь список, но Вы можете удалить все элементы этого списка.

  Если строки символов являются объектами, почему они не имеют свои собственные методы? Почему мы должны всегда импортировать модуль string?

  В Python искусственно поддерживается различие между базовыми, типами данных и пользовательскими объектами. Если допустить, чтобы все объекты имели методы, то это приведёт к тому, что пользователи, кроме всего прочего, смогут добавлять или изменять методы всех объектов. Такой подход поднимает целый пласт проблем, над разрешением которых сейчас работает Гуидо с коллегами из SIG (Special Interest Group — группа по интересам).

Вопросы и ответы

  1. Объекты должны иметь:

    а) Состояние.

    б) Функциональность.

    в) Самих себя.

    г) Тождественность.

  2. Концепции объектно-ориентированного программирования основаны на следующих понятиях:

    а) Предки, братья и сёстры, потомки, родственники по восходящей линии и просто объекты.

    б) Классы, объекты, наследование, инкапсуляция и полиморфизм.

    в) Время, пространство, деньги, стиль и класс.

    г) Класс, поведение, состояние, тождественность и печенье.

  3. Объекты являются:

    а) Совокупностью вещей.

    б) Реализациями классов.

    в) Скорее котами, чем печеньем.

    г) Откровениями Будды о природе программирования.

Ответы

  1. а, б, и г. Объекты могут иметь состояние и/или функциональность, но также должны характеризоваться тождественностью.

  2. б. Классы, объекты, наследование, инкапсуляция и полиморфизм — вот правильный ответ, хотя многие были бы не против включить в список и печенье.

  3. а, б, в и г. Все ответы правильные. Если кто-то хочет возразить против откровений Будды, давайте поспорим.

Примеры и задания

  Для ознакомления с более развёрнутым материалом по объектно-ориентированному программированию посетите раздел часто задаваемых вопросов на сервере http://www.cyberdyne-object-sys.com/oofaq2/. Не забывайте, что средства объектно-ориентированного программирования языка Python не отличаются такой же "закрытостью" и "засекреченностью", как в некоторых других языках.

  Загляните на информационную страницу группы SIG по Python, расположенную по адресу http://www.python.org/sigs/types-sig/. Информация, представленная здесь, поможет Вам разобраться с сутью некоторых проблем, которые могут возникнуть в случае устранения различий между базовыми типами данных и объектами, определяемыми пользователем.