Python на примерах. Практический курс по программированию [Алексей Николаевич Васильев] (pdf) читать онлайн

-  Python на примерах. Практический курс по программированию  [3-е издание] (и.с. На примерах) 17.87 Мб, 434с. скачать: (pdf) - (pdf+fbd)  читать: (полностью) - (постранично) - Алексей Николаевич Васильев

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

Васильев А. Н.

Python
на примерах
От простейших
конструкций
до работы со списками
и кортеж ам и

Включая
объ ектно -о риен ти ­
рованное
программ ирование
на Python

3-е
издание
Практический курс
по программированию

"Наука и Техника"
Санкт-Петербург

В асильев А . Н.

Python
на примерах
Практический курс
по программированию

3-е издание

"Наука и Техника"
Санкт-Петербург

УДК 004.43 ; Б Б К 32.973
ISBN 978-5-94387-781-0

Ва с и л ь е в а .

Н.

PYTHON НА ПРИМЕРАХ. ПРАКТИЧЕСКИЙ КУРС ПО ПРОГРАММИРОВАНИЮ.
3-Е ИЗДАНИЕ. — СПб.: Наука и Техника, 2019. — 432 с., ил.

Серия "На примерах"
В этой книге речь будет идти о том, как писать программы на языке
программирования, который называется Python (правильно читается как
Пайтон, но обычно название языка читают как питон, что тоже вполне
приемлемо). Таким образом, решать будем две задачи, одна из которых
приоритетная, а вторая, хотя и вспомогательная, но достаточно важная. Наша
основная задача, конечно же, изучение синтаксиса языка программирования
Python. Параллельно мы будем осваивать программирование как таковое,
явно или неявно принимая во внимание, что соответствующие алгоритмы
предполагается реализовывать на языке Python.
Большинство авторов книг в своих трудах рассматривают теоретические
основы языка и уделяют основное внимание базовому синтаксису языка,
не рассматривая при этом практическую сторону его применения. Эта же
книга старается восполнить недостаток практического материала, содержит
множество примеров с комментариями, которые вы сможете использовать
в качестве основы своих программных решений, изучения Python.
Материал книги излагается последовательно и сопровождается большим
количеством наглядных примеров, разноплановых практических задач и
детальным разбором их решений

Контактные телефоны издательства:
(812)412 70 26
Официальный сайт: www.nit.com.ru
© Васильев А. Н.
© Наука и Техника (оригинал-макет)

Содержание

Оглавление
Вступление

10

Глава 1.
Первые программы на языке
Python

43

Глава 2 .
Управляющие инструкции .................... 86
Глава 3.
Функции ...•••.•.••••.....•.•.•.•........•.•••.•.•..•...•.128
Глава 4.
Работа со списками и кортежами .....••.••175
Глава 5.
Множества, словари и текст...................217
Глава 6. Основы
объектно-ориентированного
программирования ..••••.....••••....•.•..•.•..•..261
Глава 7. Продолжаем знакомство с ООП .......309
Глава 8. Немного о разном ...••...••..•.•.•.•.••.......371
Заключение. О чем мы не поговорили ••••.•••••425

Содержание

Содержание
Вступление
Знакомство с Python....................................

10
11

Краткая история и особенности языка Python................................13
Немного о книге..................................................................................20
Программное обеспечение...........................................
21
Работа со средой PyScripter..............................................................34
Благодарности....................................................................................40
Обратная связь...................................................................................40

Главе 1. Первые программы на языке
Python ................................................. 43
Размышляя о программе.................................................................. 44
Пример простой программы............................................................ 46
Обсуждаем переменные................................................................... 51
Основные операторы.........................................................................56
Числовые данные.....................................................

73

Подключение модулей.......................................................................80
Тернарный оператор.......................................................................... 82
Резюме................................................................................................ 84

Глава 2 . Управляющие инструкции................ 86
Условный оператор............................................................................ 87
Оператор цикла w hile.........................................................................97
Оператор цикла for............................................................................106
Обработка исключительных ситуаций........................................... 116
Резюме...............................................................................................126

Глава 3. Ф ункции.•.......•.••.•.•.•.•••.•.••.•••....•..•.•..•128
Создание функции............................................................................129
Функции для математических вычислений................................... 133
Значения аргументов по умолчанию............................................ 135
Функция как аргумент.................................................................... 139
Рекурсия............................................................................................ 148

ta§

Содержание

Лямбда-функции............................................................................. 152
Локальные и глобальные переменные........................................... 157
Вложенные функции........................................................................ 160
Функция как результат функции..................................................... 163
Резюме...............................................................................................172

Глава 4. Работа со списками и кортеЖами ....175
Знакомство со списками..................................................................176
Основные операции со списками................................................... 184
Копирование и присваивание списков.......................................... 193
Списки и функции.............................................................................199
Вложенные списки........................................................................... 205
Знакомство с кортежами................................................................211
Резюме.............................................................................................. 214

Глава 5. Множества, словари и текст .•••.••••••••217
Множества.........................................................................................218
Словари............................................................................................. 235
Текстовые строки..............................................................................244
Резюме.............................................................................................. 257

Глава 6. Основы объектно-ориентированного
программирования ................................2 6 1
Классы, объекты и экземпляры классов........................................262
Конструктор и деструктор экземпляра класса............................. 271
Поле объекта класса........................................................................ 276
Добавление и удаление полей и методов..................................... 283
Методы и функции........................................................................... 287
Копирование экземпляров и конструктор создания копи и.......297
Резюме.............................................................................................. 307

Глава 7. Продолжаем знакомство с ООП........309
Наследование................................................................................... 310
Специальные методы и поля...........................................................325
Перегрузка операторов.................................................................. 352
Резюме.............................................................................................. 369

t
f l

Глава 8. Немного о разном .............................3 7 1
Функции с переменным количеством аргументов.......................372
Декораторы функций и классов...................................................... 380
Документирование и аннотации в функциях.................................390
Исключения как экземпляры классов............................................ 393
Итераторы и функции-генераторы................................................ 411
Резюме.............................................................................................. 423

З а кл ю ч е н и е ............................................*........*4 2 5
О чем мы не поговорили .••...•.•.....•.•.•.•....•.•.....42 5

Вступление

Введение

З н а ко м с тв о с Python
Во всем есть своя мораль,
уметь ее найти!

нужно только

Л . Кэрролл "Алиса в стране чудес"

В этой книге речь будет идти о том, как писать программы на языке про­
граммирования, который называется Python (правильно читается как пайтон, но обычно название языка читают как питон, что тоже вполне приемле­
мо). Таким образом, решать будем две задачи, одна из которых приоритет­
ная, а вторая, хотя и вспомогательная, но достаточно важная. Наша основ­
ная задача - конечно же, изучение синтаксиса язы ка программирования
Python. Параллельно мы будем осваивать азы программирования, явно или
неявно принимая во внимание, что соответствующие алгоритмы предпола­
гается реализовывать на языке Python.
Ш

На заметку
Даже если у читателя есть опыт программирования на других языках, не следует
относиться поверхностно или пренебрежительно к процессу построения алгорит­
ма программы. Правила хорошего тона в программировании подразумевают, что
написание программы начинается задолго до набора программного кода. Хорошо
взять лист бумаги и набросать общую схему того, как будет выполняться програм­
ма. А для этого процедуру решения большой и сложной задачи следует разбить
на последовательность простых действий. С одной стороны данный процесс уни­
версальный. С другой стороны, те задачи, которые мы назвали выше "простыми",
решаются посредством базовых команд или функций языка программирования,
на котором предполагается составлять программу. Поэтому обдумывая алгоритм,
полностью абстрагироваться от конкретных возможностей языка программирова­
ния вряд ли удастся. Учитывая же гибкость и эффективность языка программиро­
вания Python, следует признать, что алгоритмы даже для "классических" задач при
реализации на Python становятся проще и понятнее. Другими словами, даже если
читатель имеет опыт составления алгоритмов, знакомство с языком программи­
рования Python позволит ему посмотреть на многие знакомые вещи совершенно
по-иному.

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


Язык программирования Python - язы к высокого уровня, достаточ­
но "молодой", но очень популярный, который уже сейчас широко ис-

ш

Python

пользуется на практике и сфера применения Python постоянно рас­
ширяется.
Q

На заметку
По отношению к языкам программирования нередко применяют такие выраже­
ния, как язык высокого уровня, язык среднего уровня или язык низкого уровня. Эта
классификация достаточно условная и базируется на уровне абстракции языка.
Ведь язык, на котором разговаривают люди, немного отличается от того "языка",
который "понимают" компьютеры. Команда, написанная на простом человеческом
языке, будет совершенно неприемлемой для компьютера. Команда, готовая к ис­
полнению компьютером (машинный код), будет непонятной для большинства про­
стых смертных. Поэтому выбирать приходится между Сциллой и Харибдой. Про
языки, ориентированные на программиста, говорят, что это языки высокого уров­
ня. Про языки, ориентированные на компьютер, говорят, что это языки низкого
уровня. Промежуточная группа языков называется языками среднего уровня. Хотя
еще раз подчеркнем, что деление это достаточно условное.



Синтаксис языка Python минималистический и гибкий. Н а этом
языке можно составлять простые и эффективные программы.



Стандартная библиотека для этого языка содержит множество по­
лезных функций, что значительно облегчает процесс создания про­
граммных кодов.



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



Язык Python вполне удачный выбор для первого языка при обуче­
нии программированию.

Существуют и иные причины к тому, чтобы выучить язык программирова­
ния Python - возможно, даже весомее тех, что перечислены выше. О неко­
торых мы еще будем говорить (в контексте особенностей языка программи­
рования Python). Во всяком случае, здесь будем исходить из того, что чита­
тель для себя решение об изучении языка Python принял, или, по крайней
мере, проявляет интерес к этому языку программирования.
Ш

На заметку
Парадигма программирования - это наиболее общая концепция, которая опреде­
ляет фундаментальные характеристики и базовые методы реализации программ­
ных кодов. Например, парадигма объектно-ориентированного программирования
(сокращенно ООП) подразумевает, что программа реализуется через набор взаи­
модействующих объектов, которые, в свою очередь, обычно создаются на основе
классов. В рамках структурного программирования программа представляет со­
бой комбинацию данных и процедур (функций) для их обработки. Язык может под­
держивать сразу несколько парадигм. Так, языки Java и C# полностью объектно­

«9

Введение
ориентированные, поэтому для написания самой маленькой программы на этих
языках придется описать как минимум один класс. В языке С поддерживается па­
радигма структурного программирования, поэтому классов и объектов в языке С
нет. Зато они есть в языке C++. Последний поддерживает как парадигму объектноориентированного программирования, так и парадигму структурного программи­
рования. Как следствие, при работе с языком C++ классы и объекты можно исполь­
зовать, а можно и не использовать - в зависимости от потребностей программи­
ста и специфики решаемой задачи. Это же замечание относится к языку Python:
с одной стороны, при написании программы на языке Python у нас имеется воз­
можность прибегнуть к мощному арсеналу объектно-ориентированного програм­
мирования, а с другой стороны, часто бывают приемлемыми и методы структурно­
го программирования.

Существуют и другие, более утонченные концепции программирования.
Скажем, парадигма функционального программирования подразумевает,
что результат функции в программе определяется исключительно значе­
ниями аргументов, переданных функции, и не зависит от состояния внеш­
них (по отношению к ф ункции) переменных. Соответствующие функции
принято называть чистыми функциями, и они обладают рядом полезных
свойств, позволяющих существенно оптимизировать и ускорить вычисли­
тельный процесс. Эта концепция, как и ряд других, находят реализацию в
языке Python.
Далее обсудим некоторые важные моменты и "подводные камни", которые
могут встретиться на, местами трудном, но все же интересном и увлекатель­
ном пути освоения новых вершин в программировании.

К раткая и сто р и я и о со б е н н о сти язы ка

Python
Серьёзное отношение к чему бы то ни было в
этом мире является роковой ошибкой.

Л. Кэрролл "Алиса в стране чудес"

У языка Python есть автор - Гвидо ван Россум (G uido van Rossum). И хотя
в разработке и популяризации языка на данный момент успело поучаство­
вать много талантливых разработчиков, именно Гвидо ван Россум пожина­
ет заслуженные лавры создателя этого перспективного и популярного язы ­
ка программирования. Вообще же работа над языком началась в 80-х годах
прошлого столетия. Считается, что первая версия языка появилась в 1991
году. Касательно названия язы ка программирования Python, то формаль­
но это название рептилии. Соответственно, обычно в качестве логотипа ис­
пользуется милая (или не очень) змеючка типа "питон". И хотя практиче-

ш

Python

ски любое учебное или справочное пособие по языку Python содержит по­
вествование о том, что на самом деле Python - это не "питон", а название
юмористической передачи "Летающий цирк Монти Пайтона", для истории
это уже не важно.
Q

На заметку
Уместно вспомнить слова капитана Врунгеля: "Как вы яхту назовете, так она и по­
плывет". У языка программирования Python достаточно агрессивное название, и,
надо признать, это название себя оправдывает. Аргументом к такому утвержде­
нию может быть как гибкость и эффективность самого языка, так и та быстрота, с
которой он завоевал себе "место под солнцем" среди наиболее популярных язы­
ков программирования.

Язык Python бурно развивается. Этому способствует не только достаточно
удачная концепция языка, но также сформировавшееся сплоченное сооб­
щество разработчиков и популяризаторов языка. Немаловажен и тот факт,
что необходимое программное обеспечение, включая среды разработки, в
основной своей массе бесплатны. Все это дает основания рассматривать
Python в качестве одного из наиболее перспективных языков программи­
рования.
Н а сегодняшний день Python используется при разработке самых различ­
ных проектов, среди которых:


разработка сценариев для работы с Web и Internet-приложений;



сетевое программирование;



средства поддержка технологий H TM L и XML;



приложения для работы с электронной почтой и поддержки
In tern et-протоколов;



приложения для обслуживания всевозможных баз данных;



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



приложения с графическим интерфейсом;



создание игр и компьютерной графики,

и многое другое. Разумеется, охватить все эти темы в одной книге достаточ­
но сложно. Да мы и не ставим перед собой такой цели. Тем не менее, даже на
относительно небольшом количестве несложных примеров вполне можно
продемонстрировать элегантность и исключительную эффективность язы ­
ка Python. Этим, собственно, мы и займемся в основной части книги - то

«а

Введение

есть немного позже. Сейчас обсудим особенности и некоторые "техниче­
ские" моменты, которые важны для понимания основ программирования
на Python.
Язык Python относится к интерпретируемым языкам программирования,
что имеет определенные последствия. Формально то, что язы к программи­
рования интерпретируемый, означает, что программный код выполняется
с помощью специальной программы. Программа называется интерпрета­
тором. Интерпретатор выполняет программный код построчно (с предва­
рительным анализом выполняемого кода). Недостаток такого подхода со­
стоит в том, что, во-первых, ошибки выявляются фактически на этапе вы­
полнения программы и, во-вторых, скорость выполнения программы отно­
сительно невысокая. Поэтому нередко используется более сложная схема:
исходный программный код компилируется в промежуточный код, а уже
этот промежуточный код выполняется непосредственно интерпретатором.
В этом случае скорость выполнения программы увеличивается, но вместе с
ней увеличивается и запрос на системные ресурсы. Примерно по такой схе­
ме выполняется программный код, написанный на языке Python.
Ш

На заметку
Напомним, что помимо интерпретируемых, существуют компилируемые языки
(имеется в виду компиляция в машинный код). В этом случае исходный программ­
ный код компилируется в исполнительный (машинный) код. Исполнительный код
выполняется (обычно) под управлением операционной системы. Компилируемые
в исполнительный код программы характеризуются относительно высокой скоро­
стью выполнения.

Если мы хотим написать программу на Python, то для этого, как минимум,
понадобится набрать соответствующий программный код. Здесь возможны
варианты, но в принципе код набирается в любом текстовом редакторе, а
соответствующий файл сохраняется с расширением р у или pyw (для про­
грамм с графическим интерфейсом). При первом запуске (после внесения
изменений в программный код) создается промежуточный код, который за­
писывается в файл с расширением рус. Если после этого в программу изме­
нения не вносились, то при выполнении программы будет выполняться со­
ответствующий рус- файл. После внесения изменений в программу она при
очередном запуске перекомпилируется в рус- файл. Это общая схема. Нам,
на самом деле, она интересна исключительно в плане общего развития, хотя
на практике иногда бывает важно учитывать особенности выполнения про­
граммы, написанной на языке Python.
Как отмечалось выше, программный код можем набирать хоть в текстовом
редакторе. Но вот для выполнения такого программного кода понадобит­

т

Python

ся специальная программа, которая называется интерпретатором. Дру­
гими словами, для работы с Python на компьютер необходимо установить
программу-интерпретатор. Мы отдельно обсудим этот вопрос. Сейчас же
только заметим, что обычно используется не просто интерпретатор, а инте­
грированная среда разработки, которая, кроме прочего, включает в себя как
интерпретатор, так и редактор программных кодов.
Интерпретатор выполняет программу команда за командой. Поэтому в
принципе, если программа состоит из нескольких команд, ее можно органи­
зовать в виде файла с программным кодом, а затем "отправить" этот файл на
выполнение. Еще один вариант - "передавать" интерпретатору для выпол­
нения по одной команде. И тот, и другой режимы возможны и поддержива­
ются интерпретатором языка Python. Мы будем составлять и запоминать
программные коды в файлах - то есть используем "традиционный" подход.
Q

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

Поскольку язы к Python развивается интенсивно и от версии к версии в син­
таксис и структуру языка вносятся изменения, важно учитывать, для какой
версии языка Python составляется (и предполагается использовать) про­
граммный код. Особенно это важно с учетом того обстоятельства, что при
внесении изменений принцип обратной совместимости работает далеко не
всегда: если программный код корректно выполняется в более старых вер­
сиях языка, то совсем не факт, что будет выполняться при работе с более но­
выми версиями. Однако паниковать по этому поводу не стоит. Обычно про­
блема несовместимости кодов для разных версий связана с особенностями
синтаксиса некоторых функций или конструкций языка и достаточно лег­
ко устраняется.
Ш

На заметку
На момент написания книги актуальной является версия Python 3.3. Именно она
использовалась для тестирования примеров в книге. При появлении новых вер­
сий или стандартов языка, для обеспечения совместимости программных кодов,
имеет смысл просмотреть тот раздел справочной системы, в котором описывают­
ся нововведения. Сделать это можно, например, на официальном сайте поддерж­
ки языка Python www.python.org/doc/ в разделе с названием What’s New In Python
(в переводе означает Что нового в Python).

т

Введение

Но все это технические детали, которые хоть и важны, но все же не пер­
востепенны. А первостепенными для нас будут синтаксис языка Python и
его основные управляющие инструкции. И собственно здесь нас ждет мно­
го приятных сюрпризов.
Ш

На заметку
Особенно много сюрпризов будет для читателей, которые знакомы с такими язы­
ками программирования, как Java или C++, например. Но это, кстати, совсем не
означает, что новичков в программировании язык Python оставит равнодушными.
Просто те, кто изучал основы ООП (объектно-ориентированное программирова­
ние) и программирует на упомянутых языках, значительно расширят свой круго­
зор в плане методов и приемов программирования, а также в некотором смысле
им предстоит изменить свое представление о языках программирования.

Синтаксис языка Python более чем интересен. Во-первых, он прост, поня­
тен и нагляден. В некотором смысле его можно даже назвать по-спартански
лаконичным. Одновременно с этим программные коды, написанные на
Python, обычно легко читаются и анализируются, а объем программного
кода намного меньше, если сравнивать с аналогичными программами, на­
писанными на других языках программирования. В качестве иллюстрации
в листинге В. 1 приведен код программы на языке C++, в результате выпол­
нения которой в консольное окно выводится сообщение Hello, world!.
Листинг В .1. Программа на языке C + +
♦ in clu d e
u s in g namespace s td ;
i n t m a i n ( ){
cout

p r i n t {" В ы ч и с л е н и я з а к о н ч е н ы ! " )

& ■■
P r o je c ts

i

m я * ft

• B reakpoints
1 J



| N am e

t

*

< 1

C o m m a n d O u tp u t


l |



T ype

.

pi

,

72

Test R esults i N o tificatio n s f S yntax C h eck in g S tatu s ! И | P y th o n Shell
})}

» j

Run O u tp u t i

D eb u g g in g se ssio n h a s en d e d .
Н ачинаем вы числения!

Value

Значение

I

перем енной а •

4

Значение перем енной Ь -

12

Р езультат деления b /а

ш

3 .0

:

L m e c o tj

В ы числения закон чен ы !

+

W a tc h
... exam pies * chOO * Sim pieC alcs.py

l .

-

«

4

O u t p u t Cali Stack HTML
Ш Т -8

:

#

P y th o n

Рис. В.12. Результат, выполнения программы в среде Komodo IDE

Мы, тем не менее, будем пользоваться для тестирования примеров из книги
некоммерческую среду разработки PyScripter. Среда разработки PyScripter
удобная, простая и бесплатная. Установочные файлы можно свободно загру­
зить на странице https://code.google.eom/p/pyscripter в разделе D ow nloads.
Окно браузера, открытое на странице поддержки проекта PyScripter, пока­
зано на рис. В. 13.

ЕЯ

Введение

Рис. В .13. Страница поддержки проекта PyScripter

Окно среды разработки с программным кодом показано на рис. В. 14.

Рис. В. 14. Окно среды PyScripter с программным кодом

t

CD

Python

Ш

На заметку
Чтобы создать новый файл с программой, выбираем команду Новый в меню
Файл, а чтобы открыть уже существующий файл выбираем команду Открыть из
того же меню.

Д ля запуска программы на выполнение выбираем в меню Пуск команду
Пуск (рис. В. 15) или щелкаем кнопку с зеленой стрелкой на панели инстру­
ментов.
На рис. В. 16 показан результат выполнения программы.

Рис. В. 15. Запуск программы на выполнение в среде PyScripter

Результат отображается во внутреннем окне Интерпретатор Python, и это
достаточно удобно.
Ш

На заметку
При желании во внутреннем окне Интерпретатор Python можно выполнять от­
дельные команды: инструкции для выполнения вводятся после символа » > .

Введение

Рис. В. 16. Результат выполнения программы в среде PyScripter

Поскольку в ниши планы входит широкое использование среды разработ­
ки PyScripter для работы с программными кодами, далее мы более деталь­
но обсудим некоторые особенности данного приложения. Также отметим,
что если читатель в силу каких-то объективных или субъективных причин
предпочтет другую среду разработки (в том числе и одну из перечислен­
ных выше) - нет никаких проблем. Правда, на страницах книги отсутству­
ет возможность дать описание всех (или даже некоторых) наиболее попу­
лярных сред разработки для Python: книга, все-таки, посвящена язы ку про­
граммирования, а не программному обеспечению. Да и большинство пред­
лагаемых утилит для работы с программными кодами Python обычно про­
сты в использовании, универсальны в плане методов и приемов работы с
ними, а также интуитивно понятны. Хочется верить, что читатель в случае
надобности сам сможет справиться с освоением необходимого программно­
го обеспечения.

--О

Python

Работа со ср е д о й P y S c rip te r
Будь проклят тот день, когда я сел за баран­
ку этого пылесоса!

из к/ф "Кавказская пленница"

Сразу оговоримся, что полностью описывать приложение PyScripter мы
не будем: во-первых, возможности такой нет, а, во-вторых, необходимости,
если честно, тоже. Поэтому мы остановимся лишь на тех настройках и ре­
жимах, которые критичны и будут (или могут быть) полезны читателю в
процессе работы над материалом книги (имеются в виду в первую очередь,
конечно, программные коды, которые рассматриваются в книге).
Прежде всего, стоит обратить внимание, что интерфейс среды разработ­
ки PyScripter поддерживает различные языки, в том числе и русский. На
рис. В. 17 показано окно приложения PyScripter, в котором используется
русскоязычный интерфейс.

Рис. В. 17. Окно приложения PyScripter с русскоязычным интерфейсом и шаблонным
кодом в окне редактора кодов

в ш

........

#

Введение

Ш

На заметку
По умолчанию при запуске приложения PyScripter во внутреннем окне редакто­
ра кодов для нового, автоматически созданного (но еще не сохраненного) файла
предлагается шаблонный код, как это можно видеть на рис. В. 17. Этот шаблонный
код можно удалить и ввести собственный. Также пользователь может изменить на­
стройки приложения, в том числе и содержимое шаблонного кода.

Если мы хотим перейти к англоязычному интерфейсу, то в меню Вид следу­
ет выбрать подменю Язык, а в этом подменю - команду Английский, как по­
казано на рис. В. 18.

Рис. В. 18. Переходим к англоязычному интерфейсу для приложения PyScripter

В результате интерфейс приложения PyScripter станет англоязычным. Ч то­
бы вернуться обратно к русскоязычному интерфейсу, в меню View в подме­
ню Language выбираем команду Russian (рис. В. 19).
Хочется верить, что такие стандартные процедуры, как создание, сохране­
ние, открытие и закрытие рабочего документа (файла с программой) у чи­
тателя проблем не вызовут. Вместе с тем обращаем внимание на достаточно
полезную утилиту: внутреннее окно Обзор файлов, непосредственно в ко­
тором можно в системе каталогов выбрать тот документ, с которым собира­
емся работать (рис. В.20).

fa

Python

Рис. В. 19. Окно приложения PyScripter с англоязычным интерфейсом

шяш

Рис. В.20. Выбрать нужный файл с программным кодом можно
непосредственно в окне приложения PyScripter

Введение

Среда разработки PyScripter достаточно гибкая в плане настроек. Н а­
пример, чтобы настроить параметры редактора кодов (такие, скажем, как
шрифт или режим выделения синтаксических конструкций) в меню Ин­
струменты в подменю Параметры выбираем команду Свойства редактора,
как показано на рис. В.21.

Рис. В.21. Переход в режим настройки параметров редактора программных кодов

В результате откроется диалоговое окно с названием Редактор свойств, в
котором, собственно, и выполняются настройки (рис. В.22).
Одно весьма важное замечание касается программных кодов, в которых ис­
пользуется кириллический текст. Такие файлы нужно сохранять в "пра­
вильной" кодировке - иначе при последующем открытии в том месте, где
был кириллический текст, появятся "каракули". В частности, рекоменду­
ется перед сохранением ф айла в меню Редактировать в подменю Ф ор­
мат файла установить кодировку UTF-8 (рис. В.23).

?

ИЯ1

Python

Рис. В.22. Окно настройки параметров редактора программных кодов

Рис. В.23. При работе с кириллическим текстом для корректной работы
необходимо файлы сохранять в "правильной" кодировке

m

Введение

Также ранее мы отмечали, что используемые по умолчанию шаблоны про­
граммных кодов, предлагаемые пользователю в различных ситуациях (при
создании нового документа) можно изменить (отредактировать). Полезны­
ми в этом случае будут команды подменю Параметры в меню Инструмен­
ты. Н а рис. В.24 показано диалоговое окно Файлы шаблонов, с помощью
которого редактируются шаблоны.

Рис. В.24. Окно настройки шаблонов

Характеризуя ситуацию в целом, следует сказать, что приложение
PyScripter настраивается довольно просто, так что на практике проблем с
настройкой обычно не возникает даже у неподготовленных пользователей.
В случае крайней необходимости, разумеется, всегда можно обратиться к
справочной системе среды разработки PyScripter.
Ш

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

Если все же возникнут непредвиденные проблемы с программным обеспе­
чением, напомним, что на худой конец программный код можно набрать

f t f li

Python

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

Благодарности
А что передать мой король?
- Передай твой король мой пламенный при­
вет!
-

из к/ф "Иван Васильевич меняет про­
фессию"

Автор выражает искреннюю признательность сотрудникам издательства
"Наука и Техника ", благодаря слаженной и профессиональной работе кото­
рых книга стала лучше и интересней. Особая благодарность - Марку Финкову, при поддержке которого уже реализованы интересные проекты и, хо­
чется верить, много новых еще будет реализовано.
Примеры и задачи из книг по программированию обычно проходят апроба­
цию на терпеливой, но требовательной аудитории - студентах и слушателях
курсов. Им - отдельное спасибо. Ведь непосредственное общение с теми, кто
учится программированию, дает бесценный опыт для преподавателя, и это,
естественным образом, находит свое отражение в книгах.
Огромнейшей благодарности заслуживают читатели, которые присылали и
присылают письма со своими критическими замечаниями и предложени­
ями. Благодаря их неравнодушному отношению воплощены в жизнь мно­
гие замечательные идеи. От уважаемого читателя всецело зависит, будет ли
продолжен этот процесс.

Обратная связь
Я могу пронизать пространство и уйти в
прошлое.

из к/ф "Иван Васильевич меняет профес­
сию"

Свои пожелания, замечания и предложения читатели могут направлять
по электронной почте alex@vasilev.kiev.ua или vasilev@univ.kiev.ua. Книги в
первую очередь пишутся для читателей. Поэтому крайне важно знать, что в
ш

Введение

книге удалось, а что - нет. Конструктивная критика и обратная связь с чита­
телями - очень действенные инструменты, KOfopbie позволяют двигаться в
правильном направлении.
Некоторая полезная информация по этой книге и другим книгам автора
представлена на сайте www.vasilev.kiev.ua. Если же нужной читателям ин­
формации там нет, но технически ее размещение возможно, имеет смысл на­
писать письмо с предложением добавить на страницу недостающие сведе­
ния - адреса электронной почты приведены выше.

?

-cm

Глава 1
Первые программы
на языке Python

Python

- Паки, паки... иже херувимы! Ваше сиятель­
ство, смилуйтесь. Между прочим, Вы меня
не так поняли.
- Да как же тебя понять, коль ты ничего не
говоришь?

- Языками не владею, Ваше благородие.

из к/ф "Иван Васильевич меняет профес­
сию"

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

На заметку
Нередко программы, написанные на языке Python, называют сценариями. Мы не­
сколько отойдем от традиции и к термину сценарий прибегать не будем.

Начнем мы с того, что обсудим общие принципы организации программно­
го кода, написанного на Python.

Р азм ы ш ляя о программе
Это же вам не лезгинка, а твист!

из к/ф "Кавказская пленница ".

Программа в Python - это последовательность команд. Никаких специаль­
ных инструкций для формального обозначения начала или окончания кода
программы использовать не нужно. Таким образом, при написании про­
граммы мы размещаем команды одна за другой - в том порядке, как они
должны выполняться. Команда помещается в начале строки. Отступов де­
лать не нужно. В одной строке обычно по одной команде - то есть каждая ко­
манда в новой строке."

car

Глава 1. Первые программы на Python

Q

На заметку
В данном случае речь идет о "простых" командах, наподобие команды вывода в
консольное окно текстового сообщения. Как отмечалось выше, эти команды раз­
мещаются в начале строки, пробел перед ними не ставится, и в конце команды
тоже ничего ставить не нужно. Немного позже мы познакомимся с управляющими
инструкциями - такими, как оператор цикла и условный оператор. В этих синтакси­
ческих конструкциях с помощью отступов (пробелов) выделяются составные части
выражения для оператора (блок команд тела оператора). Но об этом мы погово­
рим позже. На данный момент важно запомнить, что пробел в языке Python -важ­
ный элемент с точки зрения синтаксиса и использовать его следует с крайней
осторожностью. Лишний пробел может вызвать ошибку.

В принципе, это все, что нам пока необходимо знать о структуре програм­
мы для того, чтобы приступить к написанию программного кода. Со време­
нем мы познакомимся с различными синтаксическими конструкциями, ко­
торые вносят в программный код большую интригу и делают процесс про­
граммирования интересным и в некоторых случаях непредсказуемым. Но
это будет позже.
Что важно сейчас? Сейчас важно понимать, что команды в программе
должны быть корректными как минимум с точки зрения синтаксиса язы ­
ка Python. Но даже формально правильные команды не гарантируют пра­
вильности выполнения программы. Обычно программы пишутся для реше­
ния той или иной задачи. Задача решается в соответствии с определенным
алгоритмом, который в подавляющем большинстве случаев разрабатывает
и реализует программист. То есть в программе реализуется некоторый ал­
горитм. Если этот алгоритм неправильный, то и программа выдаст не тот
результат, которого от нее ожидают. Поэтому по-хорошему написание про­
граммы начинается с выработки алгоритма, а уже затем этот алгоритм реа­
лизуется в виде команд программы. Мы для простоты исходим из предпо­
ложения, что алгоритм имеется, и его лишь необходимо "перевести" на язык
инструкций Python.
Практически любая программа оперирует с некоторыми данными. Это мо­
жет быть как реально большая база данных, так и одно-единственное значе­
ние - принципиальной разницы здесь нет. Важно то, что данные в програм­
ме должны как-то сохраняться. Мы пока что будем "сохранять" данные с
помощью переменных. В отношении переменных важны два момента:


у переменной есть имя (или название);



переменная ссылается на некоторое значение.

В принципе, значение, на которое ссылается переменная, мы можем:


прочитать;
• ш

Python



изменить.

И в том, и в другом случае мы используем, в контексте команды, имя пере­
менной. Поскольку в Python тип переменной явно не указывается (он опре­
деляется по значению, на которое ссылается переменная), то предваритель­
но объявлять переменные не нужно. Просто при первом использовании пе­
ременной ей сразу присваивается значение.
Ш

На заметку
Обычно, когда объясняют назначение переменных, сравнивают их, например, с
корзиной или банковской ячейкой. Значение переменной при такой аналогии это то, что находится в корзине/ячейке. Пока что мы можем думать о переменной
именно так. Тем не менее, в Python дела с переменными обстоят несколько иначе.
Технически переменная содержит адрес в области памяти, где хранится значение,
отождествляемое со значением переменной. Тем не менее очень часто (в простых
случаях) внешний эффект такой, как если бы переменная реально содержала свое
значение - как корзина или банковская ячейка. Пока что механизм хранения зна­
чений переменных не важен. Немного позже, когда данный вопрос станет актуаль­
ным, мы расставим все точки над "Г.

Для начала мы рассмотрим небольшую программу. В ней, кроме несколь­
ких простых команд, будут использованы комментарии. Комментарий - это
текст, предназначенный для программиста. Интерпретатором комментарий
игнорируется. Для создания комментария используют символ #. Все, что
находится справа от символа #, является комментарием.

П рим ер п р о сто й п р о гр а м м ы
Баранов - в стойло, холодильник - в дом.

и зк/ф "Кавказская пленница"

В программе, которую мы рассмотрим далее, реализуется следующий алго­
ритм:


выводится текстовое приветствие с просьбой к пользователю ука­
зать имя;



после того, как пользователь вводит имя, оно считывается и записы­
вается в переменную;



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

Для вывода сообщений в консольное окно используется функция p r i n t (),
а для считывания введенного пользователем текста (имя пользователя) ис­
пользуем функцию i n p u t ( ). Также в программе есть комментарии. П ол­
ный код программы приведен в листинге 1. 1.

еж-

Глава 1. Первые программы на Python

Листинг 1.1. Программа с вводоми выводомданных
# Выводится сообщение
p r i n t ("Давайте познакомимся!")
# Считываем в в е д е н н о е п о л ь з о в а т е л е м з н а ч е н и е .
# Р е з у л ь т а т з а п и с ы в а е т с я в п е р е м е н н у ю паше
n a m e = i n p u t ( " К а к В а с з о в у т ? ")
# Выводится н о в о е сообщение
p r i n t ("Добрый д е н ь , " , n a m e + " !")

Поскольку это первая наша "официальная" программа, исследуем вопрос
о том, как она будет выполняться в среде PyScripter и IDLE (на практике
между процессом выполнения программы в этих средах имеются некото­
рые отличия). Н а рис. 1.1 показано окно среды PyScripter с программным
кодом (перед запуском программы на выполнение).

Рис. 1.1. Окно среды PyScripter перед началом выполнения программы

В процессе выполнения программного кода сначала в окне интерпретатора
появляется первое сообщение, а затем открывается окно с полем ввода. Пе­
ред полем ввода отображается текст К а к В а с з о в у т ? , а в поле ввода мы
указываем имя пользователя (рис. 1.2 ).

«а

Python

Рис. 1.2. В процессе выполнения программы в среде PyScripter появляется
окно с полем ввода

После щелчка на кнопке ОК в окне ввода, получаем результат, как на рис. 1.3.

Рис. 1.3. Результат выполнения программы в среде PyScripter

Ш

Глава 1. Первые программы на Python

Таким образом, при работе со средой PyScripter ввод выполняется в отдель­
ное окно, которое отображается автоматически. Несколько иначе обсто­
ят дела при использовании среды IDLE. Н а рис. 1.4 показано окно среды с
программным кодом перед началом выполнения программы.

Рис. 1.4. Окно среды IDLE перед началом выполнения программы

После запуска программы на выполнение в окне интерпретатора появляет­
ся первое сообщение и, в новой строке, фраза К а к В а с з о в у т ? , после ко­
торой пользователем выполняется ввод текста, как это показано на рис. 1.5.

Рис. 1.5. В процессе выполнения программы в среде IDLE ввод текста
выполняется в консольном окне

Ввод пользователя подтверждается нажатием клавиши < E nter> . Результат
выполнения программы представлен на рис. 1.6 .

*

C l

Python

Рис. 1.6. Результат выполнения программы в среде IDLE

Таким образом, результат выполнения программы будет следующим (ж ир­
ным шрифтом выделен ввод пользователя):
Результат выполнения программы (из листинга 1 .1 )
Давайте познакомимся!
К а к В а с з о в у т ? Алексей Васильев
Добрый д е н ь , А л е к с е й В а с и л ь е в !

Ш

Назаметку
Если программа выполняется в среде IDLE, результат будет точно таким, как пока­
зано выше (разумеется, с учетом того, какое значение ввел пользователь). В сре­
де PyScripter вторая строка отсутствует. Хотя мы предполагаем использовать сре­
ду PyScripter, результат для тех программ, в которых пользователем вводятся дан­
ные, будем приводить в виде, как для среды IDLE.

Теперь рассмотрим более детально программный код, выполнение которого
приводит к такому результату (см. листинг 1 . 1):


Все, что начинается с символа #, является комментарием и на ре­
зультат выполнения программы не влияет.



Командой p r i n t ( " Д а в а й т е п о з н а к о м и м с я ! ") в окне интерпре­
татора выводится сообщение, определяемое аргументом функции
p r i n t ().



т

В команде n a m e = i n p u t ( " К а к В а с з о в у т ? ") вызывается функ­
ция i n p u t () с текстовым аргументом, а результат вызова функции
записывается в переменную паше. Как следствие в окне интерпре­
татора (окне с полем ввода) появляется текст, переданный аргумен­
том функции i n p u t (). То значение, которое пользователь ввел в

Глава 1. Первые программы на Python

окне интерпретатора (поле ввода диалогового окна) возвращается
как значение (результат) функции i n p u t (). Это и есть значение
переменной паше.


Ш

Командой p r i n t ( " Д о б р ы й д е н ь , " , паш е+" ! ") в окне интерпре­
татора выводится еще одно сообщение, которое получается объеди­
нением (конкатенацией) текста Д о б р ы й д е н ь ,, значения перемен­
ной паше и восклицательного знака.
На заметку
В последней команде у функции print () два аргумента: "Добрый день," и
паше+"!". И первый, и второй аргументы - текстовые. Они выводятся в одной
строке друг за другом, причем между ними автоматически по умолчанию добав­
ляется пробел. Что касается второго аргумента, то формально он представлен как
"сумма" текстовой переменной name (точнее, переменной с текстовым значени­
ем) и текста " ! ". Если операция сложения применяется к текстовым значениям,
результатом является текст, получающийся объединением соответствующих тек­
стовых значений. Обратите внимание, что пробел в этом случае не добавляется.
Чтобы не запутаться с добавлением или не добавлением пробела можно пользо­
ваться таким правилом: функция print () печатает свои аргументы через пробел,
а при объединении строк пробел не добавляется. Ну и, разумеется, читатель уже
заметил, что текстовые значения указываются в двойных кавычках. Это не един­
ственный способ выделения текста в Python (можно, например, использовать оди­
нарные кавычки). Мы в основном будем заключать текстовые значения (литералы)
в двойные кавычки.

Далее мы более подробно обсудим некоторые особенности работы с пере­
менными в Python.

О бсуж д а е м пе р е м е н н ы е
Бывают такие случаи, когда неплохо и соврать,

из к/ф "Ирония судьбы или с легким паром "

Мы уже имели дело с переменными. Н о это было скорее "шапочное" зна­
комство. Здесь мы уделим переменным немного больше внимания. Надо
сказать, они того заслуживают. Тем более что в Python переменные доста­
точно специфичны.
В первую очередь, что касается названия переменных: в принципе, это мо­
жет быть практически любое имя (комбинация букв, цифр и символов под­
черкивания), которое не совпадает ни с одним из ключевых слов Python.
Список ключевых слов Python представлен в таблице 1.1.

*

03

Python

Таблица 1 .1 . Ключевые слова Python
and
as
assert
break
class
continue

del
elif
else
except
F alse
finally

from
global
if
im port
in
is

None
nonlocal
not
or
pass
raise

def

for

lam bda

retu rn

True
try
w hile
w ith
yield

Ключевые слова не могут быть модифицированы, и попытка использовать
переменную с соответствующим именем приведет к ошибке.
Ш

На заметку
Заметьте, что в данном случае мы не обсуждаем назначение ключевых слов. Спи­
сок ключевых слов приведен исключительно для того, чтобы читатель знал, как не
следует называть переменные.

Помимо ключевых слов, в Python существует множество встроенных иден­
тификаторов - таких, например, как названия встроенных функций (список
встроенных идентификаторов приведен в таблице 1.2 ).
Таблица 1 .2 . Встроенные идентификаторы Python
A rithm eticE rror
A ssertionE rror
A ttrib u teE rro r
B aseE xception
B lockinglO E rror
B rokenP ipeE rror
B ufferE rror
B ytesW arning
C hildP rocessE rror
C onnectionA bortedE rror
C onnectionE rror
C onnectionR efusedE rror
C onnectionR esetE rror
D eprecationw arning
EO FError
E llip sis

т

S yntaxE rror
S yntaxw arning
System E rror
S ystem E xit
T abE rror
T im eoutE rror
True
T ypeError
U nboundLocalError
U nicodeD ecodeE rror
U nicodeE ncodeE rror
U nicodeE rror
U nicodeT ranslateE rror
U nicodeW arning
U serW arning
V alueE rror

float
form at
frozenset
g etattr
globals
h asattr
hash
help
hex
id
inp u t
in t
isin stan ce
issu b class
iter
len

Глава 1. Первые программы на Python

E nvironm entError
E xception
F alse
F ileE x istsE rro r
F ileN otF oundE rror
F loatingP ointE rror
F utureW arning
G eneratorE xit
IO Error
Im portE rror
Im portw arning
IndentationE rror
IndexE rror
In terru p ted E rro r
IsA D irectoryE rror
K eyError
K eyboardlnterrupt
LookupError
M em oryError
N am eError
None
N otA D irectoryE rror
N otIm plem ented
N otlm plem entedE rror
O SError
OverflowError
PendingD eprecationW arning
P erm issionE rror

W arning
W indowsError
Z eroD ivisionE rror
build class
debug
doc
im port
loader
name
package
abs
all
any
ascii
bin
bool
b ytearray
bytes
callab le
chr
classm ethod
com pile
com plex
copyright
cred its
d elattr
d iet
d ir

ProcessL ookupE rror
R eferenceE rror
R esourcew arning
R untim eE rror
R untim ew arning

divm od
enum erate
eval
exec
exit

S to p lteratio n

filter

Ш

licen se
lis t
lo cals
map
max
memoryview
min
next
ob ject
oct
open
ord
pow
p rin t
property
quit
range
repr
reversed
round
set
setattr
slice
so rted
statiem eth o d
str
sum
super
tu p le
type
vars
zip

На заметку
В таблице 1.2 приведены в основном названия встроенных классов исключений и
названия встроенных функций. Вообще, узнать, какие идентификаторы на данный
момент задействованы в работе интерпретатора (определены в программе) мож­
но с помощью функции d i r ( ) . Если вызвать эту функцию без аргументов (скажем,
воспользоваться командой p r i n t ( d i r ( ) ) ) , получим список (набор) идентифика­
торов (имен), которые уже "заняты". Но в этот список не будут входить названия
встроенных функций и других встроенных идентификаторов. Получить доступ к

т

Python
списку встроенных идентификаторов можно через модуль built ins. Для этого не­
обходимо командой import builtins подключить модуль, а затем вызвать функ­
цию dir () с аргументом builtins (для отображения списка встроенных иденти­
фикаторов в области вывода используем команду print (dir (builtins))). Под­
робнее о подключении модулей рассказывается в конце этой главы.

Формально мы можем задействовать переменную с именем, совпадающим с
тем или иным идентификатором. Однако это может неожиданным образом
повлиять на выполнении программы. Подобные ситуации считаются дур­
ным тоном в программировании, и их следует избегать.
Имя переменной не может начинаться с цифры. Также крайне осторожно
следует использовать подчеркивание в начале и конце имени переменной
(переменные с начальным подчеркиванием и с двойным подчеркиванием в
начале и конце имени обрабатываются по специальным правилам).
О том, что для переменных не нужно явно объявлять тип, мы уже знаем.
Однако это вовсе не означает, что типов данных в Python нет. Другое дело,
что такое понятие как тип отождествляется именно с данными, а не с пере­
менной.
Ш

На заметку
Вообще тип данных важен, по меньшей мере, с двух позиций: тип данных опреде­
ляет объем памяти, выделяемый для хранения этих данных, а также находится в
тесной связи с теми операциями, которые допустимы с данными. Во многих язы­
ках программирования переменные объявляется с указанием типа. Вот в этом слу­
чае переменную удобно представлять как корзину, на которой написано название
переменной, а значение переменной - то, что внутри корзины. Таких корзин в про­
грамме может быть много. Каждая корзина отождествляется с какой-то конкрет­
ной переменной, в каждой корзине что-то "лежит" (значение соответствующей пе­
ременной). Размер корзины определяется типом переменной, с которой мы эту
корзину отождествляем. Когда мы считываем значение переменной, то просто
"смотрим", что лежит в корзине. Когда меняем значение переменной, то вынима­
ем из корзины то, что в ней было, и помещаем туда новое содержимое.

В Python все несколько иначе. Переменные не имеют типа. Но тип есть у данных,
которые "запоминаются" с помощью переменных. Поэтому концепция обычных
корзин теряет свою актуальность. Актуальной становится концепция корзин, к ко­
торым привязаны веревочки. Переменная - это веревочка с биркой (название пе­
ременной), а корзина -это данные, на которые ссылается переменная. То есть те­
перь название переменной - это не бирка на корзине, а бирка на веревке. На кор­
зинах бирок нет. Как и ранее, размер корзины определяется характером ее содер­
жимого (тип данных). Но доступ к корзине у нас есть только через веревочку. Вере­
вочки все одинаковые и отличаются лишь названием на бирке. Что мы можем сде­
лать в такой ситуации: мы можем потянуть за веревочку и посмотреть, что находит­
ся в корзине, к которой привязана веревочка. Это аналог считывания значения пе­
ременной. Также мы можем поменять содержимое корзины или вообще отвязать

Глава 1. Первые программы на Python
от веревочки корзину и привязать веревочку к другой корзине. Это аналог измене­
ния значения переменной. Более того, мы можем привязать несколько веревочек
к одной и той же корзине. На языке программирования это означает, что несколько
переменных ссылаются на одно и то же значение. И такое в Python тоже возможно.

В Python переменные ссылаются на данные, а не содержат их, как в некото­
рых других языках программирования. Каждая переменная "помнит", в ка­
ком месте в памяти находится некоторое значение. Это значение мы ото­
ждествляем со значением переменной (хотя на самом деле значением пере­
менной является адрес ячейки памяти с соответствующими данными). Тем
не менее, когда мы обращаемся к переменной, то выполняется автоматиче­
ский переход по ссылке на данные, так что внешне иллюзия такая, как если
бы переменная реально содержала определенное значение. Какие следствия
из всего сказанного?
Во-первых, описанный выше механизм нередко является краеугольным
камнем в понимании того, что происходит при выполнении программного
кода и, в частности, при присваивании значений переменным.
Во-вторых, совершенно очевидно, что одна и та же переменная на разных
этапах выполнения программы может ссылаться не просто на разные зна­
чения, но на значения разных типов (например, сначала ссылаться на текст,
а затем на число).
В-третьих, хотя непосредственно у переменных типа нет, мы можем по­
лучить доступ через переменную к значению, на которое она ссылается, и
узнать его тип. Осталось лишь выяснить, какие типы данных вообще воз­
можны в Python.
Ш

На заметку
Для определения типа значения, на которое ссылается переменная, можно ис­
пользовать функцию ty p e ( ) . Переменная указывается аргументом функции.

Наш опыт работы с переменными пока что ограничивается текстовыми и
числовыми значениями (пример во Вступлении). Так вот, текстовые значе­
ния относятся к типу, который называется s t r . Числовые значения отно­
сятся к типу i n t (если это целые числа) или к типу f lo a t (если это действи­
тельные числа в формате значения с плавающей точкой). Приятной неожи­
данностью для любителей математических расчетов будет то, что в Python
есть встроенная поддержка для комплексных чисел. Д ля этих целей ис­
пользуются значения типа c o m p le x . Некоторые примеры несложных чис­
ловых расчетов приведены далее в этой главе.

*

H

I

Python

Значения логического типа могут принимать два значения (истина или
ложь). В Python логический тип обозначается как b o o l. С логическими вы­
ражениями мы познакомимся, когда приступим к изучению условных ин­
струкций и инструкций цикла.
Несколько позже мы познакомимся со списками, которые играют роль мас­
сивов в Python. Списки относятся к типу l i s t . Еще в Python есть множе­
ства (о них мы тоже поговорим, но не в этой главе). М ножества относятся
к типу s e t . Существуют и другие типы данных, которые мы будем изучать
постепенно, по мере нашего изучения языка Python.
Q

На заметку
В первую очередь имеются в виду такие "разновидности" данных, как кортежи
(тип tuple) и словари (тип diet). А еще в Python есть следующие типы: bytes
(неизменяемая последовательность байтов), bytearray (изменяемая последо­
вательность байтов), frozenset (неизменяемое множество), function (функ­
ция), module (модуль), type (класс). Есть также типы для специальных значений
ellipsis (для элемента Ellipsis) и NoneType (для значения None).

Как отмечалось выше, от типа данных зависит то, какие операции могут с
этими данными выполняться. М анипулировать с данными можем или с по­
мощью функций, или операторов. Какие операторы, когда и как использу­
ются в Python, мы обсудим в следующем разделе.
Щ

На заметку
В завершение этого раздела отметим, что в Python переменные можно не только
создавать, но и удалять. Для удаления переменной используется инструкция d e l,
после которой, через пробел, указывается имя удаляемой переменной. Если уда­
ляемых переменных несколько, они разделяются запятой.

О сновны е о пе р а то р ы
В от чт о крест ж ивот ворящ ий делает !

из к/ф "Иван Васильевич меняет про­
фессию"

Обычно выделяют четыре группы операторов:

ш



арифметические;



побитовые;

Глава 1. Первые программы на Python



операторы сравнения;



логические операторы.

Арифметические операторы предназначены в первую очередь для выполне­
ния арифметических вычислений. В таблице 1.3 перечислены и кратко опи­
саны основные арифметические операторы языка Python.
Сразу следует отметить, что действие арифметических операторов доста­
точно точно соответствует "математической природе" этих операторов. При
этом мы предполагаем, что речь идет о числовых расчетах.
Таблица 1 .3. Арифметические операторы

Оператор

Q

Описание

+

Оператор сложения. Вычисляется сумма двух чисел

-

Оператор вычитания. Вычисляется разность двух чисел



Оператор умножения. Вычисляется произведение двух чи­
сел

/

Оператор деления. Вычисляется отношение двух чисел

//

Оператор целочисленного деления. Вычисляется целая
часть от деления одного числа на другое

%

Оператор вычисления остатка от целочисленного деления.
Вычисляется остаток от деления одного числа на другое

**

Оператор возведения в степень. Результатом является чис­
ло, получающееся возведением первого операнда в степень,
определяемую вторым операндом

На заметку
Некоторые из перечисленных выше операторов могут применяться не только к
значениям числовых типов, но и, например, тексту или спискам. Эти темы мы пока
не затрагиваем и обсудим их позже. Напомним только, что если к одной тексто­
вой строке прибавить другую текстовую строку (с помощью оператора +), то в ито­
ге произойдет конкатенация (объединение) строк: результатом будет текстовая
строка, получающаяся объединением "суммируемых" строк. Например, в резуль­
тате выполнения команды txt="H3biK ”+"Python" переменная txt будет ссы­
латься на текстовое значение "Язык Python ".

Если мы попытаемся умножить текстовое значение на целое число (или целое чис­
ло на текст), то получим текстовую строку, получающуюся повторением (и конкате-

*

ш

Python
нацией) исходной строки (количество повторений определяется целочисленным
операндом в инструкции умножения текста и числа). Например, в результате вы­
полнения команды txt="Python "*3 переменная txt будет ссылаться на текст
"Python Python Python ", который получается троекратным повторением и
конкатенацией исходного текста " Python ".

Пример программного кода для выполнения несложных арифметических
вычислений приведен в листинге 1.2 .
Листинг 1 .2. Арифметические операторы
а = ( 5 + 2 ) * * 2 - 3 * 2 # Р е з у л ь т а т 43
Ь=6-5/2
# Результат 3.5
с=10//4+10%3
# Результат 3
# Р е з у л ь т а т ы вычислений выводим на э к р а н
p r i n t ("Р езультаты вычислений:")
p r i n t (а,Ь,с)

Результат выполнения этого программного кода представлен ниже:
Результат выполнения программы (из листинга 1.2)

Р е з у л ь т а т ы в ы ч и с л ен и й :
43 3 . 5

Q

3
На заметку
Возможно, некоторых пояснений потребует процедура вычисления значения вы­
ражения ю / / 4 + 1 0 % 3 . Так, значением выражения 10//4 является целая часть от
деления 10 на 4 - это число 2. Значение выражения Ю%3 - это число 1 (остаток от
деления 10 на 3). В результате получаем сумму 2 и 1 - то есть число 3.

Язык Python позволяет создавать очень элегантные программные коды.
Здесь мы воспользуемся возможностью, чтобы проиллюстрировать дан­
ное утверждение. Рассмотренный выше программный код мы перепи­
шем немного иначе. В частности, воспользуемся функцией e v a l (), кото­
рая позволяет вычислять выражения, заданные в текстовом формате. Н а­
пример, если некоторое алгебраическое выражение, записанное в соответ­
ствии с правилами синтаксиса языка Python, заключить в двойные кавыч­
ки, то получится текст. Если этот текст теперь указать аргументом ф унк­
ции p r i n t (), то на экране появится соответствующее алгебраическое вы ­
ражение. Если же текст (с алгебраическим выражением) передать аргумен­
том функции e v a l ( ), то соответствующее алгебраическое выражение бу­
дет вычислено. Обратимся к листингу 1.3.

а

Глава 1. Первые программы на Python

Листинг 1 .3 . Использование функции a v a l ()
а="(5+2)**2-3*2" # Текстовое значение
Ь="6-5/2"
# Текстовое значение
с="10//4+10%3" # Текстовое значение
# Результаты вычислений выводим на экран.
# Для "вычисления" текстовых выражений
# используем функцию eval()

p r i n t ("Результаты вы числений:")
p r i n t (а+" = " , e v a l ( a ) )
p r i n t ( b +" = " , e v a l ( b ) )
print(c+ " = ",eval(c))

В результате выполнения этого программного кода получаем следующее:
Результат выполнения программы (из листинга 1.3)
Р езу л ьтат ы вычислений:

(5+2)**2-3*2 = 43
6-5/2 = 3 . 5
10//4+10%3 = 3

Ш

На заметку
Результаты вычислений поясним на примере манипуляций с переменной а, кото­
рой в качестве значения присваивается выражение " (5+2) **2-3*2". Это текст.
Если бы мы не использовали двойные кавычки, было бы обычное арифметическое
выражение. Двойные кавычки арифметическое выражение превращают в текст.
Поэтому переменная а ссылается на текстовое значение. Если мы передаем пе­
ременную а аргументом функции print О , то на экране появится текстовое со­
держимое, на которое ссылается переменная - как и должно быть. Если мы ука­
зываем переменную а аргументом функции eval ( ) , то выполняется попытка вы­
числить выражение, которое представлено текстом, на который ссылается пе­
ременная а. Функция eval () достаточно "умная”. Если ее аргумент - обычный
текст (набор слов), то результатом будет этот же текст. Если, как в нашем случае,
в тексте"спрятано" арифметическое выражение - будет вычислено значение это­
го выражёния. Вообще, чтобы понять, каков будет результат выполнения функ­
ции eval ( ) , нужно представить, что выполняется команда, представленная тек­
стом в аргументе функции. Если задуматься, то легко понять, что это простое об­
стоятельство открывает перед нами грандиозные перспективы. Например, если
мы последовательно выполним команды х=3, у=7 и z="x+y", а затем команду
print(z+" = " , eval (z)), то получим вобласти вывода сообщение х+у = 10.По­
чему так? Весь секрет, очевидно, кроется в том, как обрабатывается значение пе­
ременной z функциями print () и eval ( ) . В первом случае ничего особенного не
происходит - текст "х+у" обрабатывается как текст. Во втором случае результа­
том будет значение выражения х+у, "спрятанного" в двойных кавычках. Посколь­
ку до этого переменные х и у получили числовые значения, получаем сумму двух
числовых значений.

*

ии

Python

Побитовые (или двоичные) операторы очень близки к арифметическим в
том отношении, что тоже предназначены для работы с числовыми значе­
ниями. Только в случае побитовых операторов вычисления выполняются
на уровне двоичного кода числа. Для эффективного использования побито­
вых операторов необходимо неплохо разбираться в двоичном представле­
нии чисел. Приведенная далее врезка предназначена для тех читателей, кто
с этой темой знаком не очень хорошо.
Q

На заметку
В двоичном коде число представляется в виде последовательности нулей и еди­
ниц. Старший бит используется для определения знака числа: ноль соответствует
положительному числу, а единица соответствует отрицательному числу.

В обычной жизни для записи чисел мы используем десять цифр: 0,1,2, и так
далее, до 9 включительно. С помощью этих десяти цифр мы можем записать
любое число. Достигается это благодаря позиционному представлению чи­
сел: число записывается в виде последовательности цифр. Важное значение
имеет то, на какой позиции какая цифра находится. Допустим, позиционное
представление числа имеет вид а па г

... а 1а с (черточка сверху означает, что

речь идет о позиционном представлении числа), где числовые параметры а 0
, a v ..., CLn могут принимать значения от 0 до 9 включительно. Как определить
значение такого числа? Достаточно просто. По определению имеет место
соотношение Om^n-i —

= а0 • 1 0 ° + а г • 1 0 1 + а 2 • 1 0 2 + — I- On • 1 0 п

Точно такую же схему мы можем использовать для того, чтобы запи­
сывать числа с помощью всего двух цифр - цифры 0 и цифры 1. Та­
кое представление называется двоичным кодом. Представим, что не­
которое число в позиционном представлении имеет вид ЬпЬп- г ... ЬгЬ0
, но только теперь параметры Ь0, Ь1; ..., Ьп могут принимать лишь два
значения: 0 или 1. Чему равняется число bnbn_! ...Ь^о? Ответ такой:

bnbn-x ... ЬгЬ0 = Ь0 • 2° + Ъх ■21 + Ъг ■22 + - + Ьп ■2"
Основные арифметические операции при двоичном представлении чисел
выполняются так же легко, как в привычном нам десятичном представле­
нии. Только немного видоизменяются основные правила. Например, имеют

(13В

Глава 1. Первые программы на Python

место такие соотношения: 0+ 0= 0,1+ 0= 1,0+ 1= 1,1 + 1=10, и так далее. Умно­
жение числа на два сводится к дописыванию в позиционном представлении
этого числа в конце нуля. Деление на два четного числа означает "зачерки­
вание" нуля в правой крайней позиции в двоичном коде (нечетные числа на
два без остатка не делятся).
Таким образом, любое число мы можем записать как последовательность
нулей и единиц и выполнять с этими числами все те операции, которые мы
привыкли выполнять с числами в десятичном формате. Но один вопрос
остается открытым: как записывать отрицательные числа? Опять же, если
мы пишем число на бумаге, мы просто дописываем перед ним знак "минус".
В принципе это же мы можем сделать и в случае, если число представлено
двоичным кодом. Но проблема в том, что лист бумаги - не компьютер. Для
компьютера нужен другой подход. Обычно используют принцип кодирова­
ния отрицательных чисел, который называется "дополнение до нуля". Что­
бы понять его основную идею, рассмотрим вспомогательный пример.
Допустим, есть некоторое положительное (это важно!) число, которое обо­
значим через х (икс). А что такое число —х (минус икс)? Ответ может
быть таким: это такое число, которое будучи прибавлено к исходному, даст
в результате ноль. То есть фактически по определению должно быть так:

х + (—дс) = 0. От этого момента и будем отталкиваться.
Есть такая операция, как побитовая инверсия (в Python этой цели служит
оператор -). Побитовая инверсия состоит в том, что в двоичном представ­
лении числа все нули заменяются на единицы, а все единицы заменяются
на нули. Если у нас есть некоторое положительное число X, то число ~ х по­
лучается из числа х заменой нулей и единиц на свои антиподы. Спросим
себя: а что будет, если мы вычислим сумму чисел х и ~ х? Несложно со­
образить, что в результате получится число, двоичное представление кото­
рого состоит из единиц. Действительно, на той позиции, где у числа х на­
ходится 0, у числа ~ х находится 1. Там, где у числа X находится 1, у числа

~ х находится 0. А мы уже знаем, что 0+1=1+0=1. То есть получаем такое:
х + (~ х) = 111... 11
п единиц

А теперь к полученному результату прибавим число 1 ("в игру вступает"
правило 1+ 1= 10 ).

Ш

^hon


* + ( - * ) + 1 = 1 П ...11 + 1 = 1 0 0 0 ...00 TT
Получится
следующее:
'---------- '
v----—г-*. И вот теJ
п единиц
п нулей
перь мы вспоминаем, что речь идет не просто о вычислениях, а о вычис­
лениях на компьютере. А в компьютере для запоминания чисел (в дво­
ичном коде) выделяется фиксированное количество битов - то есть ф и к­
сированное количество позиций (или разрядов) для записи числа. Если
для записи числа нужно больше разрядов, чем это отведено в компьюте­
ре, старшие разряды будут потеряны - они попросту игнорируются. Теперь
представим, что для записи чисел используется ^разрядов. Тогда мы смо­
жем записать число х, число ~ х, не будет проблем с их суммой х + (~ х )
. но вот при записи результата х + (~ х ) + 1 уже понадобится на один бит
(разряд) больше. Но для этого бита место не выделено. Поэтому в числе

х + (~ х ) + 1 — 1 000 ...00 единица в старшем бите будет потеряна, и мы
получим код из п нулёш Но это число 0! Таким образом, с учетом практи­
ки компьютерных вычислений получаем результат х + (~ х ) + 1 = 0 . С дру­
гой стороны, мы ранее договорились, что х + ( —х ) = 0. Какой из этого вы ­
вод? Очень простой: —х = ~ х + 1. Другими словами, чтобы получить дво­
ичный код отрицательного числа, нужно взять двоичный код соответствую­
щего положительного числа, по битам инвертировать его, и к полученному
значению прибавить единицу. При этом если у положительных чисел стар­
ший бит всегда нулевой, то у отрицательных чисел он всегда единичный.
Именно старший бит служит признаком положительности/отрицательности числа. Положительные числа из двоичного кода в десятичный перево­
дятся по формуле bnbn_! ... b ^ o = Ь0 • 2 ° + Ьх • 2 1 + Ь2 • 2 2 + — I- Ьп • 2п.
Если число отрицательное, то эту формулу применять нельзя. Чтобы пере­
вести отрицательное число в двоичном коде в привычное нам десятичное
представление, необходимо выполнить такие действия:

1 9



по битам инвертировать двоичное представление отрицательного
числа;



прибавить к полученному результату единицу;

*

Глава 1. Первые программы на Python



полученный двоичный код перевести к десятичному представлению
по формуле bnbn^i ... bxb0 = b0 • 2 ° + bt • 2 1 + b2 • 2 2 Н------ 1- Ьп ■2 ”
»



дописать знак "минус".

Чтобы понять, откуда взялись эти правила, достаточно заметить, что с уче­
том особенностей представления чисел в компьютере имеет место соотно­
шение —х + ~ ( —х ) + 1 = 0. Последующие рассуждения (с точностью до
обозначений) повторяют те, что приводились ранее.
Например, мы хотим узнать, каково двоичное представление для числа -5.
Начинаем с того, что определим код для числа 5. Несложно проверить, что
это такая последовательность битов: 00...000101. После побитового инвер­
тирования получаем код 11...111010. К полученному коду прибавляем 1, по­
лучаем код 11...111011. Это и есть двоичный код числа -5.
Напротив, нам хочется узнать, какое число закодировано в виде 11...110111.
Поскольку старший (самый крайний слева) бит равен 1, число отрицатель­
ное. Чтобы узнать, что же это за число, выполняем побитовое инвертирова­
ние кода. Получаем такой результат: 00...001000. Прибавляем единицу, по­
лучаем код 00..001001. Это код числа 9. Следовательно, в исходной последо­
вательности битов 11...110111 было закодировано число -9.
Побитовые операторы перечислены и описаны в таблице 1.4.
Таблица 1.4. Побитовые операторы
Оператор

*

Описание

~

Побитовая инверсия (унарный оператор - у него один операнд).
Результатом является число, получающееся заменой нулей на
единицы и единиц на нули в побитовом представлении операнда
(сам операнд при этом не меняется)

&

Побитовое И. При вычислении результата сравниваются поби­
товые представления операндов. Если на одной и той же пози­
ции в операндах стоят единицы, то в числе-результате на этой же
позиции будет единица. В противном случае (то есть если хотя
бы в одной из двух позиций нуль) в числе-результате на соответ­
ствующей позиции будет нуль

о

Python

Описание

Оператор

1

Побитовое ИЛИ. Сравниваются побитовые представления опе­
рандов. Если на одной и той же позиции в операндах стоят нули,
то в числе-результате на этой же позиции будет нуль. В против­
ном случае (то есть если хотя бы в одной из двух позиций едини­
ца) в числе-результате на соответствующей позиции будет еди­
ница
Побитовое ИСКЮЧАЮЩЕЕ ИЛИ. Результат вычисляется
сравнением побитовых представлений операндов. Если на одной
и той же позиции в операндах стоят разные значения (у одного
числа нуль, а у другого единица), то в числе-результате на этой
же позиции будет единица. В противном случае (то есть если на
соответствующих позициях в операндах стоят одинаковые чис­
ла) в числе-результате на этой позиции будет нуль

А

«

Сдвиг влево. Результат вычисляется так: в побитовом представ­
лении первого операнда выполняется сдвиг влево. Количество
разрядов, на которые выполняется сдвиг, определяется вторым
операндом. Младшие недостающие биты заполняются нулями

»

Сдвиг вправо. Для вычисления результата в побитовом пред­
ставлении первого операнда выполняется сдвиг вправо. Коли­
чество разрядов, на которые выполняется сдвиг, определяется
вторым операндом. Биты слева заполняются значением самого
старшего бита (для положительных чисел это нуль, а для отри­
цательных единица)

Чтобы проиллюстрировать методы использования побитовых операторов,
рассмотрим небольшой программный код, представленный в листинге 1.4.
Листинг 1.4. Побитовые операторы
а = 7 0>>3
Ь=~а
с=аb e l s e v a lu e _ 2
# О тображ ается р е з у л ь т а т
p rin t(re s)

Результат выполнения этого программного кода может быть таким (ж ир­
ным шрифтом выделен ввод пользователя):
Результат выполнения программы (из листинга 1.8)
В в е д и т е п е р в о е ч и с л о : 12
в в е д и т е в т о р о е ч и с л о : 30
В т о р о е ч и с л о н е меньш е п е р в о г о .

И ли таким:
результат выполнения программы \из л и ъ тн га law/

В в е д и т е п е р в о е ч и с л о : 30
в в е д и т е в т о р о е ч и с л о : 12
П ервое ч и с л о больш е в т о р о г о .

В данном случае ситуация достаточно простая. Сначала пользователем вво­
дятся два числа, которые записываются в переменные а и Ь. Д ля считыва­
ния вводимого пользователем значения мы используем функцию i n p u t ().
Но поскольку эта функция в качестве результата возвращает введенное
- ш

Python

пользователем значение в текстовом формате, для надежности преобразу­
ем считанное значение к формату числа с плавающей точкой, для чего пе­
редаем результат вызова функции i n p u t () аргументом функции f lo a t ().
Значение переменной r e s вычисляется на основе тернарного операто­
ра. В тернарном операторе проверяется условие а х 2. Следовательно, долж­
но выполняться соотношение X 2 < у < X. Если перевести это на язык Python, то
получим у=х**2. Кстати, вместо инструкции у=х**2 впол­
не законно можно было использовать выражение х**2 2.0
3 -> 2. 5
4 - > 2 .6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5
5 -> 2 .7 0 8 3 3 3 3 3 3 3 3 3 3 3 3
6 -> 2 .7 1 6 6 6 6 6 6 6 6 6 6 6 6 6 3
7 -> 2 .7 1 8 0 5 5 5 5 5 5 5 5 5 5 5 4
8 -> 2 .7 1 8 2 5 3 9 6 8 2 5 3 9 6 8 4
9 -> 2 .7 1 8 2 7 8 7 6 9 8 4 1 2 7
10 - > 2 . 7 1 8 2 8 1 5 2 5 5 7 3 1 9 2 2

Как и следовало ожидать, с увеличением количества слагаемых точ­
ность вычислений возрастает (напомним, "точное" значение равняется
е = exp (1) * 2.718281828459045).

Q

На заметку
Для вычисления экспоненты в модуле math есть функция ехр ().

Значения аргументов по умолчанию
Л учш е бы я у п а л вм ест о т ебя,

из к/ф "Бриллиантоваярука"

Д ля аргументов функций нередко задают значения по умолчанию. То есть
для аргументов функции допускается указать значение, которое будет ис­
пользовано, если аргумент функции явно не указан. Можно и иначе: если
при вызове функции аргумент не указан, то вместо него используется зна­
чение по умолчанию.
Чтобы задать значение аргумента по умолчанию, в описании функции по­
сле имени этого аргумента через знак равенства указывается значение по
умолчанию. У функции, как мы знаем, возможно наличие нескольких ар­
гументов. Часть этих аргументов может иметь значения по умолчанию, а
остальные - нет. Важно помнить, что в описании функции при перечисле-

БЕИ

Python

нии аргументов сначала указываются аргументы без значений по умолча­
нию, а аргументы со значениями по умолчанию размещаются в конце.
Ш

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

Примеры функций со значениями аргументов по умолчанию приведены в
программном коде листинге 3.3.
Листинг 3.3. Значения аргументов по умолчанию
# 1 - я ф ункция с одним а р г у м е н т о м .
# У а р г у м е н т а е с т ь з н а ч е н и е по умолчанию
d e f p r i n t _ t e x t (б х б = "3 н ач ен и е а р г у м е н т а по у м о л ч а н и ю ." ):
p r i n t (tx t)
# 2 - я функция с двумя ар гу м ен та м и .
# У в т о р о г о а р г у м е н т а е с т ь з н а ч е н и е по умолчанию
d e f s h o w _ a r g s (а,Ь = "В то р о й аргу м ен т не у к а з а н . " ) :
p r i n t (а ,Ь )
# 3 - я функция с двумя ар гу м ен та м и .
# У а р г у м е н т о в е с т ь з н а ч е н и я по ум олчанию
d e f m y _ fu n c(х= "1-й аргум ент х ." ,у = " 2 - й аргум ент у . " , ) :
p r i n t (х ,у )
# П роверяем р а б о т у 1 -й функции.
# Ф ункции п е р е д а н о д и н а р г у м е н т
p r i n t _ t e x t ( "А ргумент у к а з а н я в н о ." )
# Ф ункции а р г у м е н т ы н е п е р е д а ю т с я
p r i n t _ t e x t ()
# П роверяем р а б о т у 2 - й функции.
# Ф ункции п е р е д а н ы д в а а р г у м е н т а
s h o w _ a r g s ( "П е р в ы й а р г у м е н т . " , " В т о р о й а р г у м е н т . " )
# Ф ун к ц и и п е р е д а н о д и н а р г у м е н т
s h o w _ a r g s ( "П е р в ы й а р г у м е н т . " )
# П роверяем р а б о т у 3 -й функции.
# Ф ункции а р г у м е н т ы н е п е р е д а ю т с я
m y _ f u n c ()
# Ф ункции п е р е д а н о д и н а р г у м е н т
m y _ f u n c ( "О дин и з а р г у м е н т о в . " )
# Ф ункции п е р е д а н о д и н а р г у м е н т .
# П ереданный а р г у м е н т и ден ти ф и ц и р о в ан явн о
m y _ f u n c (у = " О д и н и з а р г у м е н т о в . " )

cm

t

Глава 3. Функции

В результате выполнения программного кода получаем следующий резуль­
тат:
Результат выполнения программы (из листинга 3.3)
А ргумент у к а з а н я в н о .
З н а ч е н и е а р г у м е н т а по ум олчанию .
П ервы й а р г у м е н т . В т о р о й а р г у м е н т .
П ервы й а р г у м е н т . В т о р о й а р г у м е н т н е у к а з а н .
1-й аргум ент х . 2 -й аргум ент у.
Один и з а р г у м е н т о в . 2 - й а р г у м е н т у .
1 - й а р г у м е н т х . Один и з а р г у м е н т о в .

Мы объявляем три функции. Все три функции действуют по одной схе­
ме: значения аргументов выводятся на экран. Мы подразумеваем, что все
три функции оперируют с текстовыми аргументами. Некоторые из этих ар­
гументов имеют значения по умолчанию. Так, у функции p r i n t _ t e x t ()
один аргумент, и у этого аргумента есть значение по умолчанию. Мы вызы­
ваем эту функцию без аргумента (в этом случае используется значение ар­
гумента по умолчанию), а также вызываем эту функцию с одним аргумен­
том (в этом случае используется переданное аргументом значение).
У функции s h o w _ a rg s () два аргумента, и у второго аргумента есть зна­
чение по умолчанию. Поэтому если вызвать функцию с одним аргументом,
то для второго аргумента используется значение по умолчанию. Если пере­
даются два аргумента, то для обоих аргументов используются те значения,
что переданы.
У функции m y _ fu n c ( ), как и в предыдущем случае, два аргумента, но те­
перь у обоих аргументов есть значения по умолчанию. Если мы вызовем эту
функцию с двумя аргументами, то интриги не будет - те значения, что пе­
реданы аргументами, будут использованы при выполнении кода функции.
Если функции при вызове передан один аргумент, то возникает вопрос: ка­
кой это аргумент - первый или второй? Ведь значение по умолчанию есть и
у первого, и у второго аргумента. Поэтому теоретически переданное функ­
ции значение может быть как первым, так и вторым аргументом.
По умолчанию считается, что функции передается первый аргумент, а для
второго используется значение по умолчанию. Но у нас есть еще одна воз­
можность: мы можем явно указать, для какого аргумента явно указано зна­
чение. В этом случае при вызове функции указывается не только значение
аргумента, но и его имя (то, которое указывалось при описании функции).
Используется формат и м я _ а р г у м е н т а = зн а ч е н и е .

*

«ш

Python

Примером может быть команда m y _ fu n c (у="О дин и з а р г у м е н т о в . " ).
В этом случае для первого аргумента используется значение по умолчанию,
а переданное функции значение - это значение второго аргумента.
Таким образом, передача аргументов при вызове функции возможна:


без указания имени аргументов, при этом значения аргументов пе­
редаются строго в той последовательности, как эти аргументы были
описаны в функции (такой способ передачи аргументов называют
позиционным);



с явным указанием имени аргументов, при этом порядок перечисле­
ния аргументов не имеет значения (такой способ называется пере­
дачей аргументов по ключу или по имени, а сами аргументы иногда
называют именованными).

Если при вызове функции часть аргументов передается по ключу, а часть позиционным способом, то в команде вызова сначала указываются позици­
онные аргументы, а затем те, что передаются по ключу. Стоит также осо­
бо обратить внимание, что способ описания аргументов (при определении
функции) не зависит того, как предполагается передавать аргументы при
вызове функции (по ключу или позиционно).
Щ

На заметку
Если у аргументов некоторой функции есть значения по умолчанию, то эта функ­
ция может вызываться с разным количеством аргументов (подробности зависят от
специфики описания функции). В языке Python можно объявлять функции с пере­
менным количеством аргументов. Этот вопрос более детально мы обсудим позже.
Здесь же сделаем общее вводное замечание.

Предположим, нам нужно описать функцию такую, чтобы ее можно было
вызывать с разным количеством аргументов. Здесь важно, что количество
аргументов не ограничено - мы наперед не знаем, сколько в принципе бу­
дет передаваться аргументов функции. В этом случае мы описываем ф унк­
цию с одним аргументом, но перед этим аргументом ставим звездочку *. Та­
кой аргумент отождествляется со списком, элементы которого формируют­
ся реальными аргументами, что переданы функции при вызове. Другими
словами, наш "звездный" аргумент в теле функции обрабатывается как спи­
сок. Но при вызове функции аргументы передаются, как обычно. Н апри­
мер, функция может быть описана так:
d e f g et_ su m (* n u m s):
s=0
f o r a in num s:
s+=a
re tu rn s

in

Глава 3. Функции

Данная функция в качестве результата будет возвращать сумму чисел, пе­
реданных аргументами функции. Скажем, значением выражения g e t _
s urn ( 1 , 3 , 5 , 2 ) будет число 1 1 , а значение выражения ge t _ s шп ( - 2 ,4 ) число 2 . Списки обсуждаются в следующей главе. Ф ункции с переменным
числом аргументов описываются в последней главе.

Ф у н кц и я ка к а р гум е н т
Я вся такая внезапная, такая противоречи­
вая вся...

и зк/ф "Покровские ворота"

Чтобы понять следующий прием, важно уточнить некоторые особенно­
сти, касающиеся объявления функций в Python. А именно, при объявле­
нии функции на самом деле создается объект типа f u n c t i o n . Ссылка на
этот объект записывается в переменную, которая указана в описании ф унк­
ции после инструкции d e f . Другими словами, имя функции (без круглых
скобок) можно использовать как переменную (которая ссылается на ф унк­
цию). Вопрос только в том, что с этой переменной можно сделать. Вариан­
тов здесь довольно много, но нас интересует в первую очередь возможность
присвоить имя функции другой переменной, а затем вызвать эту функцию
через данную переменную так, как если бы это было имя функции. При при­
сваивании переменной в качестве значения имени функции эта переменная
становится ссылкой на функцию (наравне с именем функции). Небольшой
пример, иллюстрирующий эту возможность, представлен в листинге 3.4.
Листинг 3.4. Ссылка на функцию
# И сходная функция
d ef m y _ fu n c (tx t):
p r i n t ( "Ф у н к ц и я m y _ f u n c : " , t x t )
# П ерем енной п р и с в а и в а е т с я имя функции
new _func=m y_func
# В ы зы в а е м ф ункцию ч е р е з п е р е м е н н у ю
n e w _ fu n c ( "вы зов ч е р е з n e w _ fu n c ." )

Результат выполнения этого программного кода такой:
Результат выполнения программы (из листинга 3.4)
Ф ун к ц и я m y _ f u n c :

вызов ч е р е з n e w _ fu n c.

Идея здесь реализована очень простая. Сначала мы описываем функ­
цию m y _ fu n c () (у функции один аргумент), а затем командой
n e w _ f unc= m y_f u n c переменной n e w _ fu n c в качестве значения присваи-

ш т

Python

вается имя функции m y _ fu n c. В результате и идентификатор (имя ф унк­
ции) m y_f u n c, и переменная n e w _ f u n c ссылаются на один и тот же объ­
ект - объект функции. Это означает буквально следующее: о переменной
n e w _ f u n c мы можем думать как об имени функций, причем функции той
же самой, что и функция m y_f u n c ( ). Поэтому кода выполняется команда
n e w _ f u n c ( "в ы зо в ч е р е з n ew _ f u n c . " ) , это все равно, как если бы мы
вызвали функцию m y_f u n c () с тем же точно аргументом.
Описанное свойство функций (возможность присваивать имя функции пе­
ременной) имеет ряд полезных применений, в том числе перед нами откры­
вается возможность передавать имя функции аргументом другой функции.
Скажем откровенно, возможность нетривиальная и позволяет легко (и гдето даже красиво) решать сложные задачи. Классической иллюстрацией мо­
гут быть некоторые математические задачи, в которых функциональная за­
висимость играет роль внешнего фактора: например, привычислении инте­
гралов, решении дифференциальных или алгебраических уравнений.
Далее мы рассмотрим несколько иллюстраций к тому, как одна функция пе­
редается аргументом другой функции. Начнем с простого примера, в кото­
ром описывается функция для решения алгебраических уравнений мето­
дом последовательных приближений.
Ш

На заметку
Речь идет о решении уравнения вида х = / ( х ) относительно переменной X, при
условии, что функция / ( х ) известна (задана). Суть метода последовательных
приближений состоит в том, что задано начальное приближение X q для корня
уравнения, на основании которого вычисляется первое приближение для корня
хх = / ( х 0). На основании первого приближения Х \ вычисляют второе приближе­
ние Х2 = f ( x i), и так далее. Общая рекуррентная формула для вычисления Хп+1
приближения на основе приближения х п имеет вид Хп+1 = f ( x n). Именно эту
схему мы собираемся реализовать в программе.
Понятно, что далеко не любое уравнение можно решить подобным методом. Су­
ществуют определенные критерии применимости метода последовательных при­
ближений. В частности, функция f ( x ) должна быть такой, чтобы на всей области
поиска корня выполнялось соотношение | f 'ex') | < 1 , то есть модуль производной
функции должен быть меньше единицы.

Задача состоит в том, чтобы не просто решить какое-то конкретное уравне­
ние, а написать функцию, которая бы позволяла решать указанным мето­
дом различные уравнения (разумеется, при условии, что метод вообще для
них применим). Программный код с решением этой задачи представлен в
листинге 3.5.

*® ~

*

Глава 3. Функции

Листинг 3 .5 . Метод последовательных приближений
# О писание функции д л я реш ения у р а в н е н и я
d e f s o l v e _ e q n ( f , х О ,n ) :
# Н ачальное приближ ение для корня
х=хО
# О ператор цикла для вычисления
# приближ ений д л я реш ения
fo r k in ran g e (1 ,n + 1 ):
x = f ( x ) # И тер ац и о н н ая формула
# Р е з у л ь т а т функции
re tu rn х
# Ф ункция, определяю щ ая 1 - е у р а в н е н и е
def e q n _ l(х ):
# З н а ч е н и е функции
r e t u r n (х * * 2 + 5 )/6
# Ф ункция, определяю щ ая 2 - е у р а в н е н и е
def eqn_2(х ):
# З н а ч е н и е функции
r e t u r n (6 * х -5 )* * 0 .5
# Р еш ае м 1 - е у р а в н е н и е
x = s o l v e _ e q n ( e q n _ l ,0 ,1 0 )
# О тображаем р е з у л ь т а т
p r i n t ("1 -е уравн ен и е: х = ",х )
# Р еш ае м 2 - е у р а в н е н и е
x = s o lv e _ e q n ( e q n _ 2 ,4 ,1 0 )
# О тображаем р е з у л ь т а т
p r i n t ("2 -е у равн ен и е: х = ",х )

В программе мы описываем функцию s o lv e _ e q n () с тремя аргументами.
Наши предположения относительно аргументов такие:


первый аргумент f является ссылкой на функцию, определяющую
решаемое уравнение (имеется в виду функция /(* ) в уравнении
* = /(*));



второй аргумент хО определяет начальное приближение для корня
уравнения;



третий аргумент п функции - количество итераций, по которым вы­
числяется корень.

В теле функции s o lv e _ e q n () командой х = х 0 переменной х в качестве на­
чального присваивается нулевое приближение для корня уравнения. После
этого запускается оператор цикла, в котором осуществляется п итераций
Гциклов). За каждый цикл выполняется команда x = f ( х ) , в которой мы ис-

ш>

Python

пользуем вызов функции, переданной через идентификатор f аргументом
функции s o lv e _ e q n (). Каждое выполнение команды x = f (х) приводит к
вычислению нового (следующего) приближения для корня уравнения. По
завершении оператора цикла переменная х командой r e t u r n х возвраща­
ется как результат функции s o lv e _ e q n ().
Также мы определяем две функции от одного аргумента, которые задают
уравнения для решения.
Q

На заметку
Мы решаем квадратное уравнение х2 - 6 х + 5 = 0, которое имеет два корня: х = 1 и х = 5. Для применения метода последовательных приближений это
уравнение нужно переписать. Причем для писка разных корней нужно исполь­
зовать разные представления. Связано это с необходимым условием сходимоФ5
сти метода. Так, для поиска корня х = 1 уравнение представим в виде х = — - — .
х2 + 5
6
В этом случае уравнение задается функцией х = — -— . Производная от этой функ-

X

°

ции / ' ( * ) = ; в точке предполагаемого корня х = 1 по модулю меньше единих2 + 5
цы. Функция х = — - — в программном коде реализуется функцией eqn l ( ) . Имя
этой функции будем указывать первым аргументом функции solve_eqn(). На­
чальное приближение в этом случае должно попадать в интервал - 3 < х < 3. Вто­
рой корень уравнения х = 5 не попадает в этот интервал, поэтому для его вычис­
ления необходимо изменить способ записи уравнения. Теперь представим урав­
нение в виде х = л/бх — 5. Данное уравнение задается функцией f i x ) = V 6х — 5.
Производная от этой функции ТОО =

3

= jr

по модулю меньше единицы

при 2 > з “ 2.66667 зависимость /СО = V6x — 5 в программе реализована че­
рез функцию eqn_2 ( ) . Имя этой функции передадим первым аргументом функ­
ции solve _eqn ( ) . Вторым аргументом нужно указать числовое значение, боль­
шее 7/3.
Хотя в данном случае речь фактически идет об одном уравнении, записанном в
разном виде, мы, чтобы не путаться, будем говорить о двух уравнениях.

Ф ункция e q n _ l ( ) , определяющая первое уравнение, содержит всего одну
команду r e t u r n
( х * * 2 + 5 ) / б , которой на основе значения аргумента х
вычисляется результат функции. В данном случае определяется функци­
х2 + 5

ональная зависимость /(* ) = ■
х2 + 5

х =■

и решается, соответственно, уравнение

(речь идет о корне х = 1 )•

Еще одна функция, которая называется e q n _ 2 () также предназначена
для определения уравнения для решения. Значение функции возвращает­
ся командой r e t u r n
( 6 * х - 5 ) * * 0 . 5 , где х - это аргумент функции. Та-

ЯЯI -

Глава 3. Функции

ким образом, рассматриваем функцию уравнения /(х ) = V6х - 5 , уравнение
х = V6 х - 5 (и ищем решение х = 5 ).
Д ля решения первого уравнения мы используем команду x = s o lv e _
e q n ( e q n _ l , 0 ,1 0 ) . Переменной х в качестве значения присваивается ре­
зультат вызова функции s o lv e _ e q n ( ) , первым аргументом которой пере­
дано имя e q n _ l функции, определяющей решаемое уравнение, второй ар­
гумент - начальное (в данном случае нулевое) значение для корня уравне­
ния, а третий аргумент (значение 1 0 ) определяет количество итераций, по
которым вычисляется приближение для корня уравнения (в принципе, чем
больше итераций - тем выше точность решения).
Командой p r i n t ( " 1 - е у р а в н е н и е : х = " , х) найденное решение для
корня уравнения отображается в консольном окне.
По той же схеме ищется решение для второго уравнения, только теперь
первым аргументом функции s o l v e _ e q n ( ) передается идентификатор
e q n _ 2 , а начальное значение для корня (второй аргумент) равно 4. Резуль­
тат выполнения программы показан ниже:
Результат выполнения программы (из листинга 3 .5 )
1 - е уравнение:
2 - е уравнение:

х = 0 .9 9 9 9 9 2 5 2 8 9 5 8 1 1 5 2
х = 4 .9 9 2 8 3 9 4 8 7 8 2 0 0 5 5

Как несложно заметить, найденные нами приближенные решения доста­
точно близки к точным значениям.
Далее рассмотрим задачу о решении дифференциального уравнения пер­
вого порядка у '(х ) = /( х , у (х )) с начальным условием у ( х 0) = у 0 ( зада­
ча Коши). Здесь через х обозначена независимая переменная, У 0 0 - не­
известная функция от независимой переменной, у '(х ) = — - производная
от функции у (я:) по аргументу X , а / ( х , у ) - известная (заданная) ф унк­
ция двух аргументов (функцию / ( х , у ) будем называть функцией уравне­
ния). Нам необходимо найти такую функцию у(хг), чтобы дифференциаль­
ное уравнение у '(х ) = /(х ,у ( х )) превратилось в тождество, и, кроме этого,
в точке х = х 0 функция у ( х ) принимала значение Уо (числовые параме­
тры х 0 и Уо считаем заданными).
Д ля решения уравнения в числовом виде используется наиболее простая
схема Эйлера.

*

кш

Python

ffl

На заметку
Если вкратце, то суть схемы Эйлера состоит в следующем. Интервал значений ар­
гумента от Х 0 (точка, в которой задано начальное условие) до X (точка, в которой
нужно вычислить значение функции у ( х ) ) разбивается на П (достаточно большое
число) одинаковых отрезков длиной

_ х

х° .

п
Значения

функции у ( х )

последовательно

вычисляются

в узловых точках

х к = х 0 + Ах ■к, где индекс к = 0 ,1 ,2 ,..., я, причем по договоренности х п = X.
В узловой точке Хд значение функции

у(х0)

=

у 0 определяется

из начального

условия. Для всех остальных узловых точек X^ вычисление значения функции
у к = у ( х к)

осуществляется

на

основе

рекуррентного

соотношения

Ук = Ук- 1 + Л* • /(**-1.Ук-1>
Таким образом, если мы знаем значение функции Уо в точке Хд (а мы его знаем из
начального условия), то можем вычислить значение функции У\ в точке X ] по фор­
муле
= у 0 + Ах ■/ О о , у 0>

Зная
у± , вычисляем у2 = у± + Ах • / (\хъ у г), и так
Уп = Уп- i + Ьс ■/ ( x „ - i , y „ - i ) - А уп = у(хп) - это и есть у (х ).

далее

до

Для реализации метода Эйлера по вычислению решения дифференциаль­
ного уравнения в точке описываем специальную функцию (называется
s o lv e _ d e q n () ). У этой функции четыре аргумента:


название функции, определяющей дифференциальное уравнение
(аргумент с названием f );



узловая точка, в которой задается начальное условие (аргумент с на­
званием хО);



значение искомой функции в точке начального условия (аргумент с
названием уО);



значение точки, для которой необходимо вычислить значение функ­
ции - решения дифференциального уравнения (аргумент с названи­
ем х).

В качестве результата функция будет возвращать значение решения диф ­
ференциального уравнения (для заданной точки). Соответствующий про­
граммный код приведен в листинге 3.6.

f
ш

в -

Глава 3. Функции

Листинг З.б. Решениедифференциальногоуравнения
# Импорт математического модуля
import math
# Функция для решения дифференциального уравнения
def solve_deqn(f,хО,уО, х) :
# Количество отрезков, на которые делится интервал
# поиска решения уравнения
п=1000
# Расстояние между соседними узловыми точками
d x = (х-хО)/п
# Начальная точка
х=хО
# Начальное значение функции
у=уО
# Оператор цикла для вычисления решения
for k in range(l,n+l):
# Значение функции в узловой точке
y=y+dx*f(х,у)
# Следующая узловая точка
x=x+dx
# Результат функции
return у
# Функция, определяющая дифференциальное уравнение
def diff_eqn(х,у):
# Результат функции
return 2*х-у
# Функция точного решения уравнения
def у(х):
# Результат функции
return 2 * (х-1)+5*math.e x p (-х)
# Шаг приращения по аргументу
h= 0 .5
# Вычисление результата для нескольких
# значений аргумента
for k in range(0, 6):
# Значение аргумента
x=k*h
print("Числовое решение:")
# Числовое решение
print("х =",х,"-> у(х) =",solve_deqn(diff_eqn,0,3,х))
print("Точное решение:")
# Точное решение
print("x =",х,"-> у(х) =",у(х))

При выполнении программного кода получаем такой результат:

СЭ

Python

Результат выполнения программы (из листинга 3.6)
Числовое решение:
х = 0.0 -> у(х) =
Точное решение:
х = 0.0 -> у(х) =
Числовое решение:
х = 0.5 -> у (х ) =
Точное решение:
х = 0.5 -> у(х) =
Числовое решение:
х = 1.0 -> у(х) =
Точное решение:
х = 1.0 -> у (х ) =
Числовое решение:
х = 1.5 -> у (х ) =
Точное решение:
х = 1.5 -> у (х ) =
Числовое решение:
х = 2.0 -> у (х ) =
Точное решение:
х = 2.0 -> у (х ) =
Числовое решение:
х = 2.5 -> у (х ) =
Точное решение:
х = 2.5 -> у (х ) =

3.0
3.0
2.0322741142003067
2.032653298563167
1.8384771238548225
1.8393972058572117
2.1143951442170557
2.115650800742149
2.6753226122334164
2.6766764161830636
3.4091422819998414
3.410424993119494

Хотя программного кода довольно много, программа на самом деле простая.
Проанализируем основные ее места и наиболее важные моменты.
Относительно новая для нас инструкция i m p o r t m a th нужна для импор­
та математического модуля m ath, а он, в свою очередь, понадобится нам для
того, чтобы при проверке найденного числового решения (сравнении его
с точным, аналитическим решением), можно было использовать функцию
для вычисления значения экспоненты е х р ().
В теле функции s o lv e _ d e q n (), используемой для решения дифферен­
циального уравнения, командой п = 1 0 0 0 задается количество отрезков, на
которые разбивается интервал поиска решения (имеется в виду интервал
значений аргумента функции-решения дифференциального уравнения от
х 0 до х). Тогда расстояние между соседними узловыми точками будет со­
ставлять величину dx= ( х - х 0 ) /п (длина интервала, деленная на количе­
ство отрезков, на которые разбивается интервал). В переменную х коман­
дой х = х 0 записывается значение точки, в которой задается начальное усло­
вие, а командой у = у 0 переменной у присваивается значение искомой функ-

«Б»

Глава 3. Функции

ции в точке начального условия. Таким образом, переменные х и у получа­
ют свои начальные значения. После этого запускается итерационный про­
цесс, основу которого составляет оператор цикла. Оператор цикла рассчи­
тан на выполнение п итераций, причем за каждую итерацию выполняется
две команды. Сначала командой y = y + d x * f ( х , у ) вычисляется значение
функции-решения уравнения в следующей (по сравнению с текущей) узло­
вой точке. Затем командой x= x+dx вычисляется сама узловая точка. После
выполнения оператора цикла значение переменной у возвращается в каче­
стве результата функции s o lv e _ d e q n ().
Также в программе мы определяем функцию, которая задает решаемое диф ­
ференциальное уравнение. Речь идет о функции d i f f _ e q n О , у которой
два аргумента (х и у), а в качестве результат функцией возвращается выра­
жение 2 * х -у .

Ш На заметку
Исходя из определения функции d i f f eqn () несложно сделать вывод, что речь
идет о функции уравнения f ( x , y ) = 2х —у. Следовательно, решается диффе­
ренциальное уравнение у ' ( х ) — 2х — у (х )- У этого уравнения есть аналитиче­
ское (точное) решение у ( х ) = 2 • (х — 1) + С, • ехр ( —дс), где постоянная C j
определяется из начального условия. Например, если начальное условие запи­
сывается как у ( 0 ) = 3 (а в программе при проверке результата мы использу­
ем именно такое начальное условие), то решение задачи Коши будет иметь вид
у ( * ) = 2 • (ж — 1) + 5 • ехр ( - * ) .

Чтобы сравнить результат, полученный по схеме Эйлера, с точным решени­
ем у(х) = 2 • (х - 1) + 5 • ехр (-х) уравнения, в программном коде мы объявля­
ем функцию у () с одним аргументом х, которая соответствует аналитиче­
скому решению задачи Коши для уравнения у 'О ) = 2 х - у(х) с начальным
условием у ( 0) = 3.

Ш На заметку
В теле функции у () для вычисления значения экспоненты вызывается функция
ехр () из модуля math, который мы в самом начале программы подключили с по­
мощью инструкции im p o rt. Аргументом функции ехр( ) передается показатель
экспоненты. При вызове функции ехр () явно указывается модуль math, поэтому
математическому выражению ехр ( —х ) (или то же самое, что е ~ х ) в программ­
ном коде соответствует инструкция m ath. ехр (- х ).

Проверка созданных функций выполняется так: для нескольких значений
аргумента х функции-решения уравнения мы находим числовое решение
и сравниваем его с точным решением. Д ля этого запускаем оператор цик­
ла, в котором индексная переменная к пробегает значения от 0 до 5. Пере-

СЭ

Python

менная x принимает значения k*h (h = 0 .5 - шаг приращения для аргумента
х), приближенное (числовое) решение вычисляется инструкцией s o l v e _
d e q n ( d i f f _ e q n , 0 , 3 , x ) , а для получения точного решения используем
выражение у (х ).

Рекурсия
Н и к огда не дум ай , чт о ты иная, чем м огла
бы бы т ь иначе, чем б уд уч и иной в т ех сл у ч а ­
ях, когда иначе н ельзя не быт ь.

Л. Кэрролл "Алиса в стране чудес"

При описании функций иногда удобно использовать рекурсию. Рекурсия
подразумевает, что в программном коде функции используется вызов этой
же самой функции (но обычно с другим аргументом). Лучше всего сказан­
ное проиллюстрировать на примере. В листинге 3.7 приведен программный
код функции, которой по порядковому номеру вычисляется число из по­
следовательности Фибоначчи. При описании функции использована ре­
курсия.
Щ

На заметку
Числа Фибоначчи - последовательность чисел, в которой первое и второе число
равны единице, а каждое следующее определяется как сумма двух предыдущих.
Таким образом, речь идет о числах 1, 1,2, 3, 5, 8, 13, 21 итак далее.

Листинг 3 .7 . Числа Фибоначчи
# Функция д л я вы ч и сл е н и й ч и с е л Ф и б о н а ч ч и .
# При о п и с а н и и ф у н к ц и и и с п о л ь з о в а н а р е к у р с и я
d e f F ib ( n ) :
# П ервое и в т о р о е число
# в п о с л е д о в а т е л ь н о с т и равны 1
i f n==l o r п==2:
re tu rn 1
# Числа в п о с л е д о в а т е л ь н о с т и
# р а в н о су м м е д в у х п р е д ы д у щ и х
e lse :
r e t u r n F ib ( n - 1 ) + F ib (n-2)
# П р о вер я ем р а б о т у функции
p r i n t ("Ч и сл а Ф и б о н ач чи :")
# В ы ч и с л я е м 15 п е р в ы х ч и с е л Ф и б о н а ч ч и
f o r i in ran g e (1 ,1 6 ):
# Числа п еч атаю тся в одной с т р о к е ч е р е з пробел
p r i n t ( F i b ( i ) , e n d = " ")

Д Ш --

Глава 3. Функции

В результате выполнения программы в ряд через пробел отображается 15
чисел из последовательности Фибоначчи:

Ч исла Фибоначчи:
1 1 2 3 5 8 13 21 34 55 89 1 44 2 3 3 3 77

610

Ф ункция для вычисления чисел Фибоначчи в нашем исполнении называ­
ется Fib О и у нее один аргумент (обозначен как п) - это порядковый но­
мер числа в последовательности. По этому номеру необходимо вычислить
число. Процесс вычислений мы реализуем с помощью условного операто­
ра, в котором проверяем значение аргумента функции. Точнее, проверяется
условие n = = l o r п== 2 . Условие истинно, если аргумент п равей 1 или 2 ,
то есть если речь идет о первом или втором числе в последовательности. В
этом случае число Фибоначчи равно 1 . Поэтому если условие истинно, вы­
полняется инструкция r e t u r n 1. То есть если функции Fib () значение
аргумента равно 1 или 2 , функцией возвращается значение 1 .
Пока все просто. Ситуация усложняется, если условие n = = l o r п ==2 лож ­
но. Ложно условие, если индекс у числа (его порядковый номер в после­
довательности) больше чем 2 (экзотические варианты с отрицательными и
нецелыми индексами мы не рассматриваем). И здесь мы вспоминаем пра­
вило вычисления чисел в последовательности Фибоначчи: каждое число
(кроме первых двух) - это сумма двух предыдущих.
Далее, если число Фибоначчи с порядковым номером п возвращается в ре­
зультате вызова функции командой Fib ( п ) , то два предыдущих числа это Fib (п -1 ) и Fib ( п - 2 ) . Поэтому в теле функции (с аргументом п) в
условном операторе в e l s e -блоке выполняется команда r e t u r n Fib (n 1) +Fib ( n - 2 ). Собственно, на этом все. У нас есть программный код ф унк­
ции, и этот программный код рабочий.
Ш

На заметку
Чтобы понять, почему же рекурсия "работает", разберемся, что происходит, если
мы вызываем функцию Fib ( ) . Например, если мы вызываем функцию с аргумен­
том 1 или 2 (выражение Fib (1) или Fib (2) соответственно), то в теле функции
"в игру" вступает тот блок в условном операторе, который соответствует истин­
ному условию. А вот чтобы вычислить выражение Fib (3) в e ls e -блоке условного
оператора выполняется попытка вычислить выражение Fib (2) +Fib (1) . При этом
снова вызывается функция Fib О , но только с аргументами 2 и 1. Что происхо­
дит в этом случае, мы уже знаем. После того, как вычислены значения Fib (1) и
Fib (2), может быть вычислено значение Fib (3) . Для вычисления значения вы­
ражения Fib (4) вычисляется сумма Fib (3) +Fib ( 2) , в которой нужно предвари­
тельно вычислить Fib (3) и Fib (2). Как вычисляются эти значения, мы рассма­
тривали выше. После их вычисления, вычисляется значение Fib (4) , и так далее.

с е

Python

Для проверки работы функции F ib () мы с ее помощью вычисляем и вы ­
водим в одну строку 15 первых чисел из последовательности Фибоначчи.
Ш

На заметку
По умолчанию после вывода в консольное окно функцией print () значений своих
аргументов выполняется переход к новой строке. Поэтому если вызывать подряд
несколько раз функцию print (), каждое новое сообщение (выводимый текст) бу­
дет появляться в отдельной строке. Технически причина в том, что автоматически
в конце выводимого в консоль (окно вывода) текста добавляется инструкция пере­
хода к новой строке. Чтобы изменить этот режим работы функции print () мож­
но в явном виде указать текстовое значение для аргумента end. Значение обычно
указывается по ключу, через знак равенства после названия аргумента end. Дан­
ное значение определяет символ (текст), который добавляется (вместо инструк­
ции перехода к новой строке) в конце выводимого текста. Например, в команде
print (Fib (i) ,end=" ") использована инструкция end=" ", поэтому после вы­
вода строки добавляется пробел. Переход к новой строке не осуществляется.

Хотя рекурсия и делает обычно программные коды компактными и инту­
итивно понятными, метод рекурсивного определения функции нельзя на­
звать экономным в плане использования системных ресурсов. Вместе с тем,
нередко рекурсия является единственно возможным на практике способом
реализации программного кода функции.
Рассмотрим еще один пример рекурсии. Н а этот раз перепишем пример из
листинга 3.5, в котором, напомним, представлена программа для решения
алгебраических уравнений методом последовательных приближений. Толь­
ко на этот раз функцию, с помощью которой реализуется итерационный
процесс уточнения решения уравнения, реализуем через рекурсию. Обра­
тимся к программному коду в листинге 3.8.
Листинг 3 .8 . Рекурсия для метода последовательных приближений
# О писание функции д л я реш ения у р а в н е н и я .
# И спользуем рекурсию
d e f s o l v e ( f , х О ,n ) :
# Н ачальн ое приближ ение
i f n==0:
r e t u r n хО
# Р екурси вн ое соотнош ение
e lse :
r e tu r n s o l v e ( f , f ( x O ) , n-1)
# Ф ункция, определяю щ ая у р а в н е н и е
def eq n (x ):
# З н а ч е н и е функции
r e t u r n (x * * 2 + 5 )/6
# Р еш ае м у р а в н е н и е

Ш
Ш ШчвямHHF

Глава 3. Функции

x = s o l v e ( e q n ,0 ,1 0 )
# О тображаем р е з у л ь т а т
p r i n t ( "Реш ение у р а в н е н и я :

х = ",х )

Мы немного изменили программный код: частью переписали, частью упро­
стили. Ф ункция s o l v e () предназначена для решения уравнения, опреде­
ляемого первым аргументом (ссылка на функцию уравнения f ) с началь­
ным приближением, определяемым вторым аргументом (обозначен как хО)
по количеству итераций, определяемому третьим аргументом (обозначен
как п).
Основу программного кода функции составляет условный оператор, в ко­
тором проверяется условие п==0. Что означает истинность этого условия?
Истинность этого условия означает, что мы вычисляем нулевое прибли­
жение для корня уравнения. Но нулевое приближение определяется вто­
рым аргументом хО функции s o l v e ( ) . Поэтому нет ничего удивительного
в том, что при истинном условии п==0 командой r e t u r n хО значение хО
возвращается как результат функции s o l v e (). Но если условие п==0 лож ­
но, в качестве результата возвращается выражение s o l v e ( f , f ( x 0 ) , n - 1 ) ,
в котором рекурсивно вызывается функция s o l v e ().
Ш

На заметку
Чтобы понять происхождение выражения s o lv e ( f , f l : # Условие
s * = i # У м нож ение н а и н д е к с
i - = l # У м ен ь ш ен и е и н д е к с а н а 1
r e t u r n s # Р е з у л ь т а т влож енной функции
# Влож енная функция д л я вы числения
# двойного ф акториала числа
def d f(n ):
s = l # Н ачальное зн ачен ие прои зведен и я
i= n # Н ачальное зн ачен ие и н декса
w h ile i > l : # Условие
s * = i # У м нож ение н а и н д е к с
i - = 2 # У м ен ь ш ен и е и н д е к с а н а 2

Я Ш -

Глава 3. Функции

r e t u r n s # Р е з у л ь т а т влож енной функции
# Е с л и а р г у м е н т m ode р а в е н T r u e
i f mode:
r e t u r n s f # С с ы л к а н а функцию д л я
# вычисления ф акториала
# Е с л и а р г у м е н т m ode р а в е н F a l s e
e lse :
r e t u r n d f # С с ы л к а н а функцию д л я
# вычисления двойного ф акториала
# В ы зы в а е м функцию f a c t o r () д л я в ы ч и с л е н и я ф а к т о р и а л а
p r i n t ( " 5 ! = " , f a c t o r () ( 5 ) )
p r i n t ( " 5 ! = " , f a c t o r (T rue) (5))
# В ы зы в а е м функцию f a c t o r () д л я в ы ч и с л е н и я
# двойного ф акториала
p r i n t ("5! ! = " , f a c t o r ( F a l s e ) (5))

Результат выполнения программного кода такой:
Результат выполнения программы (из листинга 3 .15 )
5! = 120
5! = 120
5 ! ! = 15

Ф ункция f a c t o r () описана с одним аргументом mode, у которого есть
значение по умолчанию True. Таким образом, функция может вызываться
без аргументов или с одним логическим аргументом. Если аргумент равен
Tr ue , функцией в качестве результата возвращается функция, предназна­
ченная для вычисления факториала числа. Если ф ункция f a c t o r () вызы­
вается с логическим аргументом F a l s e , результатом будет функция, пред­
назначенная для вычисления двойного факториала числа.
В теле функции f a c t o r () описаны две вложенные функции: функцией
s f () вычисляется факториал, а функцией d f () вычисляется двойной ф ак­
ториал. В зависимости от значения аргумента mode в условном операторе
в качестве результата функции f a c t o r () возвращается ссылка s f или d f.
Ш

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

При проверке функциональности созданной функции f a c t o r () нам на
помощь приходят команды f a c t o r () (5), f a c t o r (T ru e ) (5) (в обош т

Python

их случаях речь идет о вычислении значения 5! = 5- 4- 3- 2 - 1 = 120 ) и
f a c t o r ( F a l s e ) (5) (вы числениезначения5!! = 5 • 3 • 1 = 15).
Q

На заметку
Обратите внимание, что результатами выражений factor)), factor (True) и
factor (False) являются ссылки на функции. Поэтому данные выражения сле­
дует рассматривать как "названия" функций. Чтобы вызвать эти функции, по­
сле "названий" в круглых скобках указывается аргумент функции (в данном слу­
чае число 5). Отсюда получаем соответственно factor () (5), factor (True) (5)
и factor(False) (5).

Еще одно замечание относительно рассмотренной программы. Легко заме­
тить, что программные коды вложенных функций s f () и d f () практиче­
ски идентичны и отличаются лишь значением декремента индекса в опера­
торе цикла (величина, на которую уменьшается переменная i - при вычис­
лении обычного факториала это 1 , а при вычислении двойного фактори­
ала индекс уменьшается на 2 ). Учитывая это обстоятельство, рассмотрен­
ный ранее программный код можно было бы организовать несколько иначе.
Пример приведен в листинге 3.16.
Листинг 3 .1 6 . Факториал и двойной факториал
# Функция д л я в ы ч и с л е н и я ф а к т о р и а л а
# и двойного ф акториала
d e f fa c to r(m o d e = T ru e ):
# Влож енная функция д л я вы ч исления
# о б ы ч н о го /д во й н о го ф ак то р и ал а ч и с л а
def f (n ,d ) :
s = l # Н ачальное зн ачен ие прои зведен и я
i= n # Н ачальное зн ачен ие и н декса
w h ile i > l : # Условие
s * = i # У м нож ение н а и н д е к с
i - = d # Уменьш ение и н д е к с а
r e t u r n s # Р е з у л ь т а т влож енной функции
# Значение декрем ента для индекса
d = l i f m ode e l s e 2
# Р е з у л ь т а т функции
r e t u r n la m b d a n : f ( n , d ) # Л ям б да-ф ункция
# В ы зы в а е м функцию f a c t o r () д л я в ы ч и с л е н и я ф а к т о р и а л а
p r i n t ( " 5 ! = " , f a c t o r () ( 5 ) )
p r i n t ( " 5 ! = " , f a c t o r ( T r u e ) (5 ))
# В ы з ы в а е м функцию f a c t o r () д л я в ы ч и с л е н и я
# двойного ф акториала
p r i n t ( " 5 ! ! = " , f a c t o r ( F a l s e ) (5))

?

Глава 3. Функции

Результат выполнения этого программного кода точно такой же, как и при
выполнении программы из листинга 3.15. Что касается самого программ­
ного кода, то теперь в функции f a c t o r () описана всего одна вложенная
функция, которая называется f (), и у нее два аргумента: число п, для кото­
рого вычисляется обычный или двойной факториал, а также шаг дискрет­
ности d для уменьшения индексной переменной в операторе цикла.
Фактически, значение аргумента d определяет, какой факториал (обычный
или двойной) будет вычисляться: для обычного факториала значение ар­
гумента d равно 1, а для вычисления двойного факториала значение это­
го аргумента должно быть равно 2. В теле функции f a c t o r () командой
d = l i f mode e l s e 2 (аналог тернарного оператора) переменной d при­
сваивается значение 1 или 2 в зависимости от значения логического аргу­
мента mode функции f a c t o r (). После этого в качестве результат функци­
ей f a c t o r () возвращается ссылка на лямбда-функцию, которая задается
выражением la m b d a n : f (n , d ) .
Главная идея в том, что вложенной функции при вызове f () передаются
два аргумента, в то время как в качестве результата функция f a c t o r ()
должна возвращать функцию, у которой один аргумент. Поэтому возвра­
щать ссылку на функцию f () в качестве результата функции f a c t o r () мысль плохая. Мы поступаем иначе: создаем анонимную функцию с одним
аргументом, которая подразумевает вызов функции f () с этим же первым
аргументом, а второй аргумент функции f () - ранее определенное число­
вое значение d.
Наконец, еще один способ решения все той же задачи проиллюстрирован в
листинге 3.17.
Листинг 3 .1 7 . Еще один способ вычислить факториал
# Функция д л я в ы ч и сл е н и я ф а к т о р и а л а ч и с л а
def fa c to ria l (n):
i f n = = l:
re tu rn 1
e l s e : # Рекурсия
re tu rn n * fa c to ria l(n -1 )
# Функция д л я в ы ч и с л е н и я д в о й н о г о ф а к т о р и а л а
def d fa c to ria l (n):
i f n==l o r n==2:
re tu rn n
e l s e : # Рекурсия
re tu rn n * d fa c to ria l(n -2 )
# Функция д л я в ы ч и сл е н и я ф а к т о р и а л а
# и двойного ф акториала

Python

def

fa c to r(m o d e = T ru e ):
# Р е з у л ь т а т - с с ы л к а н а внешнюю функцию
r e t u r n f a c t o r i a l i f m ode e l s e d f a c t o r i a l
# В ы з ы в а е м функцию f a c t o r () д л я в ы ч и с л е н и я ф а к т о р и а л а
p r i n t ( " 5 ! = " , f a c t o r () ( 5 ) )
p r i n t ( " 5 ! = " , f a c t o r (T rue) (5))
# В ы з ы в а е м функцию f a c t o r () д л я в ы ч и с л е н и я
# двойного ф акториала
p r i n t ( "5! ! = " , f a c t o r ( F a l s e ) (5))

В программном коде описаны две (внеш ние) функции: f a c t o r i a l () для
вычисления факториала числа n d f a c t o r i a l O для вычисления двойного
факториала числа. При описании этих функций использовалась рекурсия.
При этом программный код функции f a c t o r () исключительно простой
и состоит всего из родной команды, которой в качестве результат функции
возвращается выражение f a c t o r i a l i f m o d e e l s e d f a c t o r i a l . Это
выражение основано на тернарном операторе и его значение равно f a c t o ­
r i a l , если аргумент m o d e принимает значение T r u e . В противном случае
результат выражения равен d f a c t o r i a l . И в том, и в другом случае речь
идет о возвращении ссылки на внешнюю (по отношению к функции f a c ­
t o r () ) функцию. Результат выполнения программного кода такой же, как
и в предыдущих случаях.
Очередной пример, который мы рассмотрим здесь - это функция, которой
аргументом передается функция, и которая в качестве результат возвраща­
ет функцию. Если более конкретно, то речь пойдет о вычислении произво­
дной.
На заметку
Производной для функции f ( j x ) называется некоторая функция (обозначаdf
ется как / ' 0 0 или
которая равна пределу отношения разности значе­
ний функции f(jx~) к приращению аргумента Ах при стремлении последнего к

, . _ / ( * + Д * )-/(* ) „

нулю: т W =
----------- — -----------. Для нас же важно следующее: существу­
ет правило (правило вычисления производной), на основе которого одной функ­
ции можно в соответствие поставить другую функцию (которая называется про­
изводной). Например, если f ( x ) = х 2, то f ' ( x ) = 2х, а если f ( x ) = — — , то
/ , ( ж ) = ~ ( 1 + х ) 2.
Вообще процедура вычисления производной - операция аналитическая. Но на
практике часто прибегают к вычислению производной в приближенном виде, для
чего выбирается малое, но конечное, приращение аргумента Ах , и в точке X про-

п,

/( * + А*) —/(х)

изводная / ' ( * ) вычисляется как отношение г ( * ) ~ ----------- --------------- . Именно
таким подходом мы воспользуемся для вычисления производной в программном
коде.

1Ш 1

Глава 3. Функции

В листинге 3.18 приведен пример программы, в которой описана функция,
позволяющая вычислять (в приближенном виде) производную.
Исходная (дифференцируемая) функция передается аргументом, а произ­
водная функция возвращается как результат.
Листинг 3.1 8 . Вычисление производной
# Фу нк ци я д л я в ы ч и с л е н и я п р о и з в о д н о й
d ef D (f ):
# Вложенная функция. Вычисляет
# приближенное зн а ч е н ие производной
d ef d f (х ,dx=0.001):
# Р е з у л ь т а т вложенной функции
r e tu r n ( f ( x + d x ) - f ( x ) ) /dx
# Р е з у л ь т а т функции - п р о и з в о д н а я
return df
# П ервая функция д л я диф ференцирования
def f 1 (х ):
r e t u r n х**2
# В торая функция дл я диф ференцирования
def f 2 (х ):
r e t u r n 1 / (1+х)
# Фу нк ци я д л я о т о б р а ж е н и я п р о и з в о д н о й в
# н е с к о л ь к и х т о ч к а х . Аргументы т а к и е :
# F - пр о и зво д н ая (приближенная)
# Nmax - к о л и ч е с т в о т о ч е к ( м и н у с о д и н )
# Хшах - п р а в а я г р а н и ц а п о а р г у м е н т у
# dx - приращение а р г у м е н т а
# f - производная (аналитически)
d e f show (F,N m ax,X m ax,dx,f):
# Точки, в которых вы ч и сл яе тс я прои звод н ая
f o r i in range(Nmax+1):
x=i*Xmax/Nmax # З н а ч е н и е а р г у м е н т а
# Приближенное и т о ч н о е з н а ч е н и е п р о и зв о д н о й
p r i n t (F (х) , F ( х , d x ) , f (х) , s e p = " - > ")
# П рои звод н ая д л я п е р в о й функции
Fl=D (fl)
# П рои звод н ая д л я в т о р о й функции
F 2 = D ( f 2)
# Значения в разных точках
# п р о и зв о д н о й дл я п е р в о й функции
p r i n t ("Производная ( х * * 2 )'= 2 х :" )
s h o w ( F 1 , 5 , 1 , 0 . 0 1 , l a m b d a х : 2*х)

Python

# Значения в разных точках
# п р о и з в о д н о й дл я в т о р о й функции
p r i n t ( "Производная ( 1 / (1 + х )) ' = - 1 / (1 + х )* * 2 :")
sh o w (F 2 ,5 , 1 , 0 . 0 1 , lambda х: -1 /(1 + х )* * 2 )

Результат получаем такой:
Результат выполнения программы (из листинга 3 .18)
Производная

0.001

- >

(х**2)'= 2х:

0.01

- >

0.0

0.4009999999999986 -> 0.4099999999999999 -> 0.4
0.8009999999999962 -> 0.8099999999999996 - > 0 . 8
1.2010000000000076 -> 1 .2 1 -> 1.2
1.6009999999999636 -> 1.6100000000000003 -> 1.6
2.0009999999996975 -> 2.0100000000000007 -> 2 .0
Производная ( 1 / (1 + х )) ' = - 1 / (1 + х )* * 2 :
-0.9990009990008542 -> -0.990099009900991 -> - 1 . 0
-0.6938662225923764 -> -0.688705234159781 ->
-0.6944444444444444
-0.5098399102682061 -> -0.5065856129686019 ->
-0.5102040816326532
-0.39038 1 0 1 1 8 6 7 6 1 3 2 -> -0 .3 8 8 1 9 8 75776396895 ->
-0.39062499999999994
-0 .3 0 84706027516315 -> -0 .3 0 6 9 3 6 77102516803 ->
-0.30864197530864196
-0.2498750624687629 -> -0.24875621890546595 -> - 0 .2 5

У функции D (), предназначенной для вычисления производной, один аргу­
мент, который обозначен как f и который отождествляет название диф ф е­
ренцируемой функции. В теле функции D () описана вложенная функция
d f () с двумя аргументами. Первый аргумент х обозначает тот аргумент, ту
точку, в которой вычисляется производная. Второй аргумент dx обозначает
приращение по аргументу, на основании которого вычисляется выражение
для производной. У аргумента dx имеется значение по умолчанию. В теле
вложенной функции d f () возвращается (как результат ф ункции) значе­
ние выражения ( f (x+dx) - f (х) ) / d x (отношение разности значений диф ­
ференцируемой функции к приращению аргумента). Ф ункция D ( ), в свою
очередь, возвращает в качестве результата ссылку d f на вложенную ф унк­
цию d f ().
Также в программе описываются две функции для вычисления на их осно­
ве производных. Речь идет о функции f 1 () (соответствует зависимости

f(jx) = х2 ) и функции f 2 () (соответствует зависимости /(*) = Y+lc )• П роиз­
водные для этих функций вычисляются командами F1=D ( f 1) и F2=D ( f 2)
Л И Я --

Глава 3. Функции

соответственно. После выполнения этих команд переменная F1 ссылается
на функцию, соответствующую производной для функции f 1 (). Аналогич­
но, переменная F2 является ссылкой на производную для функции f 2 ( ) .
Причем функциям F1 () и F2 () при вызове можно передавать один или два
аргумента. Первый аргумент определяет точку, в которой вычисляется про­
изводная, а второй аргумент, если он задан, задает приращение по аргумен­
ту для вычисления производной.
Для вычисления и отображения значений производных функций в не­
скольких точках используется функция show (). Ф ункция описана со сле­
дующими аргументами:


через F обозначена функция для вычисления производной в число­
вом виде (на этой позиции при вызове функции указываются назва­
ния F1 и F2 функций F1 () и F2 () );



переменная Nmax обозначает количество интервалов, на которые
разбивается диапазон изменения аргумента при вычислении произ­
водной (это число на единицу меньше количества точек, в которых
вычисляется производная);



переменная Хшах обозначает правую границу диапазона изменения
аргумента (левая граница равна нулю);



через dx обозначено приращение аргумента;



через f обозначено имя функции, которая определяет аналитиче­
ское значение для производной.

При вызове функции show () последним аргументом передаются лямбдафункции: lam b d a
х:
2 *х (соответствует зависимости / '( * ) = 2х )
и lam b d a х : - 1 / ( 1 +х) **2 ( /'( * ) = - ^ + ху )- При выполнении кода
функции show () отображается три значения:

?



значение производной, вычисленное на основе используемого по
умолчанию приращения аргумента (функции F1 () и F2 () вызыва­
ются с одним аргументом);



значение производной, вычисленное на основе явно указанного зна­
чения для приращения аргумента (функции F1 () и F2 () вызыва­
ются с двумя аргументами);



значение производной, вычисленное на основе аналитического вы­
ражения.

-■Д 1 1

Python

Как видим, в основном результаты вычисления производных числовыми
методами неплохо коррелируют с точными (вычисленными на основе ана­
литических выражений) значениями.

Резюме
Мастера не мудрствуют,

из к/ф "Покровские ворота"

1. При описании функции используем идентификатор d e f , после которо­
го указывается имя функции, список аргументов (в круглых скобках) и,
после двоеточия, программный код функции.
2. Инструкция r e t u r n в теле функции приводит к завершению выполне­
ния программного кода функции, а значение, указанное после инструк­
ции r e t u r n , возвращается в качестве результата функции.
3. При описании функции создается объект типа f u n c t i o n . И мя функции
является ссылкой на объект функции. Ссылка на объект функции мо­
жет присваиваться переменной. В этом случае переменная будет ссыл­
кой на функцию, и эта переменная может использоваться как имя ф унк­
ции.
4. Имя функции может передаваться аргументом другой функции.
5. Ф ункция может возвращать в качестве результат функцию. В этом слу­
чае возвращается ссылка на функцию-результат.
6 . У аргументов могут быть значения по умолчанию. Значение аргументов

по умолчанию указываются через знак равенства после имени аргумен­
тов. Аргументы со значениями по умолчанию указываются в списке ар­
гументов функции последними.
7. При описании функции в теле функции можно использовать вызов опи­
сываемой функции (обычно с другими аргументами). Такая ситуация
называется рекурсией.
8 . Лямбда-функция или анонимная функция - это функция без имени. Та­

кие функции могут использоваться, например, для передачи аргумен­
том в другие функции или возвращаться в качестве результата функции.
Описывается лямбда-функция с помощью ключевого слова lam b d a,
после которого указывается список аргументов и, через двоеточие, вы­
ражение, которое является результатом лямбда-функции.
9. Если переменной присваивается значение в теле функции, то такая пе­
ременная является локальной. Она доступна только в теле функции.
Если переменная в теле функции входит в выражения, но значение ей

ш т

Глава 3. Функции

не присваивается, то такая переменная интерпретируется как глобаль­
ная. Чтобы явно объявить переменную в теле функции как глобальную,
используют ключевое слово g l o b a l .
10. В теле функции могут описываться другие функции. Такие функции на­
зываются вложенными. Вложенные функции имеют доступ к перемен­
ным в теле вешней функции.

?

-fS J

Глава 4
Работа со списками и
кортежами

Python

Вам трудно угодить. Но я все-таки попро­
бую.

и зк/ф "Служебныйроман"

В этой главе мы поближе познакомимся со списками и кортежами. Хотя
если честно, то глава в основном посвящена спискам. С кортежами у нас со­
стоится достаточно поверхностное, "шапочное" знакомство. Причина в том,
что кортежи, на самом деле, очень "близки" к спискам, причем списки во
многих отношениях являю тся более "гибким" типом данных, по сравнению
с кортежами. Можно даже утверждать (с некоторой натяжкой), что корте­
жи - это такие особые списки. Поэтому мы сначала познакомимся со спи­
сками, определимся с основными методами их использования, а уже затем
кратко остановимся на кортежах.

Знакомство со с п и с ка м и
Огласите весь список, пожалуйста!

из к/ф "Операция Ы и другие приключе­
ния Шурика"

Хотя списки мы подробно не обсуждали, но нам уже приходилось с ними
сталкиваться. Но там, в предыдущих главах, мы в детали особо не вдава­
лись. Теперь пришло время посвятить спискам больше внимания. И, надо
сказать, они того стоят.

Список - это упорядоченный набор элементов. В некотором смысле списки
в Python играют роль, аналогичную роли массивов в иных языках програм­
мирования. Но это только "в некотором смысле". На самом деле список в
Python - это исключительно гибкая, эффективная и где-то даже уникаль­
ная штука. Достаточно сказать, что элементами списка могут быть объек­
ты (данные) разного типа. Более того, элементами списка, в свою очередь,
могут быть списки. То есть с помощью списков мы можем создавать вло­
женную структуру практически любой (в разумных пределах, разумеется)
сложности.
Q

На заметку
Если мы говорим о списке как таком, то речь идет о типе l i s t . Но у списка есть
элементы. И каждый из таких элементов может относиться к какому-то опреде­
ленному типу.

ш

в

Глава 4. Работа со списками и кортежами

Как это было с числовыми и текстовыми значениями, ссылка на список за­
писывается в переменную. Такую переменную мы обычно и будем называть
списком - если это не будет приводить к недоразумениям. Вообще же пе­
ременная ссылается на список. Сам список создается, в общем-то, просто.
Достаточно перечислить элементы списка через запятую, а всю эту после­
довательность элементов заключить в квадратные скобки. Например, ко­
мандой n u m s = [ 1 , 2 , 3 ] создается список из трех элементов, которые явл я­
ются целыми числами ( 1 , 2 и 3). Также можем воспользоваться для соз­
дания списка функцией l i s t (). В этом случае аргументами функции пе­
редаются элементы списка. Особенно удобно воспользоваться функци­
ей l i s t () для создания списков на основе текстовых значений: если ар­
гументом функции l i s t () передать текст, то в результате получим спи­
сок, элементы которого - буквы, формирующие текст. Так, при выполне­
нии команды s y m b s = l i s t ( " P y t h o n " ) создается список из букв Р, у,
t , h, о и п. Список может быть и более "изысканным": скажем,командой
d a t a = [ " t e x t " , 100, [ 5 , 10] ] создается список d a t a из трех элементов,
причем первый элемент списка - это текст " t e x t ", второй элемент списка целое число 1 0 0 , а третий элемент - список из двух элементов [ 5 , 1 0 ] .
Q

На заметку
Создавать списки можно с помощью специальных генераторов списков. В ква­
дратных скобках указывается выражение, зависящее от индексной переменной,
которая пробегает определенные значения. Диапазон изменения индексной пере­
менной также указывается в квадратных скобках. Например, командой pows_of_
two=[2**i for i in range (11) ] создается список из десяти элементов, пред­
ставляющих собой степени двойки. Если после создания списка выполнить коман­
ду print (pows of two), получим такой результат: [1, 2, 4, 8, 16, 32, 64
, 128, 256, 512, 1024 ]. В данном случае при формировании списка элементы
списка определяются выражением 2**i, и при этом переменная i пробегает зна­
чения в диапазоне от О до 10 включительно (инструкция for i in range (11)).
Каждому индексу i соответствует элемент в списке, а значение элемента равно
2**i.

Кроме диапазона изменения индексной переменной можем указать условие, ко­
торое должно выполняться для включения элемента в список. Как иллюстрацию
рассмотрим команду nums_list= [7*i+l for i in range(20) if i%4==3], ко­
торой создается список [22, 50, 78, 106, 134] (чтобы увидеть этот список,
разумно воспользоваться командой print (nums list)). Полученный список со­
стоит из чисел, которые при делении на 7 дают в остатке 1, а при делении на 4 це­
лой части от деления на 7 дают в остатке 3 (то есть если вычислить целую часть от
деления числа на 7 и поделить ее на 4, то в остатке будет 3). Как получается спи­
сок? Индекс i пробегает значения из диапазона от О до 19 включительно, и если
для текущего значения индекса i выполняется условие i%4==3 (остаток от деле­
ния индекса i на 4 равен 3), то по формуле 7*i+l вычисляется очередной эле­
мент списка.

СЕВ

Python

Обращение к элементу списка выполняется по индексу элемента. Элемен­
ты индексируются, начиная с нуля (то есть самый первый элемент имеет
нулевой индекс). Индекс указывается в квадратных скобках после имени
переменной, которая ссылается на список. Если воспользоваться приведен­
ными выше примерами, то, скажем, инструкция nums [ 0 ] является ссыл­
кой на первый элемент списка nums (то есть значение 1 ). Д ля обращения
к третьему (по порядку) элементу списка sym bs (буква t ) используем ин­
струкцию s y m b s [2].
При обращении к элементу списка можно указывать отрицательный ин­
декс - в этом случае элементы "отсчитываются" начиная с конца списка.
Так, самый последний элемент будет иметь индекс - 1 , предпоследний эле­
мент будет иметь индекс - 2 , и так далее.
Ш

На заметку
Определить количество элементов в списке позволяет функция le n (). Список пе­
редается аргументом функции: например, результатом выражения le n (symbs)
является значение 6 - количество элементов в списке symbs. Аналогично, резуль­
татом выражения le n (d a ta ) будет значение 3, поскольку в списке d a ta всего 3
элемента: (обратите внимание: хотя последний элемент в списке d ata сам явля­
ется списком, "учитывается" он именно как один элемент).
Поскольку индексация элементов списка начинается с нуля, то последний элемент
списка имеет индекс, на единицу меньший, чем длина списка.

Список можно изменять поэлементно. Другими словами, обращаясь к эле­
менту списка, мы можем не только "прочитать" значение элемента, но и из­
менить его. Например, после выполнения команды nums [ 1 ] = - 1 0 значение
второго (по порядку) элемента списка nums станет равным - 1 0 , а сам спи­
сок, (который до этого был [ 1 , 2 , 3 ] ) станет [ 1 , - 1 0 ,3 ].
При работе со списками мы можем обращаться сразу к нескольким элемен­
там. В этом случае говорят о получении среза. Фактически, речь идет о том,
что мы берем список, и "извлекаем" из него некоторую часть: все элемен­
ты, индексы которых попадают в указанный диапазон. Для большей кон­
кретики представим, что у нас есть некоторый с п и со к . Если мы хотим из­
влечь из этого списка группу элементов (подсписок) с индексами от i -го до
j -го включительно, то соответствующая инструкция будет выглядеть как
с п и с о к [ i : j + 1 ]. То есть в том месте, где мы раньше указывали индекс, те­
перь указывается, разделенные двоеточием, два индекса, которые и опреде­
ляют элементы среза. Например, значением выражения sym bs [ 1 : 4 ] явл я­
ется список из элементов sym bs [ 1 ], sym bs [ 2 ] и sym bs [ 3] , то есть спи­
сок из букв у, t и h.

(УВЕ)-

*

Глава 4. Работа со списками и кортежами

Щ

На заметку
Обратите внимание: если извлекаются элементы с индексами от i до j включи­
тельно, то в квадратных скобках перед двоеточием указывается индекс i перво­
го извлекаемого элемента, а после двоеточия указывается индекс j+ 1 - то есть
этот индекс на единицу больше, чем индекс последнего из извлекаемых элемен­
тов. Еще одно уточнение касается смысла, которым мы наделяем слово "извлека­
ется": важно понимать, что исходный список не меняется. Каким он был, таким и
останется после процедуры получения среза.

Процедура получения среза проста, удобна и часто позволяет создавать
эффективные программные коды. Существует несколько правил, которые
окажутся вполне полезными для эффективного выполнения процедуры по­
лучения среза. Д ля удобства восприятия мы выделим основные форматы, в
которых может выполняться срез. Сначала рассмотрим команду получения
среза вида с п и с о к [ i : j ], то есть когда после имени списка в квадратных
скобках (через двоеточие) указывается два индекса, причем оба эти индек­
са неотрицательные. В этом случае имеет смысл помнить, что:

*



Результатом инструкции с п и с о к [ i : j ] является список, состоя­
щий из элементов с индексами, которые больше или равны i и мень­
ше j . Например, если ш= [ 1 , 5 , 1 0 , 1 5 , 2 0 , 2 5 ] , то результатом вы­
ражения m [ 2 : 5 ] является список [ 1 0 , 1 5 , 2 0 ] (элементы со 2-го
по 4-й включительно).



Если индексы i или j превышают длину списка (количество эле­
ментов в с п и с к е можно определить с помощью инструкции вида
1 еп ( с п и с о к ) ), то соответствующий индекс (или индексы) не­
явно заменяется на значение 1 еп ( с п и с о к ) (то есть слишком
большие индексу автоматически "урезаются"). Так, для списка
ш = [ 1 , 5, 1 0 , 1 5 , 2 0 , 2 5 ] результатом выражения ш [ 2 : 1 0 0 ] будет
значение [ 1 0 , 1 5 , 2 0 , 2 5 ] , поскольку при вычислениях второй ин­
декс 100 автоматически заменяется на значение 6 (значение выра­
жения 1 еп (ш) равно б).



Если не указать первый индекс i , он по умолчанию считается ну­
левым: например, команда с п и с о к [ : j ] эквивалентна команде
с п и с о к [ 0 : j ]. Д ля списка ш командой ш [: 4 ] возвращается список
из элементов с индексами от 0 до 3 включительно (получаем список
[ 1 , 5 , 1 0 , 1 5 ] , как для команды ш [ 0 : 4 ] ).



Если не указать второй индекс j , то он по умолчанию равен дли­
не списка: например, команда с п и с о к [ i : ] эквивалентна команде
с п и с о к [ i : l e n ( с пис о к ) ]. Д ля списка ш результатом выражения

",А.
first) есть ссылки на поле first экземпляра класса и поле total объекта
класса, причем обе ссылки выполняются через экземпляр А.
По
результату
выполнения
команды
(сообщение
Класс
MyClass -> Экземпляр А в окне вывода) видим, что проблем с доступом
к полям не возникает. В принципе, нечто подобное мы наблюдали в пред­
ыдущих примерах, но здесь есть одна особеннбость. Связана она с тем, что
поле total появилось у класса MyClass уже после того, как были созда­
ны экземпляры А и В. Но, тем не менее, доступ к добавленному полю total
через экземпляры класса имеется. А вот добавление поля одному экземпля­
ру класса для другого экземпляра "остается незамеченным" (что, в принци­
пе, вполне логично).

Python

При попытке выполнить команду print (A. second) возникает ошибка
класса AttributeError: поля second у экземпляра А нет. Эту ситуацию
мы обрабатываем с помощью блока try-except. Результатом является со­
общение Такого поля у экземпляра А н е т !, которое отображается
командой print ("Такого поля у экземпляра А нет!") в exceptблоке.
Аналогично обстоят дела с экземпляром В: доступ к полю total класса
MyClass и полю second экземпляра через переменную В получаем без
проблем, а к полю first экземпляра доступа нет, поскольку это поле добав­
лялось в экземпляр А, и экземпляр В к данному полю никакого отношения
не имеет.
На следующем этапе командой del MyClass.total удаляем поле total
объекта класса MyClass, после чего командой print (A. total) (в tryблоке) пытаемся прочитать значение этого поля. Судя по тому, что в этом
случае возникает ошибка класса AttributeError (неверный атрибут) и
в except-блоке выполняется команда print ("Такого поля нет!"),
поле total действительно удалено из объекта класса MyClass. Нет досту­
па к удаленному полю total и через переменную В.
Наконец, командой d e l A.f i r s t удаляем поле f i r s t экземпляра А. Как
следствие, при попытке выполнить команду p r i n t ( A .f i r s t ) в t r y -блоке
возникает ошибка. Поэтому в e x c e p t -блоке выполняется команда p r i n t (
"Такого поля у э к з е м п л я р а А нет!").
Ш

На заметку
Чтобы легче было понять, что и как происходит при добавлении и удалении полей
объекта класса и экземпляров класса, следует учесть, что поля и их значения для
каждого экземпляра класса и объекта класса хранятся в словаре. Поле является
ключом, а значение поля - элементом словаря. При обращении к полю экземпля­
ра поиск сначала выполняется по словарю экземпляра класса, и если там нет со­
ответствующего ключа (напомним, что это на самом деле название поля), то поиск
перемещается к словарю с названиями и значениями полей объекта класса. До­
бавляя или удаляя поля, мы вносим изменения в соответствующий словарь. Поэ­
тому экземпляры, созданные до внесения изменений в объект класса, "знают" об
этих изменениях.

ш

Глава 6. Основы объектно-ориентированного программирования

М етоды и ф ун кц ии
Форму будете создавать под моим личным
контролем. Форме сегодня придается боль­
шое содержание,

из к/ф "Чародеи"

В этом разделе мы обсудим методы. Тема эта нетривиальная, но мы поста­
раемся выделить основные, так сказать, концептуальные моменты, которые
позволят читателю более-менее свободно ориентироваться в том богатстве
приемов и подходов, которые имеются в его распоряжении при программи­
ровании на Python. Начнем с небольшого примера, представленного в л и ­
стинге 6 . 10 .
Листинг 6 .1 0 . Метод экземпляра и функция класса
# Создаем к л асс
c l a s s M y C lass:
# М е то д э к з е м п л я р а
def s a y (s e lf):
# О то б р аж ается сообщ ение
p r i n t ("В сем п р и в е т ! " )
# С оздаем экзем п ляр к л а с с а
o b j = M y C l a s s ()
# В ы зы в а е м м е т о д э к з е м п л я р а .
# А ргум ентов нет
o b j . s a y ()
# В ы зы в а е м функцию к л а с с а .
# А ргум ент - э к зе м п л я р к л а с с а
M y C lass. s a y ( o b j )
# В ы зы в а е м функцию к л а с с а .
# А ргум ент - т е к с т
M y C la ss. s a y ("К акой -то т е к с т " )

Результат выполнения программного кода приведен ниже:
Результат выполнения программы (из листинга 6 .1 0 )
Всем п р и в е т !
Всем п р и в е т !
Всем п р и в е т !

Мы создаем класс M y C lass, в котором описываем метод экземпляра класса
s a y ( ). В теле метода всего одна команда p r i n t ( "В сем п р и в е т ! " ) , кото­
рой, очевидно, в окне вывода отображается сообщение. Хотя у метода есть
аргумент s e l f , этот аргумент явно не используется.

•#ИЛ

Python

В данном классе нет ничего необычного. Тем не менее, мы все же проведем
небольшой эксперимент. Д ля этого командой ob j=MyClass () создаем эк­
земпляр ob j . Затем из экземпляра вызываем метод say () (имеется в виду
команда ob j .say () ). Результат вполне ожидаем: отображается сообщение
Всем привет !. Затем последовательно выполняются команды MyClass .
say(obj) и MyClass .say ( "Какой-то текст"). Результат точно такой
же, как при выполнении команды ob j . say (). Попробуем разобраться, по­
чему же происходит именно так.
Начнем с команды M y C lass . s a y ( ob j ). В известном смысле она являет­
ся воплощением команды o b j . s a y (). Дело в том, что когда мы описыва­
ем в классе M y C la ss метод экземпляра s a y (), на самом деле мы описыва­
ем функцию s a y (). Это, в общем-то, самая обычная функция, просто опи­
сывается в теле класса. Ранее мы такие функции называли методами и вы­
зывали через экземпляр класса. Здесь мы вызываем функцию, указав вме­
сто экземпляра класса сам класс. Так тоже можно делать.
Ш

На заметку
Чтобы не запутаться, будем называть say () функцией, если речь идет о команде
MyClass . say (obj), и методом, если речь идет о команде obj . say ().

С формальной точки зрения главная особенность функции say () в том,
что при обращении к ней мы указываем еще и имя класса MyClass. Вы­
зов функции say () через имя класса - это как если бы мы вызвали самую
обычную функцию: командой MyClass .say (obj ) вызывается функция
say (), описанная в теле класса MyClass, а аргументом этой функции пе­
редается ссылка на экземпляр класса o b j .
Когда мы говорим о вызове метода say (), то подразумеваем, что вызов
осуществляется через ссылку на экземпляр класса. Как мы уже знаем, эк­
земпляр класса неявно передается первым (и в данном случае единствен­
ным) аргументом в say (). Поэтому команда obj . say () эквивалента вы­
зову функции say () из класса MyClass с аргументом obj. Следователь­
но, нет ничего удивительного в том, что выполнение команд MyClass.
say (obj ) и obj .say () приводит к одинаковым результатам. Что касает­
ся команды MyClass . say ( "Какой-то т е к с т " ), то в известном смысле
это "хулиганство", хотя и вполне законное.
Мы вызываем функцию say () из класса MyClass с аргументом, который
является текстовым значением, хотя по логике аргументом должна бы быть
ссылка на экземпляр класса. Но поскольку в теле функции say() ссыл­
ка на экземпляр класса явно не используется (то есть аргумент у функции
имеется, но в теле функции не используется), то не имеет особого значения,
т т

-

Глава 6. Основы объектно-ориентированного программирования

что же мы передаем аргументом функции - главное, чтобы аргумент просто
был.
Вообще, ситуация не такая простая, как может показаться на первый взгляд.
Чтобы ее немного прояснить, имеет смысл напомнить, что функция в P y­
thon - это некоторый объект (в общем смысле этого термина), а если точнее,
то объект типа function. Поэтому на функцию, описанную в классе, мож­
но смотреть именно как на такой объект - по аналогии к тому, как мы описы­
вали в теле класса переменные, которые играли роль полей объекта класса.
Имя функции, описанной в теле класса, сродни имени обычной перемен­
ной, описанной в теле класса. Как и в случае поля объекта класса, при ссыл­
ке на функцию, описанную в теле класса, имя класса через точку указывает­
ся перед именем функции. В контексте сказанного инструкция MyClass .
say является ссылкой на объект типа function.
Ш

На заметку
Желающие могут провести такой эксперимент: в программный код, описанный
выше, добавить команду print (type (MyClass. say)). В результате в окне выво­
да появится сообщение . Результатом выполнения команды
print (type (obj .say) ) будет сообщение cclass 'method' >.

Мы можем рассматривать имя функции как некоторый атрибут класса, к
которому обычно обращаются с указанием круглых скобок (и аргументов в
них). В частности, если функция, описанная в теле к л а с с а , вызывается че­
рез ссылку на к л а с с (то есть в формате к л а с с , функция (аргументы) ),
то фактически подразумевается вызов функции с соответствующими
аргументами.
По большому счету, здесь речь идет об обычной функции, но только "привя­
занной" к классу. В таком контексте мы можем описать в теле класса прак­
тически любую функцию и вызывать ее как обычную функцию, но только
используя в названии явную ссылку на класс. С дугой стороны понятно, что
обычно не для этого функции описываются в классе. По крайней мере, ра­
нее мы вызывали функции, описанные в теле класса, через ссылку на экзем­
пляр класса и называли такие функции методами. Здесь, разумеется, нет
противоречия.
Функции, описанные в теле класса, можно вызывать не только через класс,
но и через экземпляр класса. В известном смысле при этом мы можем гово­
рить о вызове той же самой функции, что и при вызове через имя класса, но
через экземпляр класса обращение к функции осуществляется не так "пря­
молинейно".

?

Python

Что же происходит (в общих чертах), когда мы вызываем функцию через
экземпляр класса (то есть вызываем метод)? Другими словами, как функ­
ция становится методом? А происходит примерно следующее.


Среди названий функций, описанных в теле класса, выполняет­
ся поиск имени метода, который вызывается из экземпляра класса.
Предполагаем, что функция с соответствующим названием найдена
(если нет, то возникает ошибка).



Создается объект метода (объект класса m e th o d ). Этот объект соот­
ветствует функции, вызываемой с аргументами, которые передают­
ся методу при вызове, но дополнительно первым аргументом добав­
ляется ссылка на экземпляр класса, из которого вызывается метод.

Ш

На заметку
Проще говоря, команда вида экземпляр.имя (аргументы) эквивалентна команде
класс.имя(экземпляр,аргументы).

В дальнейшем для нас наиболее важными будут две позиции:


При вызове метода первым аргументом неявно передается ссылка
на экземпляр класса.



И мя ф ункции/метода является атрибутом, которому можно при­
своить значение.

Что касается первого пункта, то он нам был известен и до этого. По поводу
второго пункта потребуются некоторые пояснения. Представить их лучше
всего на конкретном примере, что мы и сделаем. Например, совсем необяза­
тельно описывать метод экземпляра класса или функцию класса непосред­
ственно в теле класса. Мы можем описать соответствующую функцию от­
дельно от описания класса, а затем "зарегистрировать" ее как метод экзем­
пляра или как функцию класса. С помощью инструкции d e l метод экзем­
пляра класса или функцию класса можно удалить. Чтобы понять, как вы ­
полняются такие операции, рассмотрим листинг 6 . 11 .

Листинг 6.11. Добавление и удаление методов
# Создаем к л асс
c l a s s M y C lass:
pass
# Создаем экзем пляры к л а с с а
A = M y C la s s ()
B = M y C la s s ()
C = M y C la s s ()

Глава 6. Основы объектно-ориентированного программирования

# С о з д а е м п е р в у ю функцию
d ef h e l l o ():
p r i n t ("М етод э к з е м п л я р а - ' h e l l o ' " )
# С о з д а е м в т о р у ю функцию
d e f h i () :
p r i n t ("Ещ е о д и н м е т о д - ' h i ' " )
# О пределяем м етод эк зем п л яр а
A .s a y = h e llo
# О пределяем м етод эк зем п л яр а
С. say=hi
# В ы зы в а е м м е т о д э к з е м п л я р а
A . s a y ()
# В ы зы в а е м м е т о д э к з е м п л я р а
try :
В . s a y ()
# Если т а к о г о м ето д а нет
except A ttrib u te E rro r:
p r i n t ("Т акого м етода н ет")
# В ы зы в а е м м е т о д э к з е м п л я р а
С . s a y ()
# В ы зы в а е м функцию к л а с с а
try :
M y C l a s s . s a y ()
# Если т а к о й функции н ет
except A ttrib u te E rro r:
p r i n t ("Т ак ой функции н е т " )
# Удаляем м ето д э к зе м п л я р а
d e l A .s a y
# В ы зы в а е м м е т о д э к з е м п л я р а
try :
A . s a y ()
# Если т а к о г о м ето д а н ет
except A ttrib u te E rro r:
p r i n t ("Т акого м етода н ет")
# В ы зы в а е м м е т о д э к з е м п л я р а
С . s a y ()

Результат выполнения этого программного кода такой:

Результат выполнения программы (из листинга 6.11)
М етод э к з е м п л я р а - ' h e l l o '
Такого м етода нет
Еще о д и н м е т о д - ' h i '
Т акой функции н ет
Такого м етода нет
Еще о д и н м е т о д - ' h i '

fH

Python

Мы создаем класс с традиционным названием M y C lass. В классе в дан­
ном конкретном случае ничего не описано (ни полей, ни методов). Далее на
основе этого класса создается три экземпляра: А, В и С. Также мы описыва­
ем две функции h e l l o () и h i ( ) , у которых нет аргументов и которые не
возвращают результат.
При выполнении каждой из этих функций в области вывода отображает­
ся сообщение (для каждой функции свое). До этого момента не происхо­
дит ничего необычного. Откровенная "экзотика" начинается с команды
A . s a y = h e l l o , которой атрибуту s a y экземпляра А присваивается в каче­
стве значения идентификатор h e l l o - имя одной из созданных нами функ­
ций. Что все это означает и что при этом происходит?
Д ля ответа на эти вопросы следует вспомнить, что функция реализуется че­
рез специальный объект, а имя функции - это ссылка на данный объект. По­
этому в данном случае идентификатор h e l l o является ссылкой на объект,
через который реализуется функция h e l l o ().
С другой стороны, если атрибуту s a y экземпляра А присваивается ссыл­
ка на объект, реализующий функцию, то указанный атрибут будет ссылать­
ся на этот объект. Значит, атрибут s a y можно рассматривать как функцию,
причем это та же функция, что и функция h e l l o (). Поэтому, когда выпол­
няется команда A . s a y ( ) , на самом деле вызывается функция h e l l o ().
Нечто похожее происходит при выполнении команды С . s a y = h i , с поправ­
кой лишь на то, что речь идет об экземпляре С (а не А ) и функции h i () (а
не h e l l o () ). В результате при выполнении команды С . s a y () вызывается
функция h i (). А вот при выполнении команды В . s a y () возникает ошиб­
ка (типа A t t r i b u t e E r r o r ) , так как атрибута s a y у экземпляра В нет. Точ­
но так же, как нет такого атрибута и у объекта класса M y C l a s s . Поэтому ко­
манда M y C l a s s . s a y () тоже приводит к ошибке.
Чтобы удалить атрибут s a y у экземпляра А, используем команду d e l А .
say. После этого команда A . s a y () будет приводить к ошибке, а команда
С . s a y () - нет, поскольку у экземпляра С атрибут s a y не удалялся.
Ш

На заметку
Утверждать, что мы, присваивая атрибутам экземпляров класса в качестве зна­
чений ссылки на функции, создавали тем самым методы экземпляров, будет не
совсем корректно. Есть несколько важных обстоятельств, которые существен­
но охлаждают наш пыл. Во-первых, функции, ссылки на которые мы присваивали
атрибутам, не имеют доступа к экземплярам класса. Другими словами, мы не смо­
жем изменить программный код функции hello () так, чтобы при выполнении ко­
манды а .say () был бы прямой доступ к экземпляру А. Как бы ни хотелось нам рас­
сматривать say () как метод экземпляра класса, данный метод фактически к эк­
земпляру класса доступа не имеет. Это серьезный минус.

Глава 6. Основы объектно-ориентированного программирования
Далее, если бы мы взяли несколько экземпляров и атрибуту say каждого из этих
экземпляров присвоили ссылку на функцию h e llo О , то все эти экземпляры че­
рез атрибут say ссылались бы на одну и ту же функцию. Получается "общая"
функция для всех экземпляров. В том, что это действительно функция, а не ме­
тод, легко убедиться - достаточно после команды a .s a y = h e llo добавить команду
p r i n t (ty p e (A. s a y ) ). В окне вывода появится сообщение < c la s s ' fu n c tio n ' >.
Напомним, что когда проверяется тип метода экземпляра, в аналогичной ситуа­
ции появляется сообщение < c la s s 'm ethod' >. Короче говоря, тем, что мы дела­
ли выше, лучше все же особо не увлекаться.

Более эффективным может быть присваивание ссылки на функцию в каче­
стве значения атрибуту класса (разумеется, обратная процедура - удаление
атрибута со ссылкой на функцию, - тоже допускается). Небольшой пример
представлен в листинге 6 . 12.

Листинг 6.12. Добавление иудаление функции класса
# Создаем к л асс
c l a s s M y C lass:
d e f __ i n i t __ ( s e l f , n ) :
s e l f . nam e=n
# С оздаем экзем пляры к л а сс а
A = M y C lass( "A ")
B = M y C lass("B ")
# С о з д а е м функцию с а р г у м е н т о м
def h e llo ( s e lf ) :
p r i n t ("Э то э к з е м п л я р " , s e l f . n a m e , h e l l o " )
# С о з д а е м функцию с а р г у м е н т о м
def h i( s e lf ) :
p r i n t ( s e l f . nam e+": h i" )
# О п р е д е л я е м функцию к л а с с а
M y C lass. s a y = h e llo
# В ы зы в а е м м е т о д э к з е м п л я р а
A . s a y ()
# В ы зы в а е м м е т о д э к з е м п л я р а
B . s a y ()
# В ы зы в а е м функцию к л а с с а
M y C l a s s . s a y (А)
M y C l a s s . s a y (В)
# М еняем с с ы л к у н а функцию
M y C lass. sa y = h i
# В ы зы в а е м м е т о д э к з е м п л я р а
A . s a y ()
# В ы зы в а е м м е т о д э к з е м п л я р а
B . s a y ()
# В ы зы в а е м функцию к л а с с а
M y C l a s s . s a y (А)

-€ Е Э

Python

M y C la ss. say(B )
# У д а л я е м функцию к л а с с а
d e l M y C la s s .s a y
# Вы зываем м е т о д э к з е м п л я р а
try :
A . s a y ()
# Если м ето д а н ет
except A ttrib u te E rro r:
p r i n t ("Т акого м етода н ет")
# В ы зы в а е м м е т о д э к з е м п л я р а
try :
B . s a y ()
# Если м ето д а н ет
except A ttrib u te E rro r:
p r i n t ("Т акого м етода нет")
# В ы з ы в а е м функцию к л а с с а
try :
M y C l a s s . s a y (А)
# Если функции н ет
except A ttrib u te E rro r:
p r i n t ("Т акой функции н е т " )

Результат выполнения этого программного кода представлен ниже:

Результат выполнения программы (из листинга 6.12)
Это э к з е м п л я р
Это э к з е м п л я р
Это э к з е м п л я р
Э то э к з е м п л я р
A: h i
В: h i
A: h i
В: h i
Такого м етода
Такого м етода
Т акой функции

А
В
А
В

-

h e llo
h e llo
h e llo
h e llo

нет
нет
нет

В классе M yC la ss описан конструктор. При создании экземпляра клас­
са конструктору передается аргумент, который определяет значение поля
паше экземпляра класса. Мы создаем два экземпляра: А (со значением "А"
для поля nam e) и В (со значением "В" для поля паше). Также описываем
две функции h e l l o () и h i (). Причем у каждой из функций объявлено по
одному аргументу (аргумент называется s e l f ) , причем неявно предполага­
ется, что это ссылка на экземпляр класса My Cla ss, поскольку в теле ф унк­
ций имеются ссылки s e l f . name на поле паше экземпляра s e l f .

Саш*

Глава 6. Основы объектно-ориентированного программирования

Вместе с тем, обе эти функции описаны вне тела класса MyClass, так что
говорить о функции класса или методе экземпляра пока рано - рано, пока
не выполнена команда MyClass.say=heНо. В этом случае атрибуту
say класса MyClass присваивается в качестве значения ссылка на функ­
цию hello (). После этого можем вызывать метод say () для экземпляров
класса командами A. say () и В .say (). При этом фактически вызывает­
ся функция hello (), аргументом которой передается ссылка на экземпляр
класса, из которого вызывается метод say ().
Аналогичные результаты получаем при выполнении команд MyClass.
say (А) и MyClass.say (В).В них непосредственно вызывается функция
say () класса MyClass с одним аргументом.
М ы можем изменить значение атрибута say класса MyClass, выполнив,
например, команду MyClass.say=hi. После выполнения этой коман­
ды при вызове функции класса или метода экземпляра say () реально бу­
дет выполняться код функции hi (). В последнем легко убедиться с помо­
щь ю команд A.say(), В.say(), MyClass.say(А) и MyClass.say(В).
Наконец, мы можем удалить функцию say () класса MyClass командой
del MyClass .say. После это выполнение перечисленных выше команд с
вызовом функции/метода say () приводит к ошибке.
Возможны и другие варианты "действий" с функциями класса и методами
экземпляров класса. Некоторые небольшие примеры приведены в листин­
ге 6.13.
Листинг 6 .1 3 . Операции с методами и функциями
# С оздаем класс,
c l a s s M y C lass:
# К онструктор экзем пляра к л а сс а
d e f __ i n i t __ ( s e l f , n ) :
se lf.n a m e = n
# М е то д э к з е м п л я р а к л а с с а
def s a y (s e lf):
p r i n t ("К ласс M y C lass: " , s e lf.n a m e )
# С оздаем экзем пляры к л а с с а
A = M y C lass( "A ")
B = M y C lass( " B " )
# В ы зы в а е м м е т о д э к з е м п л я р а
A . sayO
B . s a y ()
# Ссылку н а м е т о д за п и с ы в а е м в перем енную
F = A .say
# В ы зы в а е м функцию
FO

ШВ

Python

# А трибуту эк зем п л яр а п р и с в а и в а е т с я т е к с т
A . s a y = " I I o n e э к з е м п л я р а А"
# П роверяем зн ач ен и е поля
p rin t(A .sa y )
# П ытаемся в ы з в а т ь м е т о д э к з е м п л я р а
try :
A . s a y ()
# Если т а к о г о м ето д а н ет
e x c e p t T ypeE rror:
p r i n t ( "Н еверная ком анда")
# В ы зы в а е м м е т о д э к з е м п л я р а
B . s a y ()
# В ы зы в а е м функцию

F ()
В классе M yC la ss описан конструктор экземпляра класса, в котором полю
name экземпляра присваивается значение. Также в классе описан метод
s a y (), отображающий текст и значение поля name экземпляра, из которо­
го вызывается метод. При создании экземпляры А и В получают для своих
полей name соответственно значения "А" и "В". У каждого из экземпля­
ров А и В есть метод s a y (), в чем несложно убедиться с помощью команд
A . s a y () и В . s a y ().
Дальше мы будем рассуждать так. Инструкция A. say представляет собой
ссылку на объект метода say () экземпляра А класса MyClass. Никто не
запрещает нам присвоить ссылку на этот объект в некоторую переменную.
Именно так мы и поступаем, когда используем команду F=A.say. В резуль­
тате ее выполнения в переменную F записана ссылка на объект - тот самый,
на который ссылается инструкция A. say. Поскольку речь идет об объек­
те метода, то вызвать этот метод можем инструкцией А . say () или F (). В
обоих случаях вызывается один и тот же метод, поэтому мы получаем один
и тот же результат.
На следующем шаге командой A. say="Поле экземпляра А" атрибуту
say экземпляра А присваивается текстовое значение. Хотя ранее атрибут
say экземпляра А ссылался на метод экземпляра, теперь это фактически
поле, которое ссылается на текст. Поэтому вызвать метод экземпляра ко­
мандой A. say () больше не получится. Если мы воспользуемся командой
A. say (), возникнет ошибка несогласования типов TypeError. Коррект­
ным является обращение A. say к текстовому полю экземпляра.
Ш

На заметку
Таким образом, у экземпляра А появляется поле say, и данное поле "перекрывает"
метод say () экземпляра. Понятно, что это общее правило.

Глава 6. Основы объектно-ориентированного программирования

При этом у экземпляра В метод s a y () "остается" и без проблем может быть
вызван командой В . s a y (). Но самое интересное, что инструкцией F () мы
по-прежнему можем вызвать метод s a y () экземпляра А. Объяснение про­
стое: переменная F ссылается на объект метода s a y () экземпляра А. Ранее
на этот объект содержалась ссылка и в атрибуте A. sa y . И хотя после при­
сваивания текстового значения атрибуту s a y инструкция А . s a y () для вы ­
зова метода использована быть не может, переменная F для этой цели впол­
не подойдет. В итоге после выполнения всего программного кода получаем
такой результат:

Результат выполнения программы (из листинга 6.13)
К ласс M y C lass: А
К л асс M y C lass: В
К л асс M y C lass: А
П ол е э к з е м п л я р а А
Н еверная команда
К л асс M y C lass: В
К л асс M y C lass: А

Хотя описанные выше возможности по манипулированию полями и мето­
дами достаточно уникальны (такие "трюки" можно делать далеко не в каж­
дом языке программирования), картина была бы неполной без одного очень
важного механизма, без которого вообще не было бы смысла рассматривать
ОО П. Речь о наследовании. Наследование, равно как и некоторые другие
моменты (например, переопределение операторов) рассматривается в сле­
дующей главе.

К о п и р о в а н и е э кз е м п л я р о в и ко н с тр у кто р
со зд а н и я ко п и и
Хотите обмануть мага? Боже, какая дет­
ская непосредственность. Я же вижу Вас
насквозь.

из к/ф '31 июня"

Конструкторы мы уже обсуждали. Мы знаем, что конструктору экземпляра
класса могут передаваться аргументы. Это с одной стороны. С другой сто­
роны есть в известном смысле "классическая" задача, которая состоит в том,
что на основе одного экземпляра класса нужно создать точно такой же. Гру­
бо говоря, актуальной является задача создания копии экземпляра класса.
Проблема в том, что обычным присваиванием значения переменным, кото­
рые ссылаются на экземпляры классов, здесь не обойтись. Ведь при присва­
ивании значения переменным просто "перебрасываются" ссылки. Так, если
■ Ш

Python

некоторая переменная х ссылается на экземпляр класса, то после выполне­
ния команды у=х переменная у будет ссылаться на тот самый экземпляр,
что и переменная х. И это не то, что нам нужно. Нам нужно, образно выра­
жаясь, чтобы переменная у ссылалась на экземпляр с точно такими же ха­
рактеристиками, как у экземпляра, на который ссылается переменная х.
Ш

На заметку
То есть нам как бы нужен точно такой же экземпляр, но другой.

В принципе для создания копии экземпляра можно воспользоваться ф унк­
циями с о р у () или d e e p c o p y () из модуля со р у . При использовании
функции с о р у () создается поверхностная копия экземпляра, в то время
как функция d e e p c o p y () позволяет создавать полные копии. Разница меж­
ду поверхностной и полной копиями проявляется, когда среди полей экзем­
пляров имеются переменные, ссылающиеся на данные изменяемых типов
(примером могут быть списки). Небольшая иллюстрация к использованию
этих функций приведена в листинге 6.14.

Листинг 6.14. Создание копии экземпляра
# И м п орт ф у н к ц и й с о р у () и d e e p c o p y ()
# и з модуля сору
from copy im p o r t co p y , d ee p co p y
# Класс
c l a s s M y C lass:
# К онструктор
def
in it
( s e l f , n a m e ,n u m s):
# З н а ч е н и е п о л я nam e
s e l f . nam e= n am e
# З н а ч е н и е п о л я num s
s e l f . nu m s= n u m s
# М е то д д л я о т о б р а ж е н и я
# зн ачен ий полей экзем п л яр а
d ef sh o w (se lf):
# П оле nam e
p rin t(" n a m e - > " , se lf.n a m e )
# П ол е nums
p rin t("n u m s - > " , se lf.n u m s)
# С оздание экзем п л яр а к л а с с а
x= M y C lass(" P y th o n " , [ 1 , 2 , 3 ] )
p r i n t ( "Э кземпляр x :" )
# О тображение п о л ей э к з е м п л я р а x
x . sh o w ()
# П оверхностная копия эк зем п л яр а х

Глава 6. Основы объектно-ориентированного программирования

у= сору(х)
# П олная коп и я э к з е м п л я р а х
z = d e e p c o p y (х)
p r i n t ( "Э кзем пляр у :" )
# О тображение полей эк зе м п л я р а у
y . s h o w ()
p r i n t ( "Э кзем пляр z : " )
# О тображение полей эк зе м п л я р а z
z . s h o w ()
p r i n t ("П оля э к з е м п л я р а х и з м е н я ю т с я ! " )
# И з м е н е н и е з н а ч е н и я п о л я паш е
# экзем пляра х
х . nam e="Java"
# И зменение зн а ч е н и я эл ем ен т а в сп и ске
# num s - п о л е э к з е м п л я р а х
x . n u m s [0]= 0
p r i n t ( "Э кзем пляр х : " )
# О тображение п о л ей э к з е м п л я р а х
x . s h o w ()
p r i n t ( "Э кзем пляр у :" )
# О тображение п о л ей э к зе м п л я р а у
y . s h o w ()
p r i n t ( "Э кзем пляр z : " )
# О тображение п о л ей э к з е м п л я р а z
z . s h o w ()

В результате выполнения программного кода получаем такое:

Результат выполнения программы (из листинга 8.14)
Э кземпляр х :
п аш е - > P y t h o n
num s - > [ 1 , 2 , 3]
Э кзем пляр у:
nam e - > P y t h o n
num s - > [ 1 , 2 , 3]
Э кземпляр z :
n am e - > P y t h o n
num s - > [ 1 , 2 , 3]
П ол я э к з е м п л я р а x и з м е н я ю т с я !
Э кземпляр x :
n am e - > J a v a
num s - > [ 0 , 2 , 3]
Э кземпляр у:
nam e - > P y t h o n
n u m s - > [ 0 , 2 , 3]
Э кземпляр z :

m

Python

паш е - >

P y th o n

nums -> [1, 2, 3]
Импорт функций copy () и deepcopy () из модуля сору выполняется ин­
струкцией from copy import copy, deepcopy. Класс, над экземпля­
рами которого будут проводиться эксперименты по "клонированию", на­
зывается MyClass. В конструкторе экземпляра класса определяются поля
name и nums. Присваиваемые полям значения передаются аргументами
конструктору. Мы предполагаем, что поле name является текстовым, а поле
nums представляет собой числовой список (хотя это, конечно, условности).
В классе описан метод экземпляра show ( ), которым выполняется отображе­
ние полей name и nums экземпляра класса. Н а этом описание класса заканчи­
вается. Далее переходим к процедуре создания копий экземпляров. Д ля этой
цели нам нужен исходный экземпляр класса, который будет копироваться.
Экземпляр класса создаем командой x=MyClass ("Python", [1,2,3]).
В результате создается экземпляр х класса MyClass. У созданного экзем­
пляра класса значением поля name есть текст "Python", а значение поля
nums -список [1,2,3]. Проверить значение полей экземпляра х можно с
помощью команды х . show ().
Копии экземпляра х создаются так: командой у = с о р у (х ) создается поверх­
ностная копия (экземпляр у) экземпляра х, а командой z = d e e p c o p y (х)
создается полная копия (экземпляр z ) экземпляра х. Н а этом этапе значе­
ния полей у экземпляров у и z такие же, как у экземпляра х: подтверждени­
ем служит результат выполнения команд у . show () и г . show ().
Н а следующем этапе командами х . name=" Java" и х . nums [ 0 ] = 0 изменя­
ется значение поля name экземпляра х и первый (с нулевым индексом) эле­
мент в списке nums, являющемся полем этого же экземпляра. Командами
х . show ( ) , у . show () и z . show () проверяем, изменились ли (и если да, то
как) поля экземпляров х, у и z. С экземпляром х все просто. Его поля изме­
нились строго в соответствии с теми командами, которые были выполнены
при присваивании значений.
Поля экземпляра z (полная копия) не изменились. Что касается экземпля­
ра у, то у него не изменилось поле name, но изменилось поле-список nums
(первый элемент стал нулевым, как и у экземпляра х). Объяснение в том,
что при создании поверхностной копии для изменяемых типов, таких как
списки, выполняется копирование ссылок, но не значений. Поэтому реаль­
но экземпляры х и у (поверхностная копия х ) ссылаются через свои поля
nums на один и тот же список, тогда как у экземпляра z (полная копия х)
поле nums "персональное".

fS §

*

Глава 6. Основы объектно-ориентированного программирования

Хотя описанный выше подход легитимен, но все же не всегда приемлем (в
силу разных причин). Существуют и иные варианты. В частности, мы мо­
жем посмотреть на ситуацию более широко и переформулировать задачу
о создании копии экземпляра как задачу о создании экземпляра класса на
основе уже существующего экземпляра. Если при этом требовать, чтобы
созданный экземпляр имел такие же значения полей исходного экземпля­
ра, то речь будет идти о создании копии.
С практической точки зрения такой подход означает, что в классе необхо­
димо описать конструктор экземпляра, аргументом которому передается
ссылка на другой экземпляр этого класса. По традиции означенный кон­
структор называется конструктором создания копии (хотя при этом может
создаваться совсем не копия).
Ш

На заметку
Проблема в том, что в Python нет перегрузки методов. Поэтому мы не можем, как
в C++ или Java, создать несколько конструкторов разных типов. В Python у экзем­
пляра класса конструктор должен быть один. Это вносит некоторую интригу в про­
цесс создания конструктора копии.

На практике очень удобно, если экземпляры класса могут создаваться раз­
ными способами. В первую очередь имеется в виду возможность переда­
вать конструктору при создании экземпляра класса различные наборы ар­
гументов. Это же замечание, кстати, относится и к обычным методам: хоро­
шо, когда метод может вызываться с разными аргументами. В таких языках
программирования, как C++, Java и C # проблема снимается с помощью ме­
ханизма перегрузки методов и функций.
В языке Python как такового механизма перегрузки методов нет. Но, откро­
венно говоря, учитывая исключительную "гибкость" языка Python, особой
необходимости в перегрузке не наблюдается. Существуют другие возмож­
ности. Одна из них - создание функций и методов с переменным количе­
ством аргументов. Этот подход описывается в последней главе.
Здесь мы рассмотрим пример класса с конструктором, которому можно пе­
редавать различное количество аргументов, включая и случай, когда аргу­
ментом передается ссылка на уже существующий экземпляр класса.
Механизм перегрузки нам удастся "обойти" благодаря назначению аргу­
ментам значений по умолчанию и воспользовавшись проверкой типа пере­
данного конструктору аргумента. Соответствующий программный код при­
веден в листинге 6.15.

t

Python

Листинг 6.15. Конструктор создания копии экземпляра
# Класс
c l a s s ComplNum:
# Конструктор создан и я экзем пляра к л а сс а
d e f __ i n i t __ ( s e l f , х = 0 , у = 0 ) :
# Если ар гу м ен т х - э к зем п л я р
# к л а с с а ComplNum
i f t y p e ( х ) ==ComplNum:
# З н а ч е н и е п о л я Re
s e l f . R e = x . Re
# З н а ч е н и е п о л я Im
s e l f . I m = x . Im
# Если аргу м ен т x - не эк зем п л я р
# к л а с с а ComplNum
e lse :
# З н а ч е н и е п о л я Re
s e l f . R e= x
# З н а ч е н и е п о л я Im
s e l f . Im =y
# М етод д л я о т о б р а ж е н и я з н а ч е н и й
# полей экзем п ляра к л асса
d ef sh o w (se lf):
p rin t("R e = " ,s e lf.R e )
p rin t(" Im = " ,s e lf.Im )
# С оздается экзем пляр класса
a = C o m p lN u m ( 1 , 2 )
# С о зд ае тс я копия эк зем п л яр а к л а с с а
b = C o m p lN u m ( а )
p r i n t ( "Э кзем пляр а : " )
# Значения полей исходного экзем п ляра
a . s h o w ()
p r i n t ("Э кзем пляр b :" )
# Значения полей эк зем п ляра-коп и и
b . sh o w ()
p r i n t ("П оля э к з е м п л я р а а и з м е н я ю т с я ! " )
# И зменяем з н а ч е н и я п о л ей и с х о д н о г о
# экзем пляра
а . R e= 10
а . Im=20
p r i n t ( "Э кзем пляр а : " )
# Значения полей и сходного экзем п ляра
a . sh o w ()
p r i n t ( "Э кзем пляр Ь :" )
# Значения полей эк зем п ляра-коп и и
b . s h o w ()

Глава 6. Основы обьектно-ориентированного программирования

Результат выполнения программного кода такой:
Результат выполнения программы (из листинга 6.15)
Э кземпляр а :
Re = 1
Im = 2
Э кзем пляр b :
Re = 1
Im = 2
П ол я э к з е м п л я р а а и з м е н я ю т с я !
Э кземпляр а :
Re = 10
Im = 20
Э кзем пляр Ь:
Re = 1
Im = 2

В представленном примере мы создаем класс ComplNum со слабым намеком
на реализацию комплексных чисел посредством экземпляров этого класса.
Во всяком случае, у экземпляров класса есть два поля: R e и I m (как бы дей­
ствительная и мнимая части комплексного числа). При создании экземпля­
ра класса аргументами конструктору можно передать значения для полей
R e и Im , а можно передать ссылку на экземпляр класса, на основе которого
будет создаваться копия.
Ш

На заметку
Конструктор описан с тремя аргументами. Первый аргумент s e l f является ссыл­
кой на экземпляр класса (тот экземпляр, что создается). Еще два аргумента обо­
значены как х и у. У каждого из этих аргументов есть нулевые значения по умол­
чанию. Поэтому формально конструктор можно вызывать без аргументов, с одним
аргументом или с двумя аргументами. Если конструктор вызывается без аргумен­
тов, то это все равно, как если бы он вызывался с двумя нулевыми аргументами.
Если конструктору передан только один аргумент, то второй считается нулевым.

В теле конструктора в условном операторе проверяется тип первого аргу­
мента. Точнее, проверяется условие t y p e ( х ) = = C o m p l N u m , которое состо­
ит в том, что тип данных, на которые ссылается аргумент х, является экзем­
пляром класса C o m p lN u m . Если это так, то аргумент х обрабатывается как
экземпляр класса C o m p lN u m .
Командами s e l f . R e = x . R e и s e l f . I m = x . I m значения полей R e и I m эк­
земпляра x присваивается соответственно полям R e и I m экземпляра s e l f
(экземпляр, который создается при вызове конструктора).



Ш

Python

Если условие t y p e (х) = = C o m p l N u m ложно, то мы неявно предполагаем,
что х и у - числовые аргументы. В этом случае командами s e l f . R e =х и
s e l f .,1ш=узначения аргументов присваиваются полям R e и I m создавае­
мого экземпляра класса.
Ш

На заметку
Таким образом, если первым (но не обязательно единственным аргументом) кон­
структору передана ссылка на экземпляр класса ComplNum, то второй аргумент
конструктора в вычислениях не используется - вне зависимости от того, передан
он явно или нет.

Также в классе C o m p lN u m описан метод экземпляра s h o w (), который ото­
бражает в области вывода значения полей экземпляра. Мы этот метод ис­
пользуем для проверки "содержимого" экземпляров класса C o m p lN u m .
Вне кода класса C o m p l N u m командой a = C o m p l N u m ( l , 2 ) создается эк­
земпляр а класса C o m p lN u m . Копия этого экземпляра создается коман­
дой b = C o m p l N u m ( а ) . Здесь аргументом конструктору экземпляра класса
C o m p lN u m передана ссылка на ранее созданный экземпляр а . Непосред­
ственной проверкой убеждаемся, что экземпляр b действительно является
копией экземпляра а: после создания экземпляра b у него такие же значе­
ния полей, как и у экземпляра а, а после изменения значений полей экзем­
пляра а значения полей экземпляра b не меняются.
Стоит отметить, что даже при описании в классе конструктора созда­
ния копии необходимо помнить об особенностях копирования данных из­
меняемых типов - таких, как списки. Так, программный код, рассмотрен­
ный нами при иллюстрации принципов использования функций с о р у () и
d e e p c o p y () из модуля с о р у , мог бы быть реализован несколько ина­
че, прибегни мы к помощи конструктора создания копий. Рассмотрим ли­
стинг 6.16, в котором показано, как может быть описан конструктор, позво­
ляющий создавать копии экземпляра класса с полями-списками.
Листинг 6.16. Конструктор создания копии и поля-списки
# Класс
c l a s s M y C lass:
# Конструктор
d e f __ i n i t __ ( s e l f , a r g , n u m s = N o n e ) :
# Если ар гу м ен т a r g - ссы лка на
# э к з е м п л я р к л а с с а M y C lass
i f ty p e (a rg )= = M y C la ss:
# З н а ч е н и е п о л я nam e
s e lf.n a m e = a r g .n a m e [:]

[ 1 , 2 , 3]
n am e - > P y t h o n

Python

num s - > [ 1 , 2 , 3]
П оля э к з е м п л я р а x и з м е н я ю т с я !
Э кзем пляр х :
nam e - > J a v a
num s - > [ 0 , 2 , 3]
Э кзем пляр у :
nam e - > P y t h o n
num s - > [ 1 , 2 , 3]

Здесь мы при описании конструктора экземпляра класса M y C l a s s преду­
смотрели возможность передать конструктору два аргумента (текст и спи­
сок), или один аргумент, который является ссылкой на уже существующий
экземпляр класса M y C l a s s (для создания копии).
Q

На заметку
Для аргумента nums в описании конструктора указано значение по умолчанию
None, что соответствует пустой ссылке. В известном смысле это такой формаль­
ный примем, поскольку совсем не указать значение по умолчанию мы не могли:
если для аргумента nums не указать значение по умолчанию, то при вызове кон­
структора создания копии пришлось бы передавать и второй, ничего не значащий
аргумент. А это не очень удобно.
Вообще, чтобы оценить разницу в подходах, желающие могут сравнить программ­
ные коды в листингах 6.14-6.16.

В конструкторе использован такой же прием, что и в предыдущем приме­
ре: проверяется, совпадает ли тип аргумента a r g с классом M y C l a s s . Спо­
соб обработки аргументов конструктора зависит от истинности или ложно­
сти этого условия. Принципиальное отличие, по сравнению с предыдущим
примером, состоит в том, что при присваивании значений полям выполня­
ется копирование значения с использованием среза (инструкция [: ] после
имени переменной, ссылающейся на текст или список). В этом случае не­
явно предполагается, что тип аргументов позволяет выполнять подобную
операцию.
U

На заметку
Напомним, что с помощью получения срез создается поверхностная копия спи­
ска. У читателя могут возникнуть вопросы относительно тех примеров, что рассма­
тривались в листинге 6.14 и листинге 6.16. Попробуем их предугадать. Для это­
го детальнее рассмотрим, что происходит при создании копии экземпляра (ли­
стинг 6.14). Там с помощью функции сору() создавалась поверхностная копия
у экземпляра х класса MyClass. У этого экземпляра х было поле-список nums.
При создании нового экземпляра у у него тоже будет поле nums. Значение это­
го поля такое же, как значение поля nums исходного экземпляра х. Но на самом
деле значение поля исходного экземпляра х - это ссылка на список (в данном слу­
чае [1,2,3]). Поэтому в новом экземпляре у поле nums будет ссылаться на тот же

m m -

Глава 6. Основы объектно-ориентированного программирования
самый список, на который ссылается поле nums экземпляра х. Если мы через эк­
земпляр х и его поле nums меняем элемент в соответствующем списке, то измене­
ния "заметит" и экземпляр у (поскольку его поле ссылается на тот же самый спи­
сок). Но если мы присвоим другое значение полю nums экземпляра х, то значение
поля nums экземпляра у останется неизменным! Обратите внимание - речь идет о
присваивании значения полю nums, а не отдельному элементу списка nums: жела­
ющие могут в программном коде из листинга 6.14 команду х. nums [0] =0 заменить
на команду х .nums=0 и проверить результат выполнения кода. Почему так проис­
ходит? Потому что при присваивании значения полю nums экземпляра х ссылка в
этом поле просто перебрасывается на новые данные. При этом поле nums экзем­
пляра у продолжает ссылаться на список.
В листинге 6.16 в конструкторе экземпляра класса при создании копии полей паше
и nums использована процедура получения среза. В этом случае создаются поверх­
ностные копии полей name ипитзисходногоэкземпляра.иссылки наэтикопиизаписываются в поля name и nums экземпляра-копии. В отношении поля nums речь идет
о поверхностной копии списка. Но поскольку в данном случае поле-список nums не
содержит в качестве элементов других списков, проблем не возникает. Они могли
бы быть, если бы в исходном экземпляре х список nums содержал внутренние спи­
ски. Например, если в листинге 6.16 команду x=MyClass ("Python", [1,2,3]) за­
менить на команду x=MyClass ("Python", [ [1,2] ,3] ) , а команду х .nums [0] =0 на команду х .nums [ О] [ 0 ] =0, то изменения в поле nums экземпляра х отразятся и
на поле nums экземпляра у.

Резюме
1. В языке Python поддерживается парадигма объектно-ориентированного
программирования (О О П ). Программные коды, написанные на языке
Python, могут содержать и оперировать классами и экземплярами клас­
сов.
2. В общем смысле класс - это некоторый шаблон, по которому создают­
ся объекты. Объект, в свою очередь, объединяет в одно целое данные и
функции (или методы) для обработки этих данных.
3. Объекты, создаваемые на основе класса, называют экземплярами класса.
Сам класс в Python также является объектом.
4. Переменные, объявленные в классе, называются полями класса. Пере­
менные, связанные с экземплярами класса, называются полями экзем­
пляра класса. Функции, описанные в классе, называют функциями
класса. Функции, связанные с экземплярами класса, называются мето­
дами экземпляра класса. Поля и методы (функции) называются атрибу­
тами (класса или экземпляра класса соответственно).
5. Описание класса начинается с ключевого слова c l a s s , после которо­
го указывается имя класса. Методы экземпляра класса описываются в
теле класса как обычные функции. При этом первый аргумент метода

m

Python

(рекомендуемое название s e l f ) по умолчанию является ссылкой на эк­
земпляр, из которого вызывается метод. При вызове метода из экзем­
пляра класса после имени экземпляра через точку указывается имя ме­
тода, а в круглых скобках - аргументы. Ссылка на экземпляр класса, из
которого вызывается метод, передается автоматически, так что при вы­
зове метода у него на один аргумент меньше, чем это было при описа­
нии метода в теле класса.
6 . Для создания экземпляра класса переменной экземпляра присваивает­

ся выражение, состоящее из имени класса и круглых скобок. В круглых
скобках могут передаваться аргументы конструктору экземпляра клас­
са.
7. Конструктор экземпляра класса - это метод, который автоматически вы ­
зывается при создании экземпляра класса. Конструктор имеет название
_i n i t __ ( ). Также существует деструктор___d e l __ (), но он на прак­
тике используется редко.
8 . Атрибуты объекта класса и экземпляра класса уже после их создания

можно менять (добавлять и удалять). Д ля удаления атрибутов можно
использовать оператор d e l . Добавление атрибутов выполняется путем
присваивания им значения.
9. Если у класса и у экземпляра класса есть поля с одинаковыми названия­
ми, поле экземпляра класса "перекрывает" поле класса. Обращение к та­
кому полю по имени (через ссылку на экземпляр класса) будет означать
обращение к полю экземпляра класса.
10. При обращении к функции, описанной в теле класса, через ссылку на
экземпляр класса, создается объект метода экземпляра класса, который
и вызывается. Вызов данного метода эквивалентен вызову соответству­
ющей функции класса с такими же аргументами, что и у метода, но еще
с добавлением дополнительного первого аргумента - ссылки на экзем­
пляр класса, из которого вызывается метод.
11. Имя функции, описанной в классе, является атрибутом этого класса.
Как всякому атрибуту, этому может быть присвоено новое значение
(имя функции), он может быть уделен или в класс может быть добавлен
новый атрибут, ссылающийся на функцию.
12. Ссылка на функцию класса или метод экземпляра класса может быть
записана в переменную, которая затем используется для вызова функ­
ции или метода.
13. Д ля создания копии экземпляра класса создают конструктор созда­
ния копии или используют функции с о р у () и d e e p c o p y () из модуля
_сору.
Ф
ш т

Глава 7
Продолжаем
знакомство с ООП

Python

Лучший способ объяснить - это самому сде­
лать.

Л. Кэрролл "Алиса в стране чудес"

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

Н асл едование
Эврика! Царские шмотки! Одевайся, царем
будешь!

из к/ф "Иван Васильевич меняет профес­
сию"

Если в общих чертах, то идея наследования состоит в том, что новый класс
создается не на "пустом месте", а на основе уже существующего класса.
Предположим, что один класс создается на основе другого класса. Тот класс,
что создается, будем называть производным классом. Тот класс, на основе
которого создается производный класс, будем называть базовым классом.
Таким образом, по определению производный класс создается на основе ба­
зового класса.
В результате наследования все поля и функции из базового класса неявным
образом "наследуются" в производном классе. С формальной точки зрения
чтобы создать производный класс на основе базового при описании произ­
водного класса после имени класса в круглых скобках указывается имя ба­
зового класса.
Другими словами при описании производного класса используем такой ша­
б л о н (жирным шрифтом выделены ключевые элементы шаблона):
c l a s s п рои звод н ы й _к ласс(б азовы й _к ласс):
# тело класса

cm

*

Глава 7. Продолжаем знакомство с ООП

Пример наследования проиллюстрирован программным кодом в листин­
ге 7.1.
Листинг 7 .1 . Наследомние классов
# Базовы й к л а с с
c l a s s B a seC la ss:
# П ол е б а з о в о г о к л а с с а
nam e_base="K nacc B a se C la ss"
# М е то д э к з е м п л я р а б а з о в о г о к л а с с а
def sa y _ b a se (se lf) :
p r i n t ("М етод s a y _ b a s e ( ) " )
# П роизводный к л а с с
c l a s s N ew C lass(B a seC la ss) :
# П ол е п р о и з в о д н о г о к л а с с а
nam e_new ="К ласс N ew C lass"
# М е то д э к з е м п л я р а п р о и з в о д н о г о к л а с с а
d ef sa y _ n e w (se lf):
p r i n t ("М етод s a y _ n e w ( ) " )
# Э кземпляр б а з о в о г о к л а с с а
o b j_ b a se= B ase C la ss О
# Э кземпляр п р о и зво д н о го к л а с с а
o b j _ n e w = N e w C l a s s ()
p r i n t ("К л асс B a s e C la s s и эк зем п л я р o b j _ b a s e : " )
# П ол е б а з о в о г о к л а с с а
p r i n t ( B a s e C l a s s . nam e_base)
# М етод э к з е м п л я р а б а з о в о г о к л а с с а
o b j _ b a s e . s a y _ b a s e ()
p r i n t ("Х п К ласс N ew C lass и э к з е м п л я р o b j _ n e w : " )
# Поле п р о и з в о д н о г о к л а с с а
p rin t(N e w C la ss.n a m e _ b a se )
# М е то д э к з е м п л я р а п р о и з в о д н о г о к л а с с а
o b j _ n e w . s a y _ b a s e ()
# Унаследованное из базо во го к л асса
# поле производного к л асса
p r i n t ( N e w C l a s s . nam e_new)
# У наследованный и з б а зо в о г о к л а с с а
# метод экзем пляра производного к л асса
o b j _ n e w . s a y _ n e w ()

В программном коде базовый класс называется BaseClass. Это са­
мый обычный класс. Обстоятельство, что данный класс послужил базо­
вым для создания нового класса, никак не отражено в классе BaseClass.
В классе BaseClass описано поле name_base с текстовым значением
"Класс BaseClass" и метод экземпляра с названием say_base (). У ме­

*

-f f f l

Python

тода один аргумент и методом в окне вывода отображается простое сообще­
ние.
Что касается производного класса, то он называется NewClass. При соз­
дании этого класса в круглых скобках после имени создаваемого произво­
дного класса указывается имя базового класса BaseClass. Непосредствен­
но в теле класса NewClass описаны поле name_new с текстовым значени­
ем "Класс NewClass" и предназначенный для вывода сообщения метод
экземпляра класса say_new() с одним аргументом. Но, поскольку класс
NewClass создается на основе класса BaseClass, то "в наследство" от это­
го класса он получает поле name_base и метод say_base (). Таким обра­
зом, у класса NewClass два поля (name_new и name_base) и два метода
(say_new () и say_base () ).
Экземпляр базового класса создается командой obj_base=BaseClass ( ) .
Экземпляр производного класса создаем командой obj_new=NewClass ().
Через класс BaseClass получаем доступ к полю name_base. Через экзем­
пляр obj_base базового класса вызываем метод say_base (). У класса
NewClass, как отмечалось, есть поля name_base и name_new, у экземпля­
ра obj_new - методы say_base () и say_new(), которые, собственно, и
вызываются. Результат выполнения описанного программного кода пред­
ставлен ниже:

Результат выполнения программы (из листинга 7.1)
Класс B a se C la ss и экзем п ляр o b j_ b a s e :
Класс B a s e C la s s
М е то д s a y _ b a s e ( )
Класс
Класс
М е то д
Класс
М е то д

N ew C lass и э к зе м п л я р o b j_ n e w :
B a se C la ss
say _ b ase()
N ew C lass
say_new ()

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

H I

Глава 7. Продолжаем знакомство с ООП

Листинг 7 .2 . Изменение базового класса
# Базовы й к л а с с
c l a s s B a seC la ss:
# П оле б а з о в о г о к л а с с а
паш е="П оле nam e"
# М етод э к з е м п л я р а б а з о в о г о к л а с с а
d ef s a y ( s e l f ):
p r i n t ("М етод s a y ( ) " )
# П роизводный к л а с с
c l a s s N ew C lass(B a seC la ss):
pass
# Э кземпляр п р о и зв о д н о го к л а с с а
o b j = N e w C l a s s ()
# П ол е п р о и з в о д н о г о к л а с с а
p rin t(N e w C la ss.n a m e )
# М е то д э к з е м п л я р а п р о и з в о д н о г о к л а с с а
o b j . s a y ()
# С о з д а е м функцию
def h e llo ( s e lf ) :
p r i n t ("Н о вы й м е т о д h e l l o О " )
# И з м е н я е м с с ы л к у н а функцию
# в базовом классе
B a se C la ss. sa y = h e llo
# И зм еняем з н а ч е н и е поля
# в базовом классе
B a s e C l a s s .паш е="Н овое з н а ч е н и е поля nam e"
# П роверяем зн а ч е н и е поля в производном к л а с с е
p rin t(N e w C la ss.n a m e )
# В ы зы в а е м м е т о д и з э к з е м п л я р а
# производного класса
o b j . s a y ()

Результат выполнения этого программного кода приведен ниже:
Результат выполнения программы (из листинга 7.2)
П о л е nam e
М е то д s a y ()
Н о в о е з н а ч е н и е п о л я nam e
Новый м е т о д h e l l o ()

В базовом классе BaseClass описано поле паше со значением
"Поле
пате" и метод say()', отображающий в окне вывода текст
"М етод
say () ". Производный класс NewClass, который создается на
основе базового класса BaseClass, никакого дополнительного кода не со­
держит. Мы создаем экземпляр o b j производного класса NewClass. Коман" Ш

Python

дами print (NewClass .name) и obj .say () проверяем, что поле name и
метод say () успешно наследованы из базового класса. Затем определяет­
ся функция hello () и командой BaseClass .say=hello ссылка на эту
функцию записывается в атрибут say базового класса BaseClass. Также
командой BaseClass.пате="Новое значение п ол я name" изменя­
ем значение поля name в базовом классе. После этого проверяем значение
поля name в производном классе с помощью команды print (NewClass.
name), а также вызываем метод say () из экземпляра производного клас­
са с помощью команды ob j . say ( ). Как видим по результату выполнения
этих команд, в производном классе использованы новые значения атрибу­
тов базового класса.
При наследовании существует возможность переопределять поля и мето­
ды, наследованные в производном классе из базового. Общая идея состоит
в том, что поле или метод, которые наследуются из базового класса, в произ­
водном классе создаются заново. Как это делается на практике, иллюстри­
рует программный код из листинга 7.3.
Листинг 7 .3 . Переопределение полей и методов
# Базовы й к л а с с
c l a s s B a seC la ss:
# П ол е б а з о в о г о к л а с с а
пате="П оле п ате б а зо в о го к л а сс а"
# М е то д э к з е м п л я р а б а з о в о г о к л а с с а
def s a y (s e lf):
p r i n t ( " М е т о д s a y () б а з о в о г о к л а с с а " )
# П роизводны й к л а с с
c l a s s N e w C la ss(B a se C la ss):
# П ол е п р о и з в о д н о г о к л а с с а
пате="П оле п ате прои зводн ого к л а с с а "
# М е то д э к з е м п л я р а п р о и з в о д н о г о к л а с с а
def s a y (s e lf):
p r i n t ( " М е т о д s a y () п р о и з в о д н о г о к л а с с а " )
# Э кземпляр б а з о в о г о к л а с с а
o b j _ b a s e = B a s e C l a s s ()
# Э кзем пляр п р о и зво д н о го к л а с с а
o b j _ n e w = N e w C l a s s ()
# А трибуты э к зе м п л я р а б а з о в о г о к л а с с а
p r i n t ( o b j _ _ b a s e . nam e)
o b j _ b a s e . s a y ()
# А трибуты э к зе м п л я р а п р о и зв о д н о го к л а с с а
p r i n t ( o b j _ n e w . nam e)
o b j _ n e w . s a y ()

ш т

.....

*

Глава 7. Продолжаем знакомство с ООП

При выполнении данного программного кода получаем следующий резуль­
тат:
Результат выполнения программы (из листинга 7 .3)
Поле п а т е
М е то д s a y
П ол е паш е
М е то д s a y

базового класса
() б а з о в о г о к л а с с а
производного класса
() п р о и з в о д н о г о к л а с с а

В этом примере мы сначала создали базовый класс BaseClass с полем
паше и методом say ( ), а затем на основе этого класса создали произво­
дный класс NewClass. И хотя при наследовании поле name и метод say ()
становятся доступными и в производном классе, мы все равно в классе
NewClass описываем поле паше и метод say ( ) , но уже другие (по сравне­
нию с базовым классом). В результате экземпляр obj_base базового клас­
са ссылается на поле и метод из базового класса, а экземпляр ob j_new про­
изводного класса ссылается на поле и метод производного класса.
Обычно на практике прибегают к переопределению методов. Причем си­
туация может быть далеко не такой простой, как было показано выше. Еще
один пример переопределения методов при наследовании приведен в л и ­
стинге 7.4.
Листинг 7 .4 . Переопределение методов
# Базовы й к л а с с
c l a s s B a seC la ss:
def
in it
(se lf,n u m ):
s e l f . id= num
def g e t( s e lf ) :
p r i n t ( " I D : ", s e l f . id )
# М е то д э к з е м п л я р а б а з о в о г о к л а с с а
def sh o w (se lf):
p r i n t ("П оле э к з е м п л я р а б а з о в о г о к л а с с а " )
s e l f . g e t ()
# П роизводны й к л а с с
c l a s s N ew C lass(B a seC la ss) :
def
in it
( s e lf , n u m ,tx t):
s u p e r ( ) . __ i n i t __ (num)
s e l f . n am e= tx t
def g e t( s e lf ) :
s u p e r ( ) . g e t ()
p r i n t ( " N a m e : " , s e l f . nam e)
# Э кземпляр б а з о в о г о к л а с с а
o b j _ b a s e = B a s e C l a s s (1)

ш т

Python

p r i n t ( "В ы зы ваем м е т о д sh o w () и з э к з е м п л я р а o b j _ b a s e : " )
# В ы зо в м е т о д а э к з е м п л я р а б а з о в о г о к л а с с а
o b j _ b a s e . s h o w ()
# Э кзем пляр п р о и зв о д н о го к л а с с а
o b j_ n e w = N e w C lass( 1 0 , " д е с я т к а " )
p r i n t ( "В ы зы ваем м е т о д sh o w () и з э к з е м п л я р а o b j _ n e w : " )
# В ы зо в м е т о д а э к з е м п л я р а п р о и з в о д н о г о к л а с с а
o b j_ n e w .sh o w ()

Прежде, чем посмотреть, каков же будет результат выполнения этого про­
граммного кода, кратко проанализируем его.
Базовый класс BaseClass содержит конструктор, в котором полю id эк­
земпляра класса присваивается значение (передается аргументом конструк­
тору). Метод экземпляра get () отображает в окне вывода значение поля
id экземпляра, из которого вызывается метод. Еще в классе BaseClass
описан метод экземпляра show(), в котором командой print ("Поле
экземпляра базового класса") в окне вывода отображается сообще­
ние, а затем командой self.get () вызывается метод экземпляра get ()
(который, напомним, отображает значение поля id экземпляра класса).
Производный класс NewClass создается на основе базового класса
BaseClass. В данном классе много новых для нас (и, возможно, непонят­
ных) инструкций. В первую очередь это конструктор. Мы предполагаем,
что у экземпляра производного класса будет два поля: поле i d и поле паше.
Необходимо, чтобы этим полям в конструкторе присваивались значения,
и, соответственно, значения этих полей должны передаваться аргументами
конструктору.
Наша идея состоит в том, чтобы в конструкторе экземпляра производного
класса вызвать конструктор экземпляра базового класса. Проблема усугу­
бляется обстоятельством, что все конструкторы во всех классах называют­
ся одинаково. Чтобы вызвать "правильный" конструктор, нам понадобит­
ся функция super (). Ф ункция возвращает в качестве результата специ­
альный прокси-объект, который делегирует вызов метода базовому классу.
С практической точки зрения на эту функцию можно смотреть как на заме­
ститель экземпляра базового класса, из которого получается вызвать исхо­
дную (определенную в базовом классе) версию метода - в том числе это ка­
сается и конструктора.
Q

На заметку
Как бы там ни было, а вызвать конструктор базового класса можно инструкцией
вида super () . __i n i t __ (аргументы ). В круглых скобках для конструктора указы­
ваются аргументы, причем без ссылки на экземпляр класса, из которого вызыва­
ется конструктор.

Ш

Глава 7. Продолжаем знакомство с ООП

Командой s u p e r () . __i n i t __ (num) в теле конструктора экземпляра
производного класса вызывается конструктор экземпляра базового клас­
са. В результате поле i d получит свое значение. Затем командой s e l f .
n a m e = tx t значение присваивается полю name экземпляра производного
класса (переменные s e l f , num и t x t обозначают аргументы конструктора
экземпляра производного класса).
В производном классе переопределяется метод g e t (). В теле метода ко­
мандой s u p e r () . g e t () вызывается версия метода из базового класса.
При выполнении этого метода, напомним, в окне вывода отображается зна­
чение поля i d экземпляра класса, из которого вызывается метод. Затем ко­
мандой p r i n t ( "N am e: s e l f . name) отображается значение поля name
производного класса.
Ш

На заметку
Если имеется некоторый метод, который определен в базовом классе и переопре­
деляется в производном классе, и нам необходимо в теле экземпляра произво­
дного класса вызвать эту исходную, базовую версию метода (а такое возможно),
используем инструкцию вида super О .метод (аргументы).
Есть альтернатива к использованию функции super ( ) . Например, если нам в про­
изводном классе нужно вызвать версию метода экземпляра из базового класса,
можем выполнить явную ссылку на базовый класс, воспользовавшись командой
вида базовый_класс.метод (self, аргументы) - это вместо команды super() .
метод (аргументы). Например, команда super () . get () могла бы выглядеть как
BaseClass.get(self).

Важный момент, на котором следует остановиться, связан с методом
show (), который описан в базовом классе, а в производном не переопре­
деляется. Поэтому производный класс наследует версию метода show () из
базового класса. Но в методе show () (в базовом классе) вызывается метод
g e t ( ), который, в свою очередь, переопределяется в производном классе.
При вызове метода show () из экземпляра производного класса будет вы­
зываться метод g e t (). Вопрос в том, какой именно это будет метод g e t () из базового класса или из производного? Ответ на этот вопрос мы получим
из результата выполнения программного кода.
Экземпляр базового класса создается командой obj_base=BaseC lass ( 1 ) .
Затем вызываем метод show () из экземпляра obj_base базового класса
(команда obj_base .show () ).
Экземпляр
производного
класса
создается
командой
obj_
new=NewClass( 1 0 , "десятка"). Командой obj_new.show() вызыва­
ется метод show () из экземпляра obj_new производного класса. Резуль­
тат выполнения всего программного кода такой:
■ ш

Python

Результат выполнения программы (из листинга 7 .4)
В ы зы в а е м м е т о д
П оле э к з е м п л я р а
ID : 1
В ы зы в а е м м е т о д
П ол е э к з е м п л я р а
I D : 10
N ame: д е с я т к а

show () и з э к зем п л я р а o b j_ b a s e :
базового класса
show () и з э к зем п л я р а o b j_ n e w :
базового класса

Особый интерес представляют последние три строки в окне вывода: это ре­
зультат выполнения команды o b j _ n e w . s h o w (). Несложно понять, что та­
кой результат может быть получен, если в теле наследованного из базово­
го класса метода s h o w () вызывается переопределенный в производном
классе метод g e t (). Причем это общее правило: если в наследуемом мето­
де есть команда вызова другого метода и этот другой метод переопределя­
ется в производном классе, то будет вызвана эта новая (переопределенная)
версия метода.
Ш

На заметку
В языке C++ такое свойство называется виртуальностью. Выражаясь терминоло­
гией языка C++, в Python все методы являются виртуальными.

До этого при создании производного класса базовый класс был всегда один.
Н а самом деле в Python производный класс можно создавать на основе сра­
зу нескольких базовых классов. Это называется множественным наследова­
нием. При множественном наследовании у производного класса не один ба­
зовый класс, а несколько базовых классов. Производный класс наследует
все атрибуты каждого из своих базовых классов. Небольшой пример с мно­
жественным наследованием представлен в листинге 7.5.
Q

На заметку
В этом примере мы как бы условно "описываем" Некоторую коробку или ящик. У
этого ящика есть геометрические размеры (ширина, высота, глубина), масса (или
вес - хотя, если честно, это далеко не одно и то же), а еще цвет. На основе линей­
ных размеров вычисляется объем (произведение ширины, высоты и глубины). Все
единицы измерений безразмерные.
В классе BoxSize предусмотрена возможность записывать линейные размеры и
вычислять объем. Класс BoxParams предусматривает возможность запоминания
таких параметров, как вес и цвет. На основе классов BoxSize и BoxParams путем
наследования создается класс Box.,

*
д о

Глава 7. Продолжаем знакомство с ООП

Листинг 7 .5 . Наследование нескольких базовых классов
# П ервы й б а з о в ы й к л а с с
c l a s s B o x S ize:
# Конструктор
d e f __ i n i t __ ( s e l f , w i d t h , h e i g h t , d e p t h ) :
# П рисваивание зн а ч е н и й полям э к зем п л я р а
s e l f . w id th = w id th
s e l f . h e ig h t= h e ig h t
s e l f . d ep th = d ep th
# М е то д д л я в ы ч и с л е н и я о б ъ е м а
def v o lu m e (se lf):
# Р е зу л ь т а т - п р ои зведен и е полей экзем п ляра
r e t u r n s e l f . w i d t h * s e l f . h e i g h t *s e l f . d e p th
# М е тод д л я о т о б р а ж е н и я з н а ч е н и й п о л е й э к з е м п л я р а
# и р е з у л ь т а т а в ы з о в а м е т о д а v o l u m e ()
def sh o w (se lf):
# П ол я э к з е м п л я р а к л а с с а
p r i n t ( "Р азм еры и объем ящ и к а:")
p r i n t ("Ш ири н а:" , s e l f . w i d t h )
p r i n t ("В ы со та :", s e l f . h e i g h t )
p r i n t ("Г лубина: " , s e l f . d ep th )
# Р е з у л ь т а т в ы з о в а м е т о д а v o l u m e ()
p r i n t ("О б ъ е м :", s e l f . v o l u m e ())
# Второй базовы й к л а с с
c l a s s B oxP aram s:
#- К о н с т р у к т о р
d e f __ i n i t __ ( s e l f , w e i g h t , c o l o r ) :
# П рисваивание зн ач ен и й полям э к зем п л я р а
s e l f . w eig h t= w eig h t
s e l f .c o lo r= c o lo r
# М е тод д л я о т о б р а ж е н и я з н а ч е н и й п о л е й э к з е м п л я р а
def sh o w (se lf):
# О тображение зн а ч е н и й п олей
p r i n t ( "Д ополнительные парам етры ящ ика:")
p r i n t ("В ес ( м а с с а ) : " , s e l f . w e ig h t)
p r i n t ("Ц вет:" , s e l f . c o lo r)
# П роизводны й к л а с с
c l a s s B o x (B o x S iz e,B o x P aram s):
# Конструктор
d e f __ i n i t __ ( s e l f , w i d t h , h e i g h t , d e p t h , w e i g h t , c o l o r ) :
# В ы зо в к о н с т р у к т о р а п е р в о г о б а з о в о г о к л а с с а
B o x S i z e . __ i n i t __ ( s e l f , w e i g h t , h e i g h t , d e p t h )
# В ы зо в к о н с т р у к т о р а в т о р о г о б а з о в о г о к л а с с а
B o x P a r a m s . __ i n i t __ ( s e l f , w e i g h t , c o l o r )
# В ы зо в м е т о д а s h o w ( ) э к з е м п л я р а к л а с с а

СПЗ

Python

self.show()
# Переопределение метода show()
def show(self):
# Вызов метода show() из первого базового класса
BoxSize.show(self)
# Вызов метода show() из второго базового класса
BoxParams.show(self)
# Создаем экземпляр производного класса
obj=Box(10,20,30,5,"зеленый")

В базовом классе BoxSize описан конструктор, в котором полям width,
height и depth экземпляра класса присваиваются значения (тем самым
при создании экземпляра класса этому экземпляру добавляются соответ­
ствующие поля).
Q

На заметку
Обратите внимание, что аргументы конструктора width, height, depth и поля эк­
земпляра класса называются одинаково. Если в теле конструктора мы просто пи­
шем название width, height или depth, то имеются в виду аргументы конструк­
тора. Для ссылки на поля экземпляра класса используем инструкции self.width,
self.height и self.depth с явным указанием аргумента конструктора self,
отождествляемого со ссылкой на экземпляр класса.
Для тех, кто знаком с C++, Java или C # подчеркнем: в Python нет сокращенной фор­
мы обращения к полям экземпляра класса.

Также в классе BoxSize описывается метод volume (), предназначенный
для вычисления объема: результатом метода является произведение self.
width* self.height* self.depth значений всех трех полей экземпляра.
Метод show () содержит команды по отображению в окне вывода значений
полей экземпляра и результата вызова метода volume ( ) .
В базовом классе BoxParams в конструкторе задаются значения двух по­
лей, а метод show () в этом классе описан так, что при его вызове отобража­
ются значения этих полей.
Производный класс Box создается на основе базовых классов BoxSize и
BoxParams: названия этих классов указываются в круглых скобках после
имени производного класса. В производном классе описывается конструк­
тор, у которого достаточно много аргументов.
В теле конструктора последовательно вызываются конструкторы базовых
классов: командой BoxSize. init
(self,weight, height, depth)
вызывается конструктор экземпляра класса BoxSize, а командой Box­
Params.__init
(self,weight, color) вызывается конструктор эк­
земпляра класса BoxParams. И в том и в другом случае конструктор вы-

шт-

................................ ....................................... ....... ......................................... -

Глава 7. Продолжаем знакомство с ООП

зывается как ф ункция класса с явным указанием имени класса и передачей
первым аргументом конструктору ссылки s e l f на экземпляр класса.
Также в конструкторе вызывается метод s h o w (). Но здесь мы теоретически
должны были бы столкнуться с проблемой: в каждом из базовых классов
B o x S i z e и B o x P a r a m s есть метод с названием s h o w (), и каждый из этих
методов наследуется в производном классе B o x . В принципе, в такой ситуа­
ции по умолчанию при обращении к методу s h o w () вызывалась бы версия
метода из класса B o x S i z e , поскольку в списке базовых классов этот класс
указан первым.
Ш

На заметку
В экземпляре производного класса при обращении к полю или методу, если это
поле или метод не описаны в теле производного класса, поиск начинается в базо­
вых классах, причем строго в соответствии с тем порядком, в котором классы ука­
заны в списке наследования (в круглых скобках после имени производного клас­
са).

Но мы идем другим путем - путем переопределения метода s h o w () в про­
изводном классе B o x . В теле метода в классе B o x командами B o x S i z e .
s h o w () и B o x P a r a m s . s h o w () последовательно вызываются версии этого
метода соответственно из класса B o x S i z e и B o x P a r a m s . Здесь действует
тот же принцип, что и при переопределении конструктора класса B o x : ме­
тоды вызываются как функции класса через ссылку на объект класса и пе­
редачей аргументом ссылки на экземпляр класса.
Помимо описания классов, в программном коде есть всего одна команда
o b j = B o x ( 1 0 , 2 0 , 3 0 , 5 , " з е л е н ы й " ) , которой создается экземпляр про­
изводного класса B o x . при этом вызывается конструктор, а в конструкторе
вызывается метод s h o w (), которым отображаются значения всех полей эк­
земпляра о Ь j . Так что результат получаем следующий:
Результат выполнения программы (из листинга 7 .5)

Размеры и объем ящика:
Ширина: 5
Высота: 20
Глубина: 30
Объем: 3000
Дополнительные параметры ящика:
Вес (масса): 5
Ц вет:

зеленый

Кроме множественного наследования может использоваться многократное
наследование. При многократном наследовании производный класс являет~ж т

Python

ся базовым для другого класса. Если учесть, что одновременно допустимо
использовать и множественное наследование, становится очевидным, что
структура "родственных отношений" между классами может быть очень
сложной.
Щ

На заметку
Часто все сводится к определению того, как при многократном и одновремен­
но множественном наследовании производным классом наследуются из разных
классов атрибуты (методы/функции) с одинаковыми названиями. Речь идет вот о
чем: представим, что из экземпляра производного класса вызывается метод или
из производного класса вызывается функция, а в структуре наследования клас­
сов есть методы/функции с таким же именем. Вопрос в том, какая версия (из ка­
кого класса) метода или функции будет вызываться? В общем упрощенном виде
ответ на этот вопрос сводится к тому, что последовательно перебирается цепоч­
ка наследования классов до первого "совпадения”. Базовые классы перебираются
в том порядке, как они указаны при создании производного класса. Причем базо­
вые классы "просматриваются" вместе со своими базовыми классами. Хотя и тут
не так все просто. Неприятности возникают в случае, если один и тот же класс ока­
зывается "разными путями", прямо или опосредованно, несколько раз среди на­
следуемых классов. Например, класс А является базовым для классов в и с, а на
их основе создается класс о. В результате получается, что класс D наследует класс
А "дважды": через класс в и через класс с. А могут быть и более трагичные "хитро-

Рис. 7.1. Схема наследования классов. Наследование изображается направленной
стрелкой (от базового класса к производному классу). Цифрами обозначен порядок
наследования классов в случае множественного наследования. Классы с описанной
функцией hi() выделены темным цветом

Глава 7. Продолжаем знакомство с ООП
сплетения". В подобных случаях в Python применяется специальный алгоритм, ко­
торым определяется цепочка наследования классов, причем в этой цепочке каж­
дый класс встречается только единожды.

Мы по этому поводу рассмотрим небольшой пример, представленный в л и ­
стинге 7.6. В приведенном там программном коде описаны несколько клас­
сов, причем одни из них наследуют другие. Общая схема наследования
классов представлена на рис. 7.1.
Три базовых класса ( A l p h a , B r a v o и C h a r l i e ) содержат описание ф унк­
ции h i ( ) , а остальные классы наследуют (различными путями) эти классы.
Программный код такой:

Листинг 7.8. Многократное наследование
# Описываем к л а с с ы
c l a s s A lp h a:
d e f h i () :
p r i n t ("К ласс A lp h a")
c l a s s B ravo:
d e f h i () :
p r i n t ("К ласс B ravo")
c la s s C h a rlie :
d ef h i ():
p r i n t ("К ласс C h a r l ie " )
c la s s D e lta (A lp h a ):
pass
c la s s E c h o (D e lta ):
pass
c la s s F o x tro t(B ra v o ,A lp h a ):
pass
c la ss G o lf(F o x tro t):
pass
c la s s H o te l(E c h o ,C h a rlie ,G o lf):
pass
# В ы зы в а е м ф у н к ц и и к л а с с о в
E c h o . h i ()
G o l f . h i ()
H o t e l . h i ()

Результат выполнения программного кода следующий:

Результат выполнения программы (из листинга 7.6)
К л асс A lp h a
К ласс B ravo
Класс C h a r lie

- iB I

Python

После описания классов в программном коде последовательно выполняют­
ся команды E c h o . h i (), G o l f . h i () и H o t e l . h i (). Разберем, что проис­
ходит при выполнении каждой из этих команд.
При выполнении команды E c h o . h i () выполняется поиск функции h i ()
в теле класса E c h o . Непосредственно в этом классе функция не описана. Но
класс E c h o создается на основе класса D e l t a . В этом классе функция с име­
нем h i () также не описана, поэтому поиск функции выполняется в классе
A l p h a - базовом классе для класса D e l t a . В результате вызывается ф унк­
ция из класса A l p h a и в окне вывода появляется сообщение К л а с с A l p h a .
Здесь ситуация простая, поскольку простая цепочка наследования: Класс
E c h o наследует класс D e l t a , а класс D e l t a наследует класс A l p h a . Поиск
функции h i () осуществляется в соответствии с этой цепочкой наследова­
ния.
При выполнении команды G o l f . h i () поиск функции h i () сначала про­
исходит в теле класса G o l f . Но там такая функция не описывалась. По­
скольку класс G o l f создавался на основе класса F o x t r o t , поиск функции
h i () выполняется в этом классе. В теле этого класса функция h i () также
не описана. Поиск продолжается в базовых классах для класса F o x t r o t :
это классы B r a v o и A l p h a (в том порядке, как они указаны при наследова­
нии). Так как в классе B r a v o есть описание функции h i ( ), она и вызыва­
ется, в результате чего в окне вывода появляется сообщение К л а с с B r a v o .
Если бы в классе B r a v o функции h i () не оказалось, поиск бы продолжил­
ся в классе A l p h a .
Более сложная ситуация с командой H o t e l . h i (). По логике для поис­
ка функции h i () классы должны были бы проверяться в такой последо­
вательности: сначала H o t e l , E c h o , D e l t a , A l p h a , затем C h a r l i e , а далее
G o l f , F o x t r o t , B r a v o и A l p h a - пока не будет найден первый класс в этой
последовательности, в котором описана функция h i ( ). В этой цепочке пер­
вый класс, в котором описана функция h i (), - класс A l p h a . Поэтому если
бы все так и было, появилось бы сообщение К л а с с A l p h a . Но на самом
деле появляется сообщение К л а с с C h a r l i e . Почему так?
На самом деле полный и исчерпывающий ответ сводился бы к описанию
алгоритма, используемого в Python при формировании цепочки наследо­
вания, когда одни и те же классы многократно наследуются в производном
классе. Нам такие "технические" подробности вряд ли понадобятся, поэто­
му мы ограничимся общей идеей с апелляцией к нашему примеру. Итак, в
формальной последовательности наследуемых классов класс A l p h a встре­
чается дважды. А нужно, чтобы он там встречался всего один раз. Причем
важна позиция, на которой этот класс будет находиться в цепочке наследо-

fill}---

Глава 7. Продолжаем знакомство с ООП

вания (поскольку она определяет, в какой последовательности просматри­
ваются классы при поиске функции h i () ).
Базовый принцип состоит в том, чтобы избегать ситуаций, когда сначала
просматривается базовый класс, а затем производный от него. В данном
конкретном случае теоретически имеется два варианта цепочки наследо­
вания для класса H o t e l : E c h o , D e l t a , A l p h a , C h a r l i e , G o l f , F o x t r o t ,
B r a v o или E c h o , D e l t a , C h a r l i e , G o l f , F o x t r o t , B r a v o , A l p h a . В пер­
вом случае класс A l p h a просматривается перед классом F o x t r o t , для ко­
торого класс A l p h a является базовым.
Как мы упоминали выше, это плохой вариант. Во втором случае такой про­
блемы нет. Поэтому цепочка наследования классов для класса H o t e l на
самом деле выглядит так: E c h o , D e l t a , C h a r l i e , G o l f , F o x t r o t , B r a v o ,
A l p h a . Двигаясь по этой цепочки при поиске кода функции h i () (речь, на­
помним, идет о выполнении команды H o t e l . h i () ) "останавливаемся" на
классе C h a r l i e , в котором эта функция описана.
Щ

На заметку
Чтобы увидеть "цепочку наследования" для производного класса, разумно вос­
пользоваться полем __ mro__этого класса. В частности, если в рассмотренном
выше примере добавить команду print (Hotel.__mro__) ,увидим, в какой после­
довательности наследуются классы в классе Hotel.
Вообще, несложно придумать такую схему наследования классов, которая явля­
ется недопустимой в том смысле, что не позволяет установить последователь­
ность или цепочку наследования классов. Например, класс С наследует классы А и
в, класс Dнаследует классы в и А, а класс Е наследует классы с и D. В такой ситуа­
ции возникает ошибка типа TypeError, связанная с невозможностью установить
цепочку наследования.

В следующем разделе речь пойдет о некоторых специальных методах и по­
лях , которые позволяют существенно расширить возможности классов и эк­
земпляров классов в Python.

С пециальны е м е то д ы и поля
Ж и вьем б рат ь дем онов!

из к/ф "Иван Васильевич меняет профес­
сию"

В Python есть группа методов, названия которых начинаются и заканчива­
ются двойным подчеркиванием. Такие методы предназначены для работы с
классами и экземплярами классов, и позволяют выполнять некоторые весь­
ма специфичные операции. Поэтому такие методы обычно называют специ­

альными.

csi

Python

Q

На заметку
По крайней мере, с двумя специальными методами мы уже знакомы: это конструк­
тор __ init__() и деструктор__ del__() .

Также есть группа полей, которые в названии в начале и конце содержат по
два символа подчеркивания. Эти поля доступны для каждого класса и /и л и
экземпляра по умолчанию.
Q

На заметку
На первый взгляд может показаться странным, что у классов, которые создаются
пользователем, есть атрибуты, которые пользователем не описаны. Но здесь сле­
дует учесть, что начиная с версии Python 3 все классы (в том числе и созданные
пользователем), в конечном счете, являются наследниками класса object. Дру­
гими словами, существует иерархия классов, и в вершине этой иерархии находит­
ся класс оЬ j ect.

В таблице 7.1 перечислены некоторые специальные поля, которые могут
быть полезны при работе с классами.
Таблица 7.1. Специальные поля
Поле
bases
d ie t
doc

m o d u le

Возвращается список базовых классов
Возвращается словарь с атрибутами класса
Возвращается текст документирования класса
(текст, описывающий класс). Текст документиро­
вания можно непосредственно указать в теле клас­
са первой строкой или присвоить полю значение
Возвращается модуль класса

m ro

Возвращается цепочка наследования класса

name

Возвращается имя класса

q u a ln a me

m

Назначение

Возвращается полное имя класса (в точечном ф ор­
мате, отображающем структуру вложенных клас­
сов)

Глава 7. Продолжаем знакомство с ООП

Ш

На заметку
Уэкземпляров класса есть специальное поле__class___, которое позволяет опре­
делить класс, на основе которого создавался экземпляр. Поскольку класс сам яв­
ляется объектом, то у класса такое поле тоже есть. Поле__c l a s s __для объекта
класса дает (при выводе значения функцией p r i n t ()) значение c c l a s s ' t y p e ' >,
что означает принадлежность объекта класса к типу type.
Также есть очень полезная функция d i r о , которая позволяет получить список
атрибутов (полей и методов) экземпляра класса. При вызове функции экземпляр
класса передается аргументом функции.

Н ебольш ой прим ер использования перечисленны х полей приведен в л и ­
с т и н г е 7.7.
Листинг 7.7, Специальные поя*
# Класс
c l a s s A lp h a:
"К ласс A lp h a и вн утренний к л а с с B rav o "
d ef h e l l o ():
pass
# Внутренний к л а с с
c l a s s B ravo:
pass
# П роизводны й о т A lp h a к л а с с
c la s s C h a rlie (A lp h a ):
pass
# П роизводны й от C h a r l i e к л а с с
c la ss D e lta (C h a rlie ):
pass
# Создаем экзем п ляр к л а с с а
o b j = A l p h a ()
# П ол е
c la ss
экзем пляра класса
p r i n t ( " П о л е __ c l a s s __ ")
p r i n t ( " Э к з е м п л я р o b j : " , o b j . __ c l a s s __ )
# П ол е
class.
класса
p r i n t ( " К л а с с A l p h a : " , A l p h a . __ c l a s s __ )
p r i n t ( " К л а с с A l p h a . B r a v o : " , A l p h a . B r a v o . __ c l a s s __ )
p r i n t ( " К л а с с C h a r l i e : " , C h a r l i e . __ c l a s s __ )
# П ол я __ b a s e s __ и __ m r o __
p r i n t ( "Х пП оля __ b a s e s __ и __ m r o __ ")
p r i n t ( " К л а с с D e l t a , п о л е __b a s e s ___ : " , D e l t a .
b a s e s __ )
p r i n t ( " К л а с с D e l t a , п о л е __m r o ___: " , D e l t a .
m ro
)
p r i n t ( " К л а с с A l p h a , п о л е __b a s e s ___ : " , A l p h a .
b a s e s __ )
p r i n t ( " К л а с с A l p h a , п о л е __m r o ___: " , A l p h a .
m ro
)
# П ол е
doc


Ш
Ь

Python

p r i n t ( " \ п П о л е __ d o c __ ")
p r i n t ( " О п и с а н и е к л а с с а A l p h a A l p h a . __ d o c __ )
D e l t a . __ d o c __ = " К л а с с D e l t a н а с л е д у е т к л а с с C h a r l i e "
p r i n t ( " О п и с а н и е к л а с с а D e l t a D e l t a . __ d o c __ )
# П ол е __ m o d u l e __
p r i n t ("Х п П о л е __ m o d u l e __ ")
p r i n t ( "М одуль к л а с с а A l p h a A l p h a . __ m o d u l e __ )
# П ол е __ d i e t __
p r i n t ("Х п П о л е
d ie t
")
p r i n t ( " А т р и б у т ы к л а с с а A l p h a A l p h a . __ d i e t ___)
p r i n t ( "А трибуты к л а с с а A l p h a . B r a v o A l p h a . B r a v o .
d ic t_ )
p r i n t ( " А т р и б у т ы к л а с с а D e l t a D e l t a . __ d i e t __ )
# П ол я __n a m e ___ и __ q u a l n a m e __
p r i n t ("Х п П оля
nam e
и
q u aln am e
")
p r i n t ("К ласс A lp h a,
п о л е _n a m e __ A l p h a . ______ nam e
)
p r i n t ("К ласс A lp h a,
п о л е _q u a l n a m e _______ A l p h a .
q u a l n a m e __ )
p r i n t ( " К л а с с A l p h a . B r a v o , п о л е __ n a m e __ A l p h a . B r a v o . ______
nam e__ )
p r i n t ( " К л а с с A l p h a . B r a v o , п о л е __ q u a l n a m e __ A l p h a . B r a v o . ______
q u a l n a m e __ )
p r i n t ("К ласс D e lta ,
п о л е _nam e__ : " , D e l t a . ___n a m e __ )
p r i n t ("К ласс D e lta ,
п о л е _q u a l n a m e _______ D e l t a .
q u a l n a m e __)
Р езультат вы п о л н е н и я этого програм м н ого кода п ри веден ниже:

Результат выполнения программы (из листинга 7.7)
П ол е __ c l a s s __
Э к з е м п л я р o b j : < c l a s s ' __ m a i n __ . A l p h a ' >
Класс A lp h a: C c la s s 't y p e '>
Класс A lp h a .B ra v o : C c la s s 't y p e '>
Класс C h a r l ie : C c la s s 't y p e '>
П оля __ b a s e s __ и __ m r o __
К л а с с D e l t a , п о л е __ b a s e s __ : ( c c l a s s ' ___m a i n __ . C h a r l i e ' > , )
К л а с с D e l t a , п о л е __m r o __ : ( c c l a s s ' ____m a i n
. D e l t a ’ >, C c l a s s
' __ m a i n __ . C h a r l i e ' > , C c l a s s ' ___m a i n __ . A l p h a ' > , C c l a s s
'o b je c t'> )
' o b j e c t '> ,)
К л а с с A l p h a , п о л е __b a s e s __ : ( c c l a s s
К л а с с A l p h a , п о л е __m r o __ : ( C c l a s s ' ____m a i n
. A lp h a '> , C c la s s
' o b j e c t '> )
П ол е __ d o c __
Описание к л а с с а A lp h a :
Описание к л а с с а D e l t a :

mm—

Класс A lp h a и внутренний к л а с с B ravo
Класс D e lta н асл ед у ет к л асс C h a r lie

?

Глава 7. Продолжаем знакомство с ООП

П ол е __ m o d u l e __
М одуль к л а с с а A l p h a :

__ m a i n __

П ол е __ d i e t __
А т р и б у т ы к л а с с а A l p h a : { ' __ d i e t __ ' : < a t t r i b u t e ' ___d i e t __ ' o f
' A l p h a ' o b j e c t s > , ' B r a v o ' : C c l a s s ' __ m a i n __ . A l p h a . B r a v o ' > ,
' __ w e a k r e f __ ' : k a t t r i b u t e ' ___w e a k r e f __ ' o f ' A l p h a ' o b j e c t s > ,
' h e l l o ' : k f u n c t i o n A l p h a . h e l l o a t 0 x 0 2 3 2 0 1 E 0 > , ' __ m o d u l e __ ' :
' __ m a i n __ ' ,
' __ d o c __ ' : ' К л а с с A l p h a и в н у т р е н н и й к л а с с B r a v o ' }
А т р и б у т ы к л а с с а A l p h a . B r a v o : { ' __ w e a k r e f __ ' : k a t t r i b u t e ' __
w eak ref
' o f 'B r a v o ' o b j e c t s > , '
d ie t
': k a ttr ib u te '_
d ie t
' o f 'B r a v o ' o b j e c t s > , '
doc
' : N one, '
m o d u le
':
' __ m a i n __ ' }
А трибуты к л а с с а D e l t a : { '
doc
' : 'К л а с с D e l t a н а с л е д у е т
класс C h a r lie ', '
m o d u le
': '
m ain
'}
П ол я __ n am e__ и __ q u a l n a m e __
К л а с с A l p h a , п о л е __ nam e__ : A l p h a
К л а с с A l p h a , п о л е __ q u a l n a m e __ : A l p h a
Класс A lp h a .B ra v o , поле
n a m e __ : B r a v o
К л а с с A l p h a . B r a v o , п о л е __ q u a l n a m e __ : A l p h a . B r a v o
К л а с с D e l t a , п о л е __ n a m e __ : D e l t a
К л а с с D e l t a , п о л е __ q u a l n a m e __ : D e l t a

Q

На заметку
Некоторые поля (а именно,__b a s e s __и ___mro__) в качестве значений возвра­
щают кортежи (набор элементов в круглых скобках). Если кортеж состоит из одно­
го элемента, по правилам синтаксиса Python после этого элемента стоит запятая,
хотя следующего элемента в кортеже нет.

Д ал е е м ы рассм отри м сп ец и ал ь н ы е м етоды. Н а ч н ем в качестве и л л ю с т р а ­
ц и и с м е т о д а __ c a l l ___( ) , к о т о р ы й п р е д н а з н а ч е н д л я в ы п о л н е н и я д о с т а ­
точ н о бл аго р о д н о й м иссии: есл и в к лассе оп и сан этот метод, то э к зе м п л я р
класса мож но вы зы вать как функцию .
Н е б о л ь ш о й п р и м е р , и л л ю с т р и р у ю щ и й , к а к о п р е д е л я е т с я м е т о д __ c a l l ___
( ) , п р е д с т а в л е н в л и с т и н г е 7.8.
Листинг 7.8, Определение метода_call_()
# Класс
c l a s s Box:
# Конструктор
d e f __ i n i t __ ( s e l f , w i d t h , h e i g h t , d e p t h ) :
# П оля э к з е м п л я р а к л а с с а



Python

s e l f . w id th = w id th
se lf.h e ig h t= h e ig h t
s e l f . d ep th = d ep th
# О п р е д е л е н и е м е т о д а __ c a l l __ ()
d e f __ c a l l __ ( s e l f ) :
# Вы числяется п роизведение полей экзем п ляра
v o lu m e = s e lf.w id th * s e lf . h e i g h t * s e l f . d ep th
# О тоб раж ается р е з у л ь т а т вычислений
p r i n t ("О бъем р а в е н " , v o lu m e )
# С оздаем эк зем п л яр к л а с с а
o b j= B o x (1 0 ,2 0 ,3 0 )
# В ы зы в а е м э к з е м п л я р к л а с с а к а к функцию
o b j ()

Результат выполнения программного кода такой:
Результат выполнения программы (из листинга 7.8)
О бъем р а в е н

6000

В этом примере мы эксплуатируем идею с классом, который описывает не­
который "ящик" или "коробку" (в геометрическом смысле этого понятия).
Класс называется Box. В классе описан конструктор, при выполнении ко­
торого полям w id th , h e i g h t и d e p th присваиваются значения (аргумен­
ты конструктора).
Д ля вычисления объема, вместо того, чтобы описывать метод экземпляра
класса, определяем м етод__ c a l l __ (). Поскольку метод будет вызываться
(неявно) через экземпляр класса, аргументом метода указана ссылка s e l f
на экземпляр класса. В теле класса командой vo 1 ume=s e l f . w i d t h * s e l f .
h e i g h t * s e l f . d e p t h вычисляется произведение полей экземпляра класса
(то есть объем "коробки") и записывается в локальную переменную v o l ­
ume, после чего значение этой переменной отображается в окне вывода. На
этом описание класса Box заканчивается.
Вне программного кода класса Box создается экземпляр o b j этого класса, а
затем следует команда o b j () - то есть мы обращаемся к экземпляру класса
так, как если бы это была функция. Поскольку в теле класса Box определен
м етод__ c a l l __ (), то автоматически вызывается этот метод. В результате
в окне вывода появляется сообщение с информацией об объеме "коробки".
Q

На заметку
Выражаясь более "технологичным" языком, команда obj () обрабатывается как
команда B ox.__c a l l __ ( o b j).

dcfcflb........

t

Глава 7. Продолжаем знакомство с ООП

М ожно определить м етод__ c a l 1 __ () так, чтобы экземпляр класса не про­
сто "вызывался", а "вызывался" с аргументами. Несложно догадаться, что
для этого достаточно описать метод__ c a l 1 __ () с дополнительными (кро­
ме ссылки на экземпляр класса) аргументами. Пример такой ситуации при­
веден в листинге 7.9.
Листинг 7 .9 . Метод_call_() с аргументами
# Класс
c l a s s Box:
# К онструктор
def
in it
( s e lf , w id th ,h e ig h t,d e p th ):
p r i n t ( "О б ъ ем р а в е н " , s e l f ( w i d t h , h e i g h t , d e p t h ) )
# О п р е д е л е н и е м е т о д а __ c a l l __ ()
def
c a ll
( s e lf , w id th ,h e ig h t,d e p th ):
# П ол я э к з е м п л я р а к л а с с а
s e l f . w id th = w id th
s e l f . h e ig h t= h e ig h t
s e l f . d ep th = d ep th
# В ы числяется п р ои зведен и е полей экзем п ляра
v o lu m e = se lf. w id th * s e lf . h e i g h t * s e l f . d ep th
# Возвращ аемый р е з у л ь т а т
r e t u r n v o lu m e
# Создаем экзем п ляр к л а с с а
o b j= B o x (1 0 ,2 0 ,3 0 )
# В ы з ы в а е м э к з е м п л я р к а к функцию
p r i n t ("Н овое з н а ч е н и е " , o b j ( 1 , 2 , 3 ) )

Результат выполнения программного кода такой:
Результат выполнения программы (из листинга 7 .9 )
О бъ ем р а в е н 6 0 0 0
Новое з н а ч е н и е 6

В данном случае мы так определяем м е т о д __ c a l l __ ( ), что при вызове
экземпляра класса (в формате обращения к ф ункции) необходимо пере­
дать три аргумента, которые определят значения полей экземпляра клас­
са. Более того, теперь еще и возвращается результат - это объем (произве­
дение полей экземпляра класса). Существенно изменился и программный
код конструктора. Более конкретно, в теле конструктора выполняется всего
одна команда p r i n t ( "Объем р а в е н " , s e l f ( w i d t h , h e i g h t , d e p t h ) ),
в которой есть инструкция s e l f ( w i d t h , h e i g h t , d e p t h ) вызова экзем­
пляра класса s e l f с аргументами w i d t h , h e i g h t и d e p th .
Таким образом, неявный вызов метода __ c a l l __ () используется уже
в конструкторе - то есть при создании экземпляра класса. Поскольку

•СЕВ

Python

при вызове экземпляра класса возвращается результат, то у выражения
s e l f ( w i d t h , h e i g h t , d e p t h ) есть числовое значение - произведение по­
лей экземпляра класса. Это значение будет отображаться в окне вывода при
создании экземпляра класса.
Что касается непосредственно программного кода м етода__ c a l l __ (), то
он описан с четырьмя аргументами: аргумент s e l f представляет ссылку на
экземпляр класса, а аргументы w i d t h , h e i g h t и d e p t h задают значения
полей экземпляра. В теле метода командами s e l f . w i d t h = w i d t h , s e l f .
h e i g h t = h e i g h t и s e l f . d e p t h = d e p t h полям присваиваются значения,
затем командой v o l u m e = s e l f . w i d t h * s e l f . h e i g h t * s e l f . d e p t h вы­
числяется произведение полей, и, наконец, инструкцией r e t u r n v o l u m e
это значение возвращается как результат м етода__ c a l l ___( ) .
Поэтому когда командой o b j = В о х ( 1 0 , 2 0 , 3 0 ) создается экземпляр клас­
са, то при вызове конструктора полям w i d t h , h e i g h t и d e p t h экземпляра
присваиваются значения 1 0 , 2 0 и 3 0 соответственно, вычисляется произве­
дение этих полей и отображается сообщение. При выполнении команды р г
i n t ( " Н о в о е з н а ч е н и е " , o b j ( 1 , 2 , 3 ) ) указанные поля получают значе­
ния 1 , 2 и 3, вычисляется новое значение для объема "коробки" и это значе­
ние отображается в окне вывода.
Помимо м е т о д а __ c a l l __ ( ) , существуют и другие полезные специаль­
ные методы, которые условно, в зависимости от их, так сказать, назначения,
можно разбить на группы. Так, есть достаточно много методов, предназна­
ченных для преобразования экземпляров класса к базовым типам - таким,
например, как целые числа или текст. В таблице 7.2 представлены некото­
рые из таких методов. При описании все они имеют один аргумент - ссылку
на экземпляр класса (это, фактически, тот экземпляр, который нужно пре­
образовать к базовому типу). Явно эти методы обычно не вызываются. При
каких обстоятельствах они "вступают в игру", описано в таблице.
Таблица 7 .2 . Специальные методы приведения к типу
Метод

__b o o l__()

Ш

.........

Описание

Метод для приведения экземпляра к логическому
типу (тип b o o l) . Вызывается при использовании
функции b o o l () или в иных случаях, когда вы полня­
ется автоматическое приведение к логическому типу
(например, когда экземпляр указан в том месте, где
должно быть логическое значение - скажем, в услов­
ном операторе)
f

Глава 7. Продолжаем знакомство с ООП

complex

()

Метод для приведения к комплексному типу (тип
c o m p le x ). Метод вызывается при использовании
функции c o m p le x ()

__flo a t__()

Метод для приведения к числовому типу f l o a t (ф ор­
мат числа с плавающей точкой). Метод вызывается
при использовании функции f l o a t ()

__i n t __ ()

Метод для приведения к целочисленному типу (тип
i n t ) . Метод вызывается при использовании функции
i n t ()

__s t r __ ()

Метод для приведения экземпляра к текстовому ф ор­
мату (тип данных s t r ) . Метод вызывается при ис­
пользовании функции s t r () или в тех случаях, ког­
да выполняется автоматическое приведение к тексто­
вому формату

Небольшой пример с использованием этих методов приведен в листин­
ге 7.10. В этом примере мы описываем класс M y C l a s s . Экземпляр, который
создается на основе этого класса, будет иметь поле-список с числовыми эле­
ментами. Далее мы устанавливаем такие "правила" приведения экземпляра
к разным типам:


Если экземпляр приводится к целочисленному типу, то в качестве
значения возвращается целое число - количество элементов в полесписке.



При приведении экземпляра к типу f l o a t в качестве значения возвра­
щается среднее арифметическое значение элементов в поле-списке.



Если экземпляр приводится к типу c o m p le x , то в качестве значе­
ния возвращается комплексное число, которое создается по такому
алгоритму: действительная часть комплексного числа - это элемент
из списка с максимальным значением, а мнимая часть комплексного
числа - это элемент из списка с минимальным значением.



К логическому типу b o o l экземпляр будет приводиться так: если в
поле-списке нечетное количество элементов, при приведении экзем­
пляра к типу b o o l возвращается значение T r u e , а если количество
элементов в списке четное, возвращается значение F a l s e .



Наконец, при приведении экземпляра к текстовому типу будет воз­
вращаться текст, в котором, кроме прочего, содержатся значения эле­
ментов из поля-списка.

€Ш

n

Теперь приступим к рассмотрению программного кода.
Листинг 7 .1 0 . Методы приведения к типу
# Класс
c l a s s M y C lass:
# К онструктор
d e f __ i n i t __ ( s e l f , n u m s ) :
# Создаем поле - п устой список
s e l f , n u m s = l i s t ()
# О ператор цикла для перебора
# э л е м е н т о в в а р г у м е н т е num s
# (это список или множество)
f o r n i n num s:
# Д обавляем новый э л ем ен т в сп и с о к
s e l f . num s. ap p en d (n )
# М етод д л я п р и в е д е н и я к т и п у s t r
d e f _ _ s t r __ ( s e l f ) :
# Н ачальное зн ачен и е т е к с т о в о й строки
бх1="3начение п о л я -с п и с к а : \ n | "
# П еребираем элем енты в сп и с ке num s- поле
# экзем пляра к ласса
f o r n in se lf.n u m s:
# Д о п о л н я е м с т р о к у новы м т е к с т о м
t x t + = s t r ( п ) +" | "
# Р езультат метода
re tu rn tx t
# М етод д л я п р и в е д е н и я к т и п у i n t
d e f __ i n t __ ( s e l f ) :
# Р езу л ьтат м етода (количество элем ентов
# в с п и с к е num s - п о л е э к з е м п л я р а к л а с с а )
r e tu r n le n (s e lf.n u m s )
# М е то д д л я п р и в е д е н и я к т и п у f l o a t
def
flo at
(se lf) :
# Среднее зн ачен ие элем ентов в
# с п и с к е num s - п о л е э к з е м п л я р а
a v r= su m (se lf.n u m s)/in t(se lf)
# Р езу л ь тат м етода
re tu rn avr
# М е то д д л я п р и в е д е н и я к т и п у b o o l
def
bool
(se lf) :
# Если н е ч е т н о е к о л и ч е с т в о э л ем ен т о в
i f i n t ( s e l f ) % 2== 1:
# Р езу л ьтат м етода
r e t u r n T ru e
# Если к о л и ч е с т в о э л ем ен т о в ч е т н о е

CEE}'

*

Глава 7. Продолжаем знакомство с ООП

e lse :
# Р езу л ь тат м етода
r e tu r n F a lse
# М е то д д л я п р и в е д е н и я к т и п у c o m p l e x
d e f __ c o m p l e x __ ( s e l f ) :
# М инимальное и з ч и с е л в с п и с к е
m n = m in (se lf.n u m s )
# М аксимальное и з ч и с ел в сп и ске
m x = m a x (s e lf.n u m s )
# Комплексное число
z= c o m p le x (m x ,m n )
# Р езу л ь тат метода
re tu rn z
# Создаем экзем п ляр к л а сс а
o b j= M y C lass( { 2 . 8 , 4 . 1 , 7 . 5 , 2 . 5 , 3 . 2 } )
# Вы водим н а п е ч а т ь э к з е м п л я р
# (п р ео б р азо ван и е к типу s t r )
p rin t(o b j)
# Приведение к типу i n t
p r i n t (" Э л е м е н т о в в с п и с к е i n t ( o b j ))
# П риведение к типу b o o l
if o b j:
p r i n t ("Н ечетное к о л и ч ес тв о эл ем ен то в")
# П риведение к типу flo a t
p r i n t ( "С реднее з н а ч е н и е : ", flo a t (obj ) )
# П р ео б р азо в ан и е к типу co m p lex
p r i n t ( "Минимум и м а к с и м у м : " , c o m p l e x ( o b j ) )

В результате выполнения этого кода получаем такое:
Результат выполнения программы (из листинга 7 .10 )
Значение п о л я-сп и ска:
I 4 .1 | 2 .8 I 2 .5 | 3 .2 | 7 .5 I
Элементов в с п и с к е : 5
Н ечетное к о ли ч ество элем ентов
Среднее зн а ч е н и е : 4 .0 2
Минимум и м а к с и м у м : ( 7 . 5 + 2 . 5 j )

У класса MyClass есть конструктор. У конструктора есть аргумент nums.
Этот аргумент обозначает некоторый набор элементов для формирования
поля-списка. Мы будем исходить из того, что это список или множество.
Но при создании экземпляра класса элементы организуются в виде списка.
Д ля этого в теле конструктора командой self.nums=list () у экземпля­
ра self создаем поле nums, которое вначале является пустым списком. З а ­
тем запускается оператор цикла, в котором переменная п перебирает значе­

Python

ния из списка или множества nums, переданного аргументом конструкто­
ру. За каждый цикл командой s e l f . nums . a p p e n d ( п) в поле-список nums
экземпляра s e l f добавляется с помощью метода a p p e n d () новый элемент
(переменная п).
М етод__ s t г __ () для преобразования к типу s t г также содержит оператор
цикла. В теле метода сначала командой t x t = " 3 H a 4 e H n e п о л я - с п и с к а : \
п | " определяется начальное текстовое значение для переменной t x t , а
затем в операторе цикла, в котором переменная п перебирает значения из
поля-списка n u m s экземпляра s e l f , командой t x t + = s t r ( п ) +" | " д о ­
бавляется текст со значением элемента и разделителем (в виде вертикаль­
ной черты). По завершении оператора цикла переменная t x t командой
r e t u r n t x t возвращается как результат метода.
Д ля возможности выполнять преобразование экземпляра к типу i n t в
классе M y C l a s s описывается м ето д __ i n t __ (). В теле метода всего одна
команда r e t u r n l e n (s e l f . n um s ), которой как результат метода возвра­
щается количество элементов в списке nums из экземпляра s e l f . Здесь для
вычисления количества элементов мы использовали встроенную функцию
l e n ().
М етод__ f l o a t __ () предназначен для приведения к типу floa t. В теле ме­
тода для значений элементов поля-списка nums экземпляра s e l f вычис­
ляется среднее арифметическое: сумму элементов вычисляем с помощью
встроенной функции sum (), и полученное значение делим на количество
элементов в списке. Причем для определения количества элементов мы ис­
пользуем инструкцию i n t ( s e l f ) явного приведения экземпляра s e l f к
целочисленному типу (напомним, что эта операция переопределена нами
так, что ее результатом является количество элементов в поле-списке эк­
земпляра). Результат записывается в переменную a v r , которая и возвраща­
ется как результат метода.
В теле м етода__ b o o l __ (), который описывается, чтобы приводить экзем­
пляр к типу b o o l , запускается условный оператор, в котором проверяется
условие i n t ( s e l f ) %2 == 1 : остаток от деления на 2 количества элементов
в поле-списке экземпляра s e l f должно равняться 1 . Такое случается, если
количество элементов несчетное. Как и ранее, для определения количества
элементов мы используем явное приведение экземпляра s e l f к типу i n t с
помощью функции i n t ( ) . Нелишним будет подчеркнуть, что это возмож­
но, поскольку в теле класса описан м етод__ i n t __ ().
Так вот, если количество элементов нечетное, в условном операторе ин­
струкцией r e t u r n T r u e для м етода__ b o o l __ () возвращается значение
T r u e . В противном случае возвращается значение F a l s e .

Глава 7. Продолжаем знакомство с ООП

Метод для преобразования к типу c o m p le x назы вается__ c o m p l e x __ ().
В теле метода командой mn=min ( s e l f , nu ms ) определяется минимальное
значение из набора элементов в поле-списке nums экземпляра s e l f , а ко­
мандой mx=max ( s e l f , n um s ) определяется максимальное значение из на­
бора элементов в поле-списке nums экземпляра s e l f . Затем мы создаем
комплексное число командой z = c o m p l e x (mx,mn). Это комплексное чис­
ло возвращается как результат м етода__ c o m p l e x __ ().
Ш

На заметку
Мы описываем метод__complex__ ( ) , чтобы можно было использовать функцию
complex ( ) , передавая ей аргументом ссылку на экземпляр класса. При описании
метода__complex__ () мы вызываем функцию complex ( ) , но здесь проблемы
нет, поскольку мы эту функцию вызываем "в стандартной", "штатной" ситуации, с
двумя аргументами типа float.

Экземпляр
класса
создается
командой
obj=M yC la
s s ( { 2 . 8 , 4 . 1 , 7 . 5 , 2 . 5 , 3 . 2 } ) . Аргументом конструктору передается
множество из числовых значений, хотя это мог бы быть и список. Как бы
там ни было, экземпляр o b j создан, у этого экземпляра есть поле-список
nums, и еще этот экземпляр можно приводить к базовым типам. Именно эти
возможности проверяем. Для начала пытаемся "напечатать" экземпляр, для
чего используем команду p r i n t ( o b j ).
Хотя формально мы здесь функцию s t r ( ) с аргументом-экземпляром
не вызываем, по контексту команды p r i n t ( o b j ) запускаеттся авто­
матическое приведение к типу s t r . Поэтому при выполнении команды
p r i n t ( ob j ) в окне вывода фактически отображается текстовое значение,
которое возвращается в качестве результата м етодом __ s t r __ () с аргу­
ментом - экземпляром o b j .
Аналогичная ситуация с автоматическим приведением, только теперь к
типу b o o l , имеет место при выполнении условного оператора, в котором
условием указан экземпляр o b j . Во всех остальных случаях приведение
выполняется явным образом с вызовом соответствующей функции и пере­
дачей ссылки на экземпляр класса аргументом функции.
В рассмотренном выше примере для определения количества элементов в
списке-поле экземпляра класса мы определяли м ето д __ i n t __ ( ), в кото­
ром вызывалась функция 1 еп (), которой, в свою очередь, определялось ко­
личество элементов в поле-списке экземпляра класса. Можно было пойти
иным путем: описать в теле класса м етод __ 1 еп __ (). Этот метод вызыва­
ется, если вызывается функция 1 еп () с аргументом - экземпляром соответ­
ствующего класса.

""fEEEl

Python

Другими словами, если описать в классе м ето д __ 1 е п __ (), то можно бу­
дет вызывать функцию 1 еп () для экземпляра этого класса. В листинге 7.11
приведен пример описания м етода__ 1 е п __ (). Кроме этого метода, также
описаны методы (из тех, что пока нам незнаком ы ) i n d e x
() (вызы ва­
ется при использовании функций b i n (), o c t () и h e x () ) и
round
()
(вызывается при использовании функции r o u n d () ).
Листинг 7 .1 1 . "Округление" экземпляра и другие операции
# Класс
c l a s s M y C lass:
# Конструктор
d e f __ i n i t __^ ( s e l f , t x t ) :
# П р и с в а и в а н и е з н а ч е н и я полю
# экзем пляра класса
s e l f . n am e= tx t
# М етод д л я п р и в е д е н и я к т е к с т о в о м у т и п у
d e f __ s t r ___ ( s e l f ) :
# Р езу л ь тат м етода - значение
# п о л я паш е э к з е м п л я р а к л а с с а
r e tu r n se lf.n a m e
# М е то д д л я в ы ч и с л е н и я " д л и н ы "
# э к зе м п л я р а к л а с с а функцией 1 еп ()
d e f __ l e n ___ ( s e l f ) :
# Р езультат метода - количество
# с и м в о л о в в п о л е n am e э к з е м п л я р а к л а с с а
r e t u r n l e n ( s e l f . n am e)
# М етод, которы й в ы з ы в а е т с я при и с п о л ь з о в а н и и
# ф у н к ц и й b i n ( ) , o c t () и h e x ( )
d e f __ i n d e x ___ ( s e l f ) :
# К о л и ч е с т в о п р о б е л о в плю с е д и н и ц а
p = s e l f . n a m e . c o u n t (" " ) + 1
# Р езультат метода
re tu rn р
d e f __ r o u n d ___ ( s e l f ) :
# П р и с в а и в а н и е з н а ч е н и я полю nam e
s e l f . nam e="C 6poc з н а ч е н и я "
# Р е зу л ь т ат - ссы лка на экзем п ляр к л а сс а
re tu rn s e lf
# Н ачальное т е к с т о в о е зн ачен ие
tx t= " P a 3 , два, три, четы ре, п ять . "
# Уточняем т е к с т о в о е зн а ч е н и е
tx t+ = "\n B b n iien З а й ч и к п о г у л я т ь . "
# Создаем экзем п ляр к л а с с а
o b j= M y C lass(tx t)
# Э кземпляр " п е ч а т а е т с я " в окне вывода

в Л -

Глава 7. Продолжаем знакомство с ООП

p rin t(o b j)
# В ы ч и с л я е м к о л и ч е с т в о с и м в о л о в в п о л е nam e
p r i n t (" К о л и ч е с т в о б у к в ( с и м в о л о в ) l e n ( o b j ))
# К о л и ч е с т в о с л о в ( п р о б е л о в ) в п о л е nam e
p r i n t ( " К о л и ч е с т в о с л о в : " , o b j . __ i n d e x __ ( ) )
# Д во и ч н ы й к о д
p r i n t ("В д в о и ч н о м к о д е b i n ( o b j ) )
# В осьмеричный код
p r i n t ("В в о с ь м е р и ч н о м к о д е : " , o c t ( o b j ) )
# Ш естнадцатеричный код
p r i n t ("В ш е с т н а д ц а т е р и ч н о м к о д е : " , h e x ( o b j ) )
# Вы полняем " о к р у г л е н и е " э к з е м п л я р а к л а с с а
p r i n t ( r o u n d ( o b j ))
# Вы водим " н а п е ч а т ь " э к з е м п л я р к л а с с а
p rin t(o b j)

Результат выполнения программы такой:
Результат выполнения программы (из листинга 7 .1 1 )
Р аз, д в а, три, четы ре, п ять .
Вышел З а й ч и к п о г у л я т ь .
К о л и ч е с т в о б у к в ( с и м в о л о в ) : 52
К оличество слов : 8
В двоичном к о д е : 0 Ы 0 0 0
В в о с ь м е р и ч н о м к о д е : ОоЮ
В ш е с т н а д ц а т е р и ч н о м к о д е : 0x8
Сброс зн ач ен и я
Сброс зн ачен и я

Класс называется M y Cl a ss и в этом классе есть конструктор. Конструкто­
ру передается текстовый аргумент, ссылка на который присваивается в ка­
честве значения полю name экземпляра класса. Также ради удобства в клас­
се описан м етод__ s t r __ ( ), благодаря чему функции p r i n t () можно пе­
редавать аргументом экземпляр класса. При этом будет отображаться зна­
чение, на которое ссылается поле name экземпляра класса.
М ето д __ l e n __ () для вычисления "длины" экземпляра класса (если эк­
земпляр передается аргументом функции l e n () ) содержит всего одну ин­
струкцию r e t u r n l e n ( s e l f . n a m e ) : результатом метода возвращается
длина (количество символов) текстового поля name экземпляра s e l f . По­
этому каждый раз, когда мы будем вызывать функцию l e n () и передавать
ей аргументом ссылку на экземпляр класса, результатом будет количество
символов в поле name этого экземпляра.

?
- Ш

Python

М е то д __ i n d e x __ (), который вызывается при использовании функций
b i n ( ) , o c t () и h e x (), содержит команду p = s e l f . n a m e . c o u n t (" " ) + 1 ,
которой в переменную р записывается количество пробелов в поле name
экземпляра s e l f , плюс единица. Количество пробелов подсчитывается с
помощью встроенного метода c o u n t (). Значение переменной р возвраща­
ется как результат метода.
Ш

На заметку
Если исходить из того, что между словами в тексте находится пробел, то количе­
ство пробелов на единицу меньше, чем количество слов в тексте. В этом смысле
результат, возвращаемый методом__i n i t __ () можно интерпретировать (с опре­
деленной долей условности, конечно) как количество слов в текстовом поле name
экземпляра.

В теле м етода__ r o u n d __ () командой s e l f . n a m e = " C 6 p o c з н а ч е н и я "
полю name присваивается значение, и затем командой r e t u r n s e l f ссыл­
ка на экземпляр s e l f , из которого вызывается метод, возвращается в каче­
стве результата метода.
Ш

На заметку
Как отмечалось ранее, метод__round__ () задействуется, когда функция round О
вызывается с экземпляром класса (в данном случае MyClass). Например, если
ссылку на экземпляр обозначить o b j, то при выполнении команды round ( o b j) на
самом деле выполняется команда o b j . __round__ ( ) . При выполнении этой ко­
манды (в силу того, как мы описали метод__s e l f __ ( ) ) полю name присваивает­
ся значение "Сброс значения", а результатом выражения round (obj ) является
ссылка на экземпляр o b j.

Для проверки функциональности описанных методов в переменную t x t
записывается текстовое значение, а затем на основе этого текстового зна­
чения командой o b j = M y C l a s s ( t x t ) создаем экземпляр o b j класса My­
C l a s s . Далее следует серия команд, в которых используется ссылка на этот
экземпляр. Так, сначала выполняется команда p r i n t ( o b j ). В этом случае
"в игру" вступает м етод__ s t r ___( ) .
В результате отображается значение поля п а ш е экземпляра o b j . Количе­
ство символов в поле name отображается при выполнении команды p r i n
t ( " К о л и ч е с т в о б у к в ( с и м в о л о в ) l e n ( o b j ) ) . В этой команде ис­
пользована инструкция l e n ( o b j ), которая вычисляется путем вызова ме­
тода __ l e n __ () из экземпляра o b j . Количество пробелов отображаем ко­
мандой p r i n t ( " К о л и ч е с т в о с л о в : " , o b j . __ i n d e x
() ). Мы здесь
явно вызываем метод
index
().

е э

*

Глава 7. Продолжаем знакомство с ООП

При вычислении инструкций bin (obj ) ,oct (obj ) и h e x (obj ) тоже вы­
зывается этот метод, но числовой результат (а это, с математической точки
зрения, каждый раз один и тот же результат) отображается соответственно
в двоичной, восьмеричной и шестнадцатеричной системах счисления.
Наконец, при выполнении команды print (round (obj ) ) полю name эк­
земпляра obj присваивается значение "Сброс з н а ч е н и я " . Поскольку ре­
зультатом round (obj ) является ссылка на экземпляр obj, а также благо­
даря описанному в классе MyClass м етоду __ str__ ( ), при выполнении
команды p r i n t ( r o u n d ( ob j ) ) появляется сообщение Сброс зн а ч е н и я .
Чтобы удостовериться, что экземпляр o b j изменился, выполняем команду
p r i n t ( o b j ).
Группа м етодов__ s e t i t e m __ ( ) , ___g e t i t e m __ () и ___d e l i t e m __ () ав­
томатически вызываются соответственно при присваивании значения эк­
земпляру через индекс, считывании значения экземпляра через индекс и
удалении значения экземпляра через индекс.
Ш

На заметку
Эти же методы используются для обработки операций присваивания значения,
считывания значения и удаления значения по ключу.

В листинге 7.12 приведен пример использования этих методов. Там мы
описываем класс MyClass для создания экземпляров, у которых есть поле
nums, представляющее собой числовой список. Количество элементов в
списке определяется полем класса Nmax.
Описывая м етоды __ s e t i t e m __ ( ) , ___g e t i t e m __ () и ___d e l i t e m __ (),
мы хотим добиться эффекта, чтобы можно было обращаться к элементам
поля nums экземпляра (для присваивания значения, считывания значения
и удаления значения), указывая индекс непосредственно для экземпляра
класса. Причем при указывании индекса, если этот индекс выходит за допу­
стимые пределы, выполняется циклическая перестановка.
Программный код выглядит так:
Листинг 7 .1 2 . Операции с экземплярами через индекс
# Класс
c l a s s M y C lass:
# П ол е к л а с с а
№пах=5
# Конструктор
def
in it
(se lf) :
# Количество элем ентов

-cm

Python

n=MyClass.Nmax
# Список с нулевыми э л ем ен т ам и
self.num s= [0 fo r i in range (n )]
# Метод п р и в е д е н и я к т е к с т о в о м у типу
d e f __ s t r __ ( s e l f ) :
# Текстовая переменная
t x t —" I "
# Фо р м и р у е м т е к с т
fo r s in self.num s:
# Добавляем к т е к с т у фрагмент
t x t + = " " + s t r ( s ) + " I"
# Результат метода
return tx t
# Метод д л я п р и с в а и в а н и я з н а ч е н и я по и н д е к с у
d e f __ s e t i t e m __ ( s e l f , i , v ) :
# Остаток от деления
k=i % le n (s e lf.n u m s )
# Присваивание значения
s e l f . num s[k]=v
# Метод д л я с ч и т ы в а н и я з н а ч е н и я по и н д е к с у
d e f __ g e t i t e m __ ( s e l f , i ) :
# Остаток от деления
k = i % l e n ( s e l f . nums)
# Значение элемента
r e t u r n s e l f . n u m s [ k]
# Метод д л я у д а л е н и я з н а ч е н и я по и н д е к с у
def
d e l i t e m __( s e l f , i ) :
# Остаток от деления
k=i % le n (s e lf.n u m s )
# Новое з н а ч е н и е э л е м е н т а
s e l f . n u m s [ k ] ="*"
# Создается экземпляр класса
o b j = M y C l a s s ()
# Содержимое с п и с к а
p rin t(o b j)
# Новые з н а ч е н и я э л е м е н т о в с п и с к а
o b j [0]=100
o b j [ 2 ] =-3
o b j [24]=123
# Содержимое с п и с к а
p rin t(o b j)
# Считывание зн ачений элем ентов списка
p r i n t ("Элемент с индексом 4 : " , o b j [4])
p r i n t ("Элемент с индексом 7 : " , o b j [7])
# Удаление элем ентов списка
d e l o b j [0]

fE § ~

Глава 7. Продолжаем знакомство с ООП

d e l o b j [9]
# Содержимое с п и с к а
prin t(o b j)

При выполнении этого кода получаем такой результат:
Результат вылолненж1 программы (из листинга 7,
О

О

О

о

о

1
1 1 0 0 | 0 | - 3 | 0 1 1 23 |
Э л е м е н т с и н д е к с о м 4 : 123
Элемент с индексом 7: -3
1 * 1 0 | -3 | 0 | * 1

В классе M y C lass полю Nmax присваивается значение 5. Это означает, что
поле-список экземпляра класса будет содержать 5 элементов. При создании
экземпляра все элементы этого списка получают нулевые значения. В част­
ности, в теле конструктора создается список и заполняется нулями (все это
делается с помощью генератора списков). М е то д __ s t r __ () описан так,
чтобы при передаче функции p r i n t () ссылки на экземпляр класса в окне
вывода отображалось содержимое списка nums - поля экземпляра класса.
Все, что описано в теле метода __ s e t i t e m __ ()
- это на са­
мом деле то, что происходит при попытке выполнить команду вида
э к з е м п л я р [и н д е к с ] = зн а ч е н и е . Все три параметра (ссылка на
э к з е м п л я р класса, и н д е к с и присваиваемое з н а ч е н и е ) описаны как ар­
гументы м етода__ s e t i t e m __ (), - соответственно s e l f , i и v. В теле ме­
тода командой k = i % l e n ( s e l f . n um s ) вычисляется остаток от деления
индекса i на значение l e n ( s e l f .nums) (количество элементов в списке
num s). Затем командой s e l f . nums [ k] =v элементу в этом списке с индек­
сом к присваивается значение v.
Нечто похожее происходит при выполнении программного кода м етода__
g e t i t e m __ (). В данном случае речь идет о вычислении значения выраже­
ния вида э к з е м п л я р [и н д е к с ]. Разница в сравнении с предыдущим случа­
ем в том, что ранее этому выражению присваивалось значение, а теперь для
этого выражения значение вычисляется (считывание значения). Отсюда у
м етода__ g e t i t e m __ () два аргумента: ссылка на экземпляр класса s e l f и
значение индекса i . В теле метода выполняются всего две команды: коман­
дой k = i % l e n (s e l f . n um s ) вычисляется индекс к с учетом циклической
перестановки и командой r e t u r n s e l f . nums [ к] возвращается результат
метода - значение элемента с индексом к из списка nums экземпляра s e l f .
М етод__ d e l i t e m __ () автоматически вызывается при попытке удаления
значения командой вида э к з е м п л я р [ и н д е к с ] . У метода (при описании)

с э

Python

два аргумента: ссылка на э к з е м п л я р класса и и н д е к с (удаляемого элемен­
та). В теле метода традиционно командой k = i % l e n ( s e l f . nums) "уточ­
няется" индекс, а затем выполняется присваивание s e l f .nums [ k ] = " * " .
Таким образом, если будет предпринята попытка удалить элемент, то на са­
мом деле этому элементу в качестве значения будет присвоен текст " *
Ш

На заметку
Когда мы говорим об удалении элементов, то фактически речь идет о команде
вида del экземпляр [индекс]. Если мы будем удалять элементы, напрямую об­
ращаясь к полю-списку экземпляра, удаление будет выполняться так, как и долж­
но было бы быть. Это же замечание относится к присваиванию значений через ин­
декс (команда экземпляр [индекс] =значение) и считыванию значения через ин­
декс (команда экземпляр [индекс]). Кроме того, в принципе совсем необяза­
тельно, чтобы у экземпляра было поле-список. В принципе, обращение к экзем­
пляру через индекс можно реализовать совершенно иначе, не привлекая встроен­
ные в экземпляр списки.

После создания командой o b j = M y C l a s s () экземпляра o b j класса МуC l a s s , все элементы (их всего 5) списка nums экземпляра будут нулевы­
ми. Значения элементов списка nums отображаются при выполнении ко­
манды p r i n t ( ob j ). Затем несколькими командами выполняется присва­
ивание значений элементам списка nums, причем обращение к элементам
выполняется через указание индекса для экземпляра класса. Так, командой
o b j [ 0 ] = 1 0 0 элементу nums [ 0 ] (поле-список nums экземпляра o b j ) при­
сваивается значение 1 0 0 , командой o b j [ 2 ] = - 3 присваивается значение - 3
элементу nums [ 2], и, наконец, командой o b j [24 ] =123 элементу nums [4]
присваивается значение 123.
Ш

На заметку
Что касается команды obj [24 ]=123: остаток от деления 24 на 5 есть 4. Поэтому
речь идет о присваивании значения элементу массива nums с индексом 4.

После внесения изменений проверяем содержимое списка nums коман­
дой p r i n t ( o b j ). М ожно считывать значения и поэлементно, обращаясь
"персонально" к тому или иному элементу - как это делается в инструкциях
o b j [ 4 ] и o b j [ 7 ] (элементы nums [ 4 ] и nums [ 2 ] соответственно). При по­
пытке удаления элементов списка командами d e 1 o b j [0] и d e l o b j [9]
элементы nums [ 0 ] и nums [ 4 ] получают значение " * Результат проверя­
ем командой p r i n t ( o b j ).
Ш

На заметку
Инструкции obj [7] и obj [9] означают обращение к элементам nums [2] и

СВ)-

Глава 7. Продолжаем знакомство с ООП

nums [4 ] соответственно: остаток от деления 7 на 5 равен 2, а остаток от деления
9 на 5 равен 4.
Благодаря описанию специальных методов __ g e t a t t r __ (), ___g e t a t t r i b u t e __ (), ___s e t a t t r __ () и ___d e l a t t r __ () можно существенно
разнообразить операции обращения к атрибутам экземпляра класса. Метод
__ s e t a t t r __ () вызывается при присваивании значения атрибуту экзем­
пляра класса. М етод __ d e l a t t r __ () вызывается при удалении атрибута
экземпляра класса. При обращении (для считывания значения) к несуще­
ствующему атрибуту вызывается м етод__ g e t a t t r __ (). Наконец, при об­
ращении к любому атрибуту вызывается м етод__ g e t a t t r i b u t e __ ().
М е то д __ g e t a t t r __ () вызывается при обращении к несуществующему
атрибуту экземпляра класса, если в классе не описан м етод__ g e t a t t r i b ­
u t e __ (). Если м ето д ___g e t a t t r i b u t e __ () в классе описан, то вызыва­
ется он, причем при обращении к любому атрибуту - как существующему,
так и несуществующему. Метод __g e t a t t r __ (), даже если он описан в
классе, при этом игнорируется.
С м етодом __ g e t a t t r i b u t e __ () связана определенная проблема: если
в теле этого метода выполняется обращение к атрибуту экземпляра клас­
са с использованием точечного синтаксиса (то есть в формате э к з е м п л я р .
а т р и б у т ), то получается бесконечный циклический вызов м ето д а__ g e ­
t a t t r i b u t e __ ().
Действительно, если вызывается м ето д __ g e t a t t r i b u t e __ (), а при вы­
полнении этого метода выполняется обращение к атрибуту экземпляра
класса, то метод будет вызван снова. То есть в процессе выполнения метод
фактически вызывает сам себя. Но на этом процесс не заканчивается. Вы­
званный метод снова вызывает этот же метод, и так до бесконечности.
На первый взгляд ситуация кажется безнадежной, но это, разумеется, не так.
Просто обращение к атрибутам экземпляра класса следует выполнять, явно
вызывая м ет о д __ g e t a t t r i b u t e __ () как функцию из класса o b j e c t с
явным указанием ссылки на экземпляр класса и его атрибут. Поскольку
речь идет об обращении к атрибуту в теле м е т о д а __g e t a t t r i b u t e __ (),
то ссылка на экземпляр класса будет называться s e l f . Если речь идет об
обращении к а т р и б у т у этого экземпляра, то обращение к этому атрибуту
будет выглядеть как o b j e c t . __ g e t a t t r i b u t e __ ( s e l f , а т р и б у т ) .
Нечто похожее происходит, когда в теле метода__ s e t a t t r __ () (который
"отвечает" за присваивание значений атрибутам экземпляров) выполняется
присваивание значения атрибуту с использованием точечного синтаксиса.
Проблема такая: при выполнении м етода__ s e t a t t r __ () если присваива-

-€2Э

Python

ется значение атрибуту, автоматически вызывается м етод__ s e t a t t r __ (),
в теле которого вызывается м етод s e t a t t r
(), и так далее.
Чтобы этого не происходило, при присваивании значения атрибуту экзем­
пляра класса в теле м етода__ s e t a t t r __ () следует "добираться" до нуж­
ного атрибута через специальное п о л е __ d i e t __ , значением которого я в ­
ляется словарь из названий атрибутов экземпляра и их значений. Если
речь идет о присваивании значения атрибуту, то в теле м ето д а__ s e t a t ­
t r __ () соответствующая команда может выглядеть как s e l f . ___d i e t __
[ а т р и б у т ] ^ з н а ч е н и е (здесь через s e l f обозначена ссылка на экземпляр
класса).
Формально в данном случае мы в сл о в а р е __ d i e t __ изменяем значение
элемента с ключом а т р и б у т . Но поскольку набор атрибутов и значений
атрибутов экземпляра реализуется именно через этот словарь, то нужный
эффект будет достигнут - атрибут экземпляра меняет значение.
Не следует удалять поля с помощью оператора d e 1 в теле м етода__ d e 1 a t t r __ ( ). В этом случае м е т о д ___d e l a t t r __ () будет вызывать сам себя.
Если нужно удалить поле в теле м ето д а__ d e l a t t r __ (), лучше сделать
это, удалив соответствующий элемент из сл оваря__ d i e t __ .
Небольшой пример с описанием м етодов__ g e t a t t r __ ( ) , ___s e t a t t r __
() и __ d e l a t t r __ () приведен в листинге 7.13. В этом примере создается
класс MyClass. В классе описан конструктор - такой, что при создании эк­
земпляра класса полю name экземпляра присваивается значение. Также в
классе описан м ето д __ s t r __ ( ), благодаря чему при передаче экземпля­
ра аргументом методу p r i n t () отображается значение поля name этого эк­
земпляра.
Мы также хотим добиться такой реакции на действия с атрибутами экзем­
пляра класса:


Значение поля name можно изменять.



При попытке создать (путем присваивания значения) новое поле,
появляется сообщение О перация не р а зр е ш е н а !, и новое поле
не создается.



Если при считывании значения поля выполняется обращение к не­
существующему полю, появляется сообщение Т а к о г о п о л я н е т ! .



При попытке удалить поле экземпляра появляется сообщение
У д а л ят ь п оля з а п р е щ е н о ! .

е э -

?

Глава 7. Продолжаем знакомство с ООП

Собственно, для решения этих задач мы и описываем в классе MyClass ме­
тоды __ getattr__( ) , __ setattr__ () и ___delattr__ ( ). Как именно
они описаны, показано ниже:
Листинг 7 .1 3 . Обращение к полям экземпляра
# Класс
c l a s s MyClass:
# Конструктор
d e f __ i n i t __ ( s e l f , n a m e ) :
# Полю э к з е м п л я р а
# присваивается значение
s e l f . name=name
# Ме то д д л я п р и в е д е н и я э к з е м п л я р а
# к те кс то во м у значению
d e f __ s t r __ ( s e l f ) :
# Результат метода
re tu rn self.nam e
# М е то д д л я о б р а б о т к и с и т у а ц и и , к о г д а
# атрибуту присваивается значение
d e f __ s e t a t t r __ ( s e l f , a t t r , v a l ) :
# Если зн а ч е н и е п р и с в а и в а е т с я
.# полю na me
i f attr== "nam e":
s e l f . __ d i e t __ [ a t t r ] = v a l
# Если з н а ч е н и е п р и с в а и в а е т с я не
# полю name
else:
p r i n t ( "Операция не р а з р е ш е н а ! ” )
# Метод д л я о б р а б о т к и с и т у а ц и и , к о г д а
# считывается значение атрибута
def
g etattr
(self,attr):
# Р езультат метода
r e t u r n "Такого поля н е т !"
# Ме то д д л я о б р а б о т к и с и т у а ц и и , к о г д а
# атрибут удаляется
def
d elattr
(self,attr):
# О т о б р а ж а е т с я сообщение
p r i n t ("Удалять поля запрещ ено!")
# Создается экземпляр класса
o b j= M y C la s s ("Исходное значен ие")
# П р о в е р я е м з н а ч е н и е п о л я n ame
p rin t(o b j)
# Н о в о е з н а ч е н и е п о л я паше
o b j . пате="Новое значение"
# П р о в е р я е м з н а ч е н и е п о л я na me

е э

Python

p r in t (o b j)
# П р и с в а и в а е м з н а ч е н и е полю n u m b e r
o b j . num ber= 100
# П роверяем зн ач ен и е поля num ber
p rin t(o b j.n u m b e r)
# У д а л я е м п о л е nam e
d e l o b j . nam e
# П р о в е р я е м з н а ч е н и е п о л я nam e
p rin t(o b j)

Результат выполнения программного кодатакой:
Результат выполнения программы (из листинга 7.13)
И сходное зн а ч е н и е
Новое з н а ч е н и е
О перация не р а з р е ш е н а !
Такого поля н е т !
Удалять поля зап р ещ ен о !
Новое з н а ч е н и е

Рассмотрим
программные
коды
методов
__ g e t a t t r __ (),
_s e t a t t r __ () и ___d e l a t t r __ (). Самый простой код у методов
_g e t a t t r
() и d e l a t t r
(): в теле метода g e t a t t r
() резуль­
тат возвращается командой r e t u r n " Т а к о г о по л я н е т ! ", а в теле мето­
да __ d e l a t t r __ () выполняется единственная команда p r i n t ( "Уд ал ят
ь по л я з а п р е щ е н о ! " ) . С методом__ s e t a t t r __ () все немного сложнее:
в условном операторе проверяется условие a t t r = = " n a m e ", которое истин­
но, если запрашиваемый атрибут (аргумент метода a t t r ) - это поле паше.
В этом случае выполняется команда s e l f . __ d i e t __ [ a t t r ] = v a l . Здесь
в сл о вар е__ d i c t _ для экземпляра s e l f элементу с ключом a t t r при­
сваивается значение v a l (аргумент м ето д а__ s e t a t t r __ () ). Если усло­
вие a t t r = = " n a m e " ложно (то есть атрибут a t t r не является полем паше),
выполняется команда p r i n t ( "Опе рация не р а зр е ш е н а ! " ) .
После описания класса M yC la ss командой o b j = M yC lass ( "Исходное
з н а ч е н и е " ) создается экземпляр o b j , поле паше которого получает зна­
чение "И сход н ое з н а ч е н и е " . Проверить значение поля name экземпля­
ра o b j можно с помощью команды p r i n t (o b j ).
Командой o b j .па ше="Новое з н а ч е н и е " полю name присваивается но­
вое значение. Эта операция проходит успешно. Но при попытке выполнить
команду o b j . n u m b e r = 1 0 0 поле n um b er у экземпляра o b j не создается, и,
разумеется, значение ему не присваивается (вместо этого появляется сооб­
щение О перация не р а з р е ш е н а ! ). При попытке "прочитать" значение
несуществующего поля n um b er в команде p r i n t ( o b j . n u m b e r ) появля.................................................- ............................ - - - - - ............................................... —

......................................................- .................................... —

......................... ~

Глава 7. Продолжаем знакомство с ООП

ется сообщение Т а к о г о по л я н е т !. Если попытаться удалить поле name
у экземпляра ob j командой d e l ob j . name, поле name не удаляется, и по­
является сообщение Уда л я ть по ля з а п р е щ е н о !.
Еще один способ обработки операций присваивания значения полям, счи­
тывая значения полей и удаления полей, представлен в следующем приме­
ре. Как и в предыдущем случае, мы описываем м етоды __ s e t a t t r __ () и
__d e l a t t r __ (), а вместо м ет о д а ___g e t a t t r __ () описываем метод _
g e t a t t r i b u t e __ ().
Q

На заметку
Напомним, что метод__g e t a t t r __ () вызывается при обращении для считывания
знания к несуществующему полю, в то время как метод__g e t a t t r i b u t e __ О вы­
зывается при считывании значения любого поля - как существующего, так и не су­
ществующего.

Итак, в классе M yC la ss описываются м етоды __ s e t a t t r __ ( ) , __ g e t a t t r i b u t e __ () и ___d e l a t t r __ ().
При выполнении м етода__ s e t a t t r __ ():


появляется сообщение о начале выполнения метода;



появляется сообщение о том, какому полю какое значение присва­
ивается;



выполняется присваивание полю значения;



появляется сообщение о завершении выполнения метода.

При выполнении м етода__ g e t a t t r i b u t e __ ():


появляется сообщение о начале выполнения метода;



появляется сообщение о том, значение какого поля считывается;



выполняется попытка прочитать значение поля;



если поле существует, появляется сообщение о завершении метода
и возвращается значение поля;



если поля не существует, появляется сообщение о завершении ме­
тода и возвращается текстовое значение с информацией о том, что
поля не существует.

При выполнении м етода__ d e l a t t r __ ():


появляется сообщение о начале выполнения метода;



появляется сообщение о том, какое поле удаляется;
—-# m

Python

#

выполняется попытка удалить поле;

#

если поле существовало и его удаление проходит успешно, появля­
ется сообщение о завершении выполнения метода;

#

если поля не существовало, появляется сообщение о невозможно­
сти удалить поле, а затем сообщение о завершении метода.

Весь программный код представлен в листинге 7.14 и выглядит так:
Листинг 7 .1 4 . Считывание значения поля
# Класс
c l a s s M yC lass:
# М е т о д в ы з ы в а е т с я , е с л и полю
# присваивается значение
d e f __ s e t a t t r __ ( s e l f , a t t r , v a l ) :
p r i n t ( " В ы п о л н я е т с я м е т о д _ _ s e t a t t r __ ( ) : " )
1хС="*\1Полю " + s t r ( a t t r )
tx t+ = " присваивается значение " + s t r ( v a l )
p rint(txt)
# П р и с в а и в а н и е з н а ч е н и я п ол ою
s e l f . __ d i e t __ [ a t t r ] = v a l
p r i n t ( " М е т о д __ s e t a t t r __ () в ы п о л н е н . " )
# Метод в ы з ы в а е т с я , е с л и
# сч и ты в ается зн а ч е н и е поля
d e f __ g e t a t t r i b u t e __ ( s e l f , a t t r ) :
p r i n t ( " В ы п о л н я е т с я м е т о д __ g e t a t t r i b u t e __ ( ) : " )
t x t = " * ^ С ч и ты в а ет ся значение поля " + s t r ( a t t r )
p r i n t (txt)
# Результат метода
try :
# Значение поля - если поле существует
r e s = o b j e c t . __ g e t a t t r i b u t e __ ( s e l f , a t t r )
except A ttrib u te E rro r:
# Если поля не сущ ествует
res= "Y экзем пляра поля " + s t r ( a t t r )+" н е т !"
p r i n t ( " М е т о д __ g e t a t t r i b u t e __ () з а в е р ш а е т р а б о т у . " )
# Р езультат метода
retu rn res
# Метод в ы з ы в а е т с я , е с л и
# поле удал яется
d e f __ d e l a t t r __ ( s e l f , a t t r ) :
p r i n t ( " В ы п о л н я е т с я м е т о д __ d e l a t t r __ ( ) : " )
t x t = " * ^ У д а л я е т с я поле " + s t r ( a t t r )
p rin t(tx t)
try :

€ШИ~

Глава 7. Продолжаем знакомство с ООП

# Удаление поля - если поле сущ ествует
d e l s e l f . __ d i e t __ [ a t t r ]
ex cep t K eyError:
# Если т а к о г о поля не сущ ествует
p r i n t ("Нельзя удалить поле " + s t r ( a t t r ) )
p r i n t ( " М е т о д __ d e l a t t r __ () в ы п о л н е н . " )
* Создается экземпляр класса
o b j = M y C l a s s ()
* Полю na me п р и с в а и в а е т с я з н а ч е н и е
o b j . nam e="Python"
* П р о в е р я е т с я з н а ч е н и е п о л я n ame
p r i n t ("Значение поля n a m e o b j . n a m e )
* У д а л я е т с я п о л е na me
d e l obj.nam e
* П р о в е р я е т с я з н а ч е н и е п о л я n ame
p r i n t ( o b j . name)
* П о в т о р н о у д а л я е т с я п о л е na me
d e l obj.nam e
Результат в ы п о л н е н и я п рограм много кода п редставлен ниже:
Результат выполнения программы (из листинга 7 .14 )
В ы п о л н я е т с я м е т о д __ s e t a t t r __ ( ) :
* Полю na me п р и с в а и в а е т с я з н а ч е н и е P y t h o n
В ы п о л н я е т с я м е т о д __ g e t a t t r i b u t e __ ( ) :
* Считывается значение поля
d iet
М е то д __ g e t a t t r i b u t e __ () з а в е р ш а е т р а б о т у .
М е т о д __ s e t a t t r __ () в ы п о л н е н .
В ы п о л н я е т с я м е т о д __ g e t a t t r i b u t e __ ( ) :
* С ч и т ы в а е т с я з н а ч е н и е п о л я na me
М е т о д __ g e t a t t r i b u t e __ () з а в е р ш а е т р а б о т у .
З н а ч е н и е пол я nam e: P y th o n
():
В ы п о л н я е т с я м е т о д __ d e l a t t r
* У д а л я е т с я п о л е name
В ы п о л н я е т с я м е т о д __ g e t a t t r i b u t e
():
* С ч и т ы в а е т с я з н а ч е н и е п о л я ___ d i c t __
М е т о д __ g e t a t t r i b u t e __ () з а в е р ш а е т р а б о т у .
М е т о д __ d e l a t t r __ () в ы п о л н е н .
В ы п о л н я е т с я м е т о д __ g e t a t t r i b u t e __ ( ) :
* С ч и т ы в а е т с я з н а ч е н и е п о л я na me
М е т о д __ g e t a t t r i b u t e __ () з а в е р ш а е т р а б о т у .
У э к з е м п л я р а п о л я n ame н е т !
В ы п о л н я е т с я м е т о д __ d e l a t t r __ ( ) :
* У д а л я е т с я п о л е name
В ы п о л н я е т с я м е т о д __ g e t a t t r i b u t e __ ( ) :
* Считывается значение поля
d iet

Python

Метод __g e t a t t r i b u t e __ () заверш ает работу.
Нельзя удалить поле паше
Метод __d e l a t t r __() выполнен.
Чтобы понять, почему появляются именно такие сообщения, во внимание
нужно принять следующие обстоятельства.


После создания экземпляра класса командой o b j = M y C l a s s () у
этого экземпляра нет полей (тех, что созданы пользователем). При
выполнении команды o b j . n a m e = " P y t h o n " у экземпляра o b j по­
является поле паше со значением " P y t h o n " . При этом вызывает­
ся м етод__ s e t a t t r __ (), в теле которого выполняется обращение
к специальному п о л ю __ d i e t __ . Обращение к п о л ю __ d i e t __ в
свою очередь приводит к вызову м етода__ g e t a t t r i b u t e __ ().



При
проверке
значение
поля
паше
командой
p r i n t ( " З н а ч е н и е п о л я паше : " , o b j . паше) вызывается метод
__ g e t a t t r i b u t e __ ().



При удалении поля name командой d e l o b j . name вызывается ме­
тод __ d e l a t t r __ (). Поскольку в процессе удаления поля выпол­
няется обращение к специальному п ол ю __ d i e t __ , то автоматиче­
ски вызывается и м етод__ g e t a t t r i b u t e __ ().



После удаления поля name значение этого поля проверяется коман­
дой p r i n t ( o b j . n a m e ) . Как следствие вызывается м е т о д __ g e ­
t a t t r i b u t e __ ().



Попытка повторного удаления поля name командой d e l o b j .
name приводит к вызову м етода__ d e l a t t r __ (), а при выполне­
нии этого метода - к вызову м етода__ g e t a t t r i b u t e __ ().

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

Перегрузка операторов
П е р е за гр у зк а заверш ен а. С част ливого пут и!

из к/ф "Гостья из будущего "

В Python существует возможность определять для экземпляров пользова­
тельских классов такие операции, как сложение, умножение, вычитание,
деление, и ряд других. Иными словами, мы имеем возможность так опи­
сать класс, что экземпляры этого класса можно будет складывать, вычитать,
умножать, делить и так далее - как, например, обычные числа. Что получаm

Глава 7. Продолжаем знакомство с ООП

ется в результате - это вопрос другой, и ответ на него всецело зависит от нас
и нашей фантазии.
Чтобы та или иная операция (например, сложение) стала доступна для вы­
полнения с экземплярами класса, достаточно в классе описать определен­
ный специальный метод (каждому оператору соответствует метод, который
автоматически вызывается при обработке выражения с этим оператором).
Если такой метод в классе описан, то операция, соответствующая данному
методу, будет доступна для экземпляров класса.
Например, оператору сложения + соответствует м етод __ a d d __ ( ). Если в
классе описан м е т о д __ a d d __ ( ) , то при вычислении выражения, в кото­
ром к экземпляру класса что-то прибавляется, будет вызван (автоматиче­
ски) м етод__ a d d __ ().
Небольшой пример с классом, в котором описан метод _ a d d __ (), пред­
ставлен в листинге 7.15.
Листинг 7 .1 5 . Перегрузка оператора сложения
# К л а с с с о п и с а н и е м м е т о д а __ a d d __ ()
c l a s s Adder:
# Конструктор
d e f __ i n i t __ ( s e l f , n u m b e r ) :
# Полю э к з е м п л я р а п р и с в а и в а е т с я
# значение
s e l f . numberenumber
# Метод д л я п р и в е д е н и я к т е к с т о в о м у
# типу
def
str
(self):
# Формируется т е к с т
txt="3H a4eH ne поля number = "
txt+ = str(self.num ber)
# Р езультат метода
return tx t
# Описание м е т о д а дл я о п е р а ц и и сложения
def
add
(self,x ):
# Вычисляется числовое значение
n u m b e r = s e l f . number+x
# Создается экземпляр класса
tmp=Adder(number)
# Р е з у л ь т а т м етода - ссылка на
# экземпляр класса
r e t u r n tmp
# Создается экземпляр класса
a = A d d e r (10)
J К экзем пляру к л а с с а д о б ав л яетс я число

■#1Ж1

Python

b=a+5
# Проверяем поле number 1 -го экзем пляра
p r i n t ( а)
# Проверяем поле number 2 - г о экзем пляра
print(b)

Результат выполнения программного кода такой:
Результат выполнения программы (из листинга 7 .15 )
З н а ч е н и е п о л я n u m b e r = 10
З н а ч е н и е п о л я n u m b e r = 15

В классе A d d e r есть конструктор, при выполнении которого полю n um ber
экземпляра присваивается значение. М е т о д __ s t r __ () описан так, что
при передаче экземпляра класса аргументом методу p r i n t () отображает­
ся значение поля n u m b er экземпляра.
Интерес представляет м етод__ a d d __ ( ) , описанный с двумя аргументами:
аргумент s e l f традиционно обозначает ссылку на экземпляр класса, из ко­
торого вызывается метод, а аргумент х обозначает числовое (как мы пред­
полагаем) значение, которое прибавляется к экземпляру класса.
В теле метода командой number=self.number+x вычисляется сум­
ма поля number экземпляра self и аргумента х. Результат записывает­
ся в локальную переменную number. Данное значение передается аргу­
ментом конструктору при создании нового экземпляра класса командой
tmp=Adder (number). Этот экземпляр класса возвращается как результат
метода командой return tmp.
Таким образом, при прибавлении к экземпляру класса числового значения
создается новый экземпляр класса, поле n u m b er которого равняется сумме
значений поля n u m b er исходного экземпляра и прибавляемого к этому эк­
земпляру числа.
Вне тела класса командой a=Adder(10) создается экземпляр а класса
Adder со значением 10 для поля number. Благодаря тому, что в классе
Adder описан м етод__ add__ ( ) , становится возможной команда Ь=а+5, в
которой вычисляется "сумма" экземпляра а и числа 5, а результат, - ссыл­
ка на новый экземпляр класса, - записывается в переменную Ь. После это­
го переменные а и b ссылаются на экземпляры класса Adder. Проверить,
что речь идет о разных экземплярах, легко: достаточно выполнить команды
print(а) и print(Ь).

f& l

*

Глава 7. Продолжаем знакомство с ООП

Ш

На заметку
Если мы вместо того, чтобы к экземпляру класса прибавлять число, попытаемся
к числу прибавить экземпляр (например, попробуем выполнить команду Ь=5+а),
появится сообщение об ошибке. Дело в том, что метод__add__ () "обрабатыва­
ет" только прибавление к экземпляру числа, но не наоборот. Чтобы можно было
к числу прибавлять экземпляр, следует в классе Adder описать еще и метод
__radd__ (). Программный код этого метода мог бы быть таким:
d e f __radd__ ( s e l f , x ) :
r e tu r n s e lf+ x
В данном случае мы в теле метода __radd__ () неявно вызывается метод
__add__ ( ) , поскольку именно этот метод будет задействован при вычислении вы­
ражения s e l f ■
+х, в котором к экземпляру класса прибавляется число.
В данном случае мы определяем операции сложения экземпляра с числом и числа
с экземпляром так, что результаты идентичны. Но необходимости в этом не было мы могли бы определить эти операции по-разному.

Операторов, которые можно перегружать, достаточно много. Их обычно
разбивают на три группы: математические операторы, двоичные операто­
ры и операторы сравнения. Далее в таблице 7.3 перечислены перегружае­
мые математические операторы. При этом мы используем такие обозначе­
ния: через o b j обозначен экземпляр класса, в котором перегружается ме­
тод, а другой операнд (если такой имеется) обозначен через х.
Таблица 7 .3. Перегружаемые математические операторы
Метод

add

Оператор

()

__rad d __ ()

iadd

sub
rsub

t

()

()
()

Пример

Описание

+

obj +х

Сложение экземпляра класса
o b j с операндом х

+

x+obj

Сложение операнда х с эк­
земпляром класса o b j

+=

obj +=х

Сложение экземпляра класса
o b j с операндом х и присва­
ивание значения переменной
экземпляра o b j

-

obj -х

Вычитание из экземпляра
класса o b j операнда х

-

x-obj

Вычитание из операнда х эк­
земпляра класса o b j

- i r a

Python

Метод

isub

mul

rm ul

imul

Оператор

()

()

()

()

truediv
rtruediv

itruediv

floordiv

rfloo rdi v

iflo o rd iv

fat*-

()
()

()

()

()

()

Пример

Описание

-=

o b j- = x

Вычитание из экземпляра
класса o b j операнда х и при­
сваивание результата пере­
менной экземпляра класса
obj

•к

obj *x

Вычисление
произведения
экземпляра класса o b j и опе­
ранда X



x*obj

Вычисление
операнда х
класса o b j

к—

obj *=x

Умножение экземпляра клас­
са o b j на операнд х и присва­
ивание результата перемен­
ной экземпляра класса o b j

/

obj /x

Деление экземпляра класса
o b j на операнд х

/

x/obj

Деление операнда х на экзем­
пляр класса o b j

/=

o b j /= x

Деление экземпляра класса
o b j на операнд х и присваи­
вание результата переменной
экземпляра класса o b j

//

obj//x

Целочисленное деление эк­
земпляра класса o b j на опе­
ранд X

//

x / / obj

Целочисленное деление опе­
ранда х на экземпляр класса
obj

o b j / /=x

Целочисленное деление эк­
земпляра класса o b j на опе­
ранд х и присваивание ре­
зультата переменной экзем­
пляра класса o b j

//=

произведения
и экземпляра

Глава 7. Продолжаем знакомство с ООП

Метод

Оператор

%

mod__ ()

Описание

Пример

o b j %х

Остаток от деления экзем­
пляра класса o b j на операнд
X

__ r m o d __ ()

im od

pow

()

()

rpow

()

x%obj

Остаток от деления операн­
да х на экземпляр класса o b j

%=

o b j %=x

Остаток от деления экзем­
пляра класса o b j на операнд
х и присваивание результа­
та переменной экземпляра
класса o b j

★★

o b j **x

Возведение экземпляра клас­
са o b j в степень операнда х

★★

x**obj

Возведение операнда х в сте­
пень экземпляра класса o b j

%

__ i p o w __ ()

★ -к—

o b j **=x

Возведение экземпляра клас­
са o b j в степень операнда х и
присваивание результата пе­
ременной экземпляра клас­
са o b j

__ n e g __ ()

-

-obj

Применение унарного (один
операнд) оператора "минус"
к экземпляру класса o b j

__ p o s __ ()

+

+obj

Применение унарного (один
операнд) оператора "плюс" к
экземпляру класса o b j

м одуль

ab s(o b j)

Вычисление модуля экзем­
пляра класса o b j

abs

()

В таблице 7.4 перечислены перегружаемые побитовые операторы. Как и в
предыдущем случае через ob j обозначен экземпляр класса, в котором пере­
гружается оператор, а через х - другой операнд.

*

....n m

Python

Таблица 7.4. Перегружаемые побитовые операторы
Оператор

Метод

invert
and

()

()

Описание

~

~obj

Побитовая инверсия экзем­
пляра класса o b j

&

ob j &x

Побитовое и для экземпля­
ра класса o b j и операнда х

rand

()

&

x&obj

Побитовое и для операнда х
и экземпляра класса o b j

iand

()

&=

o b j &=x

Побитовое и для экземпля­
ра класса o b j и операнда х
с присваиванием результа­
та переменной экземпляра
класса o b j

1

ob j | x

Побитовое или для экзем­
пляра класса o b j и операн­
да X

1

x | obj

Побитовое или для операн­
да х и экземпляра класса
obj

1=

o b j t =x

Побитовое или для экзем­
пляра класса o b j и операн­
да х с присваиванием ре­
зультата переменной экзем­
пляра класса o b j

A

o b j Лх

Побитовое
исключающее
или для экземпляра класса
o b j и операнда х

_or__()

ro r

()

__ i o r __ ()

xor

in

Пример

()

rxor

()

A

x Ao b j

Побитовое
исключающее
или для операнда х и экзем­
пляра класса o b j

ixor

()

A __

o b j л=х

Побитовое
исключающее
или для экземпляра класса
o b j и операнда х с присваи­
ванием результата перемен­
ной экземпляра класса o b j

Глава 7. Продолжаем знакомство с ООП

Метод

Оператор

Пример

Описание

__ l s h i f t __ ()

«

o b j х

Сдвиг вправо для экзем­
пляра класса o b j на опе­
ранд X

rrsh ift

»

x»obj

Сдвиг вправо для операнда
х на экземпляр класса o b j

irsh ift

>>=

o b j >>=x

Сдвиг вправо для экзем­
пляра класса o b j на опе­
ранд х с присваиванием ре­
зультата переменной экзем­
пляра класса o b j

О
О

rsh ift

()

О
О

Таблица 7.5 содержит сведения о перегружаемых операторах сравнения
(оЬ j - экземпляр класса, а х - другой операнд).
Таблица 7.5. Перегружаемые операторы сравнения
Оператор

Пример

Описание

__ e q __ ()

о
сг
(—
1II
II
X

Метод

Равенство экземпля­
ра класса o b j и опе­
ранда X

__ п е __ ()

o b j !=х

Неравенство экзем­
пляра класса o b j и
операнда х

obj

o b j >х

Экземпляр
класса
o b j больше операн­
да x

__ 1 е __ ()

=x

Экземпляр
класса
o b j не меньше опе­
ранда X

in

х i n obj

Операнд х входит
в экземпляр класса
obj

c o n t a i n s __ ()

Операторов, подходящих для перегрузки, достаточно много. Здесь важно
понять простую штуку: мы в принципе можем перегружать операторы как
угодно (в определенных пределах, конечно). Важно только то, какой это
оператор: бинарный (тот, которому нужно два операнда) или унарный (у
оператора один операнд). Пример, в котором выполняется перегрузка неко­
торых из перечисленных выше операторов, приведен в листинге 7.16.
Q

На заметку
Далее описывается класс для реализации такого математического объекта, как
вектор. Если абстрагироваться от геометрической интерпретации вектора, то
с некоторой натяжкой можно утверждать, что вектор - это набор из трех пара­
метров, которые называются координатами вектора. С векторами могут выпол­
няться некоторые операции. Так, если имеется два вектора а = ( а 1( а 2, а 3) и
b = (Ь 1,Ь 2< ^ з), то можно вычислить сумму векторов С = а + Ь. Поопределению это вектор С = (с1( С2, С3) с координатами, равными сумме координат скла­
дываемых векторов: Ск =
+ Ь * (индекс к = 1 ,2 ,3 ). Аналогично вычисляется
разность векторов: с

=

d

— Ь, причем координаты вектора с

= (с^ , С2, С3) рав­

ны ск = dfc — fofc (индекс к = 1 ,2 ,3 ).
Скалярным произведением векторов d = ( а 1(

а2, а3) и b

=

(Ь1( Ь2, Ь3)

назы­

вается число d • b =
+ а 2 Ь2 + я 3Ь3, то есть сумма произведений соот­
ветствующих координат векторов. При умножении вектора на число все его коорди­
наты умножаются на это число: если а = ( а г, а 2, а 3), то A d = (Яа1(Яа2,Я а3)
. Аналогично вектор делится на число: нужно поделить каждую координату вектора
а _ кй ! а 2 а 3\
на это число - по определению J — ("д-’ ~х
Существует такая характеристи­
ка, как модуль вектора. Модуль вектора - это число, равное корню квадратному из
скалярного произведения вектора на самого себя. Для вектора d = ( а 1( а 2, а 3)
модуль|а| = V а • d = a f + а 2 + а 3.

Глава 7. Продолжаем знакомство с ООП

Листинг 7.16. Перегрузка операторов
# Фу нк ци я д л я в ы ч и с л е н и я к в а д р а т н о г о к о р н я
from math im p o r t s q r t
# Класс для реализации вектора
c l a s s V ector:
# Конструктор
d e f __ i n i t __ ( s e l f , х = 0 , у = 0 , z = 0 ) :
# Пол ям п р и с в а и в а ю т с я з н а ч е н и я
s e l f . х=х
s e l f .у=у
s e l f . z =z
# Метод д л я п р и в е д е н и я к т е к с т о в о м у т и п у
def
str
(self):
# Текстовое значение
tx t= " < "+ str(s e lf.х )+ "| "
tx t+ = s tr(s e lf.у )+ "| "
t x t + = s t r ( s e l f . z ) +">"
# Результат метода
return tx t
# Сложение в е к т о р о в
def
add
(self,o b j):
# Создается экземпляр класса
t = V e c t o r ()
# Значения полей экзем пляра
t .x = s e lf .x+obj.x
t .y = s e lf .y+obj.у
t . z = s e lf . z+ obj. z
# Результат метода
return t
# Сложение в е к т о р о в с п р и с в а и в а н и е м
d e f __ i a d d __ ( s e l f , o b j ) :
# Изменяем эк зем пляр
self= self+ obj
# Результат метода
return se lf
# Ум н оже ни е в е к т о р а н а в е к т о р и л и
# вектора на число
d e f __ m u l __ ( s e l f , p ) :
# П роверяем тип а р г у м е н т а
i f t y p e (р) — V e c t o r :
# Произведение векторов

*

fg H

Python

re s = s e lf.x*p.x + self.y*p.y + self.z*p. z
# Результат метода
return res
# Произведение вект о р а на число
else:
s e l f . x*=p
s e l f . y*=p
s e l f . z*=p
# Результат метода
return self# Ум н оже ни е ч и с л а н а в е к т о р
d e f __ r m u l __ ( s e l f , p ) :
# Р езультат метода
retu rn self*p
# Минус п е р е д в е к т о р о м
d e f __ n e g __ ( s e l f ) :
# Результат метода
return V e c to r (- s e lf.x ,- s e lf.y ,- s e lf.z )
# Разность векторов
d e f __s u b __ ( s e l f , o b j ) :
# Результат метода
return -obj+ self
# Разность векторов с присваиванием
d e f __ i s u b __ ( s e l f , o b j ) :
# Изменяем объект
s e l f = - o b j +s e l f
# Результат метода
return se lf
# Мод уль в е к т о р а
d e f __ a b s __ ( s e l f ) :
# Результат метода
return s q r t( s e lf * s e lf )
# Деление в е к т о р а на число
d e f __ t r u e d i v __ ( s e l f , p ) :
# Результат метода
return se lf* (l/p )
# Равенство векторов
d e f __ e q __ ( s e l f , o b j ) :
# Если равны зн а ч е н и я полей
i f s e l f .x = = o b j. х and s e l f . y = = o b j. у and s e l f . z= = o b j. z :
r e t u r n True
# Если зн а ч е н ия полей разные
else:
r e tu rn F alse
# Неравенство векторов
d e f __ n e __ ( s e l f , o b j ) :

Щ

-

Глава 7. Продолжаем знакомство с ООП

# Р езультат метода
r e tu r n not self==obj
# Один в е к т о р " м е н ь ш е " д р у г о г о
d e f __ I t ’__ ( s e l f , o b j ) :
# Е сл и м о ду л ь п е р в о г о в е к т о р а меньше
# модуля в т о р о го в е кт о р а
i f abs ( s e l f ) < a b s ( o b j):
r e t u r n True
# Если э т о не т а к
else:
re tu rn False
# Один в е к т о р " б о л ь ш е " д р у г о г о
def
gt
(self,o b j):
# Если модуль п е р в о г о в е к т о р а больше
# модуля второго векто р а
i f a b s ( s e l f ) > a b s(o b j):
r e t u r n True
# Если это не та к
else:
r e tu rn F alse
# Один в е к т о р " н е б о л ь ш е " д р у г о г о
def

(self,o b j):
# Если модуль п е р в о г о в е к т о р а не больше
# модуля второго вектора
i f a b s ( s e lf ) < = a b s ( o b j):
r e t u r n True
# Если эт о не та к
else:
re tu rn F alse
# Один в е к т о р " н е м е н ь ш е " д р у г о г о
def
ge
(self,obj) :
# Е с л и м о д у л ь п е р в о г о в е к т о р а н е м е нь ше
# модуля вт о р о го ве к т о р а
i f abs ( s e l f ) > = a b s ( o b j ):
r e t u r n True
# Если э т о не та к
else:
re tu rn False
# Побитовая инверсия для вектора
def
in v ert
(self) :
# Присваиваются зн а ч е н и я полям
s e l f .x= 10-self.х
s e l f . у=1 0 - s e l f . у
s e l f .z= 10-self. z
# Р езультат метода
return se lf

€31

Python

# Сдвиг в л е в о ( э к з е м п л я р к л а с с а - первый оп еран д)
d e f __ l s h i f t __ ( s e l f , n ) :
# Выполняются ц и к л и ч е с к и е п е р е с т а н о в к и п о л ей
fo r i in ra n g e (n ):
se lf.x ,s e lf.y ,se lf.z = se lf.y ,se lf.z ,se lf.x
# Р езультат метода
return se lf
# Сдвиг вл ево (экземпляр к л а с с а - второй операнд)
d e f __ r l s h i f t __ ( s e l f , n ) :
# Р езультат метода
r e tu r n self>>n
# Сдвиг в п р а в о ( э к з е м п л я р к л а с с а - первый оп еран д)
d e f __ r s h i f t __ ( s e l f , n ) :
# Выполняется циклическая п е р е ст ан о в к а полей
fo r i in range ( n ) :
se lf.x ,se lf.y ,se lf.z = se lf.z ,se lf.x ,se lf.y
# Р езультат метода
return s e lf
# Сдвиг вправо (экземпляр к л а с с а - второй операнд)
d e f __ r r s h i f t __ ( s e l f , n ) :
# Результат метода
return s e l f « n
# Векторы
p r i n t ( " В е к т о р ы : ")
a= V ector(1,2,-1)
b=V ector (1 ,- 1 ,3 )
c=~V ector (9,8,11)
p r i n t ("a = ",a)
p r i n t ( " b =",b)
p r i n t ( " c = " , c)
# Вычисление модуля
p r i n t ( " Модул ь в е к т о р а . " )
p r i n t ( " |a | = ",abs(a))
p r i n t ( " |b| = " ,a b s (b ))
p r i n t ( " |c | = ",abs(c))
# Сравнение векторов
p r i n t ("Сравнение в е к т о р о в ." )
p r i n t ( " a = = b ->",a==b)
p r in t( " a ! = b ->",a!= b)
p r i n t ( " a = = c ->",a==c)
p r in t( " a < b ->",ab)
p r in t( " a < = c ->",a=c)
# Операции с векторам и
p r i n t ( "Сум м а в е к т о р о в : " )

«2»-

Глава 7. Продолжаем знакомство с ООП

p r i n t ( " а + Ь = " , а+ Ь )
с+=а
p r i n t ("с+=а - > " ,с )
p r i n t ("Разность векторов:")
p r i n t ("а-Ь = " , а-Ь)
с-=а
p r i n t ("с-=а -> " ,с )
p r i n t ( " У м н ож ен ие в е к т о р о в : " )
p r in t( " a * b =",a*b)
p r i n t ( " У м н ож ен ие и д е л е н и е в е к т о р а н а ч и с л о : " )
p r i n t ("а*3 = " , а*3)
p r i n t ( " а = " , а)
p r i n t ( " 2 * b = " , 2 *Ь )
p r i n t ( " Ь = " , Ь)
p r i n t ("-Ь = ",-Ь )
p r i n t ("Ь = " , Ь )
p r i n t ("а/3 = ",а/3 )
p r i n t ( " a =",а)
p r i n t ("Циклические п е р е с т а н о в к и :")
v= V ecto r(1,2,3)
p r i n t ( " v = " , v)
p r i n t ( " v « l =", v « l )
p r i n t ("v >>1 = " , v >>1)
p r i n t ("2>>v = " , 2 » v )
p r i n t ("2 10
s e c o n d - > 20
o t h e r - > (3 0 , 40 , 5 0 , 60, 70)
b y n a m e - > {}
2 - й сп о со б вы зова функции,
f i r s t - > 10
s e c o n d - > 20
o t h e r - > (5 0 , 60, 70)
bynam e -> { ' t h i r d ' : 30, ' f o u r t h ' :
3 - й сп особ вы зо ва функции,
f i r s t - > 10
s e c o n d - > 20
o t h e r - > ()
bynam e -> { ' t h i r d ' : 30, ' f o u r t h ' :
4 - й сп о со б вы зо ва функции,
f i r s t - > 10
s e c o n d - > 20
o t h e r - > ()
b y n a m e - > {}
5 - й сп особ вы зо ва функции,
f i r s t - > 10
s e c o n d - > 20
o t h e r - > ()
bynam e -> { ' t h i r d ' : 30, ' f o u r t h ' :

40}

40}

40}

Если функция вызывается с передачей всех аргументов позиционными спо­
собом (как в команде show_me ( 1 0 , 2 0 , 3 0 , 4 0 , 5 0 , 6 0 , 7 0 ) ) , то первое и
второе значения - это значения аргументов f i r s t и s e c o n d , а все остальные
аргументы формируют кортеж, являю щ ийся значением аргумента o t h e r .
Аргумент bynam e в этом случае является пустым словарем.
В команде sh o w _ m e ( 1 0 , 2 0 , 5 0 , 6 0 , 7 0 , t h i r d = 3 0 , f o u r t h = 4 0 ) кро­
ме позиционных аргументов, есть аргументы, переданные по ключу (при­
чем речь не идет об аргументах f i r s t и s e c o n d ) . Первые два значения - ар-

- ш ш

Python

гументы f i r s t и s e c o n d , все остальные позиционные аргументы - элемен­
ты кортежа o t h e r , а словарь bynam e формируется на основе аргумен­
тов, переданных по ключу. Более конкретно, значения аргументов f i r s t и
s e c o n d -это 10 и 2 0 , кортеж o t h e r состоит из трех элементов (50, 6 0 , 7 0 ) ,
а словарь bynam e содержит элементы со значениями 30 и 40 с клю­
чами t h i r d и f o u r t h соответственно. Похожая ситуация с командой
show_me ( 1 0 , 2 0 , t h i r d = 3 0 , f o u r t h = 4 0 ) , только кортеж o t h e r в этом
случае пустой.

Ш На заметку
Обратите внимание, что аргументы third и fourth в описании функции
show me () отсутствуют. Другими словами, при вызове функции мы указываем
ключи, которых как бы и нет. Тем не менее, так можно делать - язык Python предо­
ставляет возможности по обработке таких ситуаций.

В команде show_me ( s e c o n d = 2 0 , f i r s t = 1 0 ) первые обязательные ар­
гументы указаны по ключу, а других аргументов нет. Поэтому и кортеж
o t h e r , и словарь bynam e в этом случае пустые.
Наконец, в команде show_me ( f i r s t = 1 0 , s e c o n d = 2 0 , t h i r d = 3 0 , f o u r
t h = 4 0 ) все аргументы указаны по ключу. Как следствие, аргумент o t h e r
является пустым кортежем.

На заметку

Ш

Если при вызове функции аргументом ей передать словарь, перед которым ука­
зать две звездочки * * , то этот словарь будет автоматически преобразован в спи­
сок именованных аргументов: ключи словаря служат названиями аргументов, а
значения словаря - значениями аргументов функции.

Д е ко р а то р ы ф ун кц и й и кл а ссо в
Молодой человек, а вы неправильно одеты,

из к/ф "Гостья из будущего"

Чтобы понять суть и назначение декораторов функций, сделаем некоторые
предварительные замечания. Так, результатом функции может быть ф унк­
ция.

Ш На заметку
Пример функции, которая в качестве результата возвращает функцию, приведен
в главе 3 в разделе, посвященном описанию лямбда-функций, а также в разделе,
специально посвященном этой теме.

ш

-

Глава 8. Немного о разном

Аргументом функции также может быть функция. Поэтому никто и ничто
не запрещает описать нам функцию, которая аргументом принимает ф унк­
цию и результатом возвращает функцию. Для большей конкретики обозна­
чим через f () некоторую функцию, a F () - это будет функция, которая ар­
гументом принимает функцию и результатом возвращает функцию. Ф унк­
ции F () в качестве аргумента может быть передана функция f () (аргумен­
том указывается имя функции). Таким образом, выражение F (f ) представ­
ляет собой функцию.
Результат этого выражения можно присвоить в качестве значения перемен­
ной, и эта переменная будет ссылаться на объект функции. Другими сло­
вами, такую переменную можно будет рассматривать как функцию. Мало
того, этой переменной может быть f - мы можем воспользоваться командой
f=F (f ). В этой команде нет ошибки. Ее выполнение приведет к переопре­
делению функции f () .

Q

На заметку
Команда f=F (f) выполняется так. Вначале переменная f ссылается на некоторый
объект, который определяет функцию f ( ) . При выполнении команды f=F ( f) сна­
чала рассчитывается результат выражения F(f). При этом используется текущая
ссылка на объект функции f (). Результатом выражения F (f) является новый объ­
ект, определяющий некоторую функцию. Ссылка на эту функцию записывается в
переменную f. Внешний эффект от этих действий сводится к тому, что меняется
функция f ().

Таким образом, с помощью функции F () мы можем выполнить преобра­
зование функции f (). Для этого достаточно воспользоваться командой
f=F (f ). Такого же эффекта можно добиться, если перед описанием ф унк­
ции f () поставить инструкцию @F (знак 0 и имя функции F (), на осно­
ве которой выполняется преобразование). То есть речь идет об инструкции
вида
0F
def f (аргументы) :
# тело функции
Об инструкции @F говорят, что это декоратор функции. Пример использо­

вания декоратора функций приведен в листинге 8.5.

Q

На заметку
Далее мы рассматриваем "математический" пример. В частности, мы рассматри­
ваем выражение вида ехр ( - / ( х ) 2), где / ( х ) есть некоторая функция. Если бы нам
предстояло часто использовать такого рода выражения (для разных функций / (х )
), то разумно было бы использовать декоратор функций. А именно, мы описываем

-ш ш

Python
функцию F ( f ) такую, что F ( / ) ( x ) = exp ( - / О ) 2). Функцию F(f~) достаточно опи­
сать один раз. Затем используя ее как декоратор, можем определять функции для
вычисления выражений вида ехр ( —/ ( х ) 2), при этом определяя фактически только
код для вычисления выражения / ( х ) .

Листинг 8.5. Декоратор функций
# И м п орт м а т е м а т и ч е с к и х ф у н к ц и й
fro m m a th im p o r t e x p , s i n , c o s , t a n
# Функция д л я и с п о л ь з о в а н и я в д е к о р а т о р е
d e f F (f ) :
# Л ям б да-ф ункция (аноним ная функция)
re s= la m b d a х: exp ( - f (х)**2)
re tu rn res
# Функция д л я и с п о л ь з о в а н и я в д е к о р а т о р е
def Q (f):
# В нутренняя функция
def q (х ):
re tu rn ta n (f (x ))
re tu rn q
# Функция с д е к о р а т о р о м
0F # Д е к о р а т о р
def f ( x ) :
re tu rn sin (x )
# Функция с д е к о р а т о р о м
0F # Д е к о р а т о р
def g (x ):
r e tu r n cos(x)
# Функция с двум я д е к о р а т о р а м и
0Q # В т о р о й д е к о р а т о р
0F
# П ервы й д е к о р а т о р
def h (х ):
re tu rn х
# П еременная
п=5
# З н а ч е н и я функций в р азн ы х т о ч к а х
p r i n t ("Ф ункция f ( ) : " )
f o r i in ra n g e (n+ 1):
z = i/n
p rin t(f(z ),
e x p (-sin (z )* * 2 ))
p r i n t ( "Ф у н к ц и я g ( ) : " )
f o r i in ra n g e (n+ 1):
z = i/n
p rin t(g (z ),
exp(-cos (z)**2))
p r i n t ( "Ф ункция h ( ) : " )

?

Глава 8. Немного о разном

f o r i in ra n g e (n+1):
z= i/n
print(h(z),
t a n ( e x p ( - z * * 2 ) ))

При выполнении программного кода получаем такой результат:
Результат выполнения программы (из листинга 8.5)
Функция f ( ) :
1 .0 -> 1 .0
0 .9 6 1 ,2 9 9 2 7 0 2 8 8 7 9 9 -> 0 .9 6 1 2 9 9 2 7 0 2 8 8 7 9 9
0 .8 5 9 2 9 1 8 6 1 8 9 7 4 2 7 6 -> 0 .8 5 9 2 9 1 8 6 1 8 9 7 4 2 7 6
0 .7 2 7 0 0 5 5 8 2 4 2 6 8 4 8 7 -> 0 .7 2 7 0 0 5 5 8 2 4 2 6 8 4 8 7
0 .5 9 7 7 3 9 7 8 5 4 3 2 2 5 1 8 -> 0 .5 9 7 7 3 9 7 8 5 4 3 2 2 5 1 8
0 .4 9 2 5 9 2 3 0 3 1 9 6 0 3 1 7 6 -> 0 .4 9 2 5 9 2 3 0 3 1 9 6 0 3 1 7 6

Функция д ():
0 .3 6 7 8 7 9 4 4 1 1 7 1 4 4 2 3 3
0 .3 8 2 6 8 9 8 1 6 3 1 5 9 1 3 6 4
0 .4 2 8 1 1 9 3 1 2 5 2 2 1 9 3 5
0 .5 0 6 0 2 0 1 0 5 0 2 2 3 1 3 6
0 .6 1 5 4 5 0 8 2 0 1 3 4 7 3 9 1
0 .7 4 6 8 2 3 3 6 4 4 4 2 7 0 6 7

->
->
->
->
->
->

0 .3 6 7 8 7 9 4 4 1 1 7 1 4 4 2 3 3
0 .3 8 2 6 8 9 8 1 6 3 1 5 9 1 3 6 4
0 .4 2 8 1 1 9 3 1 2 5 2 2 1 9 3 5
0 .5 0 6 0 2 0 1 0 5 0 2 2 3 1 3 6
0 .6 1 5 4 5 0 8 2 0 1 3 4 7 3 9 1
0 .7 4 68233644427067

Функция h ( ) :
1 .5 5 7 4 0 7 7 2 4 6 5 4 9 0 2 3 -> 1 .5 5 7 4 0 7 7 2 4 6 5 4 9 0 2 3
1 .4 3 0 7 6 0 2 5 7 7 4 0 1 1 0 6 -> 1 .4 3 0 7 6 0 2 5 7 7 4 0 1 1 0 6
1 .1 4 3 2 6 6 4 7 4 5 0 3 9 4 8 -> 1 .1 4 3 2 6 6 4 7 4 5 0 3 9 4 8
0 .8 3 8 3 2 3 9 2 8 8 2 8 9 5 5 8 -> 0 .8 3 8 3 2 3 9 2 8 8 2 8 9 5 5 8
0 .5 8 2 2 8 5 6 8 1 1 3 6 0 4 5 -> 0 .5 8 2 2 8 5 6 8 1 1 3 6 0 4 5
0 .3 8 5 4 2 5 5 9 1 7 6 9 0 9 8 1 3 -> 0 .3 8 5 4 2 5 5 9 1 7 6 9 0 9 8 1 3

В самом начале программного кода мы импортируем из модуля m a t h ф унк­
ции е х р ( ) для вычисления экспоненты, s i n () для вычисления синуса,
c o s () для вычисления косинуса и t a n () для вычисления тангенса. Далее
описывается функция F (), у которой, как предполагается, аргумент f - имя
другой функции. В теле функции переменной r e s присваивается в каче­
стве значения ссылка на анонимную функцию (лямбда-функцию), объект
которой создается инструкцией la m b d a х : e x p ( - f (х ) * * 2 ). Переменная
r e s (ссылка на объект ф ункции) возвращается как результат функции F ( ) .
Ф ункция Q () также возвращает в качестве результат функцию (поэтому
может и будет использована в декораторе). В теле функции описывается
внутренняя функция q (), которая в качестве результата возвращает выра­
жение t a n ( f (х) ), где х - аргумент функции q (), a f - аргумент функции
Q (). Имя функции q (ссылка на объект функции q ( ) ) возвращается как ре­
зультат функции Q ().

i«EEl

Python

Ш

На заметку
Таким образом, результатом выражения Q ( / ) является функция, которая для аргу­
мента X возвращает значение ( 2 ( / ) ( * ) = t g ( f (дс)) (тангенс от значения / 0 0 ) .

Формально функция f () описана так, что для аргумента х возвращается
значение s i n ( х ) . Но поскольку перед описанием функции есть декоратор
0 F, то это все равно, как если бы после определения функции f () выполня­
лась команда f = F (f ). В результате функция f () определяется как такая,
что значение выражения f (х) есть e x p ( - s i n ( x ) * * 2 ).
Похожая ситуация с функцией g ( ). Поскольку перед функцией есть де­
коратор @F, а в теле функции результат возвращается командой r e ­
tu rn
c o s ( х ) (через х обозначен аргумент функции g () ), то на самом
деле результатом вычисления инструкции g ( х ) является значение е х р ( c o s (х )* * 2 ).

Что касается функции h ( ), то перед ее описанием есть два дескриптора: 0Q
и @F. Это эквивалентно выполнению команды h = Q ( F ( h ) ) после определе­
ния функции h (). В итоге значение выражения h (х) эквивалентно значе­
нию выражения t a n (ехр ( - х * * 2 ) ).
Далее в программном коде для нескольких значений аргумента х (в диапа­
зоне от 0 до 1 ) вычисляются значения f ( x ) , g ( x ) и h ( х ) . Д ля сравнения
в явном виде вычисляются также и соответствующие математические вы ­
ражения.
Аналогично тому, как декораторы используются для функций, могут ис­
пользоваться и декораторы для классов.
Ш

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

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

ЯИКt •Г*

Глава 8. Немного о разном

Итак, предположим, что функция F () для аргумента - объекта класса А,
возвращает значение - объект класса F ( А ) . Если класс А описан с декорато­
ром @F, то это все равно, как если бы после описания класса А выполнялась
команда A = F ( А ) . Пример приведен в листинге 8 .6 .
Листинг 8.6. Декоратор классов
# Функция с а р г у м е н т о м - к л а с с о м и
# р езу льтато м - объектом к ласса
d e f F (А ):
# Внутренний к л а с с
c l a s s A lp h a (А ):
# М е то д э к з е м п л я р а в н у т р е н н е г о к л а с с а
def h i (se lf) :
p r i n t ("К ласс A lp h a !")
# Р е з у л ь т а т функции - о б ъ е к т к л а с с а
r e t u r n A lp h a
# Функция с а р г у м е н т о м - к л а с с о м и
# р езу льтато м - объектом к ласса
d e f Q (А ):
# Внутренний к л а с с
c l a s s B ra v o (А ):
# М е то д э к з е м п л я р а в н у т р е н н е г о к л а с с а
def h i( s e l f ) :
p r i n t ("К ласс B rav o !")
# Р е з у л ь т а т функции - о б ъ е к т к л а с с а
r e t u r n B ravo
# Класс с декоратором
0F # Д е к о р а т о р к л а с с а
class F irs t:
# М е то д э к з е м п л я р а к л а с с а
def h e llo (se lf) :
p r i n t ("К ласс F i r s t ! " )
# Класс с декоратором
0F # Д е к о р а т о р к л а с с а
c l a s s Second:
# М е тод э к з е м п л я р а к л а с с а
def h e llo (se lf):
p r i n t ("К ласс S e c o n d !")
# К ласс с двумя дек ораторам и
0Q # В т о р о й д е к о р а т о р к л а с с а
0F # П ервы й д е к о р а т о р к л а с с а
c l a s s T h ird :
# М е то д э к з е м п л я р а к л а с с а
def h e llo ( s e lf ) :
p r i n t ("К ласс T h ir d ! " )

""■ © Э

Python

# Функция д л я в ы з о в а м е т о д о в э к з е м п л я р а
d ef sh o w _ o b j(o b j):
# Класс экзем п ляра
p r i n t ( " К л а с с э к з е м п л я р а : " , o b j . __ c l a s s __ )
# В ы зо в м е т о д о в э к з е м п л я р а
o b j . h i ()
o b j . h e l l o ()
# Функция д л я о т о б р а ж е н и я х а р а к т е р и с т и к к л а с с а
d e f s h o w _ c la s s (А ):
# Имя к л а с с а
p r i n t ("И мя к л а с с а : " , А . __ n a m e __ )
# Базовы й к л а сс
p r i n t ( " Б а з о в ы й к л а с с : " , А . __ b a s e s __ )
# Ц епочка н ас л ед о в ан и я
p r i n t ( " Ц е п о ч к а н а с л е д о в а н и я : " , А . __ m r o __ )
# Создание экзем п ляров классо в
o n e = F i r s t ()
t w o = S e c o n d ()
t h r e e = T h i r d ()
# М етоды э к з е м п л я р о в
p r i n t ("Э кзем пляры к л а с с о в ." )
fo r obj in [o n e ,tw o ,th r e e ] :
sh o w _ o b j(o b j)
# Х арактеристики классов ■
p r i n t ( " К л а с с ы . ")
fo r A in [F irs t,S e c o n d ,T h ird ]:
sh ow _class(A )

Выполнение программного кода приводит к таким результатам:
Результат выполнения программы (из листинга 8.6)
Экзем пляры к л а с с о в .
К л а с с э к з е м п л я р а : < c l a s s ' __ m a i n __ . F . < l o c a l s > . A l p h a '>
Класс A lp h a !
Класс F i r s t !
К л а с с э к з е м п л я р а : < c l a s s ' __ m a i n __ . F . < l o c a l s > . A l p h a 1>
Класс A lp h a !
Класс Second!
К л а с с э к з е м п л я р а : < c l a s s ' __ m a i n __ . Q . < l o c a l s > . B r a v o ' >
Класс B ravo!
Класс T h ird !
Классы.
Имя к л а с с а : A l p h a
Б а з о в ы й к л а с с : ( < c l a s s ' __ m a i n __ . F i r s t ' > , )
Ц е п о ч к а н а с л е д о в а н и я : ( < c l a s s ' __ m a i n __ . F . < l o c a l s > . A l p h a ' > ,
< c l a s s ' __ m a in __ . F i r s t ' > , < c l a s s ' o b j e c t ' > )

Ш

-

Глава 8. Немншо о разном

Имя к л а с с а : A l p h a
Б а з о в ы й к л а с с : ( C c l a s s ' __ m a i n __ . S e c o n d ' > , )
Ц е п о ч к а н а с л е д о в а н и я : ( C c l a s s ' __ m a i n __ . F . < l o c a l s > . A l p h a ' > ,
C c l a s s ' __ m a i n __ . S e c o n d ' > , c c l a s s ' o b j e c t ' > )
Имя к л а с с а : B r a v o
Б а з о в ы й к л а с с : ( C c l a s s ' __ m a i n __ . F . c l o c a l s > . A l p h a ' > , )
Ц е п о ч к а н а с л е д о в а н и я : ( C c l a s s ' __ m a i n __ . Q . c l o c a l s > . B r a v o ' > ,
C c l a s s ' __ m a i n __ . F . c l o c a l s > . A l p h a ' > , c c l a s s ' ___m a i n __ . T h i r d ' > ,
C c la ss 'o b je c t'> )

В этом примере мы описываем две функции, которые используются в деко­
раторах класса. Также с использованием декораторов создаются три класса.
Есть и другой вспомогательный код. Рассмотрим все это поэтапно.
Ф ункции F () аргументом, как мы предполагаем, передается класс (обо­
значен как А). В теле функции описан внутренний класс, которой называ­
ется A l p h a . Класс A l p h a создается на основе класса А путем наследова­
ния. Класс A l p h a , таким образом, наследует атрибуты класса А, и при этом
в теле класса A l p h a описан метод h i ( ). Действие метода сводится к ото­
бражению в окне вывода текстового значения. Ссылка на внутренний класс
возвращается в качестве результата функции F ( ) .
Аналогичным образом описана функция Q ( ) , только ее внутренний класс
называется B r a v o . Он тоже создается наследованием того класса, что пере­
дан аргументом функции Q ( ). У внутреннего класса B r a v o также, как и у
класса A l p h a из функции F (), имеется метод h i (). Этот метод также ото­
бражает сообщение - только другое. Результатом функция Q () возвращает
ссылку на класс B r a v o . Поэтому функция Q ( ) , как и функция F ( ) , может
использоваться в декораторе класса.
Класс F i r s t описан с декоратором 0 F . В теле класса описан метод
h e l l o (). При создании класса F i r s t происходит следующее:


Сначала создается объект класса F i r s t в соответствии с тем, как
непосредственно описан код класса, а ссылка на объект класса запи­
сывается в переменную F i r s t . Н а этом этапе класс содержит лишь
метод экземпляра h e l l o () (плюс специальные поля и методы).



Далее вызывается функция F ( ) , аргументом которойпередается
ссылка F i r s t на объект соответствующего класса. При выполнении
кода функции на основе класса F i r s t создается внутренний (ло­
кальный) класс A l p h a . У этого класса, помимо метода экземпляра
h i (), появляется еще и метод экземпляра h e l l o ().



Ссылка на объект внутреннего класса, созданного при вызове ф унк­
ции F (), присваивается в качестве значения переменной F i r s t . В
результате переменная ссылается на объект класса, у которого есть
-С И

Python

методы экземпляра hi () (метод из класса Alpha функции F () ) и
hello () (описан непосредственно в классе First).
В этой схеме есть несколько важных для понимания моментов. Во-первых,
хотя класс мы называем First, на самом деле переменная с таким названи­
ем будет ссылаться на класс, который создавался при вызове функции F ().
А это локальный класс Alpha. Поэтому если воспользоваться инструкци­
ей First.__name__,то в качестве значения получим имя класса Alpha. А
вот базовым для этого класса является класс с именем First. И это имен­
но тот класс, который был создан в самом начале, и ссылка на который пер­
воначально записывалась в переменную First. Во-вторых, если создать эк­
земпляр для класса, на который ссылается переменная First (например,
командой one=First ()), то этот экземпляр, на самом деле, будет экзем­
пляром упомянутого выше локального класса Alpha (класса, созданного
при вызове функции F ()). В этом легко убедиться, обратившись к полю
_с l a s s
экземпляра.
Класс Second также описан с декоратором 0F. Ситуация сходна с преды­
дущей:


Создается объект класса Second так, как описан код этого класса.
Класс содержит метод экземпляра hello ( ) .



Вызывается функция F (). Аргументом функции передается ссылка
на объект класса Second. При этом наследованием класса Second
создается внутренний класс Alpha (но это не тот класс, который
создавался при создании класса First).



Ссылка на объект созданного внутреннего класса присваивается пе­
ременной Second.

Таким образом, переменная Second ссылается на объект класса, который
был создан наследованием "исходного" класса Second. Если запросить имя
класса Second через специальное п о л е__ паше__ , то оно будет Alpha.
Ш

На заметку
Хотя значения поля
name__для классов First и Second совпадают (речь об
имени Alpha), на самом деле First и Second - это разные классы. Просто зна­
чением поля__пате__ является "локальное" имя класса. В данном случае клас­
сы создаются при вызове функции F ( ) . Каждый раз создается новый класс, с "ло­
кальным” именем Alpha. Но поскольку речь идет о локальных пространствах имен
при вызове функции, конфликта по причине совпадения названий не возникает (то
есть классы с одинаковыми локальными названиями попадают в разные простран­
ства имен).

ю

-

?

Глава 8. Немного о разном

Объект класса, на который ссылается переменная Second, имеет методы
экземпляра hi () из локального класса Alpha и hello (), описанный не­
посредственно в теле класса Second.
Более сложная ситуация с классом Third. Этот класс описан с двумя деко­
раторами: @F и 0Q. Происходит следующее:


Создается объект класса Thi rd.



Ссылка на объект класса Third передается в функцию F (). В ре­
зультате создается новый объект класса: класс Third наследуется в
локальном классе Alpha.



Ссылка на указанный объект передается в функцию Q (). В теле
этой функции наследованием переданного аргументом класса соз­
дается новый класс с локальным названием Bravo.



Ссылка на новый объект класса записывается в переменную Third.

Таким образом, локальное имя класса, на который ссылается переменная
Third, есть Bravo. У экземпляра этого класса есть два метода: метод hi ()
из класса Bravo, созданного при вызове функции Q (), и метод hello (),
описанный в самом классе Third.
Ш

На заметку
В описанной выше схеме сначала создается объект класса с методом hi () из ло­
кального класса Alpha, а затем этот метод фактически переопределяется или
перекрывается методом hi (> из локального класса Bravo. Тем не менее, че­
рез экземпляр three и переменную Third можно "добраться" и до версии ме­
тода hi () из класса Alpha. Для этого достаточно воспользоваться командой
super (Third, three) .hi ( ) , которой с помощью функции super () из экземпля­
ра three вызывается метод hi ( ) , описанный в классе, являющемся базовым для
класса, на который ссылается переменная Third.

Ф ункция show_obj () предназначена для вызова методов экземпляров
классов (тех, на которые ссылаются переменные First, Second и Third).
Д ля отображения характеристик самих классов (название класса, базовый
класс и цепочка наследования) предназначена функция show_class ( ) .

t
- - с

и

Python

Д о ку м е н ти р о в а н и е и анно та ци и в
ф ункциях
В ваш ем во зр а ст е уж е п ора
вр а т ь к ак следует .

научи т ься

из к/ф "Гостья из будущего"

Есть некоторые полезные свойства функций, которое можно использо­
вать на практике для повышения читабельности программного кода. В пер­
вую очередь следует сказать об особой роли первой (после названия ф унк­
ции) текстовой строки в описании функции. Эта строка служит для доку­
ментирования - создания особого комментария, доступ к которому мож­
но получить программными методами. Более конкретно, у каждой ф унк­
ции есть специальное п о л е __ doc__ (два подчеркивания в начале, два под­
черкивания в конце). Если после имени функции через точку указать поле
_doc__, то значением будет текстовая строка, указанная в самом начале в
теле функции. Небольшой пример приведен в листинге 8.7.
Листинг 8.7. Документирование функции
# Ф ун к ц и я
d ef h i ():
"Э та функция п р о с т о вы водит со о б щ е н и е ."
p r i n t ("Д окументирование функции.")
# В ы з ы в а е м функцию
h i ()
# О писание функции
p r i n t ( h i . __ d o c __ )

Результат выполнения программного кода такой:
Результат выполнения программы (из листинга 8.7)
Д окументирование функции.
Эта функция п р о с т о вы водит с о о б щ ен и е.

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

(ШЕ1"

Глава 8. Немного о разном

тов есть значения по умолчанию, то они указываются через знак равенства
после аннотации к аргументу.
Аннотация для результата функции указывается через стрелку -> по­
сле круглых скобок с описанием аргументов функции, но перед двоеточи­
ем, которым завершается строка описания заголовка функции. Общий ша­
блон функции с аннотациями для аргументов и результата такой (жирным
шрифтом выделены ключевые элементы):
def и м я _ ф у н к ц и и ( а р г у м е н т : а н н о т а ц и я = з н а ч е н и е ,
а р г у м е н т : аннотация=*значение, . . . ) -> а н н о т а ц и я :
# код функции

Д ля получения доступа к аннотациям, использованным в функции, из объ­
екта функции вызывается специальное п о л е__ a n n o t a t i o n s __ . Значение
этого свойства - словарь, ключами которого являю тся названия аргументов,
а значения - аннотации к аргументам. Аннотация к результату функции со­
ответствует ключу r e t u r n . Пример создания и использования функции с
аннотациями приведен в листинге 8 .8 .

# Функция с а н н о т а ц и я м и
# для аргументов и р е зу л ь та та
d e f sh o w (а:"п ер в ы й а р г у м е н т " ,b : in t= 0 )-> N o n e :
p r i n t ("а = " ,а )
p r i n t ( " b = ",b )
# В ы зы в а е м функцию
s h o w (1 0 )
s h o w (1 0 ,2 0 )
# И спользованны е ан н отац и и :
p r i n t ( s h o w . __ a n n o t a t i o n s __ )
# А ннотацияч для ар гу м ен та а
a n n t = s h o w . __ a n n o t a t i o n s __ [ " а " ]
p r i n t ( "А ргумент a : " , a n n t )
# А ннотация дл я р е з у л ь т а т а
r e s = s h o w . __ a n n o t a t i o n s __ [ " r e t u r n " ]
p r i n t ("В озвращ аем ы й р е з у л ь т а т : " , r e s )

При выполнении программного кода получаем следующее:
Рпауиыут ■ ЫППЯМОИШ1 программы (м? нмгтммга Я.Я)
а
b
а
Ь

=
=
=
=

10
0
10
20

■■■■fill

{ ' b ' : C c l a s s ' i n t ' > , ' а ' : 'п е р в ы й а р г у м е н т ' ,
А ргум ент a : первый ар гу м ен т
В о з в р а щ а е м ы й р е з у л ь т а т : N one

'r e t u r n ':

N one}

Важно понимать, что наличие аннотаций не влияет алгоритм выполнения
кода функции, равно как наличие аннотации для результата функции ре­
ально не ограничивает свободу программиста в плане типа возвращаемого
функцией значения. Другими словами, аннотации - это такая "декоратив­
ная" составляющая в описании функции. Тем не менее, некоторую пикант­
ность в определение функции они могут привнести. В листинге 8.9 приве­
ден еще один небольшой пример функции, в которой в качестве аннотации
для аргумента и результата функции указаны команды отображения текста
в окне вывода.
Листинг 8 .9 . Аннотации как команды
d e f d e m o (a rg :p r i n t ("Здесь есть а р гу м ен т. " ) ) - > p r i n t ("Р езу л ь та та
не б у д е т . " ) :
p r i n t ("В ас п р и в е т с т в у е т dem o!")
p rin t(a rg )
p r i n t ( " В ы з ы в а е м функцию d e m o ( ) : " )
d e m o ( "К о д ф у н к ц и и в ы п о л н е н . " )

Результат выполнения программного кода такой:
Результат выполнения программы (из листинга 8 .9)
Здесь есть аргум ент.
Р е зу л ь т а т а не б у д ет.
В ы зы в а е м функцию d e m o ( ) :
В а с п р и в е т с т в у е т dem o!
Код ф у н к ц и и в ы п о л н е н .

Чтобы понять смысл происходящего, нужно учесть, что аннотации не просто
"принимаются к сведению" - они выполняются. Выполняются при создании
объекта функции. А объект функции создается при выполнении кода с опи­
санием функции (не путать с выполнением кода при вызове функции). Дру­
гими словами, аннотации будут выполнены в том месте и в то время, когда
интерпретатор "доберется" до программного кода с описанием функции. В
рассматриваемом примере команды p r i n t ( " З д е с ь е с т ь а р г у м е н т . " )
и p r i n t ( " Р е з у л ь т а т а н е б у д е т . " ) , которые мы использовали как ан­
нотации для аргумента и результата функции, будут выполнены при "про­
смотре" интерпретатором кода с описанием функции. Это происходит толь­
ко раз.
При вызове функции эти команды уже выполняться не будут. Поэтому
текст " З д е с ь е с т ь а р г у м е н т . " и " Р е з у л ь т а т а н е б у д е т . " ото­
f ®

Глава 8. Немного о разном

бражается еще до того, как была вызвана функция demo (). А после вызо­
ва функции demo () эти сообщения уже не появляются, зато отображается
текст "В ас п р и в е т с т в у е т dem o! " и "Код ф ункции в ы п о л н е н к а к
следствие выполнения команд в теле функции demo ().

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

И скл ю че н ия к а к э кзе м п л я р ы кл а ссо в
О т рицат ельны й р е зу л ь т а т - эт о тож е р е ­
зульт ат .

из к/ф "Гостья из будущего"

С обработкой исключительных ситуаций мы немного знакомы. Пришел че­
ред расширить наши познания в этой области. А именно, более пристальное
внимание мы уделим тому обстоятельству, что типы исключений описыва­
ются классами, а сами исключения являю тся экземплярами классов. Кро­
ме того, нам предстоит познакомиться с некоторыми полезными механиз­
мами - такими, например, как генерирование исключительных ситуаций и
создание пользовательских классов исключений.
Хотя кое-что об обработке ошибок (исключительных ситуаций) мы уже
знаем, здесь нелишним будет напомнить основные моменты. Итак, контро­
лируемый код помещается в блок t r y . Код, который предназначен для об­
работки ошибки, размещается в блоке e x c e p t . Блоков, помеченных клю­
чевым словом e x c e p t , может быть несколько. Каждый блок соответствует
ошибке определенного типа. Тип (или, точнее, класс) ошибки, которая об­
рабатывается в соответствующем e x c e p t -блоке, указывается после ключе­
вого слова e x c e p t .
Д ля описания различных ошибок предусмотрены специальные классы, ко­
торые называют классами встроенных исключений. Эти классы образуют
иерархию, основанную на наследовании. Общая иерархическая структура
классов встроенных ислючений представлена на рис. 8 . 1.
Q

На заметку
Как видим, классов достаточно много. В вершине иерархии находится класс BaseException. У этого класса всего четыре производных класса (GeneratorExit, Keyboardlnterrupt, SystemExit и Exception), причем только у класса
Exception есть производные классы. Поэтому не будет преувеличением сказать,
что подавляющее большинство классов встроенных ошибок - потомки (прямые
или опосредованные) класса Exception.

.......................................................................... € Ш

Python

Рис. 8 .1 . Иерархия классов встроенных исключений

Глава 8. Немного о разном

Когда при выполнении программного кода возникает ошибка, автомати­
чески определяется класс, которому эта ошибка соответствует, и для этого
класса создается экземпляр. Образно выражаясь, созданный таким образом
экземпляр содержит основную информацию о возникшей ошибке. К этому
экземпляру можно получить доступ и использовать его в явном виде в про­
цессе обработки исключения.
Мы уже знаем из предыдущих глав, как перехватывать исключения разных
типов. Здесь мы рассмотрим еще несколько вопросов, связанных с обработ­
кой исключительных ситуаций. Практически все они, так или иначе, под­
разумевают использование парадигмы О О П и обращение к классам встро­
енных ошибок. В частности, нас будет интересовать:


как выполнять в одном except-блоке обработку для исключений
нескольких типов;



как получить доступ к экземпляру класса, описывающему исключе­
ние (доступ к экземпляру исключения);



как генерировать исключения;



как создавать пользовательские классы исключений.

Допустим, необходимо реализовать обработку исключительных ситуа­
ций так, чтобы нескольких разных типов ошибок обрабатывались одина­
ково. Что нужно учесть в этом случае? Есть два момента. Во-первых, если в
except-блоке указан определенный класс исключения, то перехватывать­
ся будут исключения не только данного класса, но и исключения, относя­
щиеся к производным классам.
Например, в except-блоке для исключений класса Exception будут пе­
рехватываться практически все исключения, поскольку класс Exception
является базовым (по цепочке наследования) для всех классов, кроме
GeneratorExit, Keyboardlnterrupt, SystemExit и BaseException
(см. рис. 8.1). Во-вторых, в except-блоке можно указать не только один
класс исключения, а целый кортеж, элементами которого являются назва­
ния классов исключений. В таком except-блоке будут перехватываться
все исключения из кортежа. Например, предположим, что для перехвата и
обработки исключения используется следующий шаблон:
try :
# контролируемый код
ex c e p t (A rith m e tic E rro r,T y p e E rro r, V a lu e E rro r):
# код для обработки
e x c e p t W a rn in g :
# код для обработки

~ m

i

Python

Что это означает? Это означает, что при возникновении ошибки в tryблоке она будет перехвачена и обработана в первом except-блоке, если
тип ошибки относится к классу ArithmeticEr ror (включая производные
классы FloatingPointError, OverflowError и ZeroDivisionError),
TypeError или ValueError. Если тип ошибки иной, "в игру" вступает
второй except-блок. В этом блоке обрабатываются ошибки, которые отно­
сятся к классу Warning и всем его производным классам.
Мы уже знаем, что если есть класс, то на его основе, скорее всего, можно
создать экземпляр класса. По отношению к ошибкам мы говорим о классе,
который соответствует той или иной исключительной ситуации. Но где же
экземпляры таких классов?
В принципе, никто не запрещает нам создать экземпляр класса обычным
способом, как мы до этого создавали экземпляры прочих классов - через
имя класса, указав в круглых скобках (если нужно) аргументы для кон­
структора и присвоив результат такой инструкции некоторой переменной
(ссылка на экземпляр класса). Хотя обычно (но не всегда) используют те
экземпляры, что автоматически создаются при возникновении исключи­
тельной ситуации. То есть такой экземпляр всегда существует, если уж воз­
никла ошибка. Другое дело, как мы этот экземпляр использует (или не ис­
пользуем).
В случае если в e x c e p t -блоке мы все же решили явно использовать экзем­
пляр исключения, шаблон командного кода будет таким (жирным ш риф­
том выделены ключевые элементы кода):
try:
# контролируемый код

except к л а с с _ и с к л ю ч е н и я as э к з е м п л я р :
# код для обработки

Если коротко, то после названия класса исключения через ключевое слово
a s указывается формальное название для экземпляра класса исключения,
который будет автоматически создан при возникновении ошибки. Именно
черед это имя в блоке обработки исключения следует обращаться к экзем­
пляру класса исключения. В качестве иллюстрации к использованию эк­
земпляра класса исключения рассмотрим небольшой пример, представлен­
ный в листинге 8 . 10 .
Листинг 8 .1 0 . Экземпляр класса исключения
# И м п орт ф у н к ц и й и з м о д у л я r a n d o m
from random im p o r t s e e d , r a n d i n t
p r i n t ("П ерехват исклю чений.")
# Список и з дв ух эл ем ен т о в

ши

?

Глава 8. Немного о разном

num s= [1 ,2 ]
# Слово и з ш ести букв
tx t= "P y th o n "
# Ц елочисленная переменная
а=10
# Список и з т р е х э л ем ен то в
n a m e s= [n u m s,a ,tx t]
# И нициализация г е н е р а т о р а случайны х чи сел
s e e d (123)
# О ператор цикла
f o r i i n r a n g e (10) :
# Контролируемый код
try :
# С лучайное ц ел о е чи сл о в д и а п а з о н е от 0 до 2
n = r a n d i n t (0 ,2 )
p r i n t ( " С г е н е р и р о в а н о ч и с л о : " , п)
# К оли чество эл ем ен то в в сп и ске или т е к с т е .
# При п о п ы т к е в ы з в а т ь функцию 1 е п ( ) д л я ц е л о г о
# ч и с л а в о з н и к а е т ошибка (н е к о р р е к т н ы й ти п данных)
p r i n t ( " К о л и ч е с т в о э л е м е н т о в : " , l e n ( n a m e s [ п ] ))
# Н еверный и н д е к с и л и д е л е н и е н а н оль
n a m e s [п + 1 ] / /= п
# О б р а б о т к а ошибки, с в я з а н н о й с
# н ек о р р ек тн ы м типом данных
e x c e p t T y p eE rro r as e r r :
# Э кземпляр исклю чения п ер ед ан
# а р г у м е н т о м в функцию p r i n t ()
p r i n t ( "О ш ибка: " , e r r )
# О б р а б о т к а ош ибок, с в я з а н н ы х с н ек о р р ек тн ы м и н д е к с о м
# или попыткой д елен и я н а ноль
ex c ep t (L o o k u p E rro r,A rith m e tic E rro r) as e r r :
p r i n t ( "П роблема с в ы ч и сл ен и я м и :" )
# О п р е д е л я е м к л а с с ош ибк и п о э к з е м п л я р у к л а с с а
p r i n t ( " К л а с с ош ибки - " , е г г . __ c l a s s ___)
# С т р о к а и з 45 с и м в о л о в ф о р м и р у е т с я с помощью
# оп ератора повторения
p r i n t ("-"*45)
p r i n t ("Р абота за в е р ш е н а .")

Результат выполнения программного кода может быть таким (а может быть
и несколько иным, поскольку многое зависит от использованного генерато­
ра случайных чисел):
Результат выполнения программы (из листинга 8 .10 )
П ерехват исклю чений.
С генерировано число:

О

СО

Python

Количество элем ентов: 2
П роблема с в ы ч и сл ен и ям и :
К л а с с о ш и б к и - < c l a s s ' Z e r o D i v i s i o n E r r o r 1>
Сгенерировано ч и с л о : 1
О ш ибка: o b j e c t o f t y p e ' i n t '

h a s no le n ( )

Сгенерировано число: О
Количество элем ен тов: 2
П роблема с в ы ч и сл ен и ям и :
К л а с с ош ибк и - < c l a s s ' Z e r o D i v i s i o n E r r o r ' >
Сгенерировано число: 1
О ш ибка: o b j e c t o f t y p e ' i n t ' h a s n o l e n ()
Сгенерировано число: 1
О ш ибка: o b j e c t o f t y p e ' i n t ' h a s n o l e n ()
Сгенерировано ч и с л о : О
Количество элем ен тов: 2
Проблема с вы числениями:
К л а с с ош ибк и - c c l a s s ' Z e r o D i v i s i o n E r r o r ' >
Сгенерировано ч и с л о : О
К оличество элем ен тов: 2
Проблема с вы числениям и:
К л а с с ош ибк и - < c l a s s ' Z e r o D i v i s i o n E r r o r ' >
Сгенерировано ч и с л о : 1
О ш ибка: o b j e c t o f t y p e ' i n t '

h a s no le n ( )

Сгенерировано ч и с л о : 2
К оличество э л ем ен т о в : 6
П роблема с вы ч и сл ен и ям и :
К л а с с ош ибки - < c l a s s ' I n d e x E r r o r ' >
Сгенерировано ч и с л о : 2
К оличество э л ем ен т о в : 6
Проблема с вы числениям и:
К л а с с ошибки - C c l a s s ' I n d e x E r r o r '>
Р абота заверш ена.

В этом примере мы используем генератор случайных чисел, поэтому пре­
жде всего командой f r o m random i m p o r t s e e d ,
r a n d i n t импор­
тируем из модуля ran d o m функции s e e d ( ) и r a n d i n t (). Ф ункция
r a n d i n t () предназначена для генерирования целых случайных (псевдосв т

............................................................................ J

Глава 8. Немного о разном

лучайных) чисел. Ф ункция s e e d () используется для инициализации гене­
ратора случайных чисел.
Q

На заметку
Хотя с генератором случайных чисел мы уже имели дело, нелишним будет кое-что
напомнить.
Как бы мы ни пытались сгенерировать случайное число, на самом деле это неподъ­
емная задача. Максимум, чего мы можем добиться - создать иллюзию того, что
число случайное. Поэтому генерируемые "случайные" числа правильнее было бы
называть "псевдослучайными". Так, собственно, их и называют. Технически про­
цесс генерирования псевдослучайных чисел состоит в том, что по некоторой фор­
муле или алгоритму вычисляются числа. Для начала вычислений необходимо, по
крайней мере, одно начальное, "затравочное" число. Когда это число задано, за­
пускается процесс вычислений. За все это отвечает группа утилит, которую мы
называем генератором случайных чисел. Действия, связанные с определением
"начального" состояния генератора случайных чисел, называются инициализа­
цией генератора случайных чисел. Для выполнения инициализации следует ука­
зать число и передать его аргументом функции seed ( ) . Поскольку, как мы уже зна­
ем, вычисление случайных (псевдослучайных) чисел - процесс строго детермини­
рованный, а "начальная точка" в этом процессе определяется используемым для
инициализации числом, то используя в качестве "инициализатора" одно и то же
число, будем получать одну и ту же последовательность чисел. Если инициализа­
ция генератора случайных чисел в явном виде не выполнена, обычно выполняется
неявная инициализация с использованием системного времени.

Командой n u m s = [ l , 2 ] создается список из двух элементов, командой
t x t = " P y t h o n " создаем текст, а числовая переменная определяется коман­
дой а = 1 0 . Наконец, командой nam es= [nums, a , t x t ] создается список из
трех элементов, один из которых - список, другой - целое число, а третий текст. Первый и третий элементы могут быть указаны аргументом функции
1 еп (), которая, как известно, в качестве результата возвращает количество
элементов для списка и количество букв для текста.
И нициализация генератора случайных чисел выполняется командой
s e e d (123) . В данном случае аргумент функции s е е d () играет формаль­
ную роль - можно указать и другое число.
Затем запускается оператор цикла (10 итераций). За каждый цикл выпол­
няется группа команд, которые размещены в блоке t r y . А именно, выпол­
няются такие команды: командой n = r a n d i n t ( 0 , 2 ) генерируется случай­
ное целое число в диапазоне от 0 до 2 включительно, и это число записы­
вается в переменную п. Далее в инструкции l e n (names [п] ) выполняется
попытка вычислить количество элементов в элементе списка nam es с ин­
дексом п.
Если значение переменной п равно 0 или 2 , то проблем с выполнением ин­
струкции не возникает, поскольку элемент с индексом 0 - это список, а эле........................ ..........................................................................................

................... Я В Б 1

Python

мент с индексом 2 - текст. Но если значение переменной п равняется 1 , то
возникает ошибка класса Т у р е Е г г о г , так как элемент с индексом 1 - целое
число. Д ля перехвата данной ошибки есть специальный e x c e p t -блок, в ко­
тором экземпляр ошибки класса Т у р е Е г г о г обозначен как e r r (инструк­
ция Т у р е Е г г о г a s e r r в e x c e p t -блоке).
В этом e x c e p t -блоке выполняется команда p r i n t ( " О ш и б к а e r r ) .
Здесь
экземпляр
ошибки
e rr
передан
аргументом
функции
p r i n t (), что приводит к автоматическому приведению экземпля­
ра e r r к текстовому формату (экземпляры классов исключений под­
держивают такую операцию). Экземпляр e r r будет замещен текстом
" o b j e c t o f t y p e ' i n t ' h a s no l e n ( ) " (что означает объект типа

'infue имеет (функции) 1еп()).
Все это происходит, напомним, если выполнение инструкции
len (names [n]) приводит к ошибке. Но если выполнение инструкции к
ошибке не приводит, то ошибка точно возникнет при выполнении коман­
ды names [п +1 ] / / = п . Здесь сделана попытка изменить значение элемента
names [п + 1 ] , вычислив результат целочисленного деления текущего зна­
чения этого элемента на значение переменной п. Необходимо учесть, что уж
если дело дошло до выполнения команды names [п + 1 ] , то значение п рав­
но 0 или 2 (при значении 1 ошибка возникает на предыдущем шаге). При
значении 0 индекс п +1 дает значение 1 . Элемент с таким индексом в спи­
ске names имеется. Но беда в том, что значение этого элемента мы пытаем­
ся поделить на ноль. Такой самонадеянный проступок приводит к ошибке
класса ZeroDivisionError.
Если значение переменной п равно 2 , то деления на ноль нет, но индекс п +1
равен 3, и элемента с таким индексом в списке names нет. В результате воз­
никает ошибка класса IndexError. Для перехвата и той, и другой ошибки
предназначен except-блок с кортежем из классов исключений LookupError и ArithmeticError. Экземпляр класса ошибки обозначен как err.
Q

На заметку
Класс исключения ZeroDivisionError является производным классом от клас­
са ArithmeticError. Класс исключения IndexError является подклассом клас­
са LookupError. Поэтому в except-блоке с кортежем из классов исключений
LookupError и ArithmeticError перехватываются исключения классов
ZeroDivisionError и IndexError.

В блоке при обработке ошибки выполняется обращение к полю __ с l a s s __
экземпляра ошибки e r r . Значением этого поля является название класса, к
которому относится экземпляр e r r .

*

Глава 8. Немного о разном

Q

На заметку
В программном коде есть команда p r i n t (" - " * 4 5 ). В данном случае отображает­
ся строка, которая получается повторением символа
45 раз.

Как отмечалось ранее, исключения можно не только перехватывать и обра­
батывать, но и генерировать. Зачем генерировать исключения - вопрос от­
дельный. Например, метод генерирования исключений можно рассматри­
вать как некий способ имитации условного оператора или выполнение ана­
лога безусловного перехода в программном коде. Но как бы там ни было, ге­
нерирование исключений как механизм программирования существует, и
мы с ним кратко познакомимся.
С технической точки зрения для генерирования исключения (ошибки) до­
статочно после инструкции r a i s e указать экземпляр класса исключения.
Экземпляр класса исключения можно или создать, или воспользоваться
"готовым" - например тем, что создается автоматически, когда ошибка ре­
ально возникает, и затем передается в блок обработки исключения. Пример
в листинге 8.11 иллюстрирует обе эти ситуации.
Листинг 8 .1 1 . Генерирование исключения
# С оздаем э к зе м п л я р исклю чения
e r r _ o n e = Z e r o D i v i s i o n E r r o r ( "О ш ибка д е л е н и я н а н о л ь ! " )
p r i n t ( "С ей час \" в о з н и к н е т \ " о ш и б к а.")
p r i n t ( " П е р в а я :" )
# Описание э к з е м п л я р а исклю чения
p rin t(e rr_ o n e )
# Контролируемый код
try :
# Г е н е р и р о в а н и е ош ибки
r a is e err_one
# О б р а б о т к а ош ибк и
e x c e p t Z e ro D iv isio n E rro r as err_ tw o :
p r i n t ( " В т о р а я :" )
# О п и с а н и е ош ибк и
p rin t(e rr_ tw o )
# Контролируемый код
try :
# П о в т о р н о е г е н е р и р о в а н и е ош ибки
r a i s e e rr_ tw o
# О б р а б о т к а ош ибки
ex c ep t Z e ro D iv isio n E rro r as e r r _ th r e e :
p r i n t ( " Т р е т ь я :")
# О п и с а н и е ош ибки
p rin t(e rr_ th re e )

•Ш Ш

Python

# Контролируемый код
try :
# Попытка д е л е н и я н а н ол ь

а=1/0
# О б р а б о т к а ош ибки
ex c e p t Z e ro D iv isio n E rro r as
p r i n t ( " Ч е т в е р т а я :" )
# О п и с а н и е ош ибки
p rin t(e rr_ fo u r)
p r i n t ( "Больш е н и к а к и х о ш и б о к ." )

err_ fo u r:

При выполнении программного кода получаем такой результат:
Результат выполнения программы (из листинга 8 .1 1 )
С ейчас "в о з н и к н е т " ош ибка.
П ервая:
Ошибка д е л е н и я н а н о л ь !
В торая:
Ошибка д е л е н и я н а н о л ь !
Третья:
Ошибка д е л е н и я н а н о л ь !
Ч етвертая:
d i v i s i o n by ze ro
Больше н и к а к и х ош ибок.

В этом программном коде командой err_one=ZeroDivisionError
("Ошибка деления на ноль!") мы создаем экземпляр егг_опе ис­
ключения класса ZeroDivisionError. Текст, который передается аргу­
ментом конструктору при создании экземпляра, служит описанием ошиб­
ки. Именно этот текст будет результатом приведения экземпляра исключе­
ния к текстовому формату: например, если передать экземпляр егг_опе в
качестве аргумента методу print ( ), в результате в окне вывода появится
текст "Ошибка деления на ноль !".
Затем в t r y -блоке выполняется команда r a i s e err_one, в результате
чего генерируется ошибка, реализованная через экземпляр егг_опе клас­
са ZeroDivisionError.
Щ

На зам етку

Обратите внимание, что создание экземпляра исключения не
означает возникновения ошибки. Для генерирования ошибки
используем инструкцию r a i s e .

Д ля обработки сгенерированной ошибки предназначен e x c e p t -блок, и эк­
земпляр обрабатываемой ошибки обозначен в нем как е г r _ t wo. В самом блош т -

Глава 8. Немного о разном

ке выполняется команда p r i n t ( e r r _ t w o ) , которой в окне вывода отобра­
жается описание ошибки. В данном случае следствие выполнения этой ко­
манды - появление в окне вывода сообщения Ошибка д е л е н и я н а н оль !.
Это то же самое сообщение, которое появлялось при выполнении коман­
ды p r i n t ( e r r _ o n e ) . Ничего удивительного здесь нет, поскольку пере­
менная e r r tw o ссылается фактически на тот же экземпляр, на который
ссылалась переменная е г г _ о п е . При перехвате сгенерированного коман­
дой r a i s e
e r r _ o n e исключения экземпляр исключения передается в
e x c e p t -блок, и в этом блоке ссылка на экземпляр ошибки выполняется че­
рез переменную e r r _ t w o .
В e x c e p t -блоке обработки ошибки имеется свой t r y -блок, в котором ко­
мандой r a i s e e r r _ t w o выполняется повторное генерирование ошибки.
Новая обработка выполняется в e x c e p t -блоке, и в этом блоке ссылка на
экземпляр ошибки выполняется через переменную e r r t h r e e . И снова
речь идет о том же самом экземпляре, что и в предыдущих случаях. С ви­
детельством тому - результат выполнения команды p r i n t ( e r r _ t h r e e )
(результат такой же, как при выполнении команд p r i n t ( e r r _ o n e ) и
p r i n t ( e r r _ t w o ) ).
Затем при попытке выполнить команду а=1 /0 снова генерируется ошибка,
связанная с деление на ноль (класс ZeroDivisionError). Но на этот раз
экземпляр исключения создается автоматически и это уже совсем иной эк­
земпляр, который к переменным err_one, err_two и err_three не име­
ет никакого отношения. Поэтому при выполнении команды print (err_
four) в except-блоке (переменная err four - ссылка на экземпляр ис­
ключения) получаем, по сравнению с предыдущими случаями, совсем иной
результат (текст, описывающий исключение).
Д ля генерирования исключений не обязательно прибегать к помощи ин­
струкции raise. Можно воспользоваться несколько иным подходом, в
основе которого использование инструкции assert. Если после инструк­
ции assert указать выражение с логическим значением False, будет сге­
нерировано исключение класса AssertionError. Небольшой пример, в
котором исключение генерируется с помощью инструкции assert, приве­
ден в листинге 8 . 12 .
Листинг 8 .1 2 . Использование инструкции assert
# Функция с л о г и ч е с к и м а р г у м е н т о м
def show (arg):
# Контролируемый код
try :
Если ар гу м ен т a r g р а в е н F a l s e
г е н е р и р у е т с я исклю чение

*

-ш т

Python

a s s e r t arg
# Е сли ошибка н е с г е н е р и р о в а н а
p r i n t ( "Ш татный р е ж и м . " )
# О бработка исклю чения
ex cep t A sse rtio n E rro r as e r r :
p r i n t ( " И с к л ю ч е н и е : " , e r r . __ c l a s s __ )
# В ы зы в а е м функцию
show (T rue)
sh o w (F a lse)

Результат выполнения этого программного кода такой:
Результат выполнения программы (из листинга 8 .12 )
Ш татный р е ж и м .
Исклю чение: C c la s s

' A s s e r t i o n E r r o r 1>

В данном случае мы описываем функцию show (), у которой, как предпо­
лагается, один логический аргумент. Этот аргумент в теле функции указы­
вается после инструкции a s s e r t . Поэтому при значении аргумента F a l s e
генерируется исключение класса A s s e r t i o n E r r o r . При обработке исклю­
чения в e x c e p t -блоке отображается значение п о л я __ c l a s s __ экземпля­
ра исключения (значение поля - это класс исключения). Если же аргумент
функции равен Tru e, исключение не генерируется, а выполняется команда
p r i n t ( "Штатный реж им . ") после a s s e r t -инструкции. Вызывая ф унк­
цию show () с разными аргументами, получаем разные результаты (точнее,
в окне вывода отображается разный текст).
Ш

На заметку
При генерировании исключения с помощью инструкции a s s e rt можно добавить
описание (текст) для экземпляра исключения. Для этого после логического значе­
ния в a s s e r t-инструкции через запятую указывается текст - тот текст, который бу­
дет отображаться при попытке "напечатать" экземпляр исключения. Например, в
результате выполнения инструкции a s s e rt F a ls e "Неожиданная ошибка" бу­
дет создан экземпляр класса A s s e rtio n E rr o r . При приведении экземпляра к тек­
стовому формату будет возвращаться текстовая строка "Неожиданная ошибка".

Еще один вопрос, который мы здесь рассмотрим, связан с созданием клас­
сов пользовательских исключений. Общий рецепт в данном случае прост:
необходимо создать новый класс путем наследования класса из иерархии
классов встроенных исключений. Обычно в качестве базового класса реко­
мендуют использовать класс Exception.
Процесс создания классов пользовательских исключений рассмотрим на
примере, представленном в листинге 8.13. В этом примере мы решаем ква-

g'fi'i

*

Глава 8. Немного о разном

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

Ш

На заметку
Напомним, что квадратное уравнение имеет вид ах2 + Ьх + С = О- В зависимо­
сти от значений параметров а , Ь и С возможны следующие варианты. Формально у
-Ъ + Vb2 - 4ас
-Ь - Vb2 - 4ас
квадратного уравнения два решения: * i = --------- ^ --------- и ** ~

>но
это в том случае, если параметр а Ф 0 и выражение D = Ь2 — 4 а с (называет­
ся дискриминантом квадратного уравнения) неотрицательно, то есть если D > О
. Причем если дискриминант нулевой (то есть D — О), то оба корня уравнения со­
впадают: х х = х г = — — , Если дискриминант отрицательный (имеет место
D < О), то на множестве действительных чисел квадратное уравнение решений не
имеет. Зато имеет два решения на множестве комплексных чисел:

-Ь -

.

-ъ + iV^lj


= ----------------

и
--------- 2а------ ■гДе через I обозначена мнимая единица (такая, что по опреде­
лению i 2 = —1). Все это имеет место, если параметр а не равен нулю. Если же
а = О, то фактически речь идет не о квадратном, а о линейном уравнении вида
с
Ь х + с = О. У этого уравнения одно решение х = но это при условии, что
Ь =£ О. Если 6 = О, то все зависит от того, равен ли нулю параметр С. При с = О
решением уравнения Ьх + с = О будет любое число. При с Ф О У уравнения
Ьх + с = О решений нет.

Понятно, что при написании программного кода для решения квадратных уравне­
ний с учетом всех перечисленных выше факторов можно использовать структуру
из вложенных условных операторов. Но мы пойдем другим путем. В приведенном
программном коде описывается два класса пользовательских исключений. Одно
исключение генерируется в случае, если уравнение не является квадратным, а
другое - если у уравнения комплексные решения (корни).

Листинг 8 .1 3 . Пользовательские классы исключений
# Импорт м а т е м а т и ч е с к о й функции
fro m m ath im p o r t s q r t
# К л а с с п о л ь з о в а т е л ь с к о й ош ибки
c l a s s L in e arE q u a tio n W arn in g (W a rn in g ):
# К онструктор
d e f __ i n i t __ ( s e l f , b , c ) :
# Контролируемый код
try :
# П р и с в а и в а н и е з н а ч е н и я полю э к з е м п л я р а .
# Возможна ошибка д е л е н и я н а н оль
s e l f . х = -с /Ь
# Е сли в о з н и к л а ошибка д е л е н и я н а ноль
except Z e ro D iv isio n E rro r:
# Если п ар ам етр нул евой

- ш т

Python

if

c==0:
# Реш ение - лю бое ч и с л о
s e l f . х="лю бое ч и с л о "
# Е с л и п а р а м е т р н е р а в е н нулю
e lse :
# Р еш ен и й н е т
s e l f . х="реш ений н е т "
# М етод д л я п р и в е д е н и я э к з е м п л я р а и ск л ю ч е н и я
# к те к с то в о м у формату
d e f __ s t r __ ( s e l f ) :
# Ф ормируется т е к с т о в о е зн а ч е н и е
# для р е з у л ь т а т а м етода
t x t = " Реш ение у р а в н е н и я : "
t x t + = s t r ( s e l f .х )
# Р езу л ь тат м етода
re tu rn tx t
# К ласс п о л ь з о в а т е л ь с к о г о исклю чения
c la s s C o m p lex R o o tsE rro r(E x c ep tio n ):
# К онструктор
d e f __ i n i t __ ( s e l f , a , b , D ) :
# Значение поля экзем пляра
se lf.x l= c o m p le x ( - b /2 /a ,s q rt(-D )/2 /a )
# Значение поля экзем пляра
s e l f . x 2 = co m p lex ( - b / 2 / a , - s q r t ( - D ) / 2 / a )
# М е то д д л я п р и в е д е н и я э к з е м п л я р а и с к л ю ч е н и я
# к т е к с т о в о м у формату
d e f __ s t r __ ( s e l f ) :
# Ф ормирование т е к с т о в о й с т р о к и для
# р е зу л ь та та метода
tx t= " x l = " + s tr(s e lf.x l)+ " \n "
t x t + = " x 2 = " + s t r ( s e l f . x2)
# Р езу л ьтат м етода
re tu rn tx t
# Функция д л я о т о б р а ж е н и я п а р а м е т р о в у р а в н е н и я
d e f s h o w _ p a ra m s(a ,b ,с ) :
# Т екст с форматированием
p r i n t ( "П арам етры : а = {0}, b = {1}, с = { 2 } : " . f o r m a t ( а , Ь , с ) )
# Функция д л я реш ен и я к в а д р а т н о г о у р а в н е н и я
d e f f i n d _ r o o t s (а , b , с) :
# О тображение п ар ам етр о в реш аем ого ур ав н ен и я
s h o w _ p a r a m s ( а , b , с)
# Контролируемый код
try :
# Если п а р а м е т р н у л ев о й
i f а==0:
# Г е н ер и р у е тся исклю чение

ш т -

Глава 8. Немного о разном

# п о л ь з о в а т е л ь с к о г о типа
r a i s e L i n e a r E q u a t i o n W a r n i n g ( b , с)
# Дискриминант ур ав н ен и я
D = b*b-4*a*c
# Е сл и д и с к р и м и н а н т меньш е н у л я
i f D