КулЛиб - Классная библиотека! Скачать книги бесплатно
Всего книг - 714765 томов
Объем библиотеки - 1414 Гб.
Всего авторов - 275159
Пользователей - 125177

Новое на форуме

Новое в блогах

Впечатления

iv4f3dorov про Дорнбург: Змеелов в СССР (Альтернативная история)

Очередное антисоветское гавно размазанное тонким слоем по всем страницам. Афтырь ты мудак.

Рейтинг: +1 ( 2 за, 1 против).
A.Stern про Штерн: Анархопокалипсис (СИ) (Боевик)

Господи)))
Вы когда воруете чужие книги с АТ: https://author.today/work/234524, вы хотя бы жанр указывайте правильный и прологи не удаляйте.
(Заходите к автору оригинала в профиль, раз понравилось!)

Какое же это фентези, или это эпоха возрождения в постапокалиптическом мире? -)
(Спасибо неизвестному за пиар, советую ознакомиться с автором оригинала по ссылке)

Ещё раз спасибо за бесплатный пиар! Жаль вы не всё произведение публикуете х)

Рейтинг: -1 ( 0 за, 1 против).
чтун про серию Вселенная Вечности

Все четыре книги за пару дней "ушли". Но, строго любителям ЛитАниме (кароч, любителям фанфиков В0) ). Не подкачал, Антон Романович, с "чувством, толком, расстановкой" сделал. Осталось только проду ждать, да...

Рейтинг: +2 ( 2 за, 0 против).
Влад и мир про Лапышев: Наследник (Альтернативная история)

Стиль написания хороший, но бардак у автора в голове на нечитаемо, когда он начинает сочинять за политику. Трояк ставлю, но читать дальше не буду. С чего Ленину, социалистам, эссерам любить монархию и терпеть черносотенцев,убивавших их и устраивающие погромы? Не надо путать с ворьём сейчас с декорациями государства и парламента, где мошенники на доверии изображают партии. Для ликбеза: Партии были придуманы ещё в древнем Риме для

  подробнее ...

Рейтинг: +1 ( 1 за, 0 против).
Влад и мир про Романов: Игра по своим правилам (Альтернативная история)

Оценку не ставлю. Обе книги я не смог читать более 20 минут каждую. Автор балдеет от официальной манерной речи царской дворни и видимо в этом смысл данных трудов. Да и там ГГ перерождается сам в себя для спасения своего поражения в Русско-Японскую. Согласитесь такой выбор ГГ для приключенческой фантастики уже скучноватый. Где я и где душонка царского дворового. Мне проще хлев у своей скотины вычистить, чем служить доверенным лицом царя

  подробнее ...

Рейтинг: +2 ( 2 за, 0 против).

Научное программирование на Python [Кристиан Хилл] (pdf) читать онлайн

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


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

Этот интенсивный вводный курс позволяет пройти путь от основ до
формирования продвинутых навыков, благодаря чему читатели смогут
быстро повысить свой уровень профессиональной подготовки. Книга
изобилует примерами и решениями, взятыми из реальной инженерной
и научной практики.
Изучение начинается с общих концепций программирования, таких как
циклы и функции в ядре Python 3, затем рассматриваются библиотеки
NumPy, SciPy и Matplotlib для вычислительного программирования
и визуализации данных. Обсуждается использование виртуального
блокнота Jupyter Notebooks для создания мультимедийных совместно
используемых документов для научного анализа. Отдельная глава посвящена анализу данных с использованием библиотеки pandas. В заключительной главе представлены более сложные темы, такие как точность
вычислений с применением чисел с плавающей точкой и обеспечение
стабильности алгоритмов.
Издание адресовано студентам, ученым, специалистам по работе
с данными, которым требуется прочная основа для решения насущных
задач с помощью Python.
Кристиан Хилл – физик и специалист в области физической химии,
в настоящее время работающий в Интернациональном агентстве по
использованию атомной энергии (International Atomic Energy Agency).
Обладает более чем 25-летним опытом программирования в области
физических наук и программирует на Python 15 лет. В своих исследованиях использует Python для создания, анализа, обработки, управления и
визуализации крупных наборов данных в области спектроскопии, физики
плазмы и материаловедения.
ISBN 978-5-97060-914-9

Интернет-магазин:
www.dmkpress.com
Оптовая продажа:
КТК “Галактика”
books@alians-kniga.ru

www.дмк.рф

9 785970 609149

Научное программирование на Python

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

Кристиан Хилл

Научное
программирование
на Python

Кристиан Хилл

Научное
программирование
на Python

Learning Scientific
Programming
with Python
Second Edition

Christian Hill

Научное
программирование
на Python

Кристиан Хилл

Москва, 2021

УДК 004.94Phyton
ББК 32.972
Х45

Х45

Кристиан Хилл
Научное программирование на Python / пер. с анг. А. В. Снастина. – М.:
ДМК Пресс, 2021. – 646 с.: ил.
ISBN 978-5-97060-914-9
Книга начинается с общих концепций программирования, таких как циклы
и функции в ядре Python 3, затем рассматриваются библиотеки NumPy, SciPy
и Matplotlib для вычислительного программирования и визуализации данных.
Обсуждается использование виртуального блокнота Jupyter Notebooks для
создания мультимедийных совместно используемых документов для научного
анализа. Отдельная глава посвящена анализу данных с использованием библиотеки pandas. В заключительной части представлены более сложные темы,
такие как точность вычислений с применением чисел с плавающей точкой
и обеспечение стабильности алгоритмов.
Издание адресовано студентам, ученым, специалистам по работе с данными,
которым требуется прочная основа для решения насущных задач с помощью
Python

УДК 004.94Phyton
ББК 32.972
Original English language edition published by Cambridge University Press is part of the
University of Cambridge. Copyright © 2020 by Christian Hill.
Russian-language edition copyright © 2021 by DMK Press. All rights reserved.
Все права защищены. Любая часть этой книги не может быть воспроизведена
в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.

ISBN (анг.) 978-1108745918
ISBN (рус.) 978-5-97060-914-9

© 2015, 2020 Christian Hill
© Оформление, издание, перевод, ДМК Пресс, 2021

Научное программирование
на Python
Изучение основных задач программирования на профессиональном уровне с нуля с реальными научными примерами и решениями, взятыми из научной и инженерной практики. Студенты и исследователи всех уровней постепенно переходят на мощный язык программирования Python как альтернативу
коммерческим программным продуктам. Этот интенсивный вводный курс
позволяет пройти путь от основ до продвинутых концепций в одной книге,
дающий возможность читателям быстро поднимать свой уровень профессиональной подготовки.
Книга начинается с общих концепций программирования, таких как циклы
и функции в ядре Python 3, затем рассматриваются библиотеки NumPy, SciPy
и Matplotlib для вычислительного программирования и визуализации данных.
Обсуждается использование виртуального блокнота Jupyter Notebooks для создания мультимедийных совместно используемых документов для научного
анализа. Отдельная глава посвящена анализу данных с использованием библиотеки pandas. В заключительной части представлены более сложные темы,
такие как точность вычислений с применением чисел с плавающей точкой
и обеспечение стабильности алгоритмов.
Кристиан Хилл (Christian Hill) – физик и специалист в области физической
химии, в настоящее время работающий в Интернациональном агентстве по
использованию атомной энергии (International Atomic Energy Agency). Он обладает более чем 25-летним опытом программирования в области физических
наук и программирует на Python 15 лет. В своих исследованиях Кристиан использует Python для создания, анализа, обработки, управления и визуализации крупных наборов данных в области спектроскопии, физики плазмы и материаловедения.

Оглавление
Благодарности....................................................................................................9
Список листингов............................................................................................10
Глава 1. Введение............................................................................................13
1.1 Об этой книге...............................................................................................13
1.2 Немного о Python.........................................................................................14
1.3 Установка Python . .......................................................................................18
1.4 Командная строка........................................................................................19

Глава 2. Ядро языка Python I......................................................................21
2.1 Командная оболочка Python.......................................................................21
2.2 Числа, переменные, операции сравнения и логические операции.........22
2.3 Объекты Python I: строки............................................................................43
2.4 Объекты Python II: списки, кортежи и циклы............................................61
2.5 Управление потоком выполнения..............................................................78
2.6 Файловый ввод/вывод.................................................................................90
2.7 Функции.......................................................................................................94

Глава 3. Небольшое отступление: простые схемы
и диаграммы...................................................................................................111
3.1 Создание простых схем.............................................................................112
3.2 Метки, надписи и настройка параметров графиков...............................117
3.3 Построение более сложных графиков......................................................127

Глава 4. Ядро языка Python II...................................................................132
4.1 Ошибки и исключения..............................................................................132
4.2 Объекты Python III: словари и множества...............................................142
4.3 Идиоматические выражения Python: синтаксический сахар................156
4.4 Сервисы операционной системы.............................................................169
4.5 Модули и пакеты........................................................................................176
4.6 ◊ Введение в объектно-ориентированное программирование..............187

Глава 5. Командная оболочка IPython и блокнотная среда
Jupyter Notebook. ..........................................................................................209
5.1 Командная оболочка IPython....................................................................209
5.2 Блокнотная среда Jupyter Notebook..........................................................225

Глава 6. Библиотека NumPy......................................................................238
6.1 Основные методы массива.......................................................................239
6.2 Чтение и запись массива в файл...............................................................274
6.3 Статистические методы............................................................................287
6.4 Многочлены...............................................................................................295

Научное программирование на Python 

7

6.5 Линейная алгебра......................................................................................312
6.6 Случайная выборка....................................................................................328
6.7 Дискретные преобразования Фурье.........................................................340

Глава 7. Библиотека Matplotlib................................................................348
7.1 Линейные графики и точечные диаграммы............................................348
7.2 Специализированная настройка и улучшение качества графика..........354
7.3 Столбиковые диаграммы, круговые диаграммы и диаграммы
в полярных координатах.................................................................................371
7.4 Аннотации для графиков...........................................................................380
7.5 Контурные диаграммы и тепловые карты...............................................394
7.6 Трехмерные графики.................................................................................406
7.7 Анимация....................................................................................................411

Глава 8. Библиотека SciPy..........................................................................418
8.1 Физические константы и специальные функции...................................418
8.2 Интегрирование и обыкновенные дифференциальные уравнения......442
8.3 Интерполяция............................................................................................472
8.4 Оптимизация, подгонка данных и численные
методы решения уравнений...........................................................................478

Глава 9. Анализ данных с помощью pandas.......................................504
9.1 Введение в pandas......................................................................................504
9.2 Чтение и запись объектов Series и DataFrame......................................... 520
9.3 Более сложное индексирование...............................................................531
9.4 Очистка и обследование данных..............................................................538
9.5 Группирование и агрегация данных........................................................551
9.6 Примеры.....................................................................................................555

Глава 10. Общие положения научного программирования........563
10.1 Арифметика с плавающей точкой..........................................................563
10.2 Стабильность и обусловленность алгоритма.........................................573
10.3 Методики программирования и разработка
программного обеспечения............................................................................578

Приложение A. Решения.............................................................................591
Приложение B. Различия между версиями Python 2 и 3..............616
Приложение C. Механизм решения обыкновенных
дифференциальных уравнений odeint в библиотеке SciPy........621
Словарь терминов.........................................................................................623
Предметный указатель...............................................................................631

Предисловие от издательства
Отзывы и пожелания
Мы всегда рады отзывам наших читателей. Расскажите нам, что вы думаете об
этой книге – что понравилось или, может быть, не понравилось. Отзывы важны
для нас, чтобы выпускать книги, которые будут для вас максимально полезны.
Вы можете написать отзыв на нашем сайте www.dmkpress.com, зайдя на
страницу книги и оставив комментарий в разделе «Отзывы и рецензии». Также
можно послать письмо главному редактору по адресу dmkpress@gmail.com;
при этом укажите название книги в теме письма.
Если вы являетесь экспертом в какой-либо области и заинтересованы в написании новой книги, заполните форму на нашем сайте по адресу http://
dmkpress.com/authors/publish_book/ или напишите в издательство по адресу
dmkpress@gmail.com.

Список опечаток
Хотя мы приняли все возможные меры для того, чтобы обеспечить высокое
качество наших текстов, ошибки все равно случаются. Если вы найдете ошибку
в одной из наших книг – возможно, ошибку в основном тексте или программном коде, – мы будем очень благодарны, если вы сообщите нам о ней. Сделав
это, вы избавите других читателей от недопонимания и поможете нам улучшить последующие издания этой книги.
Если вы найдете какие-либо ошибки в коде, пожалуйста, сообщите о них
главному редактору по адресу dmkpress@gmail.com, и мы исправим это
в следующих тиражах.

Нарушение авторских прав
Пиратство в интернете по-прежнему остается насущной проблемой. Издательство «ДМК Пресс» очень серьезно относится к вопросам защиты авторских прав и лицензирования. Если вы столкнетесь в интернете с незаконной
публикацией какой-либо из наших книг, пожалуйста, пришлите нам ссылку на
интернет-ресурс, чтобы мы могли применить санкции.
Ссылку на подозрительные материалы можно прислать по адресу dmkpress@
gmail.com.
Мы высоко ценим любую помощь по защите наших авторов, благодаря которой мы можем предоставлять вам качественные материалы.

Благодарности
Эмме, Шарлотте и Лоренсу
Множество людей прямо или косвенно помогали написать эту книгу, в частности Джонатан Теннисон (Jonathan Tennyson) из UCL, Лоуренс Ротман (Laurence
Rothman) и Айюли Гордон (Iouli Gordon) поддерживали меня во время годичного отпуска для научной работы в Центре астрофизики Гарварда и Смитсоновского института (Harvard-Smithsonian Center for Astrophysics).
Множество ошибок и промахов в первом издании этой книги было замечено
и указано всего лишь несколькими людьми, которые всегда были готовы оказать мгновенную помощь: Стэффорд Бэйнс (Stafford Baines), Мэтью Гиллман
(Matthew Gillman) и Стюарт Андерсон (Stuart Anderson). Те ошибки, которые
все еще остаются в книге, – это, само собой, исключительно мои собственные
ошибки.
Кроме того, особенно благодарен следующим людям: Хелен Рейнолдс (Helen
Reynolds), Крис Пикар (Chris Pickard), Элисон Уайтли (Alison Whiteley), Джеймс
Элиотт (James Elliott), Лианна Ишихара (Lianna Ishihara) и Мило Шаффер (Milo
Shaffer). Как и любой человек, я очень нуждался в поддержке, поощрении
и дружеском отношении Натали Хэйнс (Natalie Haynes).

Список листингов
1.1.
1.2.
1.3.
2.1.
2.2.
2.3.
2.4.
2.5.
2.6.
3.1.
3.2.
3.3.
4.1.
4.2.
4.3.
4.4.
4.5.
4.6.
4.7.
4.8.
4.9.
4.10.
6.1.
6.2.
6.3.
6.4.
6.5.
6.6.
6.7.
6.8.
6.9.
6.10.
6.11.
6.12.
6.13.
7.1.

 ывод списка имен с использованием программы, написанной на Python
В
Вывод списка имен с использованием программы, написанной на языке C
Различные способы вывода списка имен с использованием программы,
написанной на Perl
Вычисление последовательности Фибоначчи с помощью списка
Вычисление последовательности Фибоначчи без сохранения всех чисел
Определение високосного года
Виртуальный робот-черепашка
Правила определения областей видимости Python
Решение задачи «Ханойская башня»
Вывод графика функции y = sin2 x
Демонстрация действия закона Мура
Зависимость между потреблением маргарина в США и количеством разводов в штате Мэн
Обработка астрономических данных
Простые числа Мерсенна
Вывод сообщения-подсказки для скрипта, принимающего аргументы из
командной строки
Переименование файлов данных для упорядочения по дате
Задача Монти Холла
Определение абстрактного базового класса BankAccount
Класс Polymer
Распределение полимеров, созданных по модели случайного перемещения
Простой класс, представляющий двумерный вектор в декартовых координатах
Простая двумерная имитация молекулярной динамики
Создание магического квадрата
Проверка правильности квадрата судоку
Применение методов argmax и argmin
Считывание столбца значений кровяного давления
Анализ данных, полученных в ходе эксперимента по изучению эффекта
Струпа
Имитация радиоактивного распада ядер 14C
Вычисление коэффициента корреляции между температурой воздуха
и атмосферным давлением
Определение высоты жидкости в сферической емкости
Прямолинейная подгонка данных о поглощении
Линейные преобразования по двум измерениям
Линейная подгонка методом наименьших квадратов для данных, соответствующих закону Бугера–Ламберта–Бера
Моделирование распределения атомов 13C в бакминстерфуллерене C60
Размытие изображения с использованием гауссова фильтра
Точечная диаграмма демографических данных по восьми странам

Список листингов 

7.2.
7.3.
7.4.
7.5.
7.6.
7.7.
7.8.
7.9.
7.10.
7.11.
7.12.
7.13.
7.14.
7.15.
7.16.
7.17.
7.18.
7.19.
7.20.
7.21.
7.22.
7.23.
7.24.
7.25.
7.26.
7.27.
7.28.
7.29.
7.30.
7.31.
8.1.
8.2.
8.3.
8.4.

11

 редний возраст при первом вступлении в брак в США в зависимости от
С
времени
Численность населения пяти городов США в зависимости от времени
Экспоненциальный период радиоактивного распада в интервалах времени существования
Специально настроенные штриховые метки
Изменения значений нагрузки на крыло для стрижей перед оперением
Уравнение одномерной диффузии, применяемое к температуре двух
различных металлических брусков
Десять внутренних графиков с нулевым промежуточным пространством
по вертикали
Частота встречаемости букв в тексте романа «Моби Дик»
Визуализация производства электроэнергии из возобновляемых источников в Германии
Круговая диаграмма данных о выбросе в атмосферу парниковых газов
График коэффициента направленного действия (КНД) для системы из
двух антенн
График коэффициента направленного действия (КНД) для системы из
трех антенн
Аннотации со стрелками в Matplotlib
Изображение временно́й последовательности курса акций на графике с
аннотацией
Некоторые варианты применения методов ax.vlines и ax.hlines
Графическое представление электромагнитного спектра в диапазоне
250–1000 нм
Анализ отношения роста и массы тела 507 здоровых людей
Создание цветных фигур
Электростатический потенциал точечного диполя
Пример изображения контуров с заливкой цветом и определением стилей
Сравнение применения различных схем интерполяции для визуализации небольшого массива с помощью метода imshow()
Папоротник Барнсли
Тепловая карта дневных температур в Бостоне в 2019 г.
Уравнение диффузии в двумерном пространстве, примененное к распространению температуры в стальной пластине
Четыре трехмерные поверхностные диаграммы простой двумерной гауссовой функции
Трехмерная поверхностная диаграмма тора
Изображение спирали на трехмерном графике
Анимация затухающей синусоидальной кривой
Анимация затухающей синусоидальной кривой с использованием blit=True
Анимация прыгающего мяча
Наименее точно определенные физические константы
Плотности распределения вероятностей для частицы в однородном гравитационном поле
Собственные (нормальные) формы вибрации круговой мембраны барабана
Генерация изображения дифракционной диаграммы для однородной
непрерывной спирали

12

 Список листингов

8.5.
8.6.
8.7.
8.8.
8.9.
8.10.
8.11.
8.12.
8.13.
8.14.
8.15.
8.16.
8.17.
8.18.
8.19.
8.20.
8.21.
8.22.
8.23.
9.1.
9.2.
10.1.
10.2.

Гамма-функция на действительной числовой оси
Сравнение форм контуров Лоренца, Гаусса и Фогта
Сферическая гармоническая функция, определенная при l = 3, m = 2
Вычисление массы и центра масс тетраэдра с учетом его трех различных
плотностей
Кинетика реакции первого порядка
Две взаимосвязанные реакции первого порядка
Решение, описывающее действие генератора гармонических колебаний
Вычисление движения сферы, опускающейся под воздействием силы тяжести и выталкивающей силы Стокса
Решение системы химических реакций Робертсона
Вычисление и построение графика траектории сферического снаряда с
учетом сопротивления воздуха
Сравнение типов одномерной интерполяции при использовании метода
scipy.interpolation.interp1d
Двумерная интерполяция с использованием метода scipy.interpolation.interp2d
Интерполяция с переходом к уплотненной равномерной двумерной сетке с использованием объекта scipy.interpolate.RectBivariateSpline
Интерполяция по неструктурированному массиву двумерных точек с использованием scipy.interpolate.griddata
Минимизация лобового сопротивления для корпуса дирижабля
Нелинейная подгонка методом наименьших квадратов к эллипсу
Подгонка методом наименьших квадратов с весовыми коэффициентами
и без них с использованием метода curve_fit
Решение уравнения Эйлера–Лотки
Создание изображения фрактала Ньютона
Считывание текстовой таблицы с данными о витаминах
Высота полета снаряда как функция от времени
Сравнение различных размеров шагов h для численного решения дифференциального уравнения y' = −αy явным одношаговым методом Эйлера
Сравнение стабильности алгоритма при вычислении интеграла I(n) =
1

∫0 xnexdx

10.3. Функция, вычисляющая объем тетраэдра
10.4. Программа имитации бросков двух игральных кубиков, содержащая магические числа
10.5. Код имитации бросков двух игральных кубиков, улучшенный посредством использования именованных констант
10.6. Функция для преобразования различных единиц измерения температуры
10.7. Модульные тесты для функции преобразования температуры
A.1. Структурная формула алкана с прямой (неразветвленной) цепью
A.2. Подгонка методом наименьших квадратов к функции x = x0 + v0t + 1/2gt2
A.3. Вычисление вероятности нахождения q и более опечаток на заданной
странице книги
A.4. Сравнение поведения в численном представлении функций f(x) = (1 − cos2x)/x2
и g(x) = sin2x/x2 при значениях, близких к x = 0

Глава

1
Введение

1.1 Об этой книге
Эта книга предназначена для того, чтобы помочь ученым и инженерам освоить версию 3 языка программирования Python и связанных с ней библиотек:
NumPy, SciPy, Matplotlib и pandas. Для чтения книги не требуется предварительный опыт программирования и научные знания в какой-либо конкретной области. Но знакомство с некоторыми математическими дисциплинами,
такими как тригонометрия, комплексные числа и основы математического
анализа, будет полезным при выполнении примеров и упражнений.
Python – мощный язык программирования со множеством расширенных функциональных возможностей и дополнительных пакетов поддержки. Основной синтаксис языка прост и понятен для изучения, но в полном
объеме изучить его невозможно в книге такого размера. Таким образом,
наша цель – сбалансированное, но достаточно подробное введение в самые
главные функциональные возможности языка и самых важных библиотек.
В текст включено множество примеров, связанных с научными исследованиями, в конце каждого раздела приведен список вопросов (коротких задач,
предназначенных для проверки полученных знаний) и упражнений (более
сложных задач, для решения которых обычно требуется написать небольшую компьютерную программу). Хотя нет необходимости выполнять абсолютно все упражнения, для читателей будет полезно попытаться выполнить
хотя бы некоторые из них. Раздел, пример или упражнение, содержащие
более сложный материал, который можно пропустить при первом чтении,
обозначены символом ◊.
В главе 2 подробно рассматривается основной синтаксис, структуры данных
и средства управления потоком выполнения в программе на языке Python. Глава 3 представляет собой небольшое отступление с описанием использования
библиотеки pyplot для создания графических вариантов представления данных: это полезно для визуализации вывода программ в последующих главах.
В главе 4 содержится более продвинутое описание ядра языка Python и краткое
введение в объектно-ориентированное программирование. Далее следует еще
одна короткая глава 5, представляющая широко известные виртуальные блокнотные среды IPython и Jupyter, далее – главы о научном программировании
с использованием библиотек NumPy, SciPy, Matplotlib и pandas. В заключитель-

14



Введение

ной главе рассматриваются более общие темы научного программирования,
включая арифметику с плавающей точкой, обеспечение стабильности алгоритмов и стиль программирования.
Читатели, уже знакомые с языком программирования Python, могут бегло
просмотреть главы 2 и 4.
Примеры кода и решения упражнений можно загрузить с веб-сайта книги
https://scipython.com/. Следует отметить, что хотя комментарии полностью
включены в эти загружаемые программы, они не столь подробны в печатной
версии данной книги: вместо этого исходный код описывается в тексте с по­
мощью пронумерованных ссылок (например, ). Читатели, которые предпочитают вводить исходный код этих программ вручную, могут пожелать добавить собственные описательные комментарии в код.

1.2 Немного о Python
Python – это мощный язык программирования общего назначения, который разработал Гвидо ван Россум (Guido van Rossum) в 1989 году1. Python
классифицируется как язык программирования высокого уровня, в котором автоматически обрабатывается большинство фундаментальных операций (таких как управление памятью), выполняемых на уровне процессора
(«машинный код»). Python считается языком более высокого уровня, чем,
например, C, из-за его выразительного синтаксиса (который во многих случаях близок к естественному языку) и богатого разнообразия встроенных
структур данных, таких как списки, кортежи, множества и словари. Например, рассмотрим следующую программу на Python, которая выводит список
имен в отдельных строках.
Листинг 1.1. Вывод списка имен с использованием программы, написанной на Python
# eg1-names.py: вывод трех имен в консоли.
names = ['Isaac Newton', 'Marie Curie', 'Albert Einstein']
for name in names:
print(name)

Вывод:
Isaac Newton
Marie Curie
Albert Einstein

А теперь сравните исходный код из листинга 1.1 с кодом программы на C,
которая делает то же самое.

1

До настоящего момента Гвидо – «benevolent dictator for life (BDFL)» – «великодушный
диктатор, управляющий жизнью» языка Python.

1.2 Немного о Python  15
Листинг 1.2. Вывод списка имен с использованием программы, написанной на языке C
/* eg1-names.c: вывод списка имен в консоли. */
#include
#include
const char *names[] = {"Isaac Newton", "Marie Curie", "Albert Einstein"};
int main(void)
{
int i;
for (i = 0; i < (sizeof(names) / sizeof(*names)); i++) {
printf("%s\n", names[i]);
}
return EXIT_SUCCESS;
}

Даже если вы незнакомы с языком C, из листинга 1.2 можно понять, что написание кода на C даже для такой простой задачи связано с большими трудозатратами и издержками: две инструкции include для использования библиотек,
не загружаемых по умолчанию, явные объявления переменных для хранения
списка (в C это массив (array)) имен names, для счетчика i, а также явный проход по индексам этого массива в цикле for. Требуется даже вручную добавлять
символы концов строк (\n – символ перехода на новую строку). Затем этот исходный код должен быть скомпилирован, т. е. преобразован в машинный код,
который понимает процессор, прежде чем можно будет его выполнить. Более
того, здесь огромное количество возможностей совершить ошибки (программные «баги» (bugs)): попытка вывода имени, хранящегося по адресу name[10],
вероятнее всего, приведет к выводу так называемого «мусора»: компилятор
языка C не остановит вас при попытке доступа к несуществующему имени.
Та же самая программа, написанная в три строки на языке Python, проста
и выразительна: не нужно явно объявлять, что names – список строк, для цикла
не требуется счетчик, подобный i, и не приходится включать отдельные биб­
лиотеки (инструкцией import в Python). Для запуска Python-программы нужно просто ввести команду python eg1-names.py, которая автоматически вызовет
интерпретатор Python для компиляции и выполнения полученного байт-кода
(bytecode) (это особый тип промежуточного представления программы между
исходным кодом и конечным машинным кодом, который Python перенаправляет в процессор).
Синтаксис Python призван гарантировать, что «существует один – и преимущественно единственный – очевидный способ сделать это». Это отличает
Python от некоторых других широко известных языков высокого уровня, таких
как Ruby и Perl, в которых используется противоположный подход, выражающийся в кратком принципе «существует более одного способа сделать это».
Например, существует (как минимум) четыре очевидных способа вывода того
же списка имен на языке Perl2.
2

Уточняю: очевидных для программистов на языке Perl.

16

 Введение

Листинг 1.3. Различные способы вывода списка имен с использованием программы, написанной на Perl
@names = ("Isaac Newton", "Marie Curie", "Albert Einstein");
# Метод 1
print "$_\n" for @names;
# Метод 2
print join "\n", @names;
print "\n";
# Метод 3
print map { "$_\n" } @names;
# Метод 4
$" = "\n";
print "@names\n";

(Также обратите внимание на знаменитый лаконичный, но иногда непонятный синтаксис языка Perl.)

1.2.1 Преимущества и недостатки языка Python
Ниже перечислены некоторые из основных преимуществ языка программирования Python, а также причины, по котором вы, возможно, захотите его использовать:
 ясный и простой синтаксис позволяет быстро писать программы на Python и в общем сводит к минимуму возможности совершения скрытых
ошибок. При правильном подходе результатом является высококачест­
венное программное обеспечение, которое легко сопровождать и расширять;
 сама рабочая программная среда Python и связанные с ней библиотеки
бесплатны, а кроме того, представляют собой программное обеспечение
с открытым исходным кодом, в отличие от коммерческих предложений,
таких как Mathematica и MATLAB;
 поддержка многих платформ: Python доступен для каждой общедоступной компьютерной системы, в том числе Windows, Unix, Linux и macOS.
Несмотря на то что существуют расширения, зависящие от конкретной
платформы, всегда есть возможность написания кода, который будет работать на любой платформе без каких-либо изменений;
 для Python существует большая библиотека модулей и пакетов, которая
расширяет его функциональность. Многие из этих модулей и пакетов
доступны как часть «стандартной библиотеки» (Standard Library), предоставляемой вместе с интерпретатором языка Python. Другие, в том числе
библиотеки NumPy, SciPy, Matplotlib и pandas, используемые в научных
вычислениях, можно абсолютно бесплатно загрузить и установить;
 язык Python относительно прост для изучения. Синтаксис и ключевые
слова для основных операций применяются постоянно и согласованно
в большинстве случаев более продвинутого использования языка. Сообщения об ошибках в основном представляют собой разумные пред-

1.2 Немного о Python  17
положения о том, что пошло не так, в отличие от обобщенных «крахов»,
характерных для компилируемых языков высокого уровня, подобных C;
 Python – гибкий язык: его часто описывают как язык «многих парадигм»,
в котором имеются наилучшие функциональные возможности для процедурного, объектно-ориентированного и функционального программирования. Он требует совсем небольшой подготовительной работы,
обязательной в других языках, когда задачу можно решить лишь с применением одного из этих подходов.
Так в чем же дело? А в том, что у Python имеются и некоторые недостатки,
из-за которых он не подходит для абсолютно любого приложения:
 скорость выполнения программы на Python не так высока, как программ
на других, полностью компилируемых языках, таких как C и Fortran. Для
крупномасштабной вычислительной работы библиотеки NumPy и SciPy
до некоторой степени способны облегчить ситуацию, используя «скрытый внутри» скомпилированный код C, но за счет некоторого снижения
гибкости. Однако для множества приложений различия в скорости не так
значимы, и снижение скорости выполнения почти полностью компенсируется гораздо более высокой скоростью разработки. Таким образом,
процесс написания и отладки программы на Python занимает намного
меньше времени, чем аналогичный процесс разработки на C, C++ или Java;
 трудно скрыть или замаскировать исходный код программы на Python,
чтобы защитить ее от копирования и/или изменения. Но это не имеет
особого значения, так как не существует успешных коммерческих программ на Python;
 на протяжении всей истории существования Python самыми частыми
претензиями становились жалобы на излишне быстрое его развитие,
приводящее к проблемам несовместимости между версиями. В действительности существуют два самых важных различия между версиями Python 2 и Python 3 (описанные в следующем разделе и в приложении Б),
но претензии и жалобы основаны на том факте, что в группе версий Python 2 происходили основные усовершенствования и дополнения языка, из-за которых код, написанный в более поздней версии (например,
в версии 2.7), не мог работать в более ранней версии (например, в версии 2.6), хотя код, написанный для более ранней версии Python, всегда
будет работать в более поздней версии (в обеих ветвях – 2 и 3). Если вы
используете самую последнюю версию Python (см. раздел 1.3), то, вероятнее всего, не столкнетесь с этой проблемой, но некоторые дистрибутивы операционных систем, в комплект которых входит Python, достаточно консервативны и устанавливают по умолчанию более старую версию.

1.2.2 Python 2 или Python 3
1 января 2020 года Python 2 завершил свой «жизненный путь»: он больше
не будет обновляться и официально поддерживаться, поскольку более новая
версия Python 3 уже активно развивается и сопровождается. Хотя различия
между этими двумя версиями выглядят минимальными, код, написанный на
Python 3, не будет работать в среде Python 2, и наоборот: Python 3 не обеспе-

18

 Введение

чивает обратную совместимость со своим предшественником. В этой книге
изуча­ется Python 3.
Поскольку Python 3 появился в 2009 году, количество пользователей и поддержка библиотек для этой версии выросли до такого уровня, что новые пользователи не должны обнаружить особых преимуществ в изучении версии Python 2, за исключением необходимости поддержки старого кода.
Существует несколько обоснований внесения основных различий между
версиями (с приведением в негодность пользовательского исходного кода
не так-то легко примириться): Python 3 исправляет некоторые весьма неприятные особенности и нестыковки в языке, а также обеспечивает поддержку
Юникода (Unicode) для всех строк (это полностью устраняет путаницу, возникавшую при обработке юникодных и неюникодных строк в Python 2). Unicode –
это международный стандарт для представления текста в большинстве систем
обработки и записи текстовых данных в мире.
Предвижу, что у большинства читателей этой книги не возникнет никаких
проблем в процессе преобразования исходного кода между двумя версиями
Python, если это будет необходимо. Список основных различий и дополнительную информацию см. в приложении Б.

1.3 Установка Python
Официальный сайт Python www.python.org содержит подробные и простые инструкции по загрузке (скачиванию) Python. Но существует несколько полноценных дистрибутивных комплектов, содержащих библиотеки NumPy, SciPy
и Matplotlib (так называемый SciPy Stack). Эти дистрибутивы помогут избежать
необходимости скачивания и установки библиотек по отдельности:
 пакет Anaconda доступен бесплатно (в том числе и для коммерческого
использования) на сайте www.anaconda.com/distribution. Устанавливаются версии Python 2 и Python 3, но версию по умолчанию можно выбрать либо перед скачиванием, как указано на этой веб-странице, либо
после скачивания с использованием команды conda;
 аналогичный дистрибутив Enthought Deployment Manager (EDM) сущест­
вует в бесплатной версии и с различными компонентами для платных
версий, включая техническую поддержку и программное обеспечение
для разработки. Этот дистрибутив можно скачать здесь: https://assets.
enthought.com/downloads/.
В большинстве случаев один из этих дистрибутивов – это все, что вам нужно.
Ниже приведены некоторые замечания по отдельным платформам.
Исходный код (и бинарные файлы для некоторых платформ) для пакетов
NumPy, SciPy, Matplotlib и IPython по отдельности доступен на следующих
сайтах:
 NumPy: https://github.com/numpy/numpy;
 SciPy: https://github.com/scipy/scipy;
 Matplotlib: https://matplotlib.org/users/installing.html;
 IPython: https://github.com/ipython/ipython;
 Jupyter Notebook и JupyterLab: https://jupyter.org/.

1.4 Командная строка  19

Windows
Для пользователей Windows существует пара дополнительных возможностей установки полного стека SciPy: Python(x,y) (https://python-xy.github.io)
и WinPython (https://winpython.github.io/). Оба дистрибутива бесплатные.

macOS
Операционная система macOS (бывшая Mac OS X), основанная на Unix, включает в комплект Python, но обычно более старой версии Python 2. Вы не должны
удалять или обновлять этот вариант установки (он необходим операционной
системе), но можно выполнить приведенные выше инструкции по установке
Python 3 и стека SciPy. В macOS нет встроенного менеджера пакетов (приложения для установки и сопровождения программного обеспечения), но существуют два широко известных независимых менеджера пакетов: Homebrew (https://
brew.sh/) и MacPorts (www.macports.org), которые поддерживают Python 3 и соответствующие пакеты, если вы предпочитаете данный вариант.

Linux
Почти все дистрибутивы Linux обычно содержат версию Python 2, а не Python 3,
поэтому, возможно, потребуется установка новой версии по приведенным
выше ссылкам: дистрибутивы Anaconda и Enthought предлагают специальные
версии Linux. В большинстве дистрибутивов Linux имеется собственный менеджер программных пакетов (например, apt в Debian и rpm в RedHat). Менеджер пакетов можно использовать для установки Python 3 и всех необходимых
библиотек, хотя при поиске репозиториев пакетов может потребоваться некоторое исследование ресурсов интернета. Будьте внимательны: не заменяйте
и не обновляйте системный вариант установки, так как от него могут зависеть
другие приложения.

1.4 Командная строка
Большинство примеров исходного кода в этой книге написаны как независимые
программы, которые можно запускать из командной строки (command line) (или
из интегрированной среды разработки (integrated development environment –
IDE), если вы используете одну из таких сред: см. раздел 10.3.2). Для доступа
к интерфейсу командной строки (также известного как консоль или терминал)
на различных платформах выполните инструкции, приведенные ниже:
 Windows 7 и более ранние версии: Пуск > Все программы > Командная
строка. Другой вариант: в окне ввода Пуск > Выполнить ввести команду cmd;
 Windows 8: Preview (нижний левый угол экрана) > Windows System: All
apps. Другой вариант: ввод команды cmd в окне поиска, спускающегося
из верхнего правого угла экрана;
 Windows 10: из меню Пуск (значок Windows в нижнем левом углу экрана) > Служебные Windows > Командная строка. Другой вариант: ввод
команды cmd в окне поиска, вызываемого значком лупы в нижнем левом
углу экрана рядом со значком Windows;

20

 Введение

 Mac OS X и macOS: Finder > Applications > Utilities > Terminal;
 Linux: если вы не используете графический пользовательский интерфейс, то вы уже в командной строке. При использовании графического интерфейса найдите приложение Terminal (в разных дистрибутивах
по-разному, но обычно терминал находится в разделе System Utilities или
в разделе System Tools).
Команды, вводимые в командной строке, интерпретируются приложением,
которое называется командной оболочкой (shell), позволяющей пользователю
перемещаться по файловой системе и запускать разнообразные приложения.
Например, команда
python myprog.py

сообщает командной оболочке о необходимости вызова интерпретатора языка
Python с передачей в него файла myprog.py как скрипта для выполнения. Затем
результат работы этой программы возвращается в командную оболочку и выводится в консоли (в терминале).

Глава

2
Ядро языка Python I

2.1 Командная оболочка Python
В этой главе рассматриваются основы синтаксиса, структуры и типы данных
языка программирования Python. В нескольких первых разделах предполагается написание не более чем нескольких строк кода Python, которые могут быть
выполнены с использованием командной оболочки (shell) языка Python. Это
интерактивная рабочая среда: пользователь вводит инструкции на языке Python, которые выполняются немедленно сразу после нажатия клавиши Enter.
Действия, необходимые для получения доступа к встроенной командной
оболочке языка Python, отличаются в различных операционных системах. Для
запуска командной оболочки из командной строки сначала необходимо открыть окно терминала (консоли), воспользовавшись инструкциями из раздела 1.4, затем ввести команду python.
Для выхода из командной оболочки Python выполните команду exit().
После запуска командной оболочки Python вы увидите приветственное сообщение (которое может меняться в зависимости от используемой операционной системы и версии Python). В моей системе это сообщение выглядит так:
Python 3.7.5 (default , Oct 25 2019, 10:52:18)
[Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda , Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

Три правые угловые скобки (>>>) – это приглашение, или промпт (prompt),
после этих символов приглашения вы будете вводить команды языка Python.
В данной книге используется версия Python 3, поэтому вы должны проверить
номер версии в первой строке – Python 3.X.Y: здесь не особенно важны значения X и Y, представляющие минорный номер версии.
Многие дистрибутивные пакеты Python содержат немного более продвинутую командную оболочку, которая называется IDLE, с дополнительными
функциями завершения по нажатии клавиши Tab и подсветкой синтаксиса
(syntax highlighting) (ключевые слова выделяются различными цветами, когда
вы вводите их). Но мы не будем рассматривать это приложение, а предпочтем
ему более новую и более усовершенствованную рабочую программную среду
IPython, обсуждаемую в главе 5.

22



Ядро языка Python I

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

2.2 Числа, переменные, операции сравнения
и логические операции

2.2.1 Типы числовых значений
Одним из самых простых объектов языка Python являются числовые значения,
для которых определены три типа: целые числа (тип: int), числа с плавающей
точкой (тип: float) и комплексные числа (тип: complex).

Целые числа
Целые числа (integers) – это математические целые числа, такие как 1, 8, −72
и 3 847 298 893 721 407. В версии Python 3 нет ограничений по их величине (кроме ограничения по доступности оперативной памяти компьютера). Арифметика целых чисел является точной.
Для удобства разрешается разделять группы цифр (разрядов) целого числа
символом подчеркивания «_». Например, запись 299_792_458 интерпретируется как целое число 299 792 458.

Числа с плавающей точкой
Числа с плавающей точкой (floating-point numbers) являются представлением действительных чисел, таких как 1.2, −0.36 и 1 67263 × 10−7. Вообще говоря,
не существует точных значений действительных чисел в представлении Python, но эти числа хранятся в бинарном (двоичном) виде с определенной точностью (в большинстве систем точность составляет 15–16 разрядов)3, как описано в разделе 10.1. Например, дробь 4/3 хранится как двоичное равнозначное
представление 1.33333333333333325931846502…, которое приблизительно (но
не точно) равно бесконечно повторяющемуся десятичному представлению
дроби 4/3 = 1.3333…. Более того, даже числа, для которых существует точное
десятичное представление, могут не иметь точного бинарного представления: например, дробь 1/10 представлена бинарным числом, равным значению 0.10000000000000000555111512…. Из-за такой ограниченной точности
арифметика чисел с плавающей точкой не является абсолютно точной, но при
аккуратном отношении она «достаточно точна» для большинства научных
приложений.
Любое отдельное число, содержащее символ точки (.), рассматривается в Python как представление числа с плавающей точкой. Также поддерживается научный формат записи чисел с использованием буквы e или E для отделения
3

Это соответствует реализации по международному стандарту представлениячисел
с плавающей точкой двойной точности IEEE-754.

2.2 Числа, переменные, операции сравнения и логические операции

 23

значимой части числа (мантиссы) от показателя степени: например, 1.67263e-7
представляет число 1 67263 × 10−7.
Как и для целых чисел, группы разрядов можно разделять символами подчеркивания. Например, 1.602_176_634e-34.

Комплексные числа
Комплексные числа, такие как 4+3j, состоят из действительной и мнимой частей (в Python мнимая часть обозначается буквой j), каждая из которых сама
по себе является числом с плавающей точкой (даже если записана без символа точки). Таким образом, арифметика комплексных чисел не является точной, но обеспечивает такую же конечную ограниченную точность, что и числа
с плавающей точкой (float).
Комплексное число может быть определено «сложением» действительного
числа с мнимым (обозначенным суффиксом j): 2.3 + 1.2j – или посредством
отдельной передачи действительной и мнимой частей при вызове complex, например complex(2.3, 1.2).
Пример П2.1. Ввод числа после приглашения командной оболочки Python просто
возвращает это же число
>>> 5
5
>>> 5.
5.0
>>> 0.10
0.1
>>> 0.0001
0.0001
>>> 0.0000999
9.99e-05




Следует отметить, что интерпретатор Python выводит числа стандартным
способом. Например:
❶ Внутреннее представление числа 0.1, описанное выше, округляется
до 0.1, т. е. до самого короткого числового значения в этом представлении.
❷ Числа, меньшие по величине, чем 0.0001, выводятся в научном формате.
Число любого типа может быть создано из числа другого типа с помощью
соответствующего конструктора (constructior):
>>> float(5)
5.0
>>> int(5.2)
5
>>> int(5.9)
5
>>> complex(3.)
(3+0j)
>>> complex(0., 3.)
3j





24



Ядро языка Python I

❶ Обратите внимание: положительное число с плавающей точкой округля-

ется с недостатком (в меньшую сторону) во время преобразования его
в целое число. Более общее правило: метод int округляет в сторону нуля:
int(-1.4) дает результат -1.
❷ Создание объекта типа complex из объекта типа float генерирует комп­
лексное число с мнимой частью, равной нулю.
❸ Для генерации абсолютно мнимого числа необходимо явно передать два
числа в метод complex, при этом первая, действительная часть должна
быть равна нулю.

2.2.2 И
 спользование командной оболочки Python
в качестве калькулятора
Простые арифметические действия
С описанными в предыдущем разделе тремя основными типами чисел можно
использовать командную оболочку Python как простой калькулятор, применяя
операторы, описанные в табл. 2.1. Это бинарные операторы, поскольку они работают с двумя числами (операндами) для создания третьего числа (например,
при вычислении 2**3 получается результат 8).
Таблица 2.1. Основные арифметические операторы языка Python
+

*
/
//
%
**

Сложение
Вычитание
Умножение
Деление с плавающей точкой
Целочисленное деление
Деление по модулю (взятие остатка)
Возведение в степень

В версии Python 3 существует два типа операции деления: деление чисел
с плавающей точкой (/) всегда возвращает как результат число с плавающей
точкой (или комплексное число), даже если применяется к целым числам. Целочисленное деление (//) всегда округляет результат с недостатком (в меньшую
сторону), т. е. к ближайшему меньшему целому числу (floor division). Типом
результата является int, только если оба операнда имеют тип int, иначе возвращается значение типа float. Ниже приведены примеры, помогающие понять эти правила.
Обычное («истинное») деление с плавающей точкой с использованием оператора (/):
>>> 2.7 / 2
1.35
>>> 9 / 2
4.5
>>> 8 / 4
2.0

2.2 Числа, переменные, операции сравнения и логические операции

 25

Последняя операция деления возвращает значения типа float даже притом,
что оба операнда имеют тип int.
Целочисленное деление с использованием оператора (//):
>>> 8 // 4
2
>>> 9 // 2
4
>>> 2.7 // 2
1.0

Обратите внимание: оператор // может применять целочисленную арифметику (с округлением в меньшую сторону) к числам с плавающей точкой.
Оператор деления по модулю (%) возвращает остаток от целочисленного
деления:
>>> 9 % 2
1
>>> 4.5 % 3
1.5

И в этом случае возвращается значение типа int, только если оба операнда
имеют тип int.

Приоритет операторов
Арифметические операции могут быть объединены в последовательность, из-за
чего естественным образом возникает вопрос приоритета выполнения операций:
например, при вычислении выражения 2 + 4 * 3 должно получиться 14 (в результате 2 + 12) или 18 (в результате 6 * 3)? Из табл. 2.2 следует, что результат должен
быть равен 14, так как операция умножения имеет более высокий приоритет, чем
операция сложения, поэтому должна выполняться первой. Эти правила приоритета изменяются при использовании круглых скобок, например (2 + 4) * 3 = 18.
Таблица 2.2 Приоритеты арифметических операторов языка Python
**
*, /, //, %
+, −

Самый высокий приоритет
Самый низкий приоритет

Операторы с одинаковым приоритетом выполняются слева направо, за исключением операции возведения в степень (**), которая выполняется справа
налево (т. е. «сверху вниз» при записи в обычной математической форме показателей степени над строкой). Например:
>>> 6 / 2 / 4
0.75
>>> 6 / (2 / 4)
12.0
>>> 2**2**3
256
>>> (2**2)**3
64

# равнозначно 3 / 4
# равнозначно 6 / 0.5
# равнозначно 2**(2**3) == 2**8
# равнозначно 4**3

26



Ядро языка Python I

В показанных здесь примерах символ «решетка» (#) обозначает начало комментария, который игнорируется интерпретатором. Комментарии иногда будут использоваться для более подробного объяснения конкретной инструкции,
но при вводе кода для выполнения комментарии вводить не обязательно.

Методы и атрибуты числовых значений
Числовые значения в языке Python являются объектами (objects) (в действительности в Python все является объектами) и имеют определенные атрибуты (attributes), доступные при использовании формата «точка» (dot notation):
. (такое использование точки не имеет ничего общего с десятичной точкой в числах с плавающей точкой). Некоторые атрибуты являются
простыми значениями, например объекты комплексных чисел имеют атрибуты real и imag, соответствующие действительной и мнимой частям (с плавающей точкой) комплексного числа:
>>> (4 + 5j).real
4.0
>>> (4 + 5j).imag
5.0

Другими атрибутами являются методы (methods): вызываемые функции,
которые определенным образом работают со своим объектом4. Например, для
комплексных чисел существует метод conjugate, который возвращает сопряженное комплексное число:
>>> (4 + 5j).conjugate()
(4-5j)

Здесь пара пустых круглых скобок обозначает свойство вызываемости метода, т. е. выполнения вычисления сопряженного комплексного числа для
числа 4 + 5j. Если скобки не указаны, например (4 + 5j).conjugate, то мы получаем ссылку на сам метод (без его вызова) – ведь этот метод тоже является
объектом.
В действительности целые числа и числа с плавающей точкой не обладают
слишком многими атрибутами, которые имеет смысл использовать показанным выше способом, но если вам интересно узнать, сколько битов занимает целое число в оперативной памяти, то можно воспользоваться методом
bit_length. Например:
>>> (3847298893721407). bit_length()
52

Следует отметить, что Python выделяет столько памяти, сколько необходимо для точного представления целого числа.

4

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

2.2 Числа, переменные, операции сравнения и логические операции

 27

Математические функции
Две математические функции из множества, предоставляемые «по умолчанию» как встроенные (built-in) функции, – abs и round.
Функция abs возвращает абсолютное значение числа:
>>> abs(-5.2)
5.2
>>> abs(-2)
2
>>> abs(3 + 4j)
5.0

Это пример полиморфизма (polymorphism): одна и та же функция abs выполняет различные операции с различными объектами. Если в функцию передано действительное число x, то возвращается |x|, неотрицательная величина этого числа без учета знака. Если передается комплексное число z = x + iy,
то возвращается его модуль z = ( x 2 + y 2 ).
Функция round (с одним аргументом) округляет число с плавающей точкой
до ближайшего целого числа, используя для этого метод округления банкира
(Banker’s rounding)5:
>>> round(-9.62)
-10
>>> round(7.5)
8
>>> round(4.5)
4

Можно также задать количество точных разрядов после десятичной точки
как второй аргумент, передаваемый в функцию round():
>>> round(3.141592653589793 , 3)
3.142
>>> round(96485.33289, -2)
96500.0

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

В модуль math включены операции для работы с числами с плавающей точкой и целыми числами (для функций, работающих с комплексными числами,
существует другой модуль cmath). Функции вызываются с передачей одного
5

При округлении по методу банкира числа «с 0.5» всегда округляются до ближайшего
четного целого числа.

28



Ядро языка Python I

числа (иногда нескольких чисел) внутри круглых скобок (числа принимаются
как аргументы вызываемой функции). Например:
>>> import math
>>> math.exp(-1.5)
0.22313016014842982
>>> math.cos(0)
1.0
>>> math.sqrt(16)
4.0

Полный список математических функций, предоставляемых модулем math,
можно найти в онлайновой документации6. Некоторые наиболее часто используемые математические функции перечислены в табл. 2.3.
Таблица 2.3. Некоторые функции, предоставляемые модулем math. Предполагается, что аргументы, соответствующие значениям углов, задаются в радианах
math.sqrt(x)

√x

math.exp(x)

ex

math.log(x)

ln x

math.log(x, b)

logb x

math.log10(x)

log10 x

math.sin(x)

sin(x)

math.cos(x)

cos(x)

math.tan(x)

tan(x)

math.asin(x)

arcsin(x)

math.acos(x)

arccos(x)

math.atan(x)

arctan(x)

math.sinh(x)

sinh(x)

math.cosh(x)

cosh(x)

math.tanh(x)

tanh(x)

math.asinh(x)

arsinh(x)

math.acosh(x)

arcosh(x)

math.atanh(x)

artanh(x)

math.hypot(x, y)

Евклидово нормальное расстояние (гипотенуза) √(x2 + y2)

math.factorial(x)

Факториал x!

math.erf(x)

Функция ошибок по x

math.gamma(x)
math.radians(x)

Гамма-функция по x, Γ(x)

math.isclose(a, b)

Проверка равенства a и b в пределах некоторого допустимого отклонения

math.degrees(x)

Преобразование x из радианов в градусы
Преобразование x из градусов в радианы

Кроме того, модуль math предоставляет два весьма полезных нефункциональных атрибута: math.pi и math.e – это значения математических констант
π и e (основание натуральных логарифмов).
6

https://docs.python.org/3/library/math.html.

2.2 Числа, переменные, операции сравнения и логические операции

 29

Существует возможность импорта модуля math с помощью команды from
math import *, после чего становится доступным прямой доступ ко всем его
функциям:
>>> from math import *
>>> cos(pi)
-1.0

Это может оказаться удобным для работы в командной оболочке Python, но
не рекомендуется в программах на языке Python. Возникает опасность конф­
ликтов имен (особенно если таким способом импортируются функции из многих модулей), и становится трудно узнать, из какого модуля импортирована
та или иная функция. Импорт командой import math сохраняет связь функций
с пространством имен (namespace) соответствующего модуля, поэтому, даже
несмотря на то что ввод math.cos требует больше нажатий на клавиши, такой
способ делает исходный код более удобным для понимания и сопровождения.
Пример П2.2. Вполне естественно, что математические функции можно объединять
в одном выражении
>>> import math
>>> math.sin(math.pi/2)
1.0
>>> math.degrees(math.acos(math.sqrt(3)/2))
30.000000000000004

Обратите внимание на ограниченную (конечную) точность последнего выражения: точный результат arccos(√3/2) = 30°.
Тем фактом, что функция int округляет в меньшую сторону при преобразовании положительного числа с плавающей точкой в целое число, можно воспользоваться для определения количества разрядов положительного целого
числа:
>>> int(math.log10(9999)) + 1
4
>>> int(math.log10(10000)) + 1
5

2.2.3 Переменные
Что такое переменная
При создании объекта, например типа float, в программе на Python или при
использовании командной оболочки Python для этого объекта выделяется память: в компьютерной архитектуре место расположения данного фрагмента
памяти называется адресом памяти (address). Действительное значение адреса памяти объекта не очень полезно (и не очень удобно) в Python, но если вы
захотите, то можете узнать адрес, вызвав встроенный метод id:
>>> id(20.1)

30



Ядро языка Python I

4297273888

# пример

Это число является ссылкой на конкретное место в памяти, соответствующее фрагменту памяти, выделенному для хранения объекта типа float со значением 20.1.
Кроме самых простых вариантов использования, существует необходимость в хранении объектов, участвующих в вычислении или в выполнении
алгоритма, к которым можно было бы обращаться по некоторому более удобному и осмыс­ленному имени (а не по числовому адресу в памяти). Для этого
существуют переменные (variables)7. Имя переменной может быть присвоено
любому объекту («связано» с любым объектом) и использоваться для идентификации этого объекта в дальнейших вычислениях. Например:
>>> a = 3
>>> b = -0.5
>>> a * b
-1.5

В этом фрагменте кода создается объект типа int со значением 3, и ему присваивается имя a. Затем создается объект типа float со значением -0.5, и ему
присваивается имя b. Выполняется вычисление a * b: значения a и b умножаются друг на друга, и возвращается результат. Этот результат не присваивается
никакой переменной, сразу после вывода на экран он отбрасывается. Таким
образом, для хранения результата память не требуется, а для значения -1.5
типа float выделяется память только на то короткое время, которое необходимо для предъявления результата пользователю, затем память освобождается,
а значение теряется8. Если результат необходим для дальнейших вычислений,
то вы должны присвоить его другой переменной:
>>> c = a * b
>>> c
-1.5

Обратите внимание: не требуется предварительное объявление (declare) переменных до присваивания им значений (т. е. для сообщения Python о том, что имя
переменной a является ссылкой на целое число, имя переменной b – ссылкой на
число с плавающей точкой и т. д.), как это необходимо делать в некоторых языках программирования. Python – язык с динамической типизацией (dynamically
typed), поэтому необходимый тип объекта логически выводится из его определения: при отсутствии десятичной точки число 3 определяется как int. Число
-0.5 выглядит как число с плавающей точкой, и Python определяет b как float9.
7

8

9

В Python, вероятнее всего, более правильно говорить об идентификаторах объектов
(object identifiers) или об именах-идентификаторах (identifier names), нежели о переменных, но мы не будем вводить излишне строгую терминологию в этом случае.
В действительности в интерактивном сеансе работы с Python результат самого последнего вычисления сохраняется в специальной переменной с именем _ (символ
подчеркивания), поэтому он не отбрасывается до тех пор, пока не будет перезаписан
результатом следующего вычисления.
Иногда такой способ называют «утиной типизацией» (duck typing) в соответствии
с фразой, приписываемой Джеймсу Уиткомбу Райли (James Whitcomb Riley): «Когда

2.2 Числа, переменные, операции сравнения и логические операции

 31

Имена переменных
Ниже приводится несколько правил, определяющих формирование допустимых («правильных») имен переменных:
 в именах переменных учитывается регистр символов (букв): a и A – это
разные имена переменных;
 имена переменных могут содержать любую букву, символ подчеркивания (_) и любую цифру (0–9)…
 …но не должны начинаться с цифры;
 имя переменной не должно совпадать с одним из зарезервированных
ключевых слов, приведенных в табл. 2.4;
 встроенные имена констант True, False и None запрещено использовать
как имена переменных.
Таблица 2.4. Зарезервированные ключевые слова языка Python 3
and

as

assert

async

await

break

class

continue

def

del

elif

else

except

finally

for

from

global

if

import

in

is

lambda

nonlocal

not
while

or

pass

raise

return

try

with

yield

False

True

None

Большинство зарезервированных ключевых слов вряд ли подходят для
имен переменных, за исключением разве что слова lambda. Программисты
на Python часто используют имя lam при необходимости. Текстовый редактор с расширенными функциями подсвечивает ключевые слова при вводе
исходного кода программы, поэтому путаница с именами возникает редко.
Существует возможность присваивания переменной имени, совпадающего
с именем встроенной функции (например, abs или round), но после такого присваивания встроенная функция становится недоступной, поэтому присваивания
подобных имен лучше избегать – к счастью, большинство встроенных функций
имеют имена, которые вряд ли будут выбираться в реальной практике10.
В дополнение к перечисленным выше правилам ниже приводятся некоторые соглашения по стилю, определяющие общепринятые практические принципы именования переменных:
 имена переменных должны быть осмысленными (area лучше, чем a)…
 …но не слишком длинными (the_area_of_the_triangle – это слишком
громоздкое имя);
 в общем случае лучше не использовать I (буква i в верхнем регистре),
l (буква L в нижнем регистре) и букву O в верхнем регистре, так как они
очень похожи на цифры 1 и 0;

10

я вижу птицу, которая ходит как утка, плавает как утка и крякает как утка, я называю
эту птицу уткой».
Полный список имен встроенных функций см. в документации: https://docs.python.
org/3/library/functions.html.

32



Ядро языка Python I

 имена переменных i, j и k, как правило, используются для целочисленных счетчиков;
 рекомендуется использовать имена с буквами нижнего регистра с разделением слов символами подчеркивания вместо стиля именования «CamelCase»: например, mean_height, а не MeanHeight11.
Эти и многие другие правила и соглашения определены в руководстве по
стилю под названием PEP8, которое представляет собой одну из частей документации Python12 (также см. раздел 10.3.1).
Нарушение этих правил хорошего стиля не приведет к нарушению правильной работы программы, но такую программу, возможно, труднее будет
сопровождать и отлаживать – может быть, вы помогаете самому себе, соблюдая правила стиля.
Пример П2.3. Формула Герона для вычисления площади A треугольника со сторонами a, b, c:
1
A = s( s − a )( s − b)( s − c ), где s = (a + b + c ).
2
Например:
>>> a = 4.503
>>> b = 2.377
>>> c = 3.902
>>> s = (a + b + c) / 2
>>> area = math.sqrt(s * (s - a) * (s - b) * (s - c))
>>> area
4.63511081571606



❶ Не забудьте выполнить команду импорта import math, если до этого вы

пока еще не работали в сеансе Python.

Пример П2.4. Тип данных и адрес памяти объекта по имени соответствующей переменной можно получить с помощью встроенных функций type и id:
>>> type(a)

>>> id(area)
4298539728

# пример

2.2.4 Операции сравнения и логические операции
Операторы
Основные операторы, используемые в Python для сравнения объектов (например, чисел), перечислены в табл. 2.5.
11

12

Стиль имен CamelCase в Python обычно применяется для имен классов: см. раздел 4.6.2.
https://legacy.python.org/dev/peps/pep-0008/.

2.2 Числа, переменные, операции сравнения и логические операции

 33

Таблица 2.5. Операторы сравнения Python
==

Равно

!=

Не равно

>

Больше

<

Меньше

>=

Больше или равно

>> 7 == 8
False
>>> 4 >= 3.14
True

Python способен, если это возможно без двусмысленности, сравнивать объекты различных типов: целое число 4 приводится к типу float для сравнения
с числом 3.14.
Обратите особое внимание на различие между оператором == и оператором =. Один знак равенства – это присваивание (assignment), операция, которая не возвращает значение: инструкция a = 7 присваивает имя переменной a
целочисленному объекту 7, и это все. Выражение a == 7 – это проверка условия:
возвращается значение True или False, в зависимости от значения переменной a13.
Особое внимание требуется при сравнении чисел с плавающей точкой на
равенство. Поскольку числа с плавающей точкой хранятся с некоторой неточностью, вычисления с ними часто приводят к потере точности, и можно получить неожиданные результаты по неосторожности. Например:
>>> a = 0.01
>>> b = 0.1**2
>>> a == b
False

В этом примере 0.01 не может быть представлено точно как число с плавающей точкой, а хранится (в моей системе) как бинарное число, равное значению 0.010000000000000000208, а с другой стороны, результат возведения
в квадрат представления значения 0.1 в виде числа с плавающей точкой равен 0.01000000000000000194, и эти два числа не равны. Более подробно об этом
см. раздел 10.1.
13

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

34



Ядро языка Python I

В версии Python 3.5 в библиотеке math появилась функция isclose, которая
проверяет равенство двух чисел с плавающей точкой с учетом некоторого абсолютного или относительного интервала допустимого отклонения:
>>> math.isclose(0.1**2, 0.01)
True

Величина относительного допустимого отклонения может быть установлена
с помощью аргумента rel_tol, по умолчанию равного 1.e-9: это максимально
допустимая разность между двумя числами относительно абсолютного значения большего из этих чисел. Например, для проверки равенства a и b в пределах 5 % от большего из них:
>>> a = 9.5
>>> b = 10
>>> math.isclose(a, b, rel_tol=0.05)
True

При этом типе относительного сравнения могут возникать проблемы, если
одно из чисел равно нулю14. В этом случае может оказаться полезной проверка
абсолютного допустимого отклонения, значение которого передается в аргументе abs_tol (по умолчанию 0):
>>> math.isclose(0, 1.e-12) # сравнение по относительному допустимому отклонению дает
# неверный результат, если rel_tol < 1
False
>>> math.isclose(0, 1.e-12, abs_tol=1.e-10)
True

Логические операторы
Операции сравнения можно изменять и объединять с помощью ключевых
слов, обозначающих логические операторы: and, not и or. См. табл. 2.6, 2.7 и 2.8.
Таблица 2.6. Таблица истинности для оператора not
P

not P

True
False

False
True

Таблица 2.7. Таблица истинности для оператора and
P

Q

P and Q

True
False
True
False

True
True
False
False

True
False
False
False

14

Относительная разность между любым числом a и 0 равна (|a| − 0) / |a|, и это значение определенно больше значения rel_tol, если для rel_tol установлено значение
меньше 1.

2.2 Числа, переменные, операции сравнения и логические операции

 35

Таблица 2.8. Таблица истинности для оператора or
P

Q

P or Q

True

True

True

False

True

True

True

False

True

False

False

False

Например:
>>> 7.0 > 4 and -1 >= 0
False
>>> 5 < 4 or 1 != 2
True

# равнозначно True and False
# равнозначно False or True

В составных выражениях, подобных приведенным выше, первыми выполняются операторы сравнения, а затем логические операторы в порядке их
приоритетности: not, and, or. Приоритет можно изменить с помощью круглых
скобок, как для арифметических выражений. Например:
>>> not 7.5 < 0.9 or 4 == 4
True
>>> not (7.5 < 0.9 or 4 == 4)
False

Таблицы 2.6, 2.7 и 2.8 – это таблицы истинности для логических операторов.
Следует отметить, что, как и в большинстве языков программирования, в Python оператор or представляет собой вариант «включающее или» (inclusive or),
при котором A or B равно True, если A, или B, или и A, и B истинны, в отличие
от оператора «исключающее или» (exclusive or) (A xor B равно True, только если
либо A, либо B истинно, но не оба операнда одновременно15).

◊ Логические равенства и условное присваивание
В выражении логической проверки не всегда необходимо выполнять явное
сравнение для получения логического значения: Python попытается преобразовать объект в тип bool, если это необходимо. Для числовых объектов 0 преобразуется в False, а любое ненулевое значение – в True:
>>> a = 0
>>> a or 4 < 3
False
>>>
>>> not a + 1
False

15

# равнозначно: False or 4 < 3
# равнозначно: not True

В Python нет встроенного оператора xor, но его можно импортировать как функцию
с помощью инструкции from operator import xor. Вызов этой функции xor(a, b) возвращает True или False.

36



Ядро языка Python I

В этом примере операция сложения имеет более высокий приоритет, чем
логический оператор not, поэтому сначала вычисляется a+1 и дает результат 1.
Это соответствует логическому значению True, следовательно, все выражение
равнозначно выражению not True. Для явного преобразования любого объекта
в логический объект используется конструктор bool:
>>>
>>>
-2
>>>
-2
>>>
0

a = 0
a - 2 or a
4 > 3 and a - 2
4 > 3 and a





Логические выражения вычисляются слева направо, при этом предполагается, что вычисления and и or выполняются по укороченной схеме (shortcircuited): второе выражение вычисляется, только если необходимо определить
истинное значение всего выражения в целом. Приведенные выше три примера
можно проанализировать следующим образом:
❶ В первом примере сначала вычисляется a-2: результат равен −2, т. е.
равнозначен значению True, поэтому условие or выполнено, и операнд,
вычисленный как True, возвращается немедленно: −2.
❷ Проверка условия 4 > 3 дает результат True, поэтому должно быть вычислено второе выражение, чтобы определить истинность условия and.
Выражение a-2 равно −2, что также равнозначно True, следовательно, условие and выполнено, и возвращается −2 (как результат выражения, вычисленного самым последним).
❸ В последнем случае a равно 0, что равнозначно False, поэтому результатом вычисления условия and становится False, и возвращается значение 0.

None – специальное значение в языке Python

В Python определено отдельное специальное значение None с особенным типом NoneType. Оно используется для представления отсутствия определенного значения, например в случаях, когда не существует возможного значения,
и других подобных случаях. Это особенно удобно, когда нужно избежать введения произвольных значений по умолчанию (таких как 0, -1 или -99) для испорченных или отсутствующих данных.
В выражениях логического сравнения None вычисляется как False, но для
проверки равенства значения переменной x значению None необходимо использовать конструкцию
if x is None
и
if x is not None

вместо сокращенных вариантов if x и if not x16.
16

Обратите внимание: not x также вычисляется как True, если x является любым из
следующих объектов: 0, False или пустая структура данных, например пустой список,

2.2 Числа, переменные, операции сравнения и логические операции

 37

Пример П2.5. Общеизвестный прием в Python – присваивание переменной значения, возвращаемого логическим выражением:
>>> a = 0
>>> b = a or -1
>>> b
-1

Это буквально означает (предполагается, что a должно быть целым числом):
«установить b равным значению a, если не выполнено условие a == 0, а в случае
выполнения этого условия установить значение b равным −1».

2.2.5 Неизменяемость и идентичность
Объекты, рассматриваемые до настоящего момента, такие как целые числа
и логические значения, являются неизменяемыми (immutable). Неизменяемые
объекты никогда не изменяются после создания, хотя имя переменной может
быть переназначено для ссылки на другой объект, отличающийся от объекта,
которому было изначально присвоено это имя. Например, рассмотрим следующие присваивания:
>>> a = 8
>>> b = a

В первой строке создается целочисленный объект со значением 8 в памяти,
и ему присваивается имя a. Во второй строке тому же объекту присваивается
имя b. Это можно увидеть, проверив адрес памяти объекта по каждому имени:
>>> id(a)
4297273504
>>> id(b)
4297273504

Таким образом, a и b – ссылки на один и тот же целочисленный объект. Теперь предположим, что имя a переназначается для нового числового объекта:
>>> a = 3.14
>>> a
3.14
>>> b
8
>>> id(a)
4298630152
>>> id(b)
4297273504

Обратите внимание: значение b не изменилось – эта переменная продолжает
ссылаться на исходное значение 8. Переменная a теперь ссылается на новый
[ ] или пустая строка ''. Таким образом, это не самый надежный способ проверки
неравенства значения x значению None.

38



Ядро языка Python I

объект типа float со значением 3.14, расположенный по новому адресу памяти.
Именно это подразумевается под неизменяемостью: это не «переменная», которая не может изменяться, а сам объект является неизменяемым – см. рис. 2.1.

a
(a)

(b)

8

b
8

a

3.14

b

Рис. 2.1. (а) Две переменные ссылаются на одно и то же целое число; (б) после переназначения для переменной a другого значения

Более удобный способ установления того факта, что две переменные
ссылаются на один и тот же объект, – использование оператора is, который
определяет идентичность (identity) объектов:
>>> a
>>> b
>>> a
True
>>> c
>>> c
False
>>> c
True

= 2432
= a
is b
= 2432
is a
== a

Здесь присваивание c = 2432 создает абсолютно новый объект, поэтому вычисление выражения c is a дает результат False, даже притом, что a и c содержат одинаковое значение. Таким образом, эти две переменные ссылаются на
различные объекты с одинаковыми значениями.
Часто необходимо изменить значение переменной каким-либо способом,
например:
>>> a = 800
>>> a = a + 1
>>> a
801

Целые числа 800 и 801 неизменяемы: строка a = a + 1 создает новый целочисленный объект со значением 801 (правая сторона выражения вычисляется
в первую очередь) и присваивает его переменной с именем a (старое значение 800 «забывается»17, если только на это значение не ссылается какая-либо
17

То есть память, выделенная Python для этого значения, освобождается (механизмом сборки мусора – garbage collector) и становится доступной для общего использования.

2.2 Числа, переменные, операции сравнения и логические операции

 39

другая переменная). Таким образом, a указывает на разные адреса памяти до
и после выполнения этой инструкции.
Подобное переназначение переменной на результат выполнения арифметической операции выполняется настолько часто, что была введена удобная
укороченная форма ее записи: комбинированное присваивание (augmented
assignment) a += 5 равнозначно присваиванию a = a + 5. Точно так же работают
операторы -=, *=, /=, //=, %=. Но в Python не поддерживаются операции инкремента и декремента в C-стиле, такие как a++ для операции a += 118.
Пример П2.6. Python предоставляет оператор is not: более естественно написать
c is not a, чем not c is a
>>> a
>>> b
>>> b
True
>>> b
>>> b
True

= 8
= a
is a
/= 2
is not a

◊ Пример П2.7. С учетом приведенного выше описания может показаться странным
следующий факт:
>>> a = 256
>>> b = 256
>>> a is b
True

Причина в том, что Python сохраняет кеш (cache) общего пользования для
небольших целочисленных объектов (в моей системе это числа от -5 до 256).
Для повышения производительности присваивание a = 256 связывает имя
переменной a с существующим целочисленным объектом без необходимости
выделения новой памяти для него. Поскольку такое же присваивание выполняется и для переменной b, эти две переменные в данном конкретном случае
в действительности указывают на один и тот же объект. Противоположный
пример:
>>> a = 257
>>> b = 257
>>> a is b
False

18

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

40  Ядро языка Python I

2.2.6 Упражнения
Вопросы
В2.2.1. Определить результаты вычислений следующих выражений и проверить их, используя командную оболочку Python.
а)
б)
в)
г)
д)
е)
ж)
з)
и)

2.7 / 2
2 / 4 - 1
2 // 4 - 1
(2 + 5) % 3
2 + 5 % 3
3 * 4 // 6
3 * (4 // 6)
3 * 2 ** 2
3 ** 2 * 2

В2.2.2. Все операторы в табл. 2.1 являются бинарными: они обрабатывают два
операнда (числа) и возвращают одно значение. Символ «минус» (−) также используется как унарный (unary) оператор, который возвращает отрицательное
значение (точнее: изменяет знак на противоположный) одного обрабатываемого операнда. Например:
>>> a = 4
>>> b = -a
>>> b
-4

Обратите внимание: выражение b = -a (присваивающее переменной b значение a с противоположным знаком) отличается от выражения b -= a (которое
вычитает a из b и сохраняет результат в b). Унарный оператор − обладает более
высоким приоритетом, чем операторы *, / и %, но более низким приоритетом,
чем оператор возведения в степень **, поэтому, например, -2 ** 4 равно -16
(так как вычисляется −(24), а не (−2)4).
Определить результаты вычислений следующих выражений и проверить их,
используя командную оболочку Python.
а)
б)
в)
г)
д)
е)
ж)
з)

-2 ** 2
2 ** -2
-2 ** -2
2 ** 2 ** 3
2 ** 3 ** 2
-2 ** 3 ** 2
(-2) ** 3 ** 2
(-2) ** 2 ** 3

В2.2.3. Определить и объяснить результаты выполнения следующих инструкций.
а) 9 + 6j / 2
б) complex(4, 5).conjugate().imag

2.2 Числа, переменные, операции сравнения и логические операции
в)
г)
д)
е)

 41

complex(0, 3j)
round(2.5)
round(-2.5)
abs(complex(5, -4)) == math.hypot(4,5)

В2.2.4. Определить значение ii как действительное число, где i = √(−1).
В2.2.5. Объясните (необычное?) поведение следующего короткого фрагмента
исходного кода:
>>> d = 8
>>> e = 2
>>> from math import *
>>> sqrt(d ** e)
16.88210319127114

В2.2.6. Формально операция целочисленного деления a // b определяется как
округление в меньшую сторону (floor) результата деления a/b (иногда такое
округление записывают как a/b), т. е. наибольшее целое число, меньшее или
равное a / b. Тогда модуль или остаток от деления a % b (также записываемый
как a mod b) равен:
a mod b = a − ba/b.
Использовать эти определения для получения результата следующих выражений. Проверить результаты в командной оболочке Python.
а)
б)
в)
г)
д)
е)
ж)
з)

7 // 4
7 % 4
-7 // 4
-7 % 4
7 // -4
7 % -4
-7 // -4
-7 % -4

В2.2.7. Если две смежные грани правильного шестигранного игрального кубика (кости) имеют значения a и b, если смотреть сбоку и читать слева направо, то
значение на верхней грани кубика вычисляется по формуле 3(a3b – ab3) mod 7.
Определить значение на верхней грани кубика, если:
а) a = 2, b = 6;
б) a = 3, b = 5.
В2.2.8. Сколько раз необходимо сложить пополам лист бумаги (толщина
t = 0.1 мм, но можно принять и любой другой размер), чтобы достичь Луны
(расстояние от Земли d = 384 400 км)?
В2.2.9. Определить результаты вычислений следующих выражений и проверить их, используя командную оболочку Python.

42
а)
б)
в)
г)
д)
е)
ж)
з)
и)



Ядро языка Python I

not 1 < 2 or 4 > 2
not (1 < 2 or 4 > 2)
1 < 2 or 4 > 2
4 > 2 or 10/0 == 0
not 0 < 1
1 and 2
0 and 1
1 or 0
type(complex(2, 3).real) is int

В2.2.10. Объясните, почему при вычислении следующего выражения не получается результат 100.
>>> 10^2
8

Подсказка: см. документацию Python по битовым операторам (bitwise operators).

Задачи
З2.2.1. В Python нет встроенного оператора «исключающее или», но его можно сформировать из существующих операторов. Разработайте два различных
способа реализации оператора «исключающее или». Таблица истинности для
оператора xor приведена в табл. 2.9.
Таблица 2.9. Таблица истинности для оператора xor
P

Q

P xor Q

True

True

False

False

True

True

True

False

True

False

False

False

З2.2.2. Некоторые интересные вычисления с использованием модуля math:
а) Какими особенностями обладают числа sin(2017 × 5√2) и (π + 20)i?
б) Что произойдет, если попытаться вычислить выражение, такое как e1000,
генерирующее число, большее, чем максимальное число с плавающей точкой, которое может быть представлено принятым по умолчанию значением с двойной точностью. Что произойдет, если при вычислении ограничиться целочисленной арифметикой (например, при вычислении 1000!)?
в) Что произойдет, если попытаться выполнить неопределенную математическую операцию, например деление на нуль?
г) Максимальное представление числа с плавающей точкой по стандарту
IEEE-754 с двойной точностью приблизительно равно 1.8×10308. Вычислить длину гипотенузы прямоугольного треугольника с катетами 1.5×10200 и 3.5×10201:
i) напрямую используя функцию math.hypot();
ii) без использования этой функции.

2.3 Объекты Python I: строки  43
З2.2.3. В некоторых языках есть функция sign(a), которая возвращает −1, если
аргумент a отрицательный, и 1, если аргумент a положительный. В Python такой функции нет, но в модуль math включена функция math.copysign(x, y),
которая возвращает абсолютное значение x со знаком y. Как можно было бы
использовать эту функцию способом, аналогичным использованию отсутствующей функции sign(a)?
З2.2.4. Всемирная геодезическая система (сеть) (World Geodetic System) – это
комплект международных стандартов для описания формы Земли. В самой
последней версии WGS-84 земной геоид приближенно определяется как эллипсоид, принимающий форму сжатого у полюсов сфероида с главной, или
большой, полуосью эллипса a = 6 378 137.0 м и малой полуосью эллипса c =
6 356 752.314245 м.
Использовать формулу вычисления площади поверхности сжатого у полюсов сфероида
Sobl = 2πa2(1 + ((1 − e2)/e)atanh(e)), где e2 = 1 − (c2/a2),
для вычисления площади поверхности вышеописанного эллипсоида и сравнить полученный результат с площадью поверхности Земли при предположении, что Земля – сфера с радиусом 6371 км.

2.3 Объекты Python I: строки
2.3.1 Определение объекта, представляющего строку
В Python объект, представляющий строку (тип str), – это упорядоченная неизменяемая последовательность символов. Для определения переменной, содержащей постоянный текст (строковый литерал – string literal), необходимо взять
этот текст в одиночные или двойные кавычки:
>>> greeting = "Hello , Sir!"
>>> bye = 'À bientôt'

Строки можно объединять (concatenate), используя оператор + или просто
размещая их последовательно друг за другом в одной строке:
>>> 'abc' + 'def'
'abcdef'
>>> 'one ' 'two' ' three'
'one two three'

В Python нет ограничений на длину строки, поэтому строковый литерал
можно определить в одном блоке текста, заключенном в кавычки. Но для удобства чтения, как правило, лучше создавать в программе строки, длина которых
не превышает определенного максимума (рекомендуется максимальная длина 79 символов). Для размещения текстовой строки в двух и более сроках кода
используется символ продолжения строки «обратный слеш» (\), но более удачным решением является заключение строкового литерала в круглые скобки:

44



Ядро языка Python I

>>> long_string = 'We hold these truths to be self -evident ,'\
...
' that all men are created equal...'
>>> long_string = ('We hold these truths to be self -evident ,'
...
' that all men are created equal...')

Здесь определяется переменная long_string, содержащая одну строку текста
(без символов перехода на новую строку). Операция соединения не вставляет
пробелы, поэтому необходимо явно включать их в строку, если это требуется.
Пробелы в исходном коде для выравнивания строк по открывающим кавычкам в этом примере не обязательны, они лишь делают код более удобным для
чтения.
Если строка состоит из повторяющихся символов (одного или нескольких),
то можно использовать оператор * для объединения повторяющихся групп
символов заданное число раз:
>>> 'a' * 4
'aaaa'
>>> '-o-' * 5
'-o--o--o--o--o-'

Пустая строка определяется просто как s = '' (две одиночные кавычки) или
s = «».
Встроенная функция str выполняет преобразование объекта, переданного
как аргумент, в строку в соответствии с набором правил, определяемых самим
исходным объектом:
>>> str(42)
'42'
>>> str(3.4e5)
'340000.0'
>>> str(3.4e20)
'3.4e+20'

Для получения более точного описания управления форматированием
строк, представляющих числа, см. раздел 2.3.7.
Пример П2.8. Строки, объединяемые с помощью оператора +, могут повторяться
с помощью оператора *, но только если они заключены в круглые скобки:
>>> ('a'*4 + 'B') * 3
'aaaaBaaaaBaaaaB'

2.3.2 Escape-последовательности
Выбор кавычек для определения строк позволяет включать сам символ кавычки в строковый литерал – просто при определении нужно использовать другой
символ кавычки:
>>> verse = 'Quoth the Raven "Nevermore."'

2.3 Объекты Python I: строки  45
А что, если необходимо включить в строку оба типа кавычек? Или если строковый литерал должен содержать несколько строк текста? В этом случае применяются особые escape-последовательности (или esc-последовательности),
обозначаемые символом «обратный слеш» (\). Наиболее часто используемые
esc-последовательности перечислены в табл. 2.10.
Таблица 2.10. Часто используемые в Python esc-последовательности
Esc-последовательности

Описание

\’

Одиночная кавычка (‘)

\”

Двойная кавычка (“)

\n

Переход на новую строку (LF)

\r

Возврат каретки (к началу строки) (CR)

\t

Горизонтальная табуляция

\b

Возврат на одно знакоместо со стиранием символа («забой»)

\\

Сам символ «обратный слеш»

\u, \U, \N{}

Символ в кодировке Unicode (см. раздел 2.3.3)

\x

Байт в шестнадцатеричном формате

Примеры:
>>> sentence = "He said , \"This parrot's dead.\""
>>> sentence
'He said , "This parrot\'s dead."'
>>> print(sentence)
He said , "This parrot's dead."
>>> subjects = 'Physics\nChemistry\nGeology\nBiology'
>>> subjects
'Physics\nChemistry\nGeology\nBiology'
>>> print(subjects)
Physics
Chemistry
Geology
Biology




❶ Обратите внимание: простой ввод только имени переменной после при-

глашения командной оболочки Python выводит литеральное значение
этой переменной (в кавычках).
❷ Для получения требуемой строки, включающей правильную интерпретацию специальных символов, необходимо передать эту переменную во
встроенную функцию print (см. раздел 2.3.6).
С другой стороны, если нужно определить строку с включением в нее escпоследовательностей, таких как \n, без их экранирования, то определяется необработанная (или неформатируемая) строка (raw string) с префиксом r:

46



Ядро языка Python I

>>> rawstring = r'The escape sequence for a new line is \n.'
>>> rawstring
'The escape sequence for a new line is \\n.'
>>> print(rawstring)
The escape sequence for a new line is \n.

При определении блока текста, включающего несколько символов конца
строки (перехода на новую строку), зачастую неудобно все время использовать esc-последовательность \n. Этого можно избежать, воспользовавшись
строкой в тройных кавычках (triple-quoted string): переходы на новуюстроку
определяются непосредственно в строковом литерале, ограниченном символом """ или ''', и сохраняются в итоговой строке19:
a = """one
two
three"»»
>>> print(a)
one
two
three

Этот способ часто используется для создания так называемых docstrings, которые документируют блок кода в программе (см. раздел 2.7.1).
Пример П2.9. Esc-последовательность \x обозначает символ, закодированный как
однобайтовое шестнадцатеричное значение, состоящее из последовательности двух
символов. Например, заглавная (прописная) буква N кодируется значением 78 –
в шестнадцатеричном формате это значение 4e. Следовательно:
>>> '\x4e'
'N'

Невидимый (управляющий) символ backspace (возврат на одно знакоместо
с удалением символа) кодируется как шестнадцатеричное значение 08, поэтому esc-последовательность \b равнозначна коду \x08:
>>> 'hello\b\b\b\b\bgoodbye'
'hello\x08\x08\x08\x08\x08goodbye'

При передаче этой строки в функцию print() вывод формируется в соответствии с последовательностью символов в этом строковом литерале:
>>> print('hello\b\b\b\b\bgoodbye')
goodbye

2.3.3 Unicode
Строки в Python 3 состоят из символов в кодировке Unicode. Кодировка Unicode – это стандартное описание более 100 000 символов из почти всех извест19

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

2.3 Объекты Python I: строки  47
ных человеческих языков, а также множества других специализированных
символов, например научных символов. Каждому символу присвоено числовое значение (код – code point), и эти числовые значения, формирующие строку, затем кодируются как последовательность байтов20. В течение длительного
времени не существовало официального соглашения об использовании стандарта кодировки, но кодировка UTF-8, применяемая в Python 3 по умолчанию,
в настоящее время признана самой широко распространенной и наиболее час­
то применяемой21. Если ваш текстовый редактор не позволяет ввести какойлибо символ напрямую в строковый литерал, то вы можете воспользоваться соответствующим 16-битовым или 32-битовым шестнадцатеричным значением
либо именем символа по стандарту Unicode в форме esc-последовательности:
>>> '\u00E9'
# 16-битовое шестнадцатеричное значение
'é'
>>> '\u000000E9'
# 32-битовое шестнадцатеричное значение
'é'
>>> '\N{LATIN SMALL LETTER E WITH ACUTE}'
# по имени
'é'

Пример П2.10. Предположим, что ваш текстовый редактор или терминал позволяет
вводить символы Unicode, и вы можете набирать их на клавиатуре или копировать
откуда-либо (например, из веб-браузера или из текстового процессора) и вставлять
в строку. Тогда символы Unicode можно вставлять прямо в строковые литералы:
>>> creams = 'Crème fraîche, crème brûlée, crème pâtissière'

Python даже поддерживает имена переменных с использованием символов
Unicode, поэтому идентификаторами могут быть символы, не содержащиеся
в наборе ASCII:
>>> Σ = 4
>>> crème = 'anglaise'

Разумеется, из-за трудностей ввода символов, не содержащихся в наборе
ASCII, с обычной клавиатуры, а также из-за внешнего сходства многих различных символов такой способ именования переменных не рекомендуется.

2.3.4 Индексирование и вырезание строк
Индексирование (indexing, или subscripting) строки возвращает один символ
из заданной позиции. Как и все последовательности в Python, строки проиндексированы, и первому символу строки присвоен индекс 0. Это означает, что
самый последний символ в строке, состоящей из n символов, имеет индекс
n – 1. Например:
20

21

Полный список кодов см. на официальном сайте Unicode в таблицах кодов символов:
www.unicode.org/charts.
В версию Unicode-кодировки UTF-8 включен «почтенный» набор символов ASCII
в 8-битовой кодировке (в котором, например, A = 65).

48



Ядро языка Python I

>>> a = "Knight"
>>> a[0]
'K'
>>> a[3]
'g'

Символ возвращается в объекте str с длиной 1. Неотрицательный индекс отсчитывается в прямом направлении (вправо) от начала строки. Но существует
и удобный способ представления индекса для движения по строке в обратном направлении (влево): отрицательный индекс, начиная с -1 (для конечного символа):
>>> a = "Knight"
>>> a[-1]
't'
>>> a[-4]
'i'

При попытке индексирования строки за пределами ее длины возникает
ошибка (в рассматриваемом здесь примере это индекс больше 5 и меньше −6):
Python генерирует сообщение об ошибке IndexError:
>>> a[6]
Traceback (most recent call last):
File "", line 1, in
IndexError: string index out of range

Вырезание (slicing) (под)строки s[i:j] создает из исходной строки подстроку, расположенную между символами с двумя заданными индексами, включая
первый (i) символ, но исключая последний (j) символ. Если первый индекс
не задан, то подразумевается 0 (начало строки). Если второй индекс не задан,
то подстрока вырезается до конца. Например:
>>> a = "Knight"
>>> a[1:3]
'ni'
>>> a[:3]
'Kni'
>>> a[3:]
'ght'
>>> a[:]
'Knight'

На первый взгляд, такой способ кажется немного непривычным, но он гарантирует, что длина подстроки, возвращаемой как s[i:j], равна j-i (для
положительных значений i, j), а также что s[:i] + s[i:] == s. В отличие от
операции индексирования, вырезание строки за границами исходной строки
не генерирует ошибку:
>>> a = "Knight"
>>> a[3:10]
'ght'
>>> a[10:]
''

2.3 Объекты Python I: строки  49
Чтобы проверить, содержит ли исходная строка заданную подстроку, используется оператор in:
>>> 'Kni' in 'Knight':
True
>>> 'kni' in 'Knight':
False

Пример П2.11. Благодаря самой сущности операции вырезания подстроки s[m:n]
разность n-m всегда представляет длину вырезаемой подстроки. Другими словами,
чтобы вернуть r символов, начиная с индекса m, используйте s[m:m+r]. Например:
>>> s = 'whitechocolatespaceegg'
>>> s[:5]
'white'
>>> s[5:14]
'chocolate'
>>> s[14:19]
'space'
>>> s[19:]
'egg'

Пример П2.12. Третий необязательный параметр операции вырезания подстроки определяет шаг вырезания (stride). Если шаг вырезания не задан, то по
умолчанию он равен 1: возвращается каждый символ в указанном диапазоне.
Чтобы возвращался каждый k-й символ, необходимо задать шаг вырезания k. Отрицательные значения k изменяют порядок символов в вырезаемой подстроке
на противоположный (реверсируют подстроку). Например:
>>> s = 'King Arthur'
>>> s[::2]
'Kn rhr'
>>> s[1::2]
'igAtu'
>>> s[-1:4:-1]
'ruhtrA'

Последнюю операцию вырезания можно описать как выбор символов, начиная с конечного (индекс -1) до символа с индексом 4 (но не включая сам
этот символ) с шагом вырезания -1 (выбирается каждый символ с размещением в обратном порядке).
Удобный способ реверсирования (изменения порядка символов на обратный) всей строки – вырезание между границами по умолчанию (т. е. не задаются первый и последний индексы) с указанием шага вырезания -1:
>>> s[::-1]
'ruhtrA gniK'

50



Ядро языка Python I

2.3.5 Методы обработки строк
В Python строки являются неизменяемыми объектами, поэтому после присваи­
вания строку изменить невозможно. Попытка изменения приведет к ошибке,
например:
>>> a = 'Knight'
>>> a[0] = 'k'
Traceback (most recent call last):
File "", line 1, in
TypeError: 'str' object does not support item assignment

Новые строки можно формировать из существующих строк, но только как
новые объекты, например:
>>> a += ' Templar'
>>> print(a)
Knight Templar
>>> b = 'Black ' + a[:6]
>>> print(b)
Black Knight

Чтобы узнать количество символов, содержащихся в строке, используется
встроенный метод len():
>>> a = 'Earth'
>>> len(a)
5

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

Описание

center(width)

Возвращает строку, отцентрированную в новую строку с общим количеством
символов width

endswith(suffix)

Возвращает True, если строка заканчивается подстрокой suffix

startswith(prefix)

Возвращает True, если строка начинается подстрокой prefix

index(substring)

Возвращает наименьший индекс в строке, соответствующий содержащейся
в ней подстроке substring

lstrip([chars])

Возвращает копию строки, в которой удалены все начальные символы, заданные необязательным аргументом [chars]. Если аргумент [chars] не задан, то
удаляются все начальные пробелы

rstrip([chars])

Возвращает копию строки, в которой удалены все конечные (хвостовые) символы, заданные необязательным аргументом [chars]. Если аргумент [chars]
не задан, то удаляются все конечные пробелы

2.3 Объекты Python I: строки  51
Окончание табл. 2.11
Метод

Описание

strip([chars])

Возвращает копию строки, в которой удалены все начальные и конечные символы, заданные необязательным аргументом [chars]. Если аргумент [chars]
не задан, то удаляются все начальные и конечные пробелы

upper()

Возвращает копию строки, в которой все символы переведены в верхний регистр

lower()

Возвращает копию строки, в которой все символы переведены в нижний регистр

title()

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

replace(old, new)

Возвращает копию строки, в которой каждая подстрока old заменена подстрокой new

split([sep])

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

join([list])

Использует строку как разделитель при объединении списка list строк

isalpha()

Возвращает True, если все символы в строке являются алфавитными и строка
не пустая, иначе возвращается False

isdigit()

Возвращает True, если все символы в строке являются цифровыми и строка
не пустая, иначе возвращается False

Поскольку каждый из перечисленных в табл. 2.11 методов возвращает новую
строку, методы можно объединять в цепочку вызовов:
>>> s = '-+-Python Wrangling for Beginners'
>>> s.lower().replace('wrangling', 'programming').lstrip('+-')
'python programming for beginners'

Пример П2.13. Ниже показаны некоторые возможные способы обработки строк
с использованием этих методов:
>>> a = 'java python c++ fortran '
>>> a.isalpha()
False
>>> b = a.title()
>>> b
'Java Python C++ Fortran '
>>> c = b.replace(' ', '!\n')
>>> c
'Java!\nPython!\nC++!\nFortran!'
>>> print(c)
Java!
Python!
C++!
Fortran!
>>> c.index('Python')
6
>>> c[6:]. startswith('Py')
True
>>> c[6:12]. isalpha()
True





❶ Метод a.isalpha() возвращает False, потому что в строке содержатся

пробелы и символы ++.

❷ Обратите внимание: \n – это один символ.

52



Ядро языка Python I

2.3.6 Функция print
В Python 3 print является встроенной функцией (как и многие другие, с которыми мы уже знакомы, например len и round). Она принимает список объектов для вывода и необязательные аргументы end и sep, определяющие, какие
символы должны обозначать конец строки и какие символы должны использоваться для разделения выводимых объектов соответственно. Результатом отсутствия этих дополнительных аргументов будет вывод, в котором поля объектов разделяются одним символом пробела, а строка завершается символом
newline (переход на новую строку)22. Например:
>>> ans = 6
>>> print('Solve:', 2, 'x =', ans, 'for x')
Solve: 2 x = 6 for x
>>> print('Solve: ', 2, 'x = ', ans, ' for x', sep='', end='!\n')
Solve: 2x = 6 for x!
>>> print()
>>> print('Answer: x =', ans/2)
Answer: x = 3.0



❶ Обратите внимание: print() без аргументов просто выводит принятый

по умолчанию символ перехода на новую строку end.

Для отмены перехода на новую строку в конце выводимой строки необходимо для аргумента end задать пустую строку: end='':
>>> print('A line with no newline character', end='')
A line with no newline character >>>

Три символа >>> в конце выведенной строки – это приглашение (промпт)
для ввода следующей команды Python.
Пример П2.14. Функцию print можно использовать для создания простых текстовых таблиц:
>>> heading = '| Index of Dutch Tulip Prices |'
>>> line = '+' + '-'*16 + '-'*13 + '+'
>>> print(line , heading , line ,
...
'|
Nov 23 1636 |
100 |',
...
'|
Nov 25 1636 |
673 |',
...
'|
Feb 1 1637 |
1366 |', line , sep='\n')
...
+-----------------------------+
| Index of Dutch Tulip Prices |
+-----------------------------+
|
Nov 23 1636 |
100 |
|
Nov 25 1636 |
673 |
|
Feb 1 1637 |
1366 |
+-----------------------------+

22

Использование специального символа перехода на новую строку newline зависит от
операционной системы: например, в macOS это символ \n (linefeed – переход на новую строку), в Windows это два символа \r\n (carriage return + line feed).

2.3 Объекты Python I: строки  53

2.3.7 Форматирование строк
Введение в форматирование строк в версии Python 3
Можно воспользоваться строковым методом format в его простейшей форме
для вставки объектов в строку. Вот пример самого простого синтаксиса:
>>> '{} plus {} equals {}'.format(2, 3, 'five')
2 plus 3 equals five

Здесь метод format вызывается из строкового литерала с аргументами 2, 3
и 'five', которые включаются в заданном порядке в места полей замены (replacement fields), обозначенные парами фигурных скобок {}. Поля замены также
могут быть пронумерованы или поименованы, что удобно при работе с длинными строками, а еще позволяет несколько раз вставить одно и то же значение:
>>> '{1} plus {0} equals {2}'.format(2, 3, 'five')
'3 plus 2 equals five'
>>> '{num1} plus {num2} equals {answer}'.format(num1=2, num2=3, answer='five')
'2 plus 3 equals five'
>>> '{0} plus {0} equals {1}'.format(2, 2+2)
'2 plus 2 equals 4'

Обратите внимание: нумерованные поля индексируются, начиная с 0, и могут располагаться в строке в любом порядке.
Для полей замены можно задать минимальный размер в выводимой строке,
указав целочисленное значение длины после двоеточия, как показано ниже:
>>> '=== {0:12} ==='.format('Python')
'=== Python
==='

Если строка слишком длинна для заданного минимального размера, то будет вставлено столько символов, сколько необходимо (заданный размер поля
замены замещается):
>>> 'A number: '.format(-20)
'A number: ' # Для -20 не хватает 2 символов: поэтому используется поле длиной 3
символа

По умолчанию вставляемая строка выравнивается по левому краю, но это
можно изменить, определив выравнивание по правому краю или по центру.
Символы и ^ управляют выравниванием:
>>> '=== {0:12} ==='.format('Python')
'===
Python ==='
>>> '=== {0:^12} ==='.format('Python')
'===
Python
==='

В этих примерах поле замены заполняется пробелами, но символ заполнения также можно задать. Например, заполнение дефисами в самом последнем
примере:

54



Ядро языка Python I

>>> '=== {0:-^12} ==='.format('Python')
'=== ---Python --- ==='

Существует даже возможность передачи минимального размера поля как
параметра, который сам будет вставлен в подстроку. Просто замените числовой размер поля на ссылку в фигурных скобках, как показано ниже:
>>> a = 15
>>> 'This field has {0} characters: ==={1: >{2}}===.'.format(a, 'the field', a)
'This field has 15 characters: ===
the field===.'

Или в случае вставки именованных полей:
>>> 'This field has {w} characters: ==={0:>{w}}===.'.format('the field', w=a)
'This field has 15 characters: ===
the field===.'

В каждом случае второй параметр-определитель формата в этих примерах
интерпретируется как :>15.
Для вставки самих символов фигурных скобок в форматируемую строку их
необходимо продублировать, т. е. использовать {{ и }}.

Форматирование числовых значений
В Python 3 строковый метод format предоставляет разнообразные способы
форматирования числовых значений.
Спецификаторы d, b, o, x/X обозначают десятичный, бинарный, восьмеричный и шестнадцатеричный в нижнем/верхнем регистре форматы целых чисел
соответственно:
>>> a = 254
>>> 'a = {0:5d}'.format(a) # десятичный формат
'a = 254'
>>> 'a = {0:10b}'.format(a) # бинарный формат
'a = 11111110'
>>> 'a = {0:5o}'.format(a) # восьмеричный формат
'a = 364'
>>> 'a = {0:5x}'.format(a) # шестнадцатеричный формат (в нижнем регистре)
'a =
fe'
>>> 'a = {0:5X}'.format(a) # шестнадцатеричный формат (в верхнем регистре)
'a =
FE'

При выводе числа можно дополнять нулями для заполнения полей заданного размера, если перед минимальным размером поля вывода указать 0:
>>> a = 254
>>> 'a = {a:05d}'.format(a=a)
'a = 00254'

По умолчанию знак выводится, только если число отрицательное. Это поведение также можно изменить, указывая перед значением минимальной ширины поля следующие символы:
 + – всегда выводить знак числа;
 − – выводить знак только отрицательного числа (это поведение по умолчанию);

2.3 Объекты Python I: строки  55
 "" (пробел) – выводить префиксный пробел (вместо знака), только если
число положительное.
Последний вариант позволяет правильно выравнивать столбцы положительных и отрицательных чисел:
>>> print('{0: 5d}\n{1: 5d}\n{2: 5d}'.format(-4510, 1001, -3026))
-4510
1001
-3026
>>> a = -25
>>> b = 12
>>> s = '{0:+5d}\n{1:+5d}\n= {2:+3d}'.format(a, b, a+b)
>>> print(s)
-25
+12
= -13

Также существуют спецификаторы формата для чисел с плавающей точкой, которые можно выводить с выбором требуемой точности. Наиболее
часто используются спецификаторы: f – обычный формат с плавающей
точкой, e/E – экспоненциальный (или «научный») формат и g/G – общий
формат, который применяет научную форму записи для очень больших
и очень малых чисел23. Требуемая точность (количество десятичных знаков)
определяется как .p после значения минимальной ширины поля вывода.
Несколько примеров:
>>> a = 1.464e-10
>>> '{0:g}'.format(a)
'1.464e-10'
>>> '{0:10.2E}'.format(a)
' 1.46E-10'
>>> '{0:15.13f}'.format(a)
'0.0000000001464'
>>> '{0:10f}'.format(a)
' 0.000000'



❶ Обратите внимание: Python не обеспечивает защиту от такого типа

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

Форматированные строковые литералы (f-строки)
С версии 3.6 Python поддерживает более развитый способ включения значений в строки: строковый литерал, обозначенный буквой f перед открывающей кавычкой, может вычислять выражения, помещенные в фигурные скобки,
включая ссылки на переменные, вызовы функций и операции сравнения. Это
обеспечивает выразительный и компактный способ определения строковых
объектов, например, используя переменные
23

Более точно: спецификатор g/G работает как f/F для чисел в диапазоне между 10−4
и 10p, где p – требуемая точность (по умолчанию равная 6), а во всех остальных случаях работает как e/E.

56



Ядро языка Python I

>>> h = 6.62607015e-34
>>> h_units = 'J.s'

Вместо применения функции format:
>>> 'h = {h:.3e} {h_units}'.format(h=h, h_units=h_units)
'h = 6.626e-34 J.s'

можно просто написать:
>>> f'h = {h:.3e} {h_units}'
'h = 6.626e-34 J.s'

Это означает избавление от необходимости неудобного повторения вызова функции format (h=h, h_units=h_units), а длинные строки с многократным
включением значений проще читать при использовании описанного выше
способа. В общем случае это еще и более быстрый способ выполнения, поскольку такой синтаксис является частью базовой грамматики Python и не требует явных вызовов функций.
Вообще говоря, не самым лучшим решением является размещение сложных
выражений в полях замены f-строки, тем не менее в f-строку достаточно часто
включают вызовы функций и операции сравнения:
>>> name = 'Elizabeth'
>>> f'The name {name} has {len(name)} letters and {name.lower().count("e")} "e"s.'
'The name Elizabeth has 9 letters and 2 "e"s.'

или даже:
>>> letter = 'k'
>>> f'{name} has {len(name)} letters and {name.lower().count(letter)} "{letter}"s.'
'Elizabeth has 9 letters and 0 "k"s.'

Необходимо помнить о некоторых небольших ограничениях и недостатках:
кавычки, используемые внутри выражения f-строки, не должны конфликтовать
с кавычками, используемыми для ограничения самого строкового литерала
(в приведенном выше примере обратите внимание на использование ", чтобы
избежать конфликта с внешними одиночными кавычками f'...'. Кроме того,
поскольку f-строки вычисляются только один раз во время выполнения, не существует возможности определения многократно используемого «шаблона»:
>>> radius = 2.5
>>> s = f'The radius is {radius} m.'
>>> print(s)
The radius is 2.5 m.
>>> radius = 10.3
>>> print(s)
The radius is 2.5 m.

2.3 Объекты Python I: строки  57
Для такого варианта использования лучше применить привычный способ
включения значений в строку с помощью функции format:
>>> radius = 2.5
>>> t = 'The radius is {} m.'
>>> print(t.format(radius))
The radius is 2.5 m.
>>> radius = 10.3
>>> print(t.format(radius))
The radius is 10.3 m.

В этой книге будут использоваться оба способа: обычное включение значений в строку с помощью функции format и f-строки.

Старый способ форматирования в стиле языка C
Python 3 также поддерживает менее удобные спецификаторы формата в стиле
языка C, которые все еще широко применяются. В этом варианте форматирования поля замены определяются с помощью спецификаторов минимальной
ширины поля и точности, следующих за знаком процента %. Объекты, значения которых должны включаться в строку, перечисляются после конца этой
строки, и перед ними также должен быть указан знак процента %. Если объектов несколько, то они обязательно должны быть взяты в круглые скобки. Для
указания различных выводимых типов используются буквы, описанные выше,
в предыдущих подразделах. Строки обязательно должны быть явно определены как "%s". Например:
>>> kB = 1.380649e-23
>>> 'Here\'s a number: %10.2e' % kB
"Here's a number: 1.38e-23"
>>> 'The same number formatted differently: %7.1e and %12.6e' % (kB, kB)
'The same number formatted differently: 1.4e-23 and 1.380649e-23'
>>> '%s is %g J/K' % ("Boltzmann's constant", kB)
"Boltzmann's constant is 1.38065e-23 J/K"

Пример П2.15. Python может обеспечить строковое представление числовых значений, в которых разряды тысяч разделены запятыми:
>>> '{:11,d}'.format (1000000)
' 1,000,000'
>>> '{:11,.1f}'.format (1000000.)
'1,000,000.0'

Ниже показан пример еще одной таблицы, сформированной с использованием нескольких различных методов обработки строк:

58



Ядро языка Python I

title = '|' + '{:^51}'.format('Cereal Yields (kg/ha)') + '|'
line = '+' + '-'*15 + '+' + ('-'*8 + '+')*4
row = '| {:>> for line in f:
...
print(line , end='')
...
First line
Second line
...



❶ Поскольку при считывании в строке line сохраняется символ перехода

на новую строку, здесь используется аргумент end=’’, чтобы функция
print не добавляла еще один такой символ, выводящий лишнюю пус­
тую строку.

Возможно, в реальной практике потребуется весьма частое применение этого метода, если только вы действительно не захотите сохранять каждую строку
в оперативной памяти. В разделе 4.3.4 рассматривается использование ключевого слова Python with для более правильной организации обработки файлов.
Пример П2.25. Для чтения чисел из файла powers.txt, созданного в предыдущем
примере, нужно обязательно преобразовать столбцы в списки целых чисел. Для этого
каждую строку необходимо разделить на поля и каждое поле явно преобразовать
в значение типа int:
f = open('powers.txt', 'r')
squares , cubes , fourths = [], [], []
for line in f.readlines():
fields = line.split(',')
squares.append(int(fields[1]))
cubes.append(int(fields[2]))
fourths.append(int(fields[3]))
f.close()
n = 500
print(n, 'cubed is', cubes[n-1])

При выполнении данная программа выводит следующий результат:
500 cubed is 125000000

Но в реальной практической деятельности лучше использовать библиотеку
NumPy (см. главу 6) для считывания данных из файлов вместо способа, продемонстрированного в этом примере.

2.6.4 Упражнения
Задачи
З2.6.1. Монотипный род деревьев тихоокеанского побережья «Секвойя красная» (Sequoia sempervirens) включает один из самых древних и самых высоких живых организмов на Земле. Подробная информация об отдельных
деревь­ях содержится в текстовом файле redwood-data.txt (поля данных разделены символами табуляции), доступном здесь: https://scipython.com/ex/bbd.
(Эти данные опубликованы с разрешения БД Gymnosperm (голосеменные
растения) www.conifers.org/cu/Sequoia.php.)

2.6 Файловый ввод/вывод  93
Написать программу на Python для считывания этих данных и генерации
сообщения о самом высоком дереве и о дереве с наибольшим диаметром.
З2.6.2. Написать программу для считывания содержимого текстового файла
и цензурного редактирования слов, содержащихся в списке запрещенных слов,
посредством замены их букв на равное количество звездочек. В программе запрещенные слова должны храниться в нижнем регистре, но подлежащие цензурному редактированию образцы могут быть записаны в любом регистре.
Предполагается отсутствие знаков пунктуации.
Дополнительное задание: обработка текста, содержащего знаки пунктуации. Например, при заданном списке запрещенных слов ['C', 'Perl', 'Fortran'] предложение
'Some alternative programming languages to Python are C, C++, Perl , Fortran and Java.'

должно быть преобразовано в следующее предложение:
'Some alternative programming languages to Python are *, C++, ****, ******* and Java.'

З2.6.3. Индекс подобия Земле (Earth Similarity Index – ESI) – попытка вычисления физического сходства какого-либо астрономического тела (обычно планеты
или луны (спутника)) с Землей. Индекс подобия Земле определяется по формуле


x −x
ESI j = ∏ 1 − i , j i ,⊕

xi , j + xi ,⊕
i =1 
n






wi / n

.,

Параметры xi,j, их значения для Земли xi,⊕ и весовые коэффициенты wi приведены в табл. 2.15. Значения радиуса, плотности и космической скорости приняты относительно аналогичных значений для Земли. Значение ESI находится
в интервале от 0 до 1. Значения, более близкие к 1, означают большее сходство
с Землей (которая обладает индексом ESI, в точности равным 1: Земля идентична самой себе).
Таблица 2.15. Параметры, используемые для вычисления ESI
i

Параметр

Значение для Земли, xi,⊕

Весовой коэффициент, wi

1

Радиус

1.0

0.57

2

Плотность

1.0

1.07

3

Космическая скорость, vesc

1.0

0.7

4

Температура поверхности

288 К

5.58

Файл ex2-6-g-esi-data.txt доступен здесь: https://scipython.com/ex/bbc. В нем
содержатся вышеупомянутые параметры для некоторой группы астрономических тел. Использовать эти данные для вычисления индекса ESI для каждого
из этих тел. Какое из оцениваемых астрономических тел обладает свойствами,
наиболее схожими со свойствами Земли?

94



Ядро языка Python I

З2.4.6. Написать программу для считывания двумерного массива строк в список списков из файла, в котором элементы строк разделены одним или несколькими пробелами. При открытии файла может быть заранее неизвестно
количество строк m и количество столбцов n.
Например, для текстового файла
A B C D
E F G H
I J K L

должен быть создан объект grid в следующем виде:
[['A', 'B', 'C', 'D'], ['E', 'F', 'G', 'H'], ['I', 'J', 'K', 'L']]

При таком способе считывания grid содержит список строк исходного массива. После завершения считывания массива написать циклы для вывода массива по столбцам:
[['A', 'E', 'I'], ['B', 'F', 'J'], ['C', 'G', 'K'], ['D', 'H', 'L']]

Более сложная задача: вывести также все диагонали, считываемые в одном
направлении:
[['A'], ['B', 'E'], ['C', 'F', 'I'], ['D', 'G', 'J'], ['H', 'K'], ['L']]

и в другом направлении:
[['D'], ['C', 'H'], ['B', 'G', 'L'], ['A', 'F', 'K'], ['E', 'J'], ['I']]

2.7 Функции
В Python функция (function) – это набор инструкций, сгруппированных в единый блок, которому присвоено некоторое имя, благодаря чему можно многократно выполнять этот блок в программе. Использование функций дает два
основных преимущества. Во-первых, функции позволяют многократно использовать фрагмент кода без копирования его в различные части программы, а во-вторых – функции позволяют разделять сложные задачи на отдельные
процедуры, каждая из которых реализована собственной функцией: зачастую
намного проще (в том числе и с точки зрения сопровождения) отдельно писать
код для каждой процедуры, нежели кодировать всю задачу в целом.

2.7.1 Определение и вызов функций
Ключевое слово def определяет функцию, присваивает ей имя и задает список
аргументов (если это необходимо), который эта функция предположительно
принимает при вызове. Инструкции в теле функции записываются в блоке со
смещением вправо после строки с определением def. Если в какой-то момент
выполнения этого блока инструкций встречается команда return, то вызывающей стороне возвращается определенное значение. Например:

2.7 Функции  95
>>> def square(x):
...
x_squared = x**2
...
return x_squared
...
>>> number = 2
>>> number_squared = square(number)
>>> print(number , 'squared is', number_squared)
2 squared is 4
>>> print('8 squared is', square(8))
8 squared is 64





❶ Простая функция с именем square принимает один аргумент x. Функция

вычисляет выражение x**2 и возвращает вычисленное значение вызывающей стороне. Функция определяется один раз, но может вызываться
многократно.
❷ В первом примере возвращаемое значение присваивается переменной
number_squared.
❸ Во втором примере возвращаемое значение передается прямо в метод
print для вывода в консоли.

Для возвращения двух и более значений из функции необходимо упаковать
их в кортеж tuple. Например, в следующей программе определяется функция,
возвращающая оба корня квадратного уравнения ax2 + bx + c (предполагается,
что уравнение имеет два действительных корня):
import math
def roots(a, b, c):
d = b**2 - 4*a*c
r1 = (-b + math.sqrt(d)) / 2 / a
r2 = (-b - math.sqrt(d)) / 2 / a
return r1, r2
print(roots(1., -1., -6.))

При выполнении эта программа выводит вполне ожидаемый результат:
(3.0, -2.0)

Но функция не обязательно должна явно возвращать какой-либо объект:
функции, которые приходят к концу своего внутреннего блока инструкций без
обнаружения команды return, возвращают специальное значение Python None.
Определения функций могут располагаться в любом месте Pythonпрограммы, но функцию нельзя вызывать до ее определения. Функции могут
даже быть вложенными, но функция, определенная внутри другой функции,
недоступна (напрямую) за пределами содержащей ее (внешней) функции.

Строки документирования docstrings
Функция docstring – это строковый литерал, который представляет собой самую первую инструкцию в определении функции. Этот литерал должен быть
записан как текст в тройных кавычках на одной строке, если функция простая,

96



Ядро языка Python I

или на нескольких строках с начальной однострочной аннотацией и дальнейшим более подробным описанием более сложных функций. Например:
def roots(a, b, c):
"""Return the roots of ax^2 + bx + c."""
d = b**2 - 4*a*c
...



❶ Перевод docstring: """Возвращает корни квадратного уравнения ax^2 + bx +
c.""". Профессиональные программисты в основном соблюдают согла-

шение о том, что комментарии, docstrings и встроенная документация
должны быть написаны на английском языке. – Прим. перев.

Эта строка документации становится специальным атрибутом __doc__ соответствующей функции:
>>> roots.__doc__
'Return the roots of ax^2 + bx + c.'

Строка документации docstring должна содержать подробную информацию
о том, как использовать данную функцию: какие аргументы в нее передаются и какие объекты она возвращает29, но в общем случае не должна включать
по­дробности об особенной реализации алгоритмов, используемых этой функцией (лучше всего разместить такое описание в обычных комментариях, начинающихся с символа #).
Строки docstrings также используются для создания документации по классам и модулям (см. разделы 4.5 и 4.6.2).
Пример П2.26. В Python функции являются объектами первого класса (first class
objects): функции могут иметь связанные с ними идентификаторы переменных, функции могут передаваться как аргументы в другие функции и даже могут возвращаться
из других функций. Функции присваивается имя, по которому она определяется, но
это имя может быть переприсвоено для ссылки на другой объект, если такое необходимо. (Подобное переприсваивание не рекомендуется, кроме тех случаев, когда вы
точно знаете, что делаете.)
>>> def cosec(x):
...
"""Return the cosecant of x, cosec(x) = 1/sin(x)."""
...
return 1./math.sin(x)
...
>>> cosec

>>> cosec(math.pi/4)
1.4142135623730951
>>> csc = cosec
>>> csc

>>> csc(math.pi/4)
1.4142135623730951
29





В крупных проектах строки документации docstrings описывают прикладной программный интерфейс (API) проекта.

2.7 Функции  97
❶ Перевод docstring: """Возврат косеканса x, cosec(x) = 1/sin(x).""" – Прим.

перев.

❷ Операция присваивания csc = cosec присваивает идентификатор (имя

переменной) csc тому же объекту функции, на который ссылается идентификатор cosec: теперь эту функцию можно вызывать и как csc(), и как
cosec().

2.7.2 Аргументы по умолчанию и именованные аргументы
Именованные аргументы
В предыдущем примере аргументы передавались в функцию в том порядке,
в котором они были заданы в определении этой функции (в этом случае они
называются позиционными аргументами). Также возможна передача аргументов в произвольном порядке, если передавать их явно как именованные
аргументы (keyword arguments):
roots(a=1., c=-6., b=-1.)
roots(b=-1., a=1., c=-6.)

При совместной передаче неименованных (позиционных) и именованных
аргументов позиционные аргументы непременно должны записываться первыми, иначе Python не узнает, какой переменной соответствует позиционный
аргумент:
>>> roots(1., c=6., b=-1.) # Это правильная передача аргументов.
(3.0, -2.0)
>>> roots(b=-1., 1., -6.) # Ошибка: не понятно, какой аргумент предназначен для a, а какой

# для c.
File "", line 1
SyntaxError: non-keyword arg after keyword arg

Аргументы по умолчанию
Иногда необходимо определить функцию, принимающую необязательный аргумент: если вызывающая сторона не определяет значение такого аргумента,
то используется значение по умолчанию. Значение по умолчанию для подобного аргумента устанавливается в определении функции:
>>> def report_length(value , units='m'):
...
return 'The length is {:.2f} {}'.format(value , units)
>>> report_length(33.136, 'ft')
'The length is 33.14 ft'
>>> report_length (10.1)
'The length is 10.10 m'

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

98
>>>
...
...
...
>>>
[7]
>>>
[7,
>>>
[7,

 Ядро языка Python I
def func(alist = []):
alist.append(7)
return alist
func()
func()
7]
func()
7, 7]

Здесь значением аргумента по умолчанию для функции func является пус­
той список, но особенность заключается в том, что пустой список присваивается только один раз при определении этой функции. Поэтому при каждом вызове func размер этого списка увеличивается на один элемент.
Пример П2.27. Значения аргументов по умолчанию присваиваются при определении функции. Таким образом, если функция определяется с передачей аргумента,
которым по умолчанию является некоторый неизменяемый объект, то последующее
изменение значения этой переменной не будет изменять значение по умолчанию:
>>> default_units = 'm'
>>> def report_length(value , units=default_units):
...
return 'The length is {:.2f} {}'.format(value , units)
...
>>> report_length (10.1)
'The length is 10.10 m'
>>> default_units = 'cubits'
>>> report_length (10.1)
'The length is 10.10 m'

Заданные по умолчанию единицы измерения, используемые функцией report_length, не изменяются после следующего присваивания значения переменной с именем default_units: значение по умолчанию установлено как
строковый объект, связанный с именем default_units, когда компилятор Python встретил ключевое слово def (и присвоил этому объекту значение 'm'),
поэтому в дальнейшем изменить значение объекта default_units невозможно.
Это также означает, что если в качестве значения аргумента по умолчанию
присваивается изменяемый объект, то это всегда один и тот же объект, который используется при любом вызове функции без указания альтернативного
значения: см. вопрос В2.7.4 из раздела упражнений.

2.7.3 Область видимости
Функция может определять и использовать собственные переменные. Эти переменные являются локальными (local) относительно своей функции: они недоступны за пределами функции. А переменные, созданные вне пределов всех
определений функций def, являются глобальными (global) и доступны в любом
месте файла программы. Например:

2.7 Функции  99
>>> def func():
...
a = 5
...
print(a, b)
...
>>> b = 6
>>> func()
5 6

Функция func определяет локальную переменную a, но выводит обе переменные a и b. Поскольку переменная b не определена в локальной области
видимости (local scope) этой функции, Python ищет ее в глобальной области
видимости (global scope), находит b = 6, следовательно, выводится именно это
значение. Не важно, что переменная b не была определена при определении
функции, но, разумеется, переменная b должна быть обязательно определена
перед вызовом функции.
Что происходит, когда функция определяет переменную с тем же именем,
что и имя глобальной переменной? В этом случае сначала выполняется поиск
в локальной области видимости функции для разрешения конфликта имен
переменных, поэтому извлекается объект, на который указывает локальное
имя переменной. Например:
>>>
...
...
...
>>>
>>>
5
>>>
6

def func():
a = 5
print(a)
a = 6
func()
print(a)

Обратите внимание: локальная переменная a существует только в теле
функции, после этого обнаруживается, что ее имя полностью совпадает с именем глобальной переменной a. Локальная переменная исчезает после выхода
из функции и уже не замещает глобальную переменную a.
В Python правила разрешения конфликтов областей видимости можно описать аббревиатурой LEGB: сначала локальная (local – L) область видимости, затем внешняя включающая (enclosing – E) область видимости (для вложенных
функций), потом глобальная (global – G) область видимости, наконец, встроенные (built-ins – B) объекты, если так случилось, что вы дали переменной
имя, совпадающее с именем встроенной функции (например, range или len),
то конфликт имен разрешается в пользу вашей переменной (в локальной или
глобальной области видимости), а не в пользу встроенного объекта. Следовательно, далеко не лучшим решением является назначение переменным имен,
совпадающих с именами встроенных объектов.

100

 Ядро языка Python I

◊ Ключевые слова global и nonlocal
В предыдущем разделе описана возможность доступа к переменным, определенным в областях видимости, отличающихся от локальной области видимости функции. Но можно ли изменять эти переменные (связывать их с новыми
объектами)? Рассмотрим различие в поведении следующих функций:
>>> def func1():
...
print(x) # Допустимо: используемая переменная x определяется в глобальной или
# внутренней области видимости.
...
>>> def func2():
...
x += 1 # Недопустимо: запрещено изменять переменную x, если она не является локальной.
...
>>> x = 4
>>> func1()
4
>>> func2()
UnboundLocalError: local variable 'x' referenced before assignment

Если действительно требуется изменять переменные, определенные за
пределами локальной области видимости, то сначала необходимо обязательно объявить в теле функции об этом своем намерении с помощью ключевых
слов global (для переменных в глобальной области видимости) и nonlocal
(для переменных во внутренней области видимости, например когда одна
функция определена внутри другой). Для приведенного выше примера:
>>> def func2():
...
global x
...
x += 1
...
>>> x = 4
>>> func2()
>>> x
5

# Теперь допустимо: Python знает, что подразумевается x
# в глобальной области видимости.
# Ошибки нет.

Функция func2 действительно изменила значение переменной x в глобальной области видимости.
Вы должны тщательно обдумать, действительно ли необходима такая методика (не лучше ли передать x как аргумент и вернуть с помощью return
обновленное значение из функции?). Особенно в больших программах использование имен переменных в одной области видимости, которые изменяют значение (или даже тип) внутри функций, приводит к запутыванию кода,
к трудно предсказуемому поведению и к глубоко скрытым ошибкам.

2.7 Функции  101
Пример П2.28. Внимательно изучите исходный код в листинге 2.5 и попробуйте
предсказать результат до его выполнения.
Листинг 2.5. Правила определения областей видимости Python
# eg2-scope.py
def outer_func():
def inner_func():
a = 9
print('inside inner_func , a is {:d} (id={:d})'.format(a, id(a)))
print('inside inner_func , b is {:d} (id={:d})'.format(b, id(b)))
print('inside inner_func , len is {:d} (id={:d})'.format(len,id(len)))
len = 2
print('inside outer_func , a is {:d} (id={:d})'.format(a, id(a)))
print('inside outer_func , b is {:d} (id={:d})'.format(b, id(b)))
print('inside outer_func , len is {:d} (id={:d})'.format(len,id(len)))
inner_func()
a, b = 6, 7
outer_func()
print('in global scope , a is {:d} (id={:d})'.format(a, id(a)))
print('in global scope , b is {:d} (id={:d})'.format(b, id(b)))
print('in global scope , len is', len, '(id={:d})'.format(id(len)))

В этой программе определяется функция inner_func, вложенная в другую
функцию outer_func. После определения этих функций выполнение программы продолжается следующим образом:
1) инициализируются глобальные переменные a = 6 и b = 7;
2) вызывается внешняя функция outer_func:
a. функция outer_func определяет локальную переменную len = 2;
b. выводятся значения a и b – они не существуют в локальной области видимости и в какой-либо внутренней области видимости, поэтому Python
находит их в глобальной области видимости: выводятся значения этих
переменных (6 и 7);
c. выводится значение локальной переменной len (равное 2);
d. вызывается внутренняя функция inner_func:
i. определяется локальная переменная a = 9;
ii. выводится значение этой локальной переменной;
iii. выводится значение переменной b, которая не существует в локальной области видимости, поэтому Python ищет ее во внешней включающей области видимости (функции outer_func). Здесь она не найдена, поэтому Python продолжает поиск в глобальной области видимости и находит ее: выводится значение b = 7;
iv. выводится значение len – эта переменная не существует в локальной
области видимости, но во внешней включающей области видимости
определена переменная len = 2 (в функции outer_func): выводится
ее значение;

102

 Ядро языка Python I

3) п
 осле завершения выполнения функции outer_func выводятся значения
переменных a и b в глобальной области видимости;
4) выводится значение len. Эта переменная не определена в глобальной области видимости, поэтому Python выполняет поиск в собственных встроенных именах: len – встроенная функция для определения длины любой
последовательности. Сама по себе эта функция также является объектом
и предоставляет короткую строку собственного описания при выводе.
inside outer_func, a is 6 (id=232)
inside outer_func, b is 7 (id=264)
inside outer_func, len is 2 (id=104)
inside inner_func, a is 9 (id=328)
inside inner_func, b is 7 (id=264)
inside inner_func, len is 2 (id=104)
in global scope, a is 6 (id=232)
in global scope, b is 7 (id=264)
in global scope, len is (id=977)

Обратите внимание: в этом примере функция outer_func переопределяет
(возможно, неразумно) или заново связывает имя len с целочисленным объектом 2. Это означает, что исходная встроенная функция len становится недоступной в теле функции outer_func (а кроме того, недоступной и в теле вложенной функции inner_func).

2.7.4 ◊ Передача аргументов в функции
Новые пользователи Python, обладающие знаниями о других компьютерных
языках, обычно задают вопрос: аргументы передаются в функции «по значению» или «по ссылке»? Другими словами, создает ли функция собственную копию аргумента, оставляя без изменений его копию на вызывающей
стороне, или принимает «указатель» на область памяти аргумента, содержимое которой функция может изменить? Это различие важно для языков,
подобных C, но не вполне соответствует модели Python «имя-объект». Аргументы функций Python иногда (не очень удобно) обозначаются как «ссылки,
передаваемые по значению». Напомню, что в Python все является объектом,
и один и тотже объект может иметь несколько идентификаторов (которые
мы до сих пор называли просто «переменными»). Когда имя передается
в функцию, передается именно «значение», на которое указывает соответствующий объект. Возможность изменения функцией этого объекта (с точки
зрения вызывающей стороны) зависит от того, является объект изменяемым
или неизменяемым.
Пара примеров должна прояснить все окончательно. Простая функция func1,
принимающая целочисленный аргумент, получает ссылку на соответствующий целочисленный объект, с которым связывает локальное имя (которое может совпадать или не совпадать с именем глобальной переменной). Функция
не может изменить этот целочисленный объект (поскольку он является неизменяемым), поэтому любое переприсваивание локального имени просто указывает на новый объект: глобальное имя продолжает указывать на исходный
целочисленный объект.

2.7 Функции  103
>>> def func1(a):
...
print('func1: a = {},
...
a = 7
# Новое
...
print('func1: a = {},
...
>>> a = 3
>>> print('global: a = {}, id

id = {}'.format(a, id(a)))
присваивание локальной переменной a целочисленного значения 7.
id = {}'.format(a, id(a)))
= {}'.format(a, id(a)))

global: a = 3, id = 4297242592
>>> func1(a)
func1: a = 3, id = 4297242592
func1: a = 7, id = 4297242720
>>> print('global: a = {}, id = {}'.format(a, id(a)))
global: a = 3, id = 4297242592

Таким образом, функция func1 выводит 3 (внутри функции a изначально
является локальным именем для переданного исходного целочисленного объекта). Затем выводится значение 7 (то же локальное имя теперь указывает на
новый целочисленный объект с новым идентификатором), см. рис. 2.5. После
возврата из функции глобальное имя продолжает указывать на исходное значение 3.
Теперь рассмотрим передачу изменяемого объекта, например списка (list)
в функцию func2. В этом случае присваивание списку изменяет исходный объект, и все подобные изменения сохраняются после завершения вызова функции.
>>> def func2(b):
...
print('func2: b = {}, id = {}'.format(b, id(b)))
...
b.append(7)
# Добавление элемента в список.
...
print('func2: b = {}, id = {}'.format(b, id(b)))
...
>>> c = [1, 2, 3]
>>> print('global: c = {}, id = {}'.format(c, id(c)))
global: c = [1, 2, 3], id = 4361122448
>>> func2(c)
func2: b = [1, 2, 3], id = 4361122448
func2: b = [1, 2, 3, 7], id = 4361122448
>>> print('global: c = {}, id = {}'.format(c, id(c)))
global: c = [1, 2, 3, 7], id = 4361122448

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

104



Ядро языка Python I

(a)

(b)

global

a

local

a

global

a

3

local

a

7

3

Рис. 2.5. Неизменяемые объекты. Внутри функции func1: а) перед изменением присваи­
вания локальной переменной a; б) после изменения присваивания значения локальной
переменной a

(a)

global
local

(b)

global
local

c

[1, 2, 3]

b

c

[1, 2, 3, 7]

b

Рис. 2.6. Изменяемые объекты. Внутри функции func2: а) перед добавлением элемента
в список, на который указывают и глобальная переменная c, и локальная переменная b;
б) после добавления элемента в список командой b.append(7)

И все же: как передаются аргументы в Python – по значению или по ссылке?
Вероятно, самый правильный ответ таков: аргументы передаются по значению, но это значение является ссылкой на объект (который может быть изменяемым или неизменяемым).
Пример П2.29. Центральные многоугольные числа (Lazy caterer’s sequence) f(n) –
последовательность, описывающая максимальное количество кусков, на которые
можно разрезать круглую пиццу или пирог прямыми линиями при возрастающем
числе разрезов n. Очевидно, что f(0) = 1, f(1) = 2 и f(2) = 4. Для n = 3 f(3) = 7 (максимальное количество кусков получается, если линии разрезов не пересекаются в одной
общей точке). Можно показать, что применима общая рекурсивная формула
f(n) = f(n − 1) + n.
Несмотря на то что существует замкнутая форма для этой последовательности f(n) = 1/2(n2 + n + 2), можно также определить функцию увеличения списка
последовательных значений таких чисел:

2.7 Функции  105
>>>
...
...
>>>
>>>
...
...
>>>
[1,

def f(seq):
seq.append(seq[-1] + n)
seq = [1]
# f(0) = 1
for n in range(1,16):
f(seq)
print(seq)
2, 4, 7, 11, 16, 22, 29, 37, 46, 56, 67, 79, 92, 106, 121]

Список seq является изменяемым объектом, поэтому увеличивает собственный размер при каждом вызове функции f(). Переменная n, используемая
в теле функции, представляет собой имя, найденное в глобальной области видимости (это счетчик цикла for).

2.7.5 Рекурсивные функции
Функция, которая может вызывать сама себя, называется рекурсивной функцией (recursive function). Рекурсия не всегда необходима, но в некоторых ситуациях позволяет создавать изящные алгоритмы30. Например, одним из способов
вычисления факториала целого числа n ≥ 1 является определение следующей
рекурсивной функции:
>>> def factorial(n):
...
if n == 1:
...
return 1
...
return n * factorial(n - 1)
...
>>> factorial(5)
120

Здесь при вызове функции factorial(n) выполняется возврат n раз после
вызова factorial(n-1), при котором выполняется возврат n-1 раз после вызова factorial(n-2), и т. д. до тех пор, пока вызов factorial(1) не вернет 1 по
определению. Таким образом, этот алгоритм в действительности использует
рекурсивную формулу n! = n × (n − 1)!. При реализации подобных рекурсивных алгоритмов требуется особое внимание, чтобы непременно обеспечить их
останов при выполнении некоторого конкретного условия31.

30

31

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

106



Ядро языка Python I

Пример П2.30. В знаменитой задаче «Ханойская башня» рассматриваются три стержня, на одном из которых (стержень A) размещено n дисков с различными диаметрами.
Диск с наибольшим диаметром – самый нижний, над ним располагаются диски в порядке уменьшения диаметров. Задача состоит в том, чтобы переместить все диски на
третий стержень (стержень C) (расположив их в итоге в том же порядке), перемещая
по одному диску за один ход так, чтобы диск большего диаметра никогда не располагался над диском меньшего диаметра. Необходимо использовать второй стержень
(стержень B) как промежуточное временное место расположения дисков.
Эту задачу можно решить, используя следующий рекурсивный алгоритм.
Пометим диски как Di, при этом D1 – диск наименьшего размера, а Dn – диск
наибольшего размера. Тогда алгоритм выглядит так:
 переместить диски D1, D2, …, Dn-1 с A на B;
 переместить диск Dn с A на C;
 переместить диски D1, D2, …, Dn-1 с B на C.
Второй шаг представляет собой одно перемещение, но первый и третий шаги
требуют комплексного перемещения стопки из n − 1 дисков с одного стержня на
другой, а это именно та задача, которую, собственно, алгоритм и должен решить.
В коде из листинга 2.6 диски идентифицируются целыми числами 1, 2, 3, …,
хранящимися в одном из трех списков A, B и C. Начальное состояние системы: все
диски находятся на стержне A, что обозначается, например, A = [5, 4, 3, 2, 1], где
первый по индексу элемент представляет «нижний диск» на стержне, а последний
по индексу элемент – «верхний диск». По условиям задачи требуется, чтобы все
три списка всегда являлись исключительно убывающими последовательностями.
Листинг 2.6. Решение задачи «Ханойская башня»
# eg2-hanoi.py
def hanoi(n, P1, P2, P3):
""" Перемещение n дисков со стержня P1 на стержень P3. """
if n == 0:
# На этом шаге больше нет дисков для перемещения.
return
global count
count += 1
# Перемещение n - 1 дисков с P1 на P2.
hanoi(n - 1, P1, P3, P2)
if P1:
# Перемещение диска с P1 на P3.
P3.append(P1.pop())
print(A, B, C)
# Перемещение n - 1 дисков с P2 на P3.
hanoi(n - 1, P2, P1, P3)
# Инициализация состояния стержней: все n дисков находятся на стержне A.
n = 3

2.7 Функции  107
A = list(range(n, 0, -1))
B, C = [], []
print(A, B, C)
count = 0
hanoi(n, A, B, C)
print(count)

Обратите внимание: функция hanoi просто перемещает стопку дисков с одного стержня на другой: списки (представляющие стержни) передаются в нее
в определенном порядке, и функция перекладывает диски со стержня, представленного первым списком, локально обозначенным как P1, на стержень,
представленный третьим списком P3. При этом даже нет необходимости знать,
какой список обрабатывается, A, B или C.

2.7.6 Упражнения
Вопросы
В2.7.1. Каждая из приведенных ниже небольших программ пытается вывести
простую операцию суммирования двух чисел:
56
+44
----100
-----

Какие две программы работают правильно? Подробно объясните, что сделано неправильно в каждой из других программ.
а) def line():
'-----'
my_sum = '\n'.join([' 56', ' +44', line(), ' 100', line()])
print(my_sum)
б) def line():
return '-----'
my_sum = '\n'.join([' 56', ' +44', line(), ' 100', line()])
print(my_sum)
в) def line():
return '-----'
my_sum = '\n'.join([' 56', ' +44', line , ' 100', line])
print(my_sum)
г) def line():
print('-----')
print(' 56')
print(' +44')

108



Ядро языка Python I

print(line)
print(' 100')
print(line)
д) def line():
print('-----')
print(' 56')
print(' +44')
print(line())
print(' 100')
print(line())
е) def line():
print('-----')
print(' 56')
print(' +44')
line()
print(' 100')
line()

В2.7.2. В следующем фрагменте кода выполняется попытка вычисления баланса накопительного счета с годовой процентной ставкой 5 % после 4 лет при
начальном балансе 100 долларов.
>>> balance = 100
>>> def add_interest(balance , rate):
...
balance += balance * rate / 100
...
>>> for year in range(4):
...
add_interest(balance , 5)
...
print('Balance after year {}: ${:.2f}'.format(year + 1, balance))
...
Balance after year 1: $100.00
Balance after year 2: $100.00
Balance after year 3: $100.00
Balance after year 4: $100.00

Объяснить, почему этот код не работает, затем предложить правильное решение.
В2.7.3. Число харшад (число Нивена) – это целое число, которое делится нацело на
сумму своих цифр (например, 21 делится нацело на 2 + 1 = 3, следовательно, является числом харшад). Исправить следующий код, который должен возвращать
True, если n является числом харшад, или False, если n не является числом харшад:
def digit_sum(n):
""" Вычисление суммы цифр целого числа n. """
s_digits = list(str(n))
dsum = 0
for s_digit in s_digits:
dsum += int(s_digit)

2.7 Функции  109
def is_harshad(n):
return not n % digit_sum(n)

При выполнении функция is_harshad генерирует ошибку:
>>> is_harshad(21)
TypeError: unsupported operand type(s) for %: 'int' and 'NoneType'

В2.7.4. Определить и объяснить вывод при выполнении следующего кода:
def grow_list(a, lst=[]):
lst.append(a)
return lst
lst1 = grow_list(1)
lst1 = grow_list(2, lst1)
lst2 = grow_list('a')
print(lst1)
print(lst2)

Задачи
З2.7.1. В настольную игру «Скрэббл» (Scrabble; есть русская версия «Эрудит»)
играют на поле размером 15×15 клеток, строки которого обозначаются буквами (A–O), а столбцы – числами (1–15). Написать функцию, определяющую,
умещается ли слово на игровом поле, если задана позиция его первой буквы
как строка (например, 'G7'), переменная, указывающая расположение слова
по горизонтали или по вертикали, и само слово.
З2.7.2. Написать программу поиска наименьшего положительного числа n,
факториал которого не делится нацело на сумму цифр самого факториала. Например, 6 не является таким числом, потому что 6! = 720, а 720 нацело делится
на 7 + 2 + 0 = 9.
З2.7.3. Написать две функции, которые получают два списка длиной 3, представляющих трехмерные векторы a и b, вычисляют скалярное произведение
a ∙ b и векторное произведение a × b.
Написать еще две функции, возвращающие скалярное смешанное произведение a ∙ (b ∙ c) и векторное смешанное произведение a × (b × c).

З2.7.4. Правильная пирамида с высотой h и основанием, представленным правильным n-угольником с длиной стороны s, имеет объем V = 1/3Ah и общую
площадь поверхности S = A + ½nsl, где A – площадь основания, l – высота боковой грани, которую можно вычислить по апофеме многоугольника основания
a = ½s ctg(π/n) как A = ½nsa, и l = h2 + a 2 .
Использовать эти формулы для определения функции pyramid_AV, возвращающей объем V и общую площадь поверхности S при передаче в нее значений n, s и h.

110



Ядро языка Python I

З2.7.5. Дальность полета снаряда, выпущенного под углом a со скоростью v,
на ровной земной поверхности определяется по формуле
R = (v2 sin 2α) / g,
где g – ускорение свободного падения, значение которого можно принять равным 9.81 м/с2 для Земли. Максимальная высота подъема снаряда определяется
по формуле
H = (v2 sin2 α) / 2g.
(Мы пренебрегаем сопротивлением воздуха, кривизной поверхности и скоростью вращения Земли.) Написать функцию для вычисления и вывода дальности полета и максимальной высоты подъема снаряда, принимающую как
аргументы значения α и v. Протестировать функцию на значениях v = 10 м/с
и α = 30°.
З2.7.6. Написать функцию sinm_cosn, которая возвращает значение следующего определенного интеграла для целых чисел m, n > 1.
π /2


0

 ( m − 1)!!( n − 1)!! π

( m + n )!!
2

sinn θ cosmθ dθ = 
 ( m − 1)!!( n − 1)!!
в другом случае оба числа m, n четные
( m + n )!!

Совет: для вычисления двойного факториала см. задачу З2.4.6.

З2.7.7. Написать функцию, определяющую, является ли строка палиндромом
(т. е. одинаково читается в обоих направлениях), с использованием рекурсии.
З2.7.8. Тетрация (tetration) определяется как следующий гипероператор (гипер­
оператор-4) после возведения в степень. Таким образом, операцию x × n можно
записать в виде суммы x + x + x + … + x с n слагаемыми, операцию xn – в виде произведения n сомножителей x + x + x + … + x, а выражение, записанное в форме nx, равнозначно повторяющемуся возведению в степень, включающему n вхождений x:
n
2

2

4

x = xx

x
..

Например, 42 =22 = 22 = 216 = 65 536. Следует отметить, что степенная башня
вычисляется сверху вниз.
Написать рекурсивную функцию для вычисления nx и протестировать ее
(для небольших положительных действительных значений x и неотрицательных целых n, потому что тетрация генерирует очень большие числа).
Из скольких цифр состоят числа 35 и 52?

Глава

3
Небольшое отступление:
простые схемы
и диаграммы

Python становится все более распространенным языком, поэтому растет количество доступных библиотек пакетов и модулей, расширяющих функциональность этого языка. Matplotlib – одна из таких библиотек. Библиотека Matplotlib
предоставляет средства создания графических схем, которые могут включаться в приложения, отображены на экране или выведены в форме файлов изображений высокого качества для публикации.
Библиотека Matplotlib предлагает полнофункциональный объектно-ориентированный интерфейс, который более подробно описан в главе 7, но для создания простых схем в интерактивном сеансе командной оболочки упрощенный
процедурный интерфейс pyplot предоставляет удобный способ визуализации
данных. В этой короткой главе рассматривается использование pyplot вместе
с некоторыми основными функциями NumPy (библиотека NumPy более по­
дробно описана в главе 6).
В системе с установленными библиотеками Matplotlib и NumPy рекомендуется выполнять следующие инструкции импорта:
>>> import matplotlib.pyplot as plt
>>> import numpy as np

Это означает вызовы методов библиотек с префиксами plt. и np..
Замечание: в более старом модуле Python pylab была объединена функцио­
нальность модулей pyplot и numpy с возможностью импортирования всех их
функций в общее пространство имен для имитации коммерческого программного пакета MATLAB. Использование старого модуля pylab не рекомендуется,
и здесь он не рассматривается.

112



Небольшое отступление: простые схемы и диаграммы

3.1 Создание простых схем
3.1.1 Линейные графики и точечные диаграммы
Самый простой линейный (x,y) график32 создается с помощью вызова метода
plt.plot с передачей в него двух итерируемых объектов одинаковой длины
(обычно это списки числовых значений или массивы NumPy). Например:
>>>
>>>
>>>
>>>

ax = [0., 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
ay = [0.0, 0.25, 1.0, 2.25, 4.0, 6.25, 9.0]
plt.plot(ax,ay)
plt.show()

Метод plt.plot создает объект Matplot (в приведенном выше примере объект Line2D), а метод plt.show() выводит этот объект на экран. На рис. 3.1 показан результат. По умолчанию линия окрашена синим цветом.
9
8
7
6
5
4
3
2
1
0
0.0

0.5

1.0

1.5

2.0

2.5

3.0

Рис. 3.1. Простой линейный (x,y) график

Для отображения точек (x,y) в виде точечной диаграммы (диаграммы
рассеяния), а не линейного графика вызывается метод plt.scatter():
>>>
>>>
>>>
...
...
...
>>>
>>>

import random
ax, ay = [], []
for i in range(100):
ax.append(random.random())
ay.append(random.random())
plt.scatter(ax,ay)
plt.show()

Полученная в результате диаграмма показана на рис. 3.2.
32

Здесь автор включает в понятие «линейный график» (line plot) не только графики в виде
сплошной прямой линии, но и графики, сформированные в виде отдельных точек, соединенных прямыми линиями (как в приведенном ниже примере). – Прим. перев.

3.1 Создание простых схем  113
1.2
1.0
0.8
0.6
0.4
0.2
0.0
− 0.2
− 0.2

0.0

0.2

0.4

0.6

0.8

1.0

1.2

Рис. 3.2. Простая точечная диаграмма

Диаграмму можно сохранить как изображение, вызвав метод plt.
savefig(имя_файла). Требуемый формат изображения логически выводится из
расширения файла. Например:
plt.savefig('plot.png')
plt.savefig('plot.pdf')
plt.savefig('plot.eps')

# Сохранить как изображение в формате PNG.
# Сохранить как файл формата PDF.
# Сохранить в формате Encapsulated PostScript (EPS).

Пример П3.1. Рассмотрим построение графика функции y = sin2 x при −2π ≤ x ≤ 2π.
При использовании только тех средств Python, которые рассматривались в предыдущей главе, применяется описанный ниже подход.
Вычисляется и отображается 1000 точек (x,y), координаты которых сохраняются в списках ax и ay. Для формирования списка ax как абсцисс нельзя использовать range напрямую, потому что этот метод генерирует только целочисленные последовательности, так что сначала устанавливается точность
шага между каждым значением x по формуле
∆x = (xmax − xmin) / (n − 1)
(если рассматриваемые здесь n значений включают xmin и xmax, то существует
n − 1 интервалов шириной ∆x), тогда точки абсцисс вычисляются по формуле
xi = xmin + i∆x при i = 0, 1, 2, …, n − 1.
Соответствующие координаты y вычисляются по формуле
yi = sin2(xi).
Программа в листинге 3.1 реализует описанный выше подход и выводит
точки в форме простого линейного графика (см. рис. 3.3).

114

 Небольшое отступление: простые схемы и диаграммы

Листинг 3.1. Вывод графика функции y = sin2 x
# eg3-sin2x.py
import math
import matplotlib.pyplot as plt
xmin , xmax = -2. * math.pi, 2. * math.pi
n = 1000
x = [0.] * n
y = [0.] * n
dx = (xmax - xmin)/(n-1)
for i in range(n):
xpt = xmin + i * dx
x[i] = xpt
y[i] = math.sin(xpt)**2
plt.plot(x,y)
plt.show()

1.2
1.0
0.8
0.6
0.4
0.2
0.0
− 0.2
− 0.2

0.0

0.2

0.4

0.6

0.8

1.0

1.2

Рис. 3.3. График функции y = sin2 x

3.1.2 Метод linspace и векторизация
Для построения и вывода графика функции y = sin2 x в примере из предыдущего раздела потребовалось достаточно много работы, самая большая часть
которой пришлась на формирование списков x и y. Библиотека NumPy, более
подробно описанная в главе 6, может существенно упростить жизнь разработчика.
Во-первых, последовательность координат x с постоянным шагом (список x) можно создать с использованием метода linspace. Метод очень похож
на версию встроенной функции range для чисел с плавающей точкой: он принимает начальное и конечное значения, а также количество значений в последовательности и генерирует массив значений, представляющий арифме-

3.1 Создание простых схем  115
тическую прогрессию между двумя заданными числами (включая сами эти
граничные числа). Например, x = np.linspace(-5, 5, 1001) создает последовательность: −5.0, −4.99, −4.98, …, 4.99, 5.0.
Во-вторых, аналоги методов из модуля math в библиотеке NumPy могут работать с итерируемыми объектами (такими как списки или массивы NumPy). Поэтому выражение y = np.sin(x) создает последовательность значений (в действительности это массив NumPy ndarray), равных sin(xi) для каждого значения
xi из массива x:
import numpy as np
import matplotlib.pyplot as plt
n = 1000
xmin , xmax = -2*np.pi, 2*np.pi
x = np.linspace(xmin , xmax , n)
y = np.sin(x)**2
plt.plot(x,y)
plt.show()

Эта операция называется векторизацией (vectorization) и описывается более
подробно в разделе 6.1.3. Списки и кортежи можно преобразовать в объектымассивы, поддерживающие операцию векторизации, с помощью метода-конструктора array:
>>> w =
>>> w =
>>> w *
array([

[1.0, 2.0, 3.0, 4.0]
np.array(w)
100
# Каждый элемент умножается на 100.
100., 200., 300., 400.])

Для добавления второй линии в график нужно просто вызвать еще раз метод
plt.plot:
...
x = np.linspace(xmin , xmax , n)
y1 = np.sin(x)**2
y2 = np.cos(x)**2
plt.plot(x,y1)
plt.plot(x,y2)
plt.show()

Следует отметить, что после завершения вывода графика с помощью метода show или после сохранения с помощью метода savefig график становится
недоступным для повторного вывода – для этого необходимо еще раз вызвать метод plt.plot. Причина в процедурной сущности интерфейса pyplot:
каждый вызов метода pyplot изменяет внутреннее состояние объекта графика. Объект графика создается последовательными вызовами таких методов
(добавление линий, подписей и меток, установка граничных значений осей
и т. д.), затем сформированный объект графика выводится на экран или сохраняется в файле.

116



Небольшое отступление: простые схемы и диаграммы

Пример П3.2. Функция sinc описывается такой формулой:
f(x) = sin x / x.
Для построения ее графика в пределах −20 ≤ x ≤ 20 выполняется следующий
код:
>>> x = np.linspace(-20, 20, 1001)
>>> y = np.sin(x)/x
__main__:1: RuntimeWarning: invalid value encountered in true_divide
>>> plt.plot(x,y)
>>> plt.show()

Обратите внимание: несмотря на то что Python предупреждает о делении
на нуль при x = 0, график функции создается и выводится правильно: для этой
особой точки устанавливается специальное значение nan (сокращение выражения «Not a Number» – «не число»), и на графике эта точка не отображается
(см. рис. 3.4).
1.0
0.8
0.6
0.4
0.2
0.0
− 0.2
− 0.4
− 20

− 15

− 10

−5

0

5

10

15

Рис. 3.4. График функции y = sinc(x)
>>> y[498:503]
array([ 0.99893367, 0.99973335,

nan, 0.99973335, 0.99893367])

3.1.3 Упражнения
Задачи
З3.1.1. Вывести графики функций
f1(x) = ln(1 / cos2 x)

20

3.2 Метки, надписи и настройка параметров графиков  117
и

f2(x) = ln(1 / sin2 x)

в 1000 точках в диапазоне −20 ≤ x ≤ 20. Что происходит с этими функциями
в точках x = nπ/2 (n = 0, ±1, ±2, …)? Что отображается на графике в этих точках?
З3.1.2. Уравнение Михаэлиса–Ментен – наиболее известная модель ферментативных кинетических реакций:
v = d[P] / dt = Vmax[S] / (Km + [S]),
где v – скорость реакции, преобразующей субстрат S в продукт P и катализируемой ферментом. Vmax – максимальная скорость реакции (когда все ферменты
связаны с субстратом S), а константа Михаэлиса Km – это концентрация субстрата, при которой скорость реакции составляет половину от ее максимального значения.
Построить график зависимости v от [S] для реакции при Km = 0.04 М и Vmax =
0.1 М/с. Если потребуются метки для осей координат, то загляните в следующий раздел.
З3.1.3. Нормализованная гауссова функция, центрированная в точке x = 0, описывается формулой
g(x) = 1 / (σ√(2π)) exp(−x2 / 2σ2).
Вывести формы графика этой функции и сравнить их при значениях стандартного отклонения σ = 1, 1.5 и 2.

3.2 Метки, надписи и настройка параметров графиков
3.2.1 Метки и надписи
Описание графика
Для каждой линии на графике можно создать метку, передавая строковый объект в аргументе label. Но эта метка не появится на графике, если не будет вызван метод plt.legend для добавления описания графика:
plt.plot(ax, ay1, label='sin^2(x)')
plt.legend()
plt.show()

Место расположения описания по умолчанию – верхний правый угол графика, но его можно изменить, если передать в метод legend аргумент loc с одним из строковых или целочисленных значений, приведенных в табл. 3.1.

118



Небольшое отступление: простые схемы и диаграммы

Таблица 3.1. Спецификаторы места расположения описания графика
Строка

Целое число

‘best’

0

‘upper right’

1

‘upper left’

2

‘lower left’

3

‘lower right’

4

‘right’

5

‘center left’

6

‘center right’

7

‘lower center’

8

‘upper center’

9

‘center’

10

Название графика и метки осей
Графику можно присвоить название, расположенное выше осей координат,
с помощью вызова метода plt.title и передачи в него строки названия. Методы plt.xlabel и plt.ylabel управляют метками осей x и y: нужно просто передать метку как строку в эти методы. Необязательный дополнительный атрибут
fontsize устанавливает размер шрифта в пунктах. Например, приведенный
ниже код генерирует график, показанный на рис. 3.5.
t = np.linspace(0., 0.1, 1000)
Vp_uk , Vp_us = 230 * np.sqrt(2), 120 * np.sqrt(2)
f_uk , f_us = 50, 60
V_uk = Vp_uk * np.sin(2 * np.pi * f_uk * t) 
V_us = Vp_us * np.sin(2 * np.pi * f_us * t)
plt.plot(t*1000, V_uk , label='UK')
plt.plot(t*1000, V_us , label='US')
plt.title('A comparison of AC voltages in the UK and US')
plt.xlabel('Time /ms', fontsize=16.)
plt.ylabel('Voltage /V', fontsize=16.)
plt.legend()
plt.show()




❶ Напряжение вычисляется как функция от времени (t в с) в Великобри-

тании и в США, где действующее (эффективное) напряжение различно
(230 В и 120 В соответственно; кроме того, эти значения умножаются на
√2, чтобы получить двойные амплитудные значения напряжения). Также
различна частота переменного тока (50 Гц и 60 Гц).
❷ Время отображается по оси x в мс (t*1000).

3.2 Метки, надписи и настройка параметров графиков  119

Напряжение, В

300
200
100
0
100
200

UK
US

300
0

20

40
60
Время, мс

80

100

Рис. 3.5. Сравнение напряжений переменного тока в Великобритании и США

Использование LATEX в pyplot

В графиках pyplot можно использовать язык разметки LATEX, но для этого необходимо разрешить применение этой возможности в rc-настройках Matplotlib, как показано ниже:
plt.rc('text', usetex=True)

Затем нужно просто передать команду языка разметки LATEX как строку
в любую метку, выводимую этим способом. Рекомендуется использовать неформатируемые строки (r'xxx'), чтобы Python не экранировал символы с использованием обратных слешей, характерные для LATEX (см. раздел 2.3.2).
Пример П3.3. Для вывода графиков функций fn(x) = xn sin x при n = 1, 2, 3, 4 выполняется следующий код:
import matplotlib.pyplot as plt
import numpy as np
plt.rc('text', usetex=True)
x = np.linspace(-10,10,1001)
for n in range(1,5):
y = x**n * np.sin(x)
y /= max(y)
plt.plot(x,y, label=r'$x^{}\sin x$'.format(n))
plt.legend(loc='lower center')
plt.show()



❶ Для упрощения сравнения графиков они промасштабированы до макси-

мального значения 1 в рассматриваемой области значений.
Полученные графики показаны на рис. 3.6.

120

 Небольшое отступление: простые схемы и диаграммы
1.0

0.5

0.0

x1 sin x

− 0.5

x2 sin x
x3 sin x
x4 sin x

− 1.0
− 10

−5

0

5

10

Рис. 3.6. Графики функций fn(x) = xn sin x при n = 1, 2, 3, 4

3.2.2 Графики со специализированными настройками
параметров
Маркеры
По умолчанию метод plot генерирует линейный график без маркеров в отображаемых точках. Для добавления маркера в каждой точке данных на графике используется аргумент marker. Также можно определить несколько различных маркеров –
это описано в онлайновой документации (https://matplotlib.org/api/markers_api.
html). Некоторые из наиболее полезных маркеров перечислены в табл. 3.2.
Таблица 3.2. Некоторые стили маркеров библиотеки Matplotlib
Код

Маркер

Описание

.



Точка

+

Знак плюс



Кружок

×

Крестик



Ромбик



Треугольник с вершиной вниз

^



Треугольник с вершиной вверх

s



Квадратик

o
+
x
D
v

*



Звездочка

Цвета
Цвет выводимой линии и/или ее маркеров можно определить с помощью
аргумента color. Поддерживается несколько форматов определения цвета.
Во-первых, однобуквенные коды для некоторых основных цветов приведены

3.2 Метки, надписи и настройка параметров графиков  121
в табл. 3.3. Например, color='r' определяет красную линию и маркеры. Эти
цвета слишком яркие, поэтому (с версии Matplotlib 2.0) для последовательности линий на одном графике по умолчанию установлены более мягкие группы
цветов Tableau (живой цвет), строки идентификаторов которых также приведены в табл. 3.3.
Таблица 3.3. Буквенные и строковые обозначения цветов в Matplotlib
Коды основных цветов

Живые (Tableau) цвета

b = синий

tab:blue

g = зеленый

tab:orange

r = красный

tab:green

c = бирюзовый

tab:red

m = фиолетовый

tab:purple

y = желтый

tab:brown

k = черный

tab:pink

w = белый

tab:gray
tab:olive
tab:cyan

Кроме того, можно определять оттенки серого цвета как строку, представляющую
число с плавающей точкой float в диапазоне 0−1 (0. соответствует черному цвету,
1. – белому). Шестнадцатеричные строки HTML, определяющие красную, зеленую
и синюю (RGB) компоненты цвета в диапазоне 00-ff, также могут передаваться
в аргументе color (например, color=’#ff00ff’ определяет фиолетовый (magenta)
цвет). Наконец, компоненты RGB можно передавать как кортеж (tuple) из трех значений в диапазоне 0−1 (например, color=(0.5, 0., 0.) – это темно-красный цвет).

Стили и ширина линий
По умолчанию принят стиль для графика: сплошная линия шириной 1.5 пунк­
та. Для настройки этого параметра определяется аргумент linestyle (это тоже
строка). Некоторые возможные значения, определяющие стиль линии, приведены в табл. 3.4.
Таблица 3.4. Стили линий Matplotlib
Код

Стиль линии

-

Сплошная

--

Штриховая

:

Пунктирная

-.

Штрихпунктирная

Чтобы линии вообще не отображались, устанавливается значение linestyle='' (пустая строка). Толщину линии можно задать в пунктах, передавая
значение типа float в атрибуте linewidth. Например:

122

 Небольшое отступление: простые схемы и диаграммы

x = np.linspace(0.1, 1., 100)
yi = 1. / x
ye = 10. * np.exp(-2 * x)
plt.plot(x, yi, color='r', linestyle=':', linewidth=4.)
plt.plot(x, ye, color='m', linestyle='--', linewidth=2.)
plt.show()

Результат выполнения этого кода показан на рис. 3.7.
10
9
8
7
6
5
4
3
2
1
0.1

0.2

0.3

0.4

0.5

0.6

0.7

0.8

0.9

1.0

Рис. 3.7. Два различных стиля линий на одном графике

Кроме того, допустимыми являются следующие сокращенные обозначения
свойств линий графика:
 c – color – цвет;
 ls – linestyle – стиль линии;
 lw – linewidth – ширина линии.
Например:
plt.plot(x, y, c='g', ls='--', lw=2)

# Утолщенная зеленая штриховая линия.

Также возможно определение цвета, стиля линии и маркера в одной строке:
plt.plot(x, y, 'r:^')

# Красная пунктирная линия с треугольными маркерами.

Наконец, можно изобразить несколько линий, используя последовательность аргументов x, y, format:
plt.plot(x, y1, 'r--', x, y2, 'k-.')

Выводится красная штриховая линия для (x, y1) и черная штрихпунктирная
линия для (x, y2).

3.2 Метки, надписи и настройка параметров графиков  123

Границы графика
Методы plt.xlim и plt.ylim определяют границы графика по осям x и y соответственно. Они должны вызываться только после всех инструкций plt.plot,
но перед выводом или сохранением изображения. Например, следующий код
создает график по предоставленным последовательностям данных между выбранными границами (см. рис. 3.8):
t = np.linspace(0, 2, 1000)
f = t * np.exp(t + np.sin(20*t))
plt.plot(t, f)
plt.xlim(1.5,1.8)
plt.ylim(0,30)
plt.show()
30
25
20
15
10
5
0
1.5

1.55

1.60

1.65

1.70

1.75

1.80

Рис. 3.8. График, созданный с явно определенными границами данных

Пример П3.4. Закон Мура основан на следующем наблюдении: количество транзис­
торов в микросхемах центральных процессорных устройств (CPU) приблизительно
удваивается через каждые 2 года. Программа в листинге 3.2 демонстрирует действие этого закона с помощью сравнения между реальным количеством транзисторов в ЦПУ ведущих производителей с 1972 по 2012 г., затем выполняет прогнозирование по закону Мура, который можно записать в математической форме:
ni = n0 2(yi−y0)/T2,
где n0 – количество транзисторов в некотором эталонном году y0, а T2 = 2 – число
лет, необходимых для удваивания этого количества. Поскольку рассматриваются данные за 40 лет, значения ni охватывают несколько порядков величины,
поэтому удобнее применить закон Мура к логарифмам этих значений, что позволит показать линейную зависимость от y:
log10 ni = log10 n0 + ((yi − y0) / T2) log10 2.

124



Небольшое отступление: простые схемы и диаграммы

Листинг 3.2. Демонстрация действия закона Мура
# eg3-moore.py
import numpy as np
import matplotlib.pyplot as plt
# Данные - список лет:
year = [1972, 1974, 1978, 1982, 1985, 1989, 1993, 1997, 1999, 2000, 2003,
2004, 2007, 2008, 2012]
# Количество транзисторов (ntrans) в ЦПУ в млн:
ntrans = [0.0025, 0.005, 0.029, 0.12, 0.275, 1.18, 3.1, 7.5, 24.0, 42.0,
220.0, 592.0, 1720.0, 2046.0, 3100.0]
# Преобразование списка ntrans в массив NumPy и умножение каждого элемента на 1 млн.
ntrans = np.array(ntrans) * 1.e6
y0, n0 = year[0], ntrans[0]
# Линейный массив лет, охватывающий данные по отдельным годам.
y = np.linspace(y0, year[-1], year[-1] - y0 + 1)
# Время в годах, необходимое для удваивания количества транзисторов.
T2 = 2.
moore = np.log10(n0) + (y - y0) / T2 * np.log10(2)
plt.plot(year , np.log10(ntrans), '*', markersize=12, color='r',
markeredgecolor='r', label='observed')
plt.plot(y, moore , linewidth=2, color='k', linestyle='--', label='predicted')
plt.legend(fontsize=16, loc='upper left')
plt.xlabel('Year')
plt.ylabel('log(ntrans)')
plt.title("Moore's law")
plt.show()

В этом примере данные содержатся в двух списках равной длины, представляющих год и характерное (репрезентативное) количество транзисторов в ЦПУ в этом году. Приведенная выше формула закона Мура реализуется
в логарифмической форме с использованием массива лет, охватывающего
предоставленные данные. (В действительности, поскольку на логарифмической шкале это будет прямая линия, вполне достаточно всего лишь двух
точек данных.)
Для построения графика, показанного на рис. 3.9, точки данных выводятся
в виде увеличенных звездочек, а прогноз по закону Мура изображается штриховой черной линией.

3.2 Метки, надписи и настройка параметров графиков  125
9

Наблюдаемые данные
Прогнозируемые данные

log(ntrans)

8
7
6
5
4
1970 1975 1980 1985 1990 1995 2000 2005 2010
Год

Рис. 3.9. Закон Мура, моделирующий экспоненциальный рост количества транзисторов в ЦПУ

3.2.3 Упражнения
Задачи
З3.2.1. Молекула A вступает в реакцию для образования молекулы B или
C с константами скорости реакции первого порядка k1 и k2 соответственно. Таким образом:
d[A] / dt = −(k1 + k2)[A],
следовательно:
[A] = [A]0 e − (k1 + k2)t,
где [A]0 – начальная концентрация A. Значения концентрации продуктов (начиная с 0) увеличиваются в соответствии с отношением [B]/[C] = k1/k2, а закон
сохранения вещества требует соблюдения условия [B] + [C] = [A]0 − [A]. Следовательно:
[B] = k1 / (k1 + k2) [A]0 (1 − e−(k1+k2)t);
[C] = k2 / (k1 + k2) [A]0 (1 − e−(k1+k2)t).
Для реакции с коэффициентами k1 = 300 c-1 и k2 = 100 с-1 вывести график значений концентрации A, B и C в зависимости от времени с учетом начальной
концентрации реагента [A]0 = 2.0 моль/дм3.

126



Небольшое отступление: простые схемы и диаграммы

З3.2.2. Гауссово целое число – это комплексное число, действительная и мнимая части которого являются целыми числами. Гауссово простое число – это
гауссово целое комплексное число x + iy, такое, что для него выполняется одно
из следующих условий:
 одно из чисел x или y равно нулю, а второе является простым числом
в форме 4n + 3 или −(4n + 3) при некотором целом значении n ≥ 0; или
 оба числа x и y не равны нулю и x2 + y2 – простое число.
Рассмотреть последовательность гауссовых целых чисел, получаемых при
наблюдении за воображаемой частицей, изначально находящейся в состоянии c0 и перемещающейся в комплексной плоскости по следующему закону: выполняется целочисленное количество шагов в текущем направлении
(±1 в действительном или мнимом направлении), но если встречается гауссово
простое число, то выполняется поворот влево. Изначально частица ориентирована в положительном действительном направлении (∆c = 1 + 0i ⇒ ∆x = 1,
∆y = 0). Путь, который наблюдается при движении такой частицы, называется
спиралью гауссовых простых чисел (Gaussian prime spiral).
Написать программу вывода спирали гауссовых простых чисел, начиная
с c0 = 5 + 23i.
З3.2.3. Риск смерти, оцениваемый за год (принимаемый как «1 к N»), для мужчин и женщин в Великобритании в 2005 г. для различных возрастных групп
приведен в табл. 3.5. Использовать метод pyplot для вывода этих данных на
одном графике.
Таблица 3.5
Возрастные группы

Женщины

Мужчины

84

7

6

3.3 Построение более сложных графиков  127

3.3 Построение более сложных графиков
3.3.1 График в полярных координатах
Метод pyplot.plot создает график в декартовой системе координат (x,y). Для
получения графика в полярных координатах (r,θ) используется метод pyplot.
polar, в который передаются аргументы theta (обычно это независимая переменная) и r.
Пример П3.5. Кардиоида – это плоская фигура, описываемая в полярных координатах уравнением r = 2a(1 + cos θ) при 0 ≤ θ ≤ 2π:
theta = np.linspace(0, 2.*np.pi, 1000)
a = 1.
r = 2 * a * (1. + np.cos(theta))
plt.polar(theta , r)
plt.show()

Созданный этим фрагментом кода график в полярных координатах показан
на рис. 3.10.
90°
45°

135°

0.5

180°

1.0 1.5

2.0

3.03.5
2.5

225°

4.0



315°
270°

Рис. 3.10. Кардиоида при a = 1

3.3.2 Гистограммы
Гистограмма представляет распределение данных в виде последовательных
(обычно вертикальных) полос, длина которых пропорциональна числовым
значениям элементов данных, входящим в предварительно определенные
диа­пазоны (известные как интервалы гистограммы (bins)). Таким образом,
диапазон значений данных делится на интервалы, и гистограмма формируется посредством подсчета количества значений данных в каждом интервале.
В библиотеке pyplot функция hist создает гистограмму из последовательности значений данных. Количество интервалов можно передать как дополнитель-

128

 Небольшое отступление: простые схемы и диаграммы

ный необязательный аргумент bins, по умолчанию его значение равно 10. Кроме
того, по умолчанию высоты полос гистограммы равны абсолютным величинам
данных в соответствующем интервале, но установка атрибута density=True нормализует гистограмму так, что ее область (произведение высоты на ширину каждой полосы, просуммированное по всем полосам) становится единообразной.
Например, для 5000 случайных значений из нормального распределения со
средним значением 0 и стандартным отклонением 2 (см. раздел 4.5.1):
>>>
>>>
>>>
>>>
...
>>>
>>>

import matplotlib.pyplot as plt
import random
data = []
for i in range(5000):
data.append(random.normalvariate(0, 2))
plt.hist(data , bins=20, density=True)
plt.show()

Полученная гистограмма изображена на рис. 3.11.
0.20

0.15

0.10

0.05

0.00
−8

−6

−4

−2

0

2

4

6

8

10

Рис. 3.11. Гистограмма случайных данных с нормальным распределением

3.3.3 Дополнительные оси
Команда pyplot.twinx() инициализирует новый набор осей с осью x, совпадающей с первоначальной, и новой осью y. Это удобно для вывода двух и более последовательностей данных, которые совместно используют ось абсцисс
(ось x), но значения по оси y существенно отличаются по величине или представлены в других единицах измерения. Рассмотрим следующий пример.
Пример П3.6. По данным https://tylervigen.com/ существует странная и совершенно нелепая зависимость, наблюдаемая во времени, между количеством разводов
в штате Мэн (США) и потреблением маргарина на душу населения во всей стране.
Приведенные здесь две временны́е последовательности представлены в различных
единицах измерения и имеют разный смысл, поэтому они должны отображаться по
отдельным осям y, но совместно использовать общую ось x (где отмечены годы).

3.3 Построение более сложных графиков  129
Листинг 3.3. Зависимость между потреблением маргарина в США и количеством разводов
в штате Мэн
# eg3-margarine -divorce.py
import matplotlib.pyplot as plt
years = range(2000, 2010)
divorce_rate = [5.0, 4.7, 4.6, 4.4, 4.3, 4.1, 4.2, 4.2, 4.2, 4.1]
margarine_consumption = [8.2, 7, 6.5, 5.3, 5.2, 4, 4.6, 4.5, 4.2, 3.7]
line1 = plt.plot(years , divorce_rate , 'b-o', label='Divorce rate in Maine')
plt.ylabel('Divorces per 1000 people')
plt.legend()



plt.twinx()
line2 = plt.plot(years , margarine_consumption , 'r-o', label='Margarine consumption')
plt.ylabel('lb of Margarine (per capita)')
# Мы преодолели все препятствия, чтобы создать метки в том же самом блоке описания:
lines = line1 + line2
labels = []
for line in lines:
labels.append(line.get_label())
plt.legend(lines , labels)
plt.show()





9

5.2
5.0

Частота разводов в штате Мэн
Потребление маргарина в США

8

4.8

7

4.6

6

4.4

5

4.2

4

4.0

2000 2001 2002 2003 2004 2005 2006 2007 2008 2009

Потребление маргарина в фунтах
(на душу населения)

Количество разводов на 1000 человек

Пришлось проделать некоторую дополнительную работу, чтобы создать
описание, в которое включены обе строки надписей для этого графика:
❶ Метод pyplot.plot возвращает список объектов, представляющих выводимые строки, поэтому они сохраняются как line1 и line2.
❷ Эти строки объединяются.
❸ Выполняется проход в цикле по объединенным строкам, чтобы извлечь
требуемые надписи. Далее список строк и надписей можно передать непосредственно в метод pyplot.legend.
Результатом выполнения кода из листинга 3.3 является график, показанный
на рис. 3.12.

3

Рис. 3.12. Зависимость между количеством разводов в штате Мэн и потреблением
маргарина на душу населения в США

130



Небольшое отступление: простые схемы и диаграммы

3.3.4 Упражнения
Задачи
З3.3.1. Спираль можно рассматривать как фигуру, описывающую траекторию
движения точки по воображаемой прямой при одновременном вращении этой
прямой относительно начала координат с постоянной угловой скоростью. Если
точка закреплена на прямой, то описывающей фигурой будет окружность.
а) Если точка на вращающейся прямой перемещается от начала координат
с постоянной скоростью, то ее траектория соответствует архимедовой спирали. В полярных координатах уравнение архимедовой спирали записывается так: r = a + bθ. Использовать библиотеку pyplot для построения графика
спирали, определяемой параметрами a = 0, b = 2 при 0 ≤ θ ≤ 8π.
б) Если точка перемещается по вращающейся прямой со скоростью, увеличивающейся пропорционально расстоянию от начала координат, то результатом является логарифмическая спираль, уравнение которой имеет следующий вид: r = aθ. Построить график логарифмической спирали, определяемой
параметром a = 0.8, при 0 ≤ θ ≤ 8π. Логарифмическая спираль обладает свойством самоподобия: на каждом 2π витке спираль увеличивает размер, но
сохраняет форму33. Логарифмические спирали часто встречаются в природе:
от форм раковин моллюсков-наутилусов до форм космических галактик.
З3.3.2. Простая модель взаимодействия потенциалов двух атомов как функция
расстояния между ними r описывается формулой Леннарда–Джонса (LennardJones) (потенциал Леннарда–Джонса):
U(r) = B/r12 − A/r6,
где A и B – положительные константы34.
Для атомов аргона эти константы можно принять равными A = 1.024 × 10-23
Дж×нм6 и B = 1.582 × 10-26 Дж×нм12.
а) Построить график U(r). На второй оси y того же изображения построить график сил межатомного взаимодействия:
F(r) = −dU/dr = 12B/r13 − 6A/r7.
График должен показывать «самую интересную» часть этих кривых, поскольку вычисляемые значения чрезвычайно быстро увеличиваются при малых значениях r.
33

34

Швейцарский математик Якоб Бернулли (Jakob Bernoulli) был так впечатлен этим
свойством, что назвал логарифмическую спираль Spira mirabilis (чудесной спиралью)
и пожелал, чтобы на его надгробном камне была выгравирована логарифмическая
спираль с надписью «Eadem mutata resurgo» («Даже изменяясь, я остаюсь собой»).
К сожалению, на надгробный камень Бернулли по ошибке была помещенаархимедова спираль.
Такая форма записи была широко распространена во времена зарождения вычислительной техники, потому что r-12 легко вычисляется как квадрат r-6.

3.3 Построение более сложных графиков  131
Совет: вычисления можно упростить, если разделить A и B на постоянную
Больцмана 1.381 × 10-23 Дж/К, тогда U(r) будет измеряться в К (кельвинах). Какова глубина потенциальной ямы ε и расстояние минимума потенциала r0 для
этой системы?
б) При малых отклонениях от уравновешенного межатомного расстояния (на
котором F = 0) потенциал можно приблизительно представить с помощью
функции гармонического осциллятора:
V(r) = ½k(r − r0)2 + ε,
где k = |d2U/dr2|r0 = 156B/r014 − 42A/r08.
Построить графики U(r) и V(r) на одной диаграмме.
З3.3.3. Семенную шапку подсолнечника можно смоделировать следующим
образом. Определяется число n семян как s = 1, 2, …, n, и каждое семя помещается на расстоянии r = √s от центра с поворотом на угол θ = 2πs/φ относительно оси x, где φ – некоторая константа. Природа выбрала для φ золотое сечение φ = (1 + √5)/2, обеспечивающее максимально плотную упаковку семян
при росте семенной шапки подсолнечника.
Написать программу на Python для построения модели семенной шапки
подсолнечника. (Совет: использовать полярные координаты.)

Глава

4
Ядро языка Python II

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

4.1 Ошибки и исключения
Python различает два типа ошибок: синтаксические ошибки и прочие исключения (exceptions). Синтаксические ошибки – это ошибки в грамматике языка, которые выявляются перед выполнением программы. Исключения – это
ошибки времени выполнения (runtime errors): обычно они возникают при попытках выполнения недопустимой операции с некоторым элементом данных.
Различие заключается в том, что синтаксические ошибки всегда являются критическими: компилятор Python не в силах что-либо сделать, если программа
не соответствует грамматике языка. Исключения – это условия, возникающие
во время выполнения Python-программы (например, при попытке деления на
ноль), поэтому существует механизм «перехвата» и аккуратной обработки этих
условий без прекращения выполнения программы.

4.1.1 Синтаксические ошибки
Синтаксические ошибки обнаруживаются компилятором Python, при этом выводится сообщение с указанием места обнаружения ошибки. Например:
>>> for lambda in range(8):
File "", line 1
for lambda in range(8):
^
SyntaxError: invalid syntax

Так как lambda – зарезервированное ключевое слово, его нельзя использовать как имя переменной. Компилятор обнаружил его там, где ожидалось имя
переменной, поэтому возникла синтаксическая ошибка. Другой случай:

4.1 Ошибки и исключения  133
>>> for f in range(8:
File "", line 1
for f in range(8:
^
SyntaxError: invalid syntax

Здесь синтаксическая ошибка возникла, потому что во встроенную функцию range должен быть передан аргумент как целое число в круглых скобках:
двоеточие нарушает синтаксис вызова функций, поэтому Python диагностирует синтаксическую ошибку.
Поскольку строки кода Python могут быть разделены внутри скобок ("()",
"[]", "{}"), инструкция, разделенная на несколько строк, иногда может приводить к выводу сообщения SynatxError в некотором месте, отличающемся от
настоящего места расположения ошибки, например:
>>> a = [1, 2, 3, 4,
... b = 5
File "", line 4
b = 5
^
SyntaxError: invalid syntax

Здесь инструкция b = 5 синтаксически правильная: ошибка возникает изза отсутствия закрывающей квадратной скобки в предыдущем определении
спис­ка – командная оболочка Python считает, что строка является продолжением предыдущей строки, и обозначает это многоточием в начале строки (…).
Существуют два типа синтаксических ошибок SyntaxError, заслуживающих
особого внимания: ошибка IndentationError возникает при неправильном
форматировании (со сдвигом вправо) блока кода, а ошибка TabError возникает, когда символы табуляции и пробелы используются совместно, но несогласованно для сдвига блока кода35.
Пример П4.1. Самая частая синтаксическая ошибка, с которой встречаются начинающие программисты на Python, – использование оператора присваивания = вместо
оператора сравнения на равенство == в условном выражении:
>>> if a = 5:
File "", line 1
if a = 5:
^
SyntaxError: invalid syntax

Это присваивание a = 5 не возвращает значение (данная операция просто
присваивает целочисленный объект 5 имени переменной a), следовательно,
здесь отсутствует объект, соответствующий логическому значению True или
False, который может использовать оператор if, следовательно, возникает
синтаксическая ошибка SyntaxError. Это полная противоположность концепции, принятой в языке C, в котором операция присваивания возвращает
35

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

134



Ядро языка Python II

значение, присваиваемое переменной (т. е. инструкция присваивания a = 5
вычисляет значение 5, отличное от нуля, следовательно, равнозначное логическому значению True36). Такое поведение является источником многих
ошибок, которые очень трудно обнаружить, и уязвимостей с точки зрения
безопасности, поэтому оно преднамеренно исключено из языка Python на
этапе проектирования.

4.1.2 Исключения
Исключение (exception) возникает при выполнении синтаксически правильного выражения, в котором встречается ошибка времени выполнения (runtime
error). Существуют различные типы встроенных исключений, а кроме того,
возможны специализированные исключения, определяемые программистом,
если это необходимо. Если исключение не выполняет «перехват» с использованием конструкции try…except, описанной ниже, то Python выводит сообщение об ошибке (обычно осмысленное и полезное). Если исключение возникает
в теле функции (которая, возможно, в свою очередь, вызвана другой функцией и т. д.), то предъявляемое сообщение принимает форму трассировки стека в обратном направлении (stack traceback): выводится хронология вызовов
функций, которые привели к ошибке, так что можно определить место ее возникновения при выполнении программы.
С некоторыми встроенными исключениями вы уже знакомы – они встречались при использовании Python в предыдущих главах.

Исключение NameError
>>> print('4z = ', 4*z)
Traceback (most recent call last):
File "", line 1, in
NameError: name 'z' is not defined

Исключение NameError возникает, когда используется имя переменной, которое не было ранее определено: в приведенном примере команда print правильная, но Python не знает, на что указывает идентификатор z.

Исключение ZeroDivisionError
>>> a, b = 0, 5
>>> b / a
Traceback (most recent call last):
File "", line 1, in
ZeroDivisionError: float division by zero

Деление на ноль не является определенной математической операцией.

36

В языке C нет встроенных логических значений True и False. Любое отличающееся от
нуля значение считается истинным, а нулевое значение – ложным. – Прим. перев.

4.1 Ошибки и исключения  135

Исключения TypeError и ValueError
Исключение TypeError генерируется, если в выражении или в функции используется некорректный тип. Например:
>>> '00' + 7
Traceback (most recent call last):
File "", line 1, in
TypeError: Can't convert 'int' object to str implicitly

Python – (достаточно) строго типизированный язык, поэтому он не позволяет добавлять строку к целому числу37.
С другой стороны, исключение ValueError возникает, когда используемый
объект имеет правильный тип, но недопустимое значение:
>>> float('hello')
Traceback (most recent call last):
File "", line 1, in
ValueError: could not convert string to float: 'hello'

Встроенная функция float принимает строку как аргумент, поэтому выражение float('hello') не приводит к ошибке TypeError: исключение возникает, потому что конкретная строка 'hello' не может быть преобразована
в осмысленное число с плавающей точкой. Более сложный случай:
>>> int('7.0')
Traceback (most recent call last):
File "", line 1, in
ValueError: invalid literal for int() with base 10: '7.0'

Строка, выглядящая как число типа float, не может быть напрямую преобразована в тип int: для получения требуемого результата необходимо использовать выражение int(float('7.0')).
В табл. 4.1 приведен список наиболее часто встречающихся встроенных исключений с краткими описаниями.
Таблица 4.1. Часто встречающиеся исключения Python
Исключение

Причина и описание

FileNotFoundError

Попытка открыть файл или каталог, который не существует, – это исключение
представляет конкретный тип ошибки OSError

IndexError

Индексирование последовательности (например, списка или строки) с использованием индекса, выходящего за пределы диапазона

KeyError

Обращение по индексу к словарю с использованием значения ключа, которое
не существует в этом словаре (см. раздел 4.2.2)

NameError

Ссылка на локальное или глобальное имя переменной, которое не определено
предварительно

TypeError

Попытка использования объекта несоответствующего типа как аргумента встроенной операции или функции

37

В отличие, например, от языков JavaScript и PHP, где такое добавление допускается.

136



Ядро языка Python II
Окончание табл. 4.1

Исключение

Причина и описание

ValueError

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

ZeroDivisionError

Попытка деления на ноль (либо явно (с использованием оператора / или //),
либо как части операции получения остатка %)

SystemExit

Генерируется функцией sys.exit (см. раздел 4.4.1) – если это исключение не обрабатывается, то функция sys.exit выполняет выход из интерпретатора Python

Пример П4.2. Если исключение сгенерировано, но не обработано (см. раздел 4.1.3),
то Python выводит отчет с обратной трассировкой (traceback report), показывающий,
где именно в потоке выполнения программы возникла данная ошибка. Это особенно
удобно, когда ошибка возникает во вложенных функциях или внутри импортированных модулей. Например, рассмотрим следующую короткую программу38:
# exception-test.py
import math
def func(x):
def trig(x):
for f in (math.sin,
print('{f}({x})
def invtrig(x):
for f in (math.asin
print('{f}({x})
trig(x)
invtrig(x)

math.cos, math.tan):
= {res}'.format(f=f.__name__ , x=x, res=f(x)))
, math.acos , math.atan):
= {res}'.format(f=f.__name__ , x=x, res=f(x)))

func(1.2)





Функция func передает свой аргумент x в две вложенные в нее функции. Первая вложенная функция trig выполняется без проблем, но во второй вложенной
функции invtrig очевидно, что значение x находится вне домена (диапазона допустимых значений) для обратной тригонометрической функции арксинус asin:
sin(1.2) = 0.9320390859672263
cos(1.2) = 0.3623577544766736
tan(1.2) = 2.5721516221263183
Traceback (most recent call last):
File "exception-test.py", line 14, in
func(1.2)
File "exception-test.py", line 12, in func
invtrig(x)
File "exception-test.py", line 10, in invtrig
print('{f}({x}) = {res}'.format(f=f.__name__, x=x, res=f(x)))
ValueError: math domain error
38

Обратите внимание на использование конструкции f.__name__ для возврата строки,
представляющей имя функции в этой программе, например math.sin.__name__ соответствует имени функции sin.

4.1 Ошибки и исключения  137
Последовательное изучение трассировки в обратном порядке показывает,
что исключение ValueError было сгенерировано внутри вложенной функции
invtrig (строка 10, ❶), которая была вызвана из функции func (строка 12, ❷),
в свою очередь вызванной из модуля exception-test.py (т. е. из программы)
в строке 14, ❸.

4.1.3 Обработка и генерация исключений
Обработка исключений

Часто программа должна обрабатывать данные таким способом, который может стать причиной возникновения исключения. Предположим, что существует такое условие, которое не приводит к аварийному завершению программы
с ошибкой, но требует «аккуратной» обработки в определенном смысле (некорректные точки данных игнорируются, результат деления на ноль отбрасывается
и т. д.). В подобной ситуации возможно применение двух методик: проверка
значения объекта данных перед его использованием или обработка любого
сгенерированного исключения перед возобновлением выполнения программы. В Python применяется вторая методика, краткой характеристикой которой
является выражение EAFP: «It is Easier to Ask Forgiveness than to seek Permission»
(Проще попросить прощения, чем пытаться получить разрешение).
Для перехвата исключения в блоке кода необходимо поместить этот блок
кода в конструкцию try: , а код обработки любых сгенерированных исключений – в конструкцию except:. Например:
try:
y = 1 / x
print('1 /', x, ' = ',y)
except ZeroDivisionError:
print('1 / 0 is not defined.')
# ... Другие инструкции.

Не требуется никаких проверок: просто продолжается выполнение и вычисляется выражение 1/x, а ошибка, возникающая при делении на ноль, обрабатывается при необходимости. Выполнение программы продолжается
пос­ле блока except вне зависимости от того, было сгенерировано исключение
ZeroDivisionError или нет. Если возникает другое исключение (например, NameError, если переменная x не определена), то оно не будет перехвачено – это
необработанное исключение (unhandled exception), поэтому выводится сообщение об ошибке.
Для обработки более одного исключения в одном блоке except необходимо
перечислить требуемые исключения в кортеже (обязательно в скобках).
try:
y = 1. / x
print('1 /', x, ' = ',y)
except (ZeroDivisionError , NameError):
print('x is zero or undefined!)
# ... Другие инструкции.

138



Ядро языка Python II

Для отдельной обработки каждого исключения требуется несколько блоков
except:
try:
y = 1. / x
print('1 /', x, ' = ',y)
except ZeroDivisionError:
print('1 / 0 is not defined.')
except NameError:
print('x is not defined')
# ... Другие инструкции.

Предупреждение: может встречаться следующий тип конструкции:
try:
[выполняются какие-то инструкции]
except:
# Никогда так не делайте!
pass

Здесь выполняются инструкции в блоке try, но игнорируются любые сгенерированные исключения – вообще говоря, это чрезвычайно неразумное
решение, так как подобный код очень трудно сопровождать и отлаживать
(если в таком коде возникают ошибки, то вы не получите никакой информации о них). Главная цель – перехват конкретных исключений и их правильная обработка, позволяющая «проявляться» любым другим исключениям, чтобы их также можно было обработать (или не обрабатывать) другими
блоками except.
В конструкции try…except имеются два дополнительных необязательных
ключевых слова (которые при необходимости должны следовать за всеми существующими блоками except). Инструкции в блоке ключевого слова finally
выполняются всегда вне зависимости от того, было сгенерировано исключение
или нет. Инструкции в блоке ключевого слова else выполняются, только если
исключение не было сгенерировано (см. пример П4.5).

◊ Генерация исключений
Обычно исключение генерируется интерпретатором Python как результат некоторого (предусмотренного или непредвиденного) поведения программы.
Но иногда требуется, чтобы программа сама сгенерировала конкретное исключение при выполнении определенного условия. Ключевое слово raise позволяет программе принудительно сгенерировать специальное исключение
и определить особое сообщение или другие данные, связанные с этим исключением. Например:
if n % 2:
raise ValueError('n must be even!')
# Здесь можно продолжать выполнение инструкций, точно зная, что n - четное число.

Связанное с raise ключевое слово assert вычисляет условное выражение
и генерирует исключение AssertionError, если при вычислении условного выражения не получен результат, равнозначный True. Инструкции assert могут

4.1 Ошибки и исключения  139
оказаться полезными для проверки некоторого весьма важного условия в конкретный момент выполнения программы, что часто удобно при отладке.
>>> assert 2 == 2 # [Нет исключения]: 2 == 2 - результат True, поэтому ничего не происходит.
>>>
>>> assert 1 == 2 # Будет сгенерировано исключение AssertionError.
Traceback (most recent call last):
File "", line 1, in
AssertionError

Синтаксическая конструкция assert expr1, expr2 передает выражение expr2
(обычно сообщение об ошибке) в исключение AssertionError:
>>> assert 1 == 2, 'One does not equal two'
Traceback (most recent call last):
File "", line 1, in
AssertionError: One does not equal two

Python – язык с динамической типизацией, поэтому допустима передача
аргументов любого типа в функцию, даже если эта функция ожидает аргумент
конкретного типа. Иногда необходимо проверить корректность типа объекта
аргумента перед его использованием, и для этого также можно использовать
конструкцию assert.
Пример П4.3. Приведенная ниже функция возвращает представление в виде строки двумерного (2D) или трехмерного (3D) вектора, который обязательно должен быть
представлен как список (list) или кортеж (tuple), содержащий два или три элемента.
>>> def str_vector(v):
...
assert type(v) is list or type(v) is tuple ,\
...
'argument to str_vector must be a list or tuple'
...
assert len(v) in (2, 3),\
...
'vector must be 2D or 3D in str_vector'
...
unit_vectors = ['i', 'j', 'k']
...
s = []
...
for i, component in enumerate(v):
...
s.append('{}{}'.format(component , unit_vectors[i]))
...
return '+'.join(s).replace('+-', '-')



❶ Здесь метод replace('+-', '-') выполняет преобразование, например

'4i+-3j' в '4i-3j'.

140



Ядро языка Python II

Пример П4.4. Еще один пример: предположим, что имеется функция, вычисляющая векторное произведение двух векторов, представленных как объекты типа list.
Это произведение определено только для трехмерных векторов, поэтому его вызов
с передачей списков любой другой длины приводит к ошибке.
>>> def cross_product(a, b):
...
assert len(a) == len(b) == 3, 'Vectors a, b must be three-dimensional'
...
return [a[1]*b[2] - a[2]*b[1],
...
a[2]*b[0] - a[0]*b[2],
...
a[0]*b[1] - a[1]*b[0]]
...
>>> cross_product([1, 2, -1], [2, 0, -1, 3]) # Ошибка.
Traceback (most recent call last):
File "", line 1, in
File "", line 2, in cross_product
AssertionError: Vectors a, b must be three-dimensional
[Векторы a, b обязательно должны быть трехмерными]
>>> cross_product([1, 2, -1], [2, 0, -1])
[-2, -1, -4]

Пример П4.5. Ниже приведен пример использования полной конструкции try…
except…else…finally:
# try-except-else-finally.py
def process_file(filename):
try:
fi = open(filename , 'r')
except IOError:
print('Oops: couldn\'t open {} for reading'.format(filename))
return
else:
lines = fi.readlines()
print('{} has {} lines.'.format(filename , len(lines)))
fi.close()
finally:
print(' Done with file {}'.format(filename))
print('The first line of {} is:\n{}'.format(filename , lines[0]))
# Дальнейшая обработка строк.
return




process_file('sonnet0.txt')
process_file('sonnet18.txt')
❶ Внутри блока else содержимое файла считывается, только если файл был

успешно открыт.

❷ Внутри блока finally сообщение 'Done with file filename’ (Обработан

файл filename) выводится вне зависимости от того, был файл успешно
открыт или нет.

4.1 Ошибки и исключения  141
Предположим, что файл sonnet0.txt не существует, а файл sonnet18.txt
сущест­вует, тогда при выполнении этой программы выводится следующий
результат:
Oops: couldn't open sonnet0.txt for reading
Done with file sonnet0.txt
sonnet18.txt has 14 lines.
Done with file sonnet18.txt
The first line of sonnet18.txt is:
Shall I compare thee to a summer's day?

4.1.4 Упражнения
Вопросы
В4.1.1. Какой смысл имеет ключевое слово else? Почему бы не поместить инструкции блока else в начальный блок инструкций try?
В4.1.2. Какой смысл имеет ключевое слово finally? Почему бы не поместить
инструкции, которые нужно обязательно выполнить, после блока try (вне зависимости от того, было сгенерировано исключение или нет) после всей конструкции try…except?
Совет: необходимо рассмотреть, что происходит, если изменить код примера П4.5, поместив инструкции из блока finally после блока try.

Задачи
З4.1.1. Написать программу для считывания данных из файла swallow-speeds.
txt (файл доступен здесь: https://scipython.com/ex/bda) и использования этих
данных для вычисления средней скорости (свободного) полета африканской
ласточки. Использовать обработку исключений при работе со строками, содержащими некорректные точки данных.
З4.1.2. Изменить функцию из примера П4.3, которая возвращает вектор в показанной ниже форме:
>>> print(str_vector([-2, 3.5]))
-2i + 3.5j
>>> print(str_vector((4, 0.5, -2)))
4i + 0.5j - 2k

чтобы генерировалось исключение, если какой-либо элемент в массиве вектора не является действительным числом.
З4.1.3. Python соблюдает соглашение, принятое во многих языках программирования при выборе определения значения 00 = 1. Написать функцию powr(a,b),
поведение которой почти полностью совпадает с поведением выражения a**b
(или для рассматриваемого здесь случая – math.pow(a,b)), но генерирует исключение ValueError, если a и b равны нулю.

142



Ядро языка Python II

4.2 Объекты Python III: словари и множества
В Python словарь (dictionary) – это тип «ассоциативного массива» (в некоторых
языках этот тип обозначается термином «хеш» (hash)). Словарь может содержать любые объекты как значения (values), но, в отличие от таких последовательностей, как списки и кортежи, в которых элементы индексируются целыми
числами, начиная с 0, каждый элемент в словаре индексируется неповторяющимся ключом (key), который может быть любым неизменяемым объектом39.
Таким образом, словарь существует как набор пар ключ-значение (key-value).
Сами по себе словари являются изменяемыми объектами.

4.2.1 Определение и индексирование словаря
Словарь можно определить с помощью пар key: value, записанных в фигурных
скобках:
>>> height = {'Burj Khalifa': 828., 'One World Trade Center': 541.3,
'Mercury City Tower': -1., 'Q1': 323.,
'Carlton Centre': 223., 'Gran Torre Santiago': 300.,
'Mercury City Tower': 339.}
>>> height
{'Burj Khalifa': 828.0,
'One World Trade Center': 541.3,
'Mercury City Tower': 339.0,
'Q1': 323.0,
'Carlton Centre': 223.0,
'Gran Torre Santiago': 300.0}

Команда print(height) возвращает словарь в том же формате (в фигурных
скобках). Если один и тот же ключ связан с различными значениями (как 'Mercury City Tower' в приведенном выше примере), то сохраняется только самое
последнее значение: ключи в словаре не должны повторяться.
До версии Python 3.6 не обеспечивалось размещение элементов в словаре
в каком-либо определенном порядке. В версии 3.6 (и более поздних) сохраняется порядок вставки элементов. Следует отметить, что переопределение значения, связанного с ключом, как в показанном выше примере, не изменяет
порядок вставки ключа: 'Mercury City Tower' – ключ, определенный третьим,
когда ему было присвоено значение -1., позже с этим ключом было связано
другое значение 339., но он остался в третьей позиции при использовании
словаря.
Отдельный элемент можно извлечь по его ключу, применяемому в качестве
индекса, или по литеральному значению ('Q1'), или с помощью переменной,
значение которой равно ключу:
39

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

4.2 Объекты Python III: словари и множества  143
>>> height['One World Trade Center']
541.3
>>> building = 'Carlton Centre'
>>> height[building]
223.0

Элементы (значения) в словаре также можно присваивать по индексу, как
показано ниже:
height['Empire State Building'] = 381.
height['The Shard'] = 306.

Другой способ определения словаря – передача последовательности пар
(ключ, значение) в конструктор dict. Если ключами являются простые строки
(которые могут использоваться как имена переменных), то пары можно также
определять как именованные аргументы для конструктора dict:
>>> ordinal = dict([(1, 'First'), (2, 'Second'), (3, 'Third')])
>>> mass = dict(Mercury=3.301e23, Venus=4.867e24, Earth=5.972e24)
>>> ordinal[2]
# Обратите внимание: здесь 2 - ключ, а не индекс.
'Second'
>>> mass['Earth']
5.972e+24

Итеративный проход в цикле for по словарю возвращает ключи словаря
(в порядке вставки ключей):
>>> for c in ordinal:
...
print(c, ordinal[c])
...
1 First
2 Second
3 Third

Пример П4.6. Простой словарь римских цифр:
>>> numerals = {'one':'I', 'two':'II', 'three':'III', 'four':'IV', 'five':'V',
'six':'VI', 'seven':'VII', 'eight':'VIII',
1: 'I', 2: 'II', 3: 'III', 4:'IV', 5: 'V', 6:'VI', 7:'VII',
8:'VIII'}
>>> for i in ['three', 'four', 'five', 'six']:
...
print(numerals[i], end=' ')
...
III IV V VI
>>> for i in range(8,0,-1):
...
print(numerals[i], end=' ')
VIII VII VI V IV III II I

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

144



Ядро языка Python II

4.2.2 Методы словаря
Метод get()
Обращение к словарю по индексу с несуществующим ключом приводит
к ошибке:
>>> mass['Pluto']
Traceback (most recent call last):
File "", line 1, in
KeyError: 'Pluto'

Но можно использовать удобный метод get() для извлечения значения, задавая ключ, если он существует, или некоторое значение по умолчанию, если
ключ не существует. Если значение по умолчанию не задано, то возвращается
специальное значение None. Например:
>>> print(mass.get('Pluto'))
None
>>> mass.get('Pluto', -1)
-1

Методы keys, values и items
Три метода keys, values и items возвращают соответственно ключи, значения
и пары ключ-значение (в виде кортежей) словаря. В предыдущих версиях Python
эти объекты возвращались в списке, но для большинства целей это был напрасный
расход памяти: например, вызов метода keys требовал, чтобы все ключи словаря
копировались как список, по которому в большинстве случаев просто выполнялся
итеративный проход. Поэтому обычно нет необходимости в сохранении полной
новой копии ключей словаря. Версия Python 3 решает эту проблему, возвращая
итерируемый объект, который обеспечивает последовательный доступ к каждому
ключу словаря без копирования их в список. Это работает быстрее и позволяет
экономить память (что важно для весьма больших словарей). Например:
>>> planets = mass.keys()
>>> print(planets)
dict_keys(['Mercury', 'Venus', 'Earth'])
>>> for planet in planets:
...
print(planet , mass[planet])
...
Mercury 3.301e+23
Venus 4.867e+24
Earth 5.972e+24

По объекту dict_keys можно выполнять итеративный проход любое число
раз, но это не список, поэтому его невозможно индексировать и нельзя выполнять операции присваивания:
>>> planets = mass.keys()
>>> planets[0]
Traceback (most recent call last):
File "", line 1, in
TypeError: 'dict_keys' object is not subscriptable

4.2 Объекты Python III: словари и множества  145
Если действительно необходим список ключей словаря, то нужно просто передать объект dict_keys в конструктор списка list (который принимает любой
тип последовательности и создает из нее список):
>>> planet_list = list(mass.keys())
>>> planet_list
['Mercury', 'Venus', 'Earth']
>>> planet_list[0]
'Mercury'
>>> planet_list[1] = 'Jupiter'
>>> planet_list
['Mercury', 'Jupiter', 'Earth']



❶ Это последнее присваивание изменяет только список planet_list, но

ключи исходного словаря не изменяются.

Существуют аналогичные методы для извлечения значений и элементов
(пар ключ-значение) словаря: возвращаются объекты dict_values и dict_
items. Например:
>>> mass.items()
dict_items([('Mercury', 3.301e+23), ('Venus', 4.867e+24), ('Earth', 5.972e+24)])
>>> mass.values()
dict_values ([3.301e+23, 4.867e+24, 5.972e+24])
>>> for planet_data in mass.items():
...
print(planet_data)
...
('Mercury', 3.301e+23)
('Venus', 4.867e+24)
('Earth', 5.972e+24)

Пример П4.7. Словарь Python можно использовать как простую базу данных. В лис­
тинге 4.1 показан код, сохраняющий информацию о некоторых астрономических
объектах в словаре, состоящем из кортежей, где ключами являются имена объектов.
При обработке создается список значений плотности планет.
Листинг 4.1. Обработка астрономических данных
# eg4-astrodict.py
import math
# Масса (в кг) и радиус (в км) для некоторых астрономических тел.
body = {'Sun': (1.988e30, 6.955e5),
'Mercury': (3.301e23, 2440.),
'Venus': (4.867e+24, 6052.),
'Earth': (5.972e24, 6371.),
'Mars': (6.417e23, 3390.),
'Jupiter': (1.899e27, 69911.),
'Saturn': (5.685e26, 58232.),
'Uranus': (8.682e25, 25362.),
'Neptune': (1.024e26, 24622.)
}

146



Ядро языка Python II

planets = list(body.keys())
# Солнце - это не планета.
planets.remove('Sun')
def calc_density(m, r):
""" Returns the density of a sphere with mass m and radius r. """
# """ Возвращает плотность шара с массой m и радиусом r. """
return m / (4/3 * math.pi * r**3)
rho = {}
for planet in planets:
m, r = body[planet]
# Вычисление плотности планеты в г/см3.
rho[planet] = calc_density(m*1000, r*1.e5)
for planet , density in sorted(rho.items()):
print('The density of {0} is {1:3.2f} g/cm3'.format(planet , density))



❶ Метод sorted(rho.items()) возвращает список пар ключ-значение сло-

варя rho, отсортированный по ключу. Ключи – это строки, поэтому в рассматриваемом здесь примере сортировка создает список ключей в алфавитном порядке.

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

density
density
density
density
density
density
density
density

of
of
of
of
of
of
of
of

Earth is 5.51 g/cm3
Jupiter is 1.33 g/cm3
Mars is 3.93 g/cm3
Mercury is 5.42 g/cm3
Neptune is 1.64 g/cm3
Saturn is 0.69 g/cm3
Uranus is 1.27 g/cm3
Venus is 5.24 g/cm3

◊ Именованные аргументы
В разделе 2.7 рассматривался синтаксис передачи аргументов в функции. При
этом предполагалось, что функция всегда должна знать, какие аргументы могут передаваться, и эти аргументы указывались в определении функции. Например:
def func(a, b, c):

Python предоставляет несколько удобных функциональных возможностей
для обработки случаев, когда не обязательно знать, какие аргументы будет
принимать функция. При включении *args (после всех «формально определенных» аргументов) любой дополнительный позиционный аргумент помещается в кортеж args, как показано в следующем коде:
>>> def func(a, b, *args):
...
print(args)
...
>>> func(1, 2, 3, 4, 'msg')
(3, 4, 'msg')

4.2 Объекты Python III: словари и множества  147
Таким образом, в теле функции func в дополнение к формальным аргументам a=1 и b=2 доступны также аргументы 3, 4 и 'msg' как элементы кортежа
args. Этот кортеж может иметь произвольную длину. В Python встроенная
функция print работает аналогичным образом: она принимает произвольное
количество аргументов для вывода их как строки, а за этим кортежем аргументов следуют некоторые дополнительные именованные аргументы:
def print(*args , sep=' ', end='\n', file=None):

Кроме того, можно собрать произвольные именованные аргументы (см. раздел 2.7.2), передаваемые в функцию, в словаре, используя для этого синтаксис
**kwargs в определении функции. Python собирает все именованные аргументы, не указанные в определении функции, и упаковывает их в словарь kwargs.
Например:
>>>
...
...
...
>>>
d =
s =
c =

def func(a, b, **kwargs):
for k in kwargs:
print(k, '=', kwargs[k])
func(1, b=2, c=3, d=4, s='msg')
4
msg
3

Можно также использовать *args и **kwargs при вызове функции, что может оказаться удобным, например, для функций, принимающих большое
количест­во аргументов:
>>>
...
...
...
>>>
>>>
>>>
1 2
4 5

def func(a, b, c, x, y, z):
print(a, b, c)
print(x, y, z)
args = [1, 2, 3]
kwargs = {'x': 4, 'y': 5, 'z': 'msg'}
func(*args , **kwargs)
3
msg

◊ Объект defaultdict
В обычных словарях Python попытка извлечения значения с использованием
несуществующего ключа генерирует исключение KeyError. Существует полезный контейнер defaultdict, который создает подкласс встроенного объекта
dict, позволяющий определить default_factory, функцию, возвращающую
значение по умолчанию, присваиваемое отсутствующему ключу.

148



Ядро языка Python II

Пример П4.8. Для анализа длины слов в первой строке Геттисбергской речи (Авраама Линкольна) с помощью обычного словаря требуется код для перехвата исключения KeyError и установки значения по умолчанию:
text = 'Four score and seven years ago our fathers brought forth on this
continent, a new nation, conceived in Liberty, and dedicated to the proposition
that all men are created equal'
text = text.replace(',', '').lower()

# Удаление знаков препинания.

word_lengths = {}
for word in text.split():
try:
word_lengths[len(word)] += 1
except KeyError:
word_lengths[len(word)] = 1
print(word_lengths)

Использование defaultdict в этом случае позволяет написать более компактный и удобочитаемый код:
from collections import defaultdict
word_lengths = defaultdict(int)
for word in text.split():
word_lengths[len(word)] += 1
print(word_lengths)




❶ Обратите внимание: defaultdict не является встроенным объектом – он

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

❷ Здесь для функции default_factory определяется возврат значения типа

int: если ключ отсутствует, то он будет вставлен в словарь и инициализирован с помощью вызова int(), который возвращает 0.

При выполнении приведенного выше кода выводится следующий результат:
defaultdict(, {4: 3, 5: 5, 3: 9, 7: 4, 2: 3, 9: 3, 1: 1, 6: 1, 11: 1})

4.2.3 Множества
Множество set – это неупорядоченный набор неповторяющихся элементов.
Как и ключи словарей, элементы множества обязательно должны быть хешируемыми объектами. Множество удобно использовать для удаления повторяющихся элементов из последовательности и для определения объединения
(union), пересечения и разности между двумя наборами элементов. Поскольку
элементы не упорядочены, объекты типа set нельзя индексировать и выполнять в них операцию вырезания (slice), но можно производить итеративный
проход по множеству, проверять наличие элемента. Множества поддерживают
встроенную функцию len. Объект set создается с помощью перечисления его
элементов в фигурных скобках ({…}) или при передаче итерируемого объекта
в конструктор set():

4.2 Объекты Python III: словари и множества  149
>>> s = set([1, 1, 4, 3, 2, 2, 3, 4, 1, 3, 'surprise!'])
>>> s
{1, 2, 'surprise!', 3, 4}
>>> len(s)
# Мощность множества.
5
>>> 2 in s, 6 not in s
# Проверка на наличие и на отсутствие элемента в множестве.
(True , True)
>>> for item in s:
...
print(item)
...
1
2
surprise!
3
4

Метод множества add используется для добавления элементов. Для удаления
элементов существует несколько методов: remove удаляет заданный элемент,
но генерирует исключение KeyError, если элемент отсутствует в множестве.
Метод discard() делает то же самое, но не генерирует исключение. Оба метода принимают (как единственный аргумент) удаляемый элемент. Метод pop
(без аргументов) удаляет (и возвращает) произвольный элемент из множества,
а метод clear удаляет все элементы:
>>> s = {2,-2,0}
>>> s.add(1)
>>> s.add(-1)
>>> s.add(1.0)
>>> s
{0, 1, 2, -1, -2}
>>> s.remove(1)
>>> s
{0, 2, -1, -2}
>>> s.discard(3)
>>> s
{0, 2, -1, -2}
>>> s.pop()
0
>>> s
{2, -1, -2}
>>> s.clear()
set()



# OK - ничего не делает.

# (например)

# Пустое множество.

❶ Эта инструкция не добавляет новый элемент в множество, даже если су-

ществующий элемент 1 является целым числом, а добавляемый элемент
имеет тип float. Проверка 1 == 1.0 дает результат True, поэтому 1.0 считается уже существующим в этом множестве.

Объекты set имеют обширный набор методов, соответствующих свойствам математических множеств. Наиболее полезные методы перечислены
в табл. 4.2, и при их описании используются следующие термины теории множеств:

150



Ядро языка Python II

 мощность (cardinality) множества |A| – это количество элементов, содержащихся в множестве;
 два множества равны (equal), если они содержат одинаковые элементы;
 множество A является подмножеством (subset) множества B (A ⊆ B), если
все элементы A также являются элементами B, тогда B называется надмножеством, или супермножеством (superset) множества A;
 множество A является истинным, или строгим, подмножеством (proper
subset) множества B (A ⊂ B), если A является подмножеством B, но не равно ему, тогда B называется истинным, или строгим, надмножеством
(proper superset) множества A;
 объединение (union) двух множеств (A ∪ B) – это множество всех элементов из множеств A и B;
 пересечение (intersection) двух множеств (A ∩ B) – это множество всех
элементов, которые содержатся и в A, и в B;
 разность (difference) множества A и множества B (A \ B) – это множество
элементов A, которых нет в B;
 симметрическая разность (symmetric difference) двух множеств A ∆ B –
это множество элементов, содержащихся в одном из множеств A или B,
но не в обоих множествах;
 два множества называются непересекающимися (disjoint), если они
не имеют общих элементов.
Таблица 4.2. Методы множества set
Метод

Описание

isdisjoint(other)

Множество set не пересекается с множеством other?

issubset(other),
set = other

Множество set является надмножеством other?

set > other

Множество set является истинным (строгим) надмножеством
other?

union(other),
set | other | …

Объединение множества set и множества other (возможно,
нескольких множеств)

intersection(other),
set & other & …

Пересечение множества set и множества other (возможно, нескольких множеств)

difference(other),
set - other - …

Разность множества set и множества other (возможно, нескольких множеств)

symmetric_difference(other),
set ^ other ^ …

Симметрическая разность множества set и множества other
(возможно, нескольких множеств)

Существуют две формы для большинства выражений с использованием
множеств set: синтаксис операторов требует, чтобы все аргументы (операнды)
были объектами типа set, тогда как явные вызовы методов выполняют преобразование любого итерируемого аргумента в множество set.

4.2 Объекты Python III: словари и множества  151
>>> A = set((1, 2, 3))
>>> B = set((1, 2, 3, 4))
>>> A >> A.issubset((1, 2, 3, 4))
True

# OK: (1, 2, 3, 4) преобразуется в множество.

Еще несколько примеров:
>>> C, D = set((3, 4, 5, 6)), set((7, 8, 9))
>>> B | C
# Объединение.
{1, 2, 3, 4, 5, 6}
>>> A | C | D
# Объединение трех множеств.
{1, 2, 3, 4, 5, 6, 7, 8, 9}
>>> A & C
# Пересечение.
{3}
>>> C & D
set()
# Пустое множество.
>>> C.isdisjoint(D)
True
>>> B - C
# Разность.
{1, 2}
>>> B ^ C
# Симметрическая разность.
{1, 2, 5, 6}

◊ Объекты frozenset
Множества set – изменяемые объекты (можно добавлять и удалять элементы
в множестве), поэтому их невозможно хешировать, следовательно, нельзя использовать как ключи словарей или как члены других множеств.
>>> a = set((1, 2, 3))
>>> b = set(('q', (1, 2), a))
Traceback (most recent call last):
File "", line 1, in
TypeError: unhashable type: 'set'
>>>

(По той же причине списки list не могут быть ключами словарей и элементами множеств.) Но существует объект frozenset, являющийся типом неизменяемого (и хешируемого) множества40. Объекты frozenset – это неизменяемые
неупорядоченные наборы неповторяющихся объектов, и они могут использоваться как ключи словарей и элементы других множеств.
>>>
>>>
>>>
>>>

a = frozenset((1, 2, 3))
b = set(('q', (1, 2), a))
b.add(4)
a.add(4)

# OK: frozenset - хешируемый объект.
# OK: b - обычное множество.
# Недопустимо: frozenset - неизменяемый объект.

Traceback (most recent call last):
File "", line 1, in
AttributeError: 'frozenset' object has no attribute 'add'
40

В том смысле, что frozenset можно сравнивать с множествами set так же, как кортежи можно сравнивать со списками.

152



Ядро языка Python II

Пример П4.9. Простое число Мерсенна (Mercenne prime) Mi – это простое число
вида Mi = 2i − 1. Множество чисел Мерсенна, меньших некоторого числа n, можно представить как пересечение множества всех простых чисел, меньших n, Pn
с множест­вом An целых чисел, соответствующих условию 2i − 1 < n.
Программа в листинге 4.2 возвращает список простых чисел Мерсенна,
меньших 1 000 000.
Листинг 4.2. Простые числа Мерсенна
import math
def primes(n):
""" Return a list of the prime numbers f8')
In [x]: b = np.zeros((3,3), dtype=''}, va='center')
ax.annotate('thick blue arrow', xy=(0.45, 0.4), xytext=(0.6, 0.4),
arrowprops={'arrowstyle': '->', 'lw': 4, 'color': 'blue'},
va='center')
ax.annotate('double -headed arrow', xy=(0.45, 0.5), xytext=(0.01, 0.5),
arrowprops={'arrowstyle': ''}, va='center')
ax.annotate('arrow with closed head', xy=(0.55, 0.6), xytext=(0.1, 0.6),
arrowprops={'arrowstyle': '-|>'}, va='center')
ax.annotate('a really thick red arrow\nwith not much space', xy=(0.65, 0.7),
xytext=(0.1, 0.7), va='center', multialignment='right',
arrowprops={'arrowstyle': '-|>', 'lw': 8, 'ec': 'r'})
ax.annotate('a really thick red arrow\nwith space between\nthe tail and the'
'label', xy=(0.85, 0.9), xytext=(0.1, 0.9), va='center',
multialignment='right',
arrowprops={'arrowstyle': '-|>', 'lw': 8, 'ec': 'r', 'shrinkA': 10})
plt.show()

7.4 Аннотации для графиков  383
1.0

0.8

0.6

0.4

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

толстая синяя стрелка
стрелка со свойствами
по умолчанию

0.2

0.0
0.0

штриховая линия
линия со свойствами
по умолчанию

0.2

0.4

0.6

0.8

1.0

Рис. 7.15. Пример использования различных стилей стрелок

Пример П7.14. Еще один пример графика с аннотацией – курс акций BP plc (компании British Petroleum) (LSE: BP) с добавлением пары значительных событий. Необходимые данные для этого примера можно скачать с сайта Yahoo! Finance: https://
uk.finance.yahoo.com/q/hp?s=BP.L.
Листинг 7.15. Изображение временно́й последовательности курса акций на графике с аннотацией
import datetime
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.dates import strpdate2num
from datetime import datetime
def date_to_int(s):
epoch = datetime(year=1970, month=1, day=1)
date = datetime.strptime(s, '%Y-%m-%d')
return (date - epoch).days



def bindate_to_int(bs):
return date_to_int(bs.decode('ascii'))
dt = np.dtype([('daynum','i8'), ('close', 'f8')])
share_price = np.loadtxt('bp-share -prices.csv', skiprows=1, delimiter=',',
usecols=(0, 4), converters={0: bindate_to_int}, dtype=dt)
fig, ax = plt.subplots()
ax.plot(share_price['daynum'], share_price['close'], c='g')
ax.fill_between(share_price['daynum'], 0, share_price['close'], facecolor='g',
alpha=0.5)
daymin, daymax = share_price['daynum'].min(), share_price['daynum'].max()
ax.set_xlim(daymin, daymax)
price_max = share_price['close'].max()



384



Библиотека Matplotlib

def get_xy(date):
""" Return the (x, y) coordinates of the share price on a given date. """
# """Возвращает координаты (x, y) курса акций в конкретную дату. """
x = date_to_int(date)
return share_price[np.where(share_price['daynum']==x)][0]
# Горизонтальная стрелка и надпись.
x, y = get_xy('1999-10-01')
ax.annotate('Share split', (x, y), xytext = (x+1000, y), va='center',
arrowprops=dict(facecolor='black', shrink=0.05))
# Вертикальная стрелка и надпись.
x, y = get_xy('2010-04-20')
ax.annotate('Deepwater Horizon\noil spill', (x, y), xytext = (x, price_max*0.9),
arrowprops=dict(facecolor='black', shrink=0.05), ha='center')
years = range(1989, 2015, 2)
ax.set_xticks([date_to_int('{:4d}-01-01'.format(year)) for year in years])
ax.set_xticklabels(years , rotation=90)
plt.show()



❶ Необходимы дополнительные действия для считывания столбца дан-

ных: сначала декодируется считываемая из файла строка байтов в кодировку ASCII (bindate_to_int), затем используется метод datetime (см.
раздел 4.5.3) для преобразования в целое число дней, прошедших с некоторой контрольной даты (эпохи): здесь выбрано использование эпохи
Unix 1 января 1970 г. (date_to_int).
❷ Метод ax.fill_between выполняет заливку одним цветом области под
линией графика.
❸ Подписи для меток года поворачиваются (ориентируются по вертикали), чтобы обеспечить достаточное место для их размещения (читаются снизу вверх).
На рис. 7.16 показан итоговый график.
1400

Взрыв и разлив
нефти на платформе
Deepwater Horizon
Дробление
номинала
акций

1200
1000
800
600
400

2013

2011

2009

2007

2005

2003

2001

1999

1997

1993

1995

1989

0

1991

200

Рис. 7.16. Курс акций компании BP plc на графике с аннотациями

7.4 Аннотации для графиков  385

7.4.3 Линии и перекрывающие прямоугольники
Добавить произвольную прямую линию в график Matplotlib можно простой
передачей данных, соответствующих начальной и конечной точкам этой линии в метод ax.plot, например:
ax.plot([x1, x2], [y1, y2], color='k', lw=2)

Эта команда изображает отрезок прямой между точками (x1, y1) и (x2, y2).
Разумеется, такой способ становится утомительным, если требуется изобразить много не связанных друг с другом линий, но для горизонтальных и вертикальных линий существует пара удобных методов ax.hlines и ax.vlines. Метод
ax.hlines принимает обязательные аргументы y, xmin, xmax и изображает горизонтальные линии с координатами y для каждого значения из последовательности y (если значение y передано как скаляр, то изображается одна линия).
Аргументы xmin и xmax определяют начало и конец каждой линии, онимогут
быть скалярными значениями (в этом случае все линии имеют одинаковые
начальные и конечные координаты x) или последовательностью (отдельных
значений для каждой координаты, заданной в аргументе y). Метод ax.vlines
изображает вертикальные линии, а его обязательные аргументы x, ymin, ymax
аналогичны по смыслу аргументам метода ax.xlines.
Пример П7.17. Код в листинге 7.16 демонстрирует некоторые варианты применения методов ax.vlines и ax.hlines (см. рис. 7.17).

Рис. 7.17. Фигура, созданная из вертикальных и горизонтальных линий
Листинг 7.16. Некоторые варианты применения методов ax.vlines и ax.hlines
# eg7-circle -lines.py
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()

386



Библиотека Matplotlib

ax.axis('equal')
# Круг, сформированный из горизонтальных линий.
y = np.linspace(-1, 1, 100)
xmax = np.sqrt(1 - y**2)
ax.hlines(y, -xmax, xmax, color='g')
# Изображение квадрата из более толстых линий, окаймляющего круг.
ax.vlines(-1, -1, 1, lw=2, color='r')
ax.vlines(1, -1, 1, lw=2, color='r')
ax.hlines(-1, -1, 1, lw=2, color='r')
ax.hlines(1, -1, 1, lw=2, color='r')
# Несколько равномерно распределенных вертикальных линий.
ax.vlines(y[::10], -1, 1, color='b')
# Удаление штриховых меток на осях и надписей.
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
# Небольшое выравнивание снаружи квадратной области.
ax.set_xlim(-1.1, 1.1)
ax.set_ylim(-1.1, 1.1)
plt.show()

На статических графиках, таких как иллюстрации, предназначенные для вывода на печать, методы ax.hlines и ax.vlines работают успешно, но следует отметить, что ограничения линий не изменяются при изменении ограничений
осей в интерактивном графике. Существуют еще два метода ax.axhline и ax.axvline, которые просто изображают горизонтальную или вертикальную линию по
соответствующей оси при любых текущих ограничениях. Метод ax.axhline принимает аргументы y, xmin, xmax, но они обязательно должны быть скалярными
значениями (поэтому для создания нескольких линий требуются повторные вызовы), а xmin, xmax определяются в дробных координатах, так что 0 представляет
левую границу графика, а 1 – его правую границу. Аналогично определяются аргументы x, ymin, ymax метода ax.axvline. Ниже приведено несколько примеров:
ax.axhline(100, 0, 1) # Горизонтальная линия параллельно всей оси x с координатой y =
100.
ax.axhline(100)
# То же самое: xmin и xmax по умолчанию равны 0 и 1.
# Толстая, синяя, штриховая вертикальная линия с координатой x = 5 относительно центра оси y.
ax.axvline(5, 0.4, 0.6, c='b', lw=4, ls='--')

Методы ax.axhspan и ax.axvspan аналогичны описанным выше, но создают
горизонтальный или вертикальный перекрывающий прямоугольник, расположенный вдоль соответствующей оси. В метод ax.axhspan передаются аргументы ymin, ymax (в координатах данных) и xmin, xmax (в дробных единицах
измерения осей). Аналогичным образом метод ax.axvspan принимает аргументы xmin, xmax, ymin, ymax. Дополнительные именованные аргументы можно
использовать для определения стиля перекрывающего прямоугольника (который принадлежит к типу объекта Patch, см. табл. 7.11).

7.4 Аннотации для графиков  387
Таблица 7.11. Именованные аргументы для определения стиля перекрывающих прямо­
угольников
Аргумент

Описание

alpha

Настройка прозрачности в альфа-канале (0–1)

color

Настройка обоих цветов facecolor и edgecolor прямоугольника

edgecolor, ec

Настройка цвета границы прямоугольника

facecolor, fc

Настройка цвета заливки прямоугольника

fill

Определяет, должен ли закрашиваться прямоугольник (True или False)

hatch

Настройка типа штриховки прямоугольника – один из символов: '/', '\', '|', '-', '+', 'x',
'o', 'O', '.', '*'. Повторение символов дает более плотную штриховку

linestyle, ls

Настройка стиля линии прямоугольника: 'solid', 'dashed', 'dashdot', 'dotted'

linewidth, lw

Настройка толщины линии прямоугольника в пт

Пример П7.16. Программа в листинге 7.17 представляет простой график длин волн
с аннотациями, показывающий различные области электромагнитного спектра с помощью методов text, axvline, axhline и axvspan (см. рис. 7.18).
Ультрафиолетовые
волны

300

400

Видимая
часть
спектра

500

600
700
λ / нм

Инфракрасные
волны

800

900

1000

Рис. 7.18. Графическое представление электромагнитного спектра
Листинг 7.17. Графическое представление электромагнитного спектра в диапазоне 250–1000 нм
# eg7-annotate.py
import numpy as np
import matplotlib.pyplot as plt
# Диапазон длин волн, нм.
lmin, lmax = 250, 1000
x = np.linspace(lmin, lmax, 1000)
# Волна с плавно увеличивающейся длиной.
wv = (np.sin(10 * np.pi * x / (lmax+lmin -x)))[::-1]

388



Библиотека Matplotlib

fig = plt.figure()
ax = fig.add_subplot(facecolor='k')
ax.plot(x, wv, c='w', lw=2)
ax.set_xlim(250, 1000)
ax.set_ylim(-2, 2)
# Надпись и разделительная линия различных областей электромагнитного спектра.
ax.text(310, 1.5, 'UV', color='w', fontdict={'fontsize': 20})
ax.text(530, 1.5, 'Visible', color='k', fontdict={'fontsize': 20})
ax.annotate('', (400, 1.3), (750, 1.3), arrowprops={'arrowstyle': '',
'color': 'w', 'lw': 2})
ax.text(860, 1.5, 'IR', color='w', fontdict={'fontsize': 20})
ax.axvline(400, -2, 2, c='w', ls='--')
ax.axvline(750, -2, 2, c='w', ls='--')
# Горизонтальная "ось", проходящая через центр волны.
ax.axhline(c='w')
# Удаление штриховых меток и подписей к ним на оси y; подписи к штриховым меткам на оси x.
ax.yaxis.set_visible(False)
ax.set_xlabel(r'$\lambda\;/\mathrm{nm}$')
# Завершающий этап: добавление нескольких цветных прямоугольников, представляющих цвета радуги
# в видимой области спектра.
# Словарь, отображающий области длин волн (нм) в приблизительно соответствующие значения RGB.
rainbow_rgb = { (400, 440): '#8b00ff', (440, 460): '#4b0082',
(460, 500): '#0000ff', (500, 570): '#00ff00',
(570, 590): '#ffff00', (590, 620): '#ff7f00',
(620, 750): '#ff0000'}
for wv_range, rgb in rainbow_rgb.items():
ax.axvspan(*wv_range, color=rgb, ec='none', alpha=1)
plt.show()

7.4.4 ◊ Круги, многоугольники и прочие фигуры
Почти все элементы, которые изображаются на рисунке Matplotlib, являются
подклассами абстрактного базового класса Artist. Этот класс включает линии
(как объекты типа Line2D) и текст (как объекты типа Text)106. Далее важный
набор отображаемых объектов наследуется от подкласса Patch (являющегося
производным от класса Artist): двумерная фигура. Секторы (клинья) круговой
диаграммы (см. раздел 7.3) и стрелки в аннотации (см. раздел 7.4) представляют собой примеры таких объектов, которые уже встречались ранее.
Для добавления фигуры в объект Axes создается патч («заплатка»), использующий один из классов, подробно описанных в документации Matplotlib (https://matplotlib.org/api/artist_api.html), и вызывается метод ax.add_
patch(patch). Для определения цвета, толщины линий, прозрачности и т. д.
при создании патча передается один или несколько именованных аргументов, описанных в табл. 7.11.
106

В действительности существует два типа объектов Artist: примитивы и контейнеры.
Примитивы – это графические объекты (такие как простые линии типа Line2D), а контейнеры – это элементы рисунка, в которые включены отображаемые графические
объекты (например, Axes).

7.4 Аннотации для графиков  389
Практическое использование нескольких типов объектов Patch описано
в следующих подразделах.

Окружности и эллипсы
Для окружности Circle определяется центр в точке xy = (x, y) (в координатах
данных) и радиус r. Окружность создается следующими командами:
from matplotlib.patches import Circle
circle = Circle(xy, r, **kwargs)

В объект Axes созданная окружность добавляется с помощью метода ax.add_
patch:
ax.add_patch(circle)

Поддерживаемые именованные аргументы, обозначенные **kwargs, – это
обычные параметры стиля патча, описанные в табл. 7.11.
Патчи типа Ellipse похожи на окружности, но принимают аргументы width
и height (полная длина горизонтальной и вертикальной осей эллипса перед его
поворотом) и angle (угол поворота эллипса против часовой стрелки в градусах).
from matplotlib.patches import Ellipse
ellipse = Ellipse(xy, width, height, angle, **kwargs)

Пример П7.17. Код в листинге 7.18 считывает рост и массу тела 260 женщин
и 247 мужчин из набора данных, опубликованного Хайнцем (Heinz) и др.107 Этот набор данных можно скачать здесь: https://scipython.com/eg/bai. Код создает график
пар (рост, масса) для каждого человека на точечной диаграмме, и для каждого пола
изображает 3σ ковариационный эллипс вокруг средней точки. Размеры этого эллипса принимаются по (промасштабированным) собственным значениям ковариационной матрицы, и эллипс поворачивается так, чтобы его главная полуось располагалась
вдоль наибольшего собственного вектора.
Листинг 7.18. Анализ отношения роста и массы тела 507 здоровых людей
# eg7-body-mass-height.py
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
FEMALE, MALE = 0, 1
dt = np.dtype([('mass', 'f8'), ('height', 'f8'), ('gender', 'i2')])
data = np.loadtxt('body.dat.txt', usecols=(22, 23, 24), dtype=dt)
fig, ax = plt.subplots()

107

G. Heinz et al. Journal of Statistical Education 11(2), (2003). Статья доступна здесь:
https://doi.org/10.1080/10691898.2003.11910711.

390



Библиотека Matplotlib

def get_cov_ellipse(cov, center , nstd , **kwargs):
"""
Return a matplotlib Ellipse patch representing the covariance matrix
cov centered at center and scaled by the factor nstd.
"""
# """
# Возвращает патч Ellipse Matplotlib, представляющий ковариационную матрицу cov,
# отцентрированную по средней точке и промасштабированную с коэффициентом nstd.
# """
# Поиск и сортировка собственных значений и собственных векторов в убывающем порядке.
eigvals, eigvecs = np.linalg.eigh(cov)
order = eigvals.argsort()[::-1]
eigvals, eigvecs = eigvals[order], eigvecs[:, order]
# Угол поворота против часовой стрелки созданного эллипса.
vx, vy = eigvecs[:, 0][0], eigvecs[:, 0][1]
theta = np.arctan2(vy, vx)
# Ширина и высота отображаемого эллипса.
width, height = 2 * nstd * np.sqrt(eigvals)
return Ellipse(xy=center, width=width, height=height,
angle=np.degrees(theta), **kwargs)



labels, colors =['Female', 'Male'], ['magenta', 'blue']
for gender in (FEMALE, MALE):
sdata = data[data['gender']==gender]
height_mean = np.mean(sdata['height'])
mass_mean = np.mean(sdata['mass'])
cov = np.cov(sdata['mass'], sdata['height'])
ax.scatter(sdata['height'], sdata['mass'], color=colors[gender],
label=labels[gender])
e = get_cov_ellipse(cov, (height_mean, mass_mean), 3,
fc=colors[gender], alpha=0.4)
ax.add_patch(e)
ax.set_xlim(140, 210)
ax.set_ylim(30, 120)
ax.set_xlabel('Height /cm')
ax.set_ylabel('Mass /kg')
ax.legend(loc='upper left', scatterpoints=1)
plt.show()
❶ Функция np.arctan2 возвращает «арктангенс по двум аргументам»:

np.arctan2(y, x) – угол в радианах между положительным направлением оси x и точкой (x, y) (точнее, прямой из начала координат до точки (x,
y)).

На рис. 7.19 показан итоговый график.

7.4 Аннотации для графиков  391
Женщины

Масса тела, кг

Мужчины

Рост, см

Рис. 7.19. Точечные диаграммы по каждому полу, отображающие отношение массы тела
и роста 507 студентов с соответствующими ковариационными эллипсами с аннотацией

Прямоугольники
Патчи типа Rectangle создаются аналогично эллипсам Ellipse:
from matplotlib.patches import Rectangle
rectangle = Rectangle(xy, width, height, angle, **kwargs)

Но здесь кортеж xy=(x,y) определяет координаты нижнего левого угла прямоугольника. Разумеется, квадрат – это просто прямоугольник с равными значениями width и height.

Многоугольники
Патч типа Polygon создается с помощью передачи массива формы (N, 2),
в котором каждая строка представляет координаты вершины (x, y). Если дополнительный аргумент closed содержит значение True (по умолчанию), то
много­угольник будет замкнутым, т. е. начальная и конечная точки вершин совпадают. Это показано в примере П7.18.

Пример П7.18. Код в листинге 7.19 создает изображение нескольких цветных фигур
(см. рис. 7.20).
Листинг 7.19. Создание цветных фигур
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon , Circle , Rectangle
red, blue, yellow, green = '#ff0000', '#0000ff', '#ffff00', '#00ff00'
square = Rectangle((0.7, 0.1), 0.25, 0.25, facecolor=red)
circle = Circle((0.8, 0.8), 0.15, facecolor=blue)

392



Библиотека Matplotlib

triangle = Polygon(((0.05, 0.1), (0.396, 0.1), (0.223, 0.38)), fc=yellow)
rhombus = Polygon(((0.5, 0.2), (0.7, 0.525), (0.5, 0.85), (0.3, 0.525)), fc=green)
fig = plt.figure(facecolor='k')
ax = fig.add_subplot(aspect='equal')
for shape in (square, circle, triangle, rhombus):
ax.add_patch(shape)
ax.axis('off')
plt.show()

Рис. 7.20. Некоторые цветные фигуры, созданные с использованием патчей Matplotlib

Вопросы
В7.4.1. Сравнить графики функции y = x3 при −10 ≤ x ≤ 10, используя логарифмическую шкалу для оси x, для оси y и для обеих осей. В чем различие между
использованием ax.set_xscale('log') и ax.set_xscale('symlog')?
В7.4.2. Изменить пример П7.9 так, чтобы создавалась горизонтальная столбиковая диаграмма со столбцами в порядке уменьшения частоты встречаемости
букв (т. е. чтобы самая часто встречающаяся буква E была представлена нижним столбцом).

Задачи
З7.4.1. Индекс бигмака, определяемый журналом The Economist, – это неофициальный способ определения (измерения) паритета покупательной способности (ППС) между двумя валютами. Предпосылка его определения состоит
в том, что различие между ценой бигмака в закусочной Макдональд в какойлибо валюте, преобразованная в долл. США по превалирующему обменному
курсу, и ценой бигмака в США является мерой переоценки или недооценки
сравниваемой валюты (по отношению к доллару).
В файлах https://scipython.com/eg/bga представлена хронология цен на бигмак и обменные курсы для четырех валют. Для каждой валюты необходимо
вычислить процент переоценки или недооценки по формуле

7.4 Аннотации для графиков  393

( местная цена бигмака, преобразованная в долл США – цена бигмака в США)
( цена бигмака в США)

× 100

и построить график изменения этой величины как функции от времени.
З7.4.2. Построить график в виде гистограммы по данным табл. 7.12, содержащей количество случаев заболевания вирусом лихорадки Западного Нила
в США в период с 1999 по 2008 г. Два типа заболевания – нейроинвазивный
и ненейроинвазивный – должны быть отображены как отдельные столбцы на
одном графике для каждого года.
Таблица 7.12
Год

Нейроинвазивный тип

Ненейроинвазивный тип

1999

59

3

2000

19

2

2001

64

2

2002

2946

1210

2003

2866

6996

2004

1148

1391

2005

1309

1691

2006

1495

2774

2007

1227

117

2008

689

667

З7.4.3. Кружковая («пузырьковая») диаграмма представляет собой тип точечной диаграммы, который может отображать три измерения данных с использованием позиции (точки данных как координаты x и y) и размера маркера.
Метод plt.scatter может создавать кружковые диаграммы, принимая размер
маркера в атрибуте s (в кв. пт. – так что площадь маркера пропорциональна
величине в третьем измерении – см. пример П7.1).
Файлы gdp.tsv, bmi_men.tsv и population_total.tsv, доступные для скачивания по
адресу https://scipython.com/eg/bgc, содержат следующие данные, начиная с 2007 г.
для каждой страны: ВВП на душу населения в международных долл., зафиксированных в ценах 2005 г., индекс массы тела (ИМТ) мужчин (в кг/м2) и общая
численность населения. Создать кружковую диаграмму отношения ИМТ и ВВП,
на которой численность населения соответствует размеру круговых маркеров.
Предупреждение: для некоторых стран отдельные точки данных отсутствуют.
Дополнительное задание: обозначить цветом кружки по континентам, используя список из файла continents.tsv.
З7.4.4. Национальное управление океанических и атмосферных исследований США (NOAA) создает набор данных о концентрации в атмосфере дву­
окиси углерода (CO2) с 1958 г. Данные свободно доступны для всех здесь: ftp://
aftp.cmdl.noaa.gov/products/trends/co2/co2_mm_mlo.txt. Используя эти данные,

394

 Библиотека Matplotlib

создать график «интерполированных» и «трендовых» значений концентрации CO2 во времени на одном рисунке.
З7.4.5. Написать программу для построения графика функции Планка D(λ) для
спектральной плотности энергетической светимости абсолютно черного тела
при температуре T как функции от длины волны λ для Солнца (T = 5778 К):
B( λ ) =

2hc 2
1
.
5
λ exp( hc / λkBT ) − 1

Использовать массив NumPy для хранения значений функции B(λ) в диапазоне от 100 до 5000 нм, но установить сокращенный диапазон длин волн от
4000 до 0 нм. Необходимые физические константы можно принять в следующем виде: h = 6.626 × 10−34 Дж∙с, c = 2.998 × 108 м/с и kB = 1.381 × 10−23 Дж/К.
З7.4.6. Воспроизвести рис. 7.21, используя патчи типа Circle.

Рис. 7.21. Изображение, полученное с помощью патчей типа Circle Matplotlib

7.5 Контурные диаграммы и тепловые карты
До сих пор мы рассматривали построение графиков только для одномерных
данных (т. е. функции только от одной координаты). Библиотека Matplotlib
также поддерживает несколько способов построения графиков данных, являющихся функциями двух измерений.

7.5.1 Контурные диаграммы
В модуле pyplot метод contour создает контурную диаграмму из предоставленного двумерного массива. При самом простом вызове contour(Z) не требуется
никаких других аргументов: значения (x, y) индексируются в двумерном массиве Z, а интервалы контуров выбираются автоматически. Для явного определения значений координат (x, y) необходимо передать их в виде contour(X, Y, Z).
Массивы X и Y обязательно должны иметь ту же форму, что и массив Z (например, как это сделано при использовании метода np.meshgrid, см. раздел 6.1.6),

7.5 Контурные диаграммы и тепловые карты  395
или должны быть одномерными массивами, такими, что длина массива X равна
числу столбцов массива Z, а длина массива Y равна числу строк массива Z.
Уровнями контуров можно управлять с помощью дополнительного аргумента: либо скалярного значения N, определяющего общее количество уровней контуров, либо последовательности V, явно перечисляющей значения Z,
для которых необходимо отображать контуры.
Цвета контуров определяются в соответствии с цветовой картой (colormap)
Matplotlib, принятой по умолчанию. В этом процессе данные нормализуются
линейно в интервал [0, 1], который далее отображается в список цветов, используемых для определения стилей контуров по соответствующим значениям. Модуль matplotlib.cm предоставляет несколько схем цветовых карт108:
некоторыми наиболее часто применяемыми на практике являются схемы
cm.viridis (с версии Matplotlib 2.0 схема по умолчанию), cm.hot, cm.bone,
cm.winter, cm.jet, cm.Greys и cm.hsv. Если необходимо использовать цветовые
схемы с измененными на противоположные цветами, то необходимо добавлять суффикс _r в конец имени схемы (например, cm.hot_r).
Метод contour поддерживает и другой способ определения цветов – в аргументе colors, в котором передается либо один спецификатор цвета Matplotlib,
либо последовательность таких спецификаторов. На одноцветных контурных
диаграммах контуры, соответствующие отрицательным значениям, изображаются штриховыми линиями. Для толщины линий контуров можно определять
стили по отдельности или для всех вместе с помощью аргумента linewidths.

Пример П7.19. Код в листинге 7.20 создает график электростатического потенциала электрического диполя p = (qd, 0, 0) на плоскости (x, y) при q = 1.602 × 10−19 Кл, d =
1 пм с использованием точечной аппроксимации диполя (см. рис. 7.22).
×10-11
4

2

0

−2

−4
−4

−2

0

2

4
×10-11

Рис. 7.22. Контурная диаграмма электростатического потенциала точечного диполя
108

Полный список см. на веб-странице https://matplotlib.org/tutorials/colors/colormaps.
html.

396



Библиотека Matplotlib

Листинг 7.20. Электростатический потенциал точечного диполя
# eg7-elec-dipole-pot.py
import numpy as np
import matplotlib.pyplot as plt
# Заряд диполя (Кл), диэлектрическая постоянная свободного пространства (Ф/м).
q, eps0 = 1.602e-19, 8.854e-12
# Дипольное расстояние +q, -q (м) и подходящее сочетание параметров.
d = 1.e-12
k = 1/4/np.pi/eps0 * q * d
# Декартова система координат с началом в диполе (м).
X = np.linspace(-5e-11, 5e-11, 1000)
Y = X.copy()
X, Y = np.meshgrid(X, Y)
# Электростатический потенциал диполя (V) с использованием точечной аппроксимации диполя.
Phi = k * X / np.hypot(X, Y)**3
fig, ax = plt.subplots()
# Отображение контуров по значениям Phi, определяемым по уровням.
levels = np.array([10**pw for pw in np.linspace(0, 5, 20)])
levels = sorted(list(-levels) + list(levels))
# Одноцветная контурная диаграмма потенциала.
ax.contour(X, Y, Phi, levels=levels, colors='k', linewidths=2)
plt.show()

Для добавления надписей к контурам необходимо сохранить объект ContourSet, возвращаемый после вызова метода ax.contour, и передать этот объект
в метод ax.clabel (возможно, с некоторыми дополнительными параметрами,
определяющими свойства шрифта). Еще один метод ax.contourf принимает
те же аргументы, что и метод contour, но изображает закрашенные контуры
(т. е. с заливкой областей контуров). Методы ax.contour и ax.contourf можно
использовать вместе, как показано в примере П7.20.
Пример П7.20. Программа в листинге 7.21 создает график функции с закрашенными контурами, добавляет надписи для контуров и определяет некоторые специализированные стили для цветов контуров (см. рис. 7.23).
1.0
10 -0. 02

0.020

0

0.

0.04

07 0.080
0

-0

30 0 .0

80

.03

-0.0

-0
-0.050

60

0.4

00

-0.060

0.0

0.6

0

.04 -0 . 0
70
0

.0

0. 0

-0

0

0.8

0.0
0.0

0

50
0.

01

0. 0

0.2

0.2

0.4

0.6

0.8

1.0

Рис. 7.23. Двумерный график, изображающий контуры с надписями

7.5 Контурные диаграммы и тепловые карты  397
Листинг 7.21. Пример изображения контуров с заливкой цветом и определением стилей
# eg7-2dgau.py
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
X = np.linspace(0, 1, 100)
Y = X.copy()
X, Y = np.meshgrid(X, Y)
alpha = np.radians(25)
cX, cY = 0.5, 0.5
sigX, sigY = 0.2, 0.3
rX = np.cos(alpha) * (X-cX) - np.sin(alpha) * (Y-cY) + cX
rY = np.sin(alpha) * (X-cX) + np.cos(alpha) * (Y-cY) + cY
Z = (rX-cX)*np.exp(-((rX-cX)/sigX)**2) * np.exp(-((rY-cY)/sigY)**2)
fig = plt.figure()
ax = fig.add_subplot()
# Реверсированная цветовая карта Greys для контуров с заливкой.
cpf = ax.contourf(X, Y, Z, 20, cmap=cm.Greys_r)
# Установить цвета контуров и надписей так, чтобы они изображались белым цветом, когда
# заливка контура темная (Z < 0), и черным цветом, когда заливка контура светлая (Z >= 0).
colors = ['w' if level 30). Разместить вручную подписи к контурам так, чтобы они
не перекрывали точки данных на точечной диаграмме, и отформатировать
значения в подписях до одного знакоместа после десятичной точки.
З7.5.3. Уравнение адвекции в двумерном пространстве можно записать в следующем виде:
∂U
∂U
∂U
= −v x
− vy
,
∂t
∂x
∂y

где v = (vx, vy) – вектор поля скоростей, содержащий компоненты скорости vx и vy,
которые могут изменяться как функция от позиции (x, y). Применяя методику,
аналогичную используемой в примере П7.24, можно дискретизировать

406



Библиотека Matplotlib

это уравнение и решить его численными методами. При распространении
с правосторонней разностью (разностью вперед) по времени и с центральной
разностью в пространстве получаем:

ui(+n1,) j − ui(−n1,) j
ui(,nj)+1 − ui(,nj)−1 
+ v y ;i , j
ui(,nj +1) = ui(,nj) − ∆t  v x ;i , j
.
2∆x
2∆x



Реализовать это приближенное численное решение в области 0 ≤ x < 10, 0 ≤
y < 10 при дискретизации с шагами Δx = Δy = 0.1 и при начальном условии
 ( x − c x )2 + ( y − c y )2 
u0 ( x , y ) = exp  −
,


α2



где (cx, cy) = (5, 5) и α = 2. Принять поле скоростей циркулирующим с постоянной
скоростью 0.1 относительно начала координат в точке (7, 5).

З7.5.4. Множество Жюлиа (Julia set), связанное с функцией комплексного переменного f(z) = z2 + c, можно изобразить, применяя следующий алгоритм.
Для каждой точки z0 в комплексной плоскости, такой, что −1.5 ≤ Re[z0] ≤ 1.5
и −1.5 ≤ Im[z0] ≤ 1.5, выполняется итерация, соответствующая выражению
zn+1 = zn2 + c. Цвет пиксела в изображении в этой области комплексной плоскости определяется в соответствии с количеством итераций, требуемых для превышения |z| некоторого критического значения |z|max (или назначается черный
цвет, если превышение не произошло даже после определенного максимального количества итераций nmax).
Написать программу графического представления множества Жюлиа при
c = ­−0.1 + 0.65i, используя значения |z|max = 10 и nmax = 500.
З7.5.5. Средние высоты гектадов – квадратов размером 10×10 км, используемых национальным картографическим агентством Ordnance Survey Великобритании при создании карт страны, собраны в форме массива NumPy в файле
gb-alt.npy, доступном для скачивания здесь: https://scipython.com/eg/bgb.
Создать карту Британских островов, используя эти данные, с помощью
метода ax.imshow, а также сформировать дополнительные карты, принимая
среднее повышение уровня моря: а) 25 м, б) 50 м, в) 200 м. В каждом случае
вычислить процент оставшейся площади суши относительно существующей
в настоящее время.

7.6 Трехмерные графики
Библиотека Matplotlib главным образом предназначена для создания двумерных графиков, но она поддерживает и функциональные возможности для построения трехмерных графиков, вполне достаточные для множества целей.
Простейшим способом создания трехмерного графика является импорт объекта Axes3D из модуля mpl_toolkits.mplot3d и определение для аргумента projection внутреннего графика значения '3d':
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

7.6 Трехмерные графики  407
fig = plt.figure()
ax = fig.add_subplot(projection='3d')

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

7.6.1 Каркасные и объемные поверхностные диаграммы
Простейшим типом объемной поверхностной диаграммы является каркасная диаграмма («проволочная модель»), изображающая линии в трехмерной
перспективе, объединяя предоставленный двумерный массив точек Z с сеткой
значений данных, переданных в двумерных массивах X и Y (как для методов
imshow и contour). По умолчанию линии в трехмерном представлении изображаются для каждой точки в массиве, но если точек слишком много, то можно
определить значения аргументов rstride и cstride, чтобы задать шаг изображаемых точек в строках и столбцах массива соответственно.
Метод ax.plot_surface работает так же, но создает объемную поверхностную
диаграмму из закрашенных элементов (патчей). Для патчей можно установить
единый цвет в аргументе color или определить стиль с помощью специализированной цветовой схемы в аргументе cmap. Для метода ax.plot_surface аргументам rstride и cstride по умолчанию присваивается значение 10. Практическое применение обоих методов демонстрируется в примере П7.25.

Пример П7.25. Код в листинге 7.26 демонстрирует использование разнообразных
параметров при создании объемных поверхностных диаграмм. Результаты показаны
на рис. 7.28.

−2 −1
0

−2 −1
0

1

1

−1
2− 2

−1
2− 2

0

0

1

1.0

1.0

0.5

0.5

0.0
2

−2 −1
0

1

−1
2− 2

0

1

0.0
2

1.0

1.0

0.5

0.5

0.0
12

−2 −1
0

1

−1
2− 2

0

1

0.0
2

Рис. 7.28. Четыре различные трехмерные поверхностные диаграммы одной функции
109

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

408

 Библиотека Matplotlib

Листинг 7.26. Четыре трехмерные поверхностные диаграммы простой двумерной гауссовой
функции
# eg7-3d-surface-plots.py
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.cm as cm
L, n = 2, 400
x = np.linspace(-L, L, n)
y = x.copy()
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X**2 + Y**2))
fig, ax = plt.subplots(nrows=2, ncols=2, subplot_kw={'projection': '3d'})
ax[0, 0].plot_wireframe(X, Y, Z, rstride=40, cstride=40)
ax[0, 1].plot_surface(X, Y, Z, rstride=40, cstride=40, color='m')
ax[1, 0].plot_surface(X, Y, Z, rstride=12, cstride=12, color='m')
ax[1, 1].plot_surface(X, Y, Z, rstride=20, cstride=20, cmap=cm.hot)
for axes in ax.flatten():
axes.set_xticks([-2, -1, 0, 1, 2])
axes.set_yticks([-2, -1, 0, 1, 2])
axes.set_zticks([0, 0.5, 1])
fig.tight_layout()
plt.show()

В интерактивном графике направление взгляда можно изменить, если
щелкнуть и, не отпуская кнопку мыши, перетаскивать любую точку в области
графика. Для фиксации конкретного направления взгляда в статическом изображении графика необходимо явно передать требуемый угол возвышения
и азимутальный угол (в градусах, в указанном порядке) в метод ax.view_init,
как показано в примере П7.26.

Пример П7.26. Параметрическое определение тора с главным радиусом c и радиу­
сом образующей окружности a записывается в следующем виде:
x = (c + a cos θ) cos φ
y = (c + a cos θ) sin φ
z = a sin θ

Значения θ и φ должны находиться в интервале от 0 до 2π. Код в листинге 7.27 выводит два визуальных представления тора, изображаемого как объемная поверхностная диаграмма (см. рис. 7.29).

7.6 Трехмерные графики  409
(a)

(б)

Рис. 7.29. Два визуальных представления одного и того же тора: а) θ = 36°, φ = 26°,
б) θ = 0°, φ = 0°

Листинг 7.27. Трехмерная поверхностная диаграмма тора
# eg7-torus-surface.py
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
n = 100
theta = np.linspace(0, 2.*np.pi, n)
phi = np.linspace(0, 2.*np.pi, n)
theta, phi = np.meshgrid(theta, phi)
c, a = 2, 1
x = (c + a*np.cos(theta)) * np.cos(phi)
y = (c + a*np.cos(theta)) * np.sin(phi)
z = a * np.sin(theta)

fig = plt.figure()
ax1 = fig.add_subplot(121, projection='3d')
ax1.set_zlim(-3, 3)
ax1.plot_surface(x, y, z, rstride=5, cstride=5, color='k', edgecolors='w')
ax1.view_init(36, 26)
ax2 = fig.add_subplot(122, projection='3d')
ax2.set_zlim(-3, 3)
ax2.plot_surface(x, y, z, rstride=5, cstride=5, color='k', edgecolors='w')
ax2.view_init(0, 0)
ax2.set_xticks([])
plt.show()






❶ Необходимы независимые значения θ и φ в интервале (0, 2π), чтобы ис-

пользовать метод meshgrid.

❷ Обратите внимание: можно использовать именованные аргументы, та-

кие как edgecolors, для определения стиля многоугольных патчей, создаваемых методом ax.plot_surface.
❸ Угол возвышения (взгляда) над плоскостью xy равен 36°, азимутальный
угол в плоскости xy равен 26°.

410



Библиотека Matplotlib

7.6.2 Линейные графики и точечные диаграммы
Линейные графики и точечные диаграммы в трех измерениях создаются
тем же способом, что и для двух измерений: основные вызываемые методы
ax.plot(x, y, z) и ax.scatter(x, y, z) соответственно, где x, y, z – одномерные
массивы равной длины. Но для таких графиков возможно добавление только
ограниченной аннотации, если не пользоваться методами с расширенным набором функциональных возможностей.
Пример П7.27. В листинге 7.28 показан простой пример трехмерного графика спирали (винтовой линии), которая может представлять, например, свет с круговой поляризацией (см. рис. 7.30).

Рис. 7.30. Изображение света с круговой поляризацией в виде спирали
на трехмерном графике
Листинг 7.28. Изображение спирали на трехмерном графике
# eg7-circular-polarization.py
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
n = 1000
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
# График спирали по оси x.
theta_max = 8 * np.pi
theta = np.linspace(0, theta_max , n)
x = theta
z = np.sin(theta)
y = np.cos(theta)
ax.plot(x, y, z, 'b', lw=2)
# Прямая, проходящая через центр спирали.
ax.plot((-theta_max*0.2, theta_max * 1.2), (0, 0), (0, 0), color='k', lw=2)
# Компоненты sin/cos спирали (например, компоненты электрического и магнитного полей
# электромагнитной волны с круговой поляризацией).
ax.plot(x, y, 0, color='r', lw=1, alpha=0.5)
ax.plot(x, [0]*n, z, color='m', lw=1, alpha=0.5)

7.7 Анимация  411
# Удаление плоскостей осей, штриховых меток и подписей.
ax.set_axis_off()
plt.show()

7.7 Анимация
В этом разделе предлагается краткое введение в практическое использование класса FuncAnimation для создания анимированных графиков и диаграмм
в программе (скрипте) на языке Python или в виртуальной блокнотной среде
Jupyter Notebook. Библиотека Matplotlib предоставляет функциональные возможности анимации в модуле animation, который необходимо явно импортировать перед использованием:
import matplotlib.animation as animation

7.7.1 Анимация данных на графике
Простая анимированная линия
Класс FuncAnimation создает эффект анимации, многократно вызывая предоставленную функцию func, которая обновляет объекты, отображаемые в объекте
рисунка Matplotlib Figure fig. Дополнительные аргументы описаны в табл. 7.13.
Таблица 7.13. Аргументы для FuncAnimation
Аргумент

Описание

fig

Объект Matplotlib Figure, который необходимо анимировать

func

Функция, вызываемая для создания каждого кадра анимации посредством манипуляции
объектами в рисунке Figure

frames

Источник объекта, передаваемого в функцию func для каждого кадра. Если задано значение
None (по умолчанию), то передается постоянно увеличивающийся целочисленный индекс.
Также можно передавать итерируемые объекты или функцию генератора

init_func

Функция, вызываемая для создания пустого кадра анимации. Если определен аргумент
blit=True, то этот аргумент является обязательным

fargs

Любые дополнительные аргументы, передаваемые в функцию func

interval

Интервалы времени для паузы между кадрами в мс (по умолчанию 200)

repeat

Флаг, логическое значение, определяющее, должна ли анимация зацикливаться (повторяться) или нет (по умолчанию True)

blit

Флаг, логическое значение, определяющее, должна ли использоваться операция комбинирования битовых карт изображений для оптимизации анимации (по умолчанию
False). Подробнее см. в тексте

Объект рисунка Figure и содержащиеся в нем объекты Axes должны быть
созданы до вызова FuncAnimation, и любые ссылки на изображаемые объекты необходимо сохранять, чтобы с этими данными могла работать функция
анимации. Например, данные (x, y), изображаемые в объекте Line2D, могут
быть (пере)настроены с помощью собственного метода set_data. Это показано в коде листинга 7.29, где анимируется затухающая синусоидальная кривая.

412



Библиотека Matplotlib

Пример П7.28. В коде листинга 7.29 выполняется анимация затухающей синусоидальной кривой, которая, например, может представлять затухающий звук при ударе
по камертону с постоянной (фиксированной) частотой:
M(t) = sin(2πft)e−αt.
Листинг 7.29. Анимация затухающей синусоидальной кривой
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Интервал времени для анимации (с), максимальное время анимации (с).
dt, tmax = 0.01, 5
# Частота сигнала (1/с), коэффициент затухания (1/с).
f, alpha = 2.5, 1
# Эти списки будут содержать данные для построения графика.
t, M = [], []
# Изображение пустого графика, но с предварительной установкой ограничений по осям x и y.
fig, ax = plt.subplots()
line, = ax.plot([], [])

ax.set_xlim(0, tmax)
ax.set_ylim(-1, 1)
ax.set_xlabel('t /s')
ax.set_ylabel('M (arb. units)')
def animate(i):
"""Draw the frame i of the animation."""
# """Отображение кадра i анимации."""
global t, M

# Добавление этого момента времени, соответствующих ему данных и определение данных
# для линии, изображаемой на графике.
_t = i*dt
t.append(_t)
M.append(np.sin(2*np.pi*f*_t) * np.exp(-alpha*_t))
line.set_data(t, M)
# Интервал между кадрами в мс, общее количество используемых кадров.
interval, nframes = 1000 * dt, int(tmax / dt)

# Анимация повторяется один раз (установка repeat=False, чтобы анимация не зацикливалась).
ani = animation.FuncAnimation(fig, animate, frames=nframes, repeat=False,
interval=interval)
plt.show()
❶ Напомню, что метод ax.plot возвращает кортеж объектов Line2D, даже

если изображается только одна линия. Необходимо сохранить ссылку
на этот кортеж, чтобы получить возможность работать с его данными
в функции анимации animate.
❷ Объявляя списки t и M глобальными (global) объектами, мы получаем
возможность изменять их содержимое из тела функции animate.
❸ При установке интервала времени между кадрами (паузы) равным (в мс)
используемому в этой программе интервалу времени анимация выглядит «происходящей в реальном времени».

7.7 Анимация  413

M (в произвольных единицах)

Завершающий кадр анимации показан на рис. 7.31.
1. 0
0. 5
0. 0
–0. 5
–1. 0
0

1

2

3

4

5

t, с

Рис. 7.31. Завершающий кадр анимации затухающей синусоидальной кривой

Комбинирование битовых карт изображений
В примере из предыдущего раздела весь объект линии должен был перерисовываться для каждого кадра. При весьма большом объеме данных или при
сложном изображении это может замедлять анимацию. В этом случае может
помочь операция комбинирования битовых карт изображения – блиттинг (blitting – Bit Blt, сокращение от bit block transfer), методика из области компью­
терной графики, позволяющая в цикле анимации перерисовывать только те
части изображения, которые изменяются между кадрами. Это исключает необходимость перерисовки всех отображаемых точек данных.
Для использования операции блиттинга требуется некоторый дополнительный
код: необходимо определить метод, передаваемый в аргументе init_func объекту FuncAnimation. Этот передаваемый метод создает пустой кадр, но возвращает
последовательность объектов анимации, которые должны быть перерисованы
в каждом кадре. Функция func, вызываемая для каждого отображаемого кадра,
также должна возвращать последовательность измененных объектов анимации.
Пример П7.29. Код в листинге 7.30 повторяет анимацию из примера П7.28,
но использует методику блиттинга и явно передает аргументы в функцию анимации вместо объявления их глобальными объектами в теле этой функции.
Листинг 7.30. Анимация затухающей синусоидальной кривой с использованием blit=True
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Интервал времени для анимации (с), максимальное время анимации (с).
dt, tmax = 0.01, 5
# Частота сигнала (1/с), коэффициент затухания (1/с).
f, alpha = 2.5, 1
# Эти списки будут содержать данные для построения графика.
t, M = [], []

414



Библиотека Matplotlib

# Изображение пустого графика, но с предварительной установкой ограничений по осям x и y.
fig, ax = plt.subplots()
line, = ax.plot([], [])
ax.set_xlim(0, tmax)
ax.set_ylim(-1, 1)
ax.set_xlabel('t /s')
ax.set_ylabel('M (arb. units)')
def init():
return line,
def animate(i, t, M):
"""Draw the frame i of the animation."""
# """Отображение кадра i анимации."""
# Добавление этого момента времени, соответствующих ему данных и определение данных
# для линии, изображаемой на графике.
_t = i*dt
t.append(_t)
M.append(np.sin(2*np.pi*f*_t) * np.exp(-alpha*_t))
line.set_data(t, M)
return line,
# Интервал между кадрами в мс, общее количество используемых кадров.
interval, nframes = 1000 * dt, int(tmax / dt)
# Анимация повторяется один раз (установка repeat=False, чтобы анимация не зацикливалась).
ani = animation.FuncAnimation(fig, animate, frames=nframes, init_func=init,

fargs=(t, M), repeat=False, interval=interval, blit=True)
plt.show()
❶ Любые объекты, присвоенные аргументу fargs метода FuncAnimation,

будут обработаны в функции анимации.

7.7.2 Анимация других объектов Matplotlib
Для анимации других объектов Matplotlib, таких как патчи и надписи аннотации, ссылка на них должна быть сохранена и обработана в каждом кадре. Подобно объектам типа Line2D, содержащим метод set_data, эти прочие классы
также имеют методы-«установщики» (setter-методы) (например, методы set_
center, set_radius для патча Circle), использование которых демонстрируется
в примере П7.30.

Пример П7.30. Программа в листинге 7.31 выполняет анимацию прыгающего мяча
из начального положения (0, y0) с начальной скоростью (vx0, 0). Положение мяча, хронология траектории и надпись-метка высоты изменяются в каждом кадре.
Здесь в аргументе frames для FuncAnimation передается функция генератора
get_pos, которая возвращает следующее положение мяча на каждой итерации.
Это положение обрабатывается в функции анимации animate вместо целочисленного индекса текущего кадра.

7.7 Анимация  415
Листинг 7.31. Анимация прыгающего мяча
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Ускорение свободного падения, м/с2.
g = 9.81
# Максимальное значение x траектории мяча, отображаемой на графике.
XMAX = 5
# Коэффициент упругого восстановления при ударе (-v_up/v_down).
cor = 0.65
# Интервал времени для анимации.
dt = 0.005
# Начальное положение и векторы скоростей.
x0, y0 = 0, 4
vx0, vy0 = 1, 0
def get_pos(t=0):
"""A generator yielding the ball ' s position at time t."""
# """Генератор, определяющий положение мяча в момент времени t."""
x, y, vx, vy = x0, y0, vx0, vy0
while x < XMAX:
t += dt
x += vx0 * dt
y += vy * dt
vy -= g * dt
if y < 0:
# Прыжок.
y = 0
vy = -vy * cor
yield x, y



def init():
"""Initialize the animation figure."""
# """Инициализация анимируемого изображения."""
ax.set_xlim(0, XMAX)
ax.set_ylim(0, y0)
ax.set_xlabel('$x$ /m')
ax.set_ylabel('$y$ /m')
line.set_data(xdata, ydata)
ball.set_center((x0, y0))
height_text.set_text(f'Height: {y0:.1f} m')
return line, ball, height_text
def animate(pos):
"""For each frame , advance the animation to the new position , pos."""
# """Для каждого кадра анимация перемещается в новое положение pos."""
x, y = pos
xdata.append(x)
ydata.append(y)
line.set_data(xdata , ydata)
ball.set_center((x, y))
height_text.set_text(f'Height: {y:.1f} m')
return line, ball, height_text

416

 Библиотека Matplotlib

# Настройка нового рисунка Figure с равным отношением сторон, чтобы мяч выглядел круглым.
fig, ax = plt.subplots()
ax.set_aspect('equal')
# Это объекты, которые необходимо постоянно отслеживать.
line, = ax.plot([], [], lw=2)
ball = plt.Circle((x0, y0), 0.08)
height_text = ax.text(XMAX*0.5, y0*0.8, f'Height: {y0:.1f} m')
ax.add_patch(ball)
xdata, ydata = [], []
interval = 1000*dt
ani = animation.FuncAnimation(fig, animate, get_pos, blit=True,
interval=interval, repeat=False, init_func=init)
plt.show()
❶ Функция генератора будет сохранять создаваемый вектор положения

мяча (x, y) до тех пор, пока координата x мяча не достигнет значения
XMAX. Затем, когда генератор исчерпается и выдаст значение None, анимация завершится.

Завершающий кадр анимации показан на рис. 7.32.
4
Высота: 0.0 м

y, м

3
2
1
0
0

1

2

x, м

3

4

5

Рис. 7.32. Завершающий кадр анимации прыгающего мяча

7.7.3 Упражнения
Задачи
З7.7.1. Использовать класс FuncAnimation из библиотеки Matplotlib для создания анимации качающегося маятника с начальным максимальным углом отклонения от вертикали и нулевой начальной скоростью. Выполнить численное
интегрирование уравнения движения и повторить анимацию после завершения одного полного периода колебаний.
З7.7.2. Изменить код примера П7.24 для создания анимации распространения
температуры в металлической пластине по времени.

7.7 Анимация  417
З7.7.3. Лаборатория реактивного движения (JPL) НАСА сопровождает вебсервис и базу данных HORIZONS, которую можно использовать для вычисления астрономических таблиц (эфемеридов – траекторий объектов Солнечной
системы по времени). Предварительно подобранные данные с этого ресурса
можно скачать здесь: https://scipython.com/eg/bas. Использовать эти данные
для создания анимации траектории исследовательского космического аппарата Вояджер 2 с момента его запуска в августе 1977 г. до конца 1999 г. В этот
период включено несколько гравитационных промежуточных маневров, когда
аппарат проходил рядом с крупными планетами. Необходимо рассматривать
только координаты (X, Y) значимых космических тел.

Глава

8
Библиотека SciPy

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

8.1 Физические константы и специальные функции
Полезный пакет scipy.constants содержит принятые по международным
стандартам значения и допустимые погрешности физических констант. Кроме
того, пакет scipy.special предоставляет множество алгоритмов для вычисления функций, часто используемых в научных исследованиях, математическом
анализе и инженерных расчетах, в том числе следующие:
 функции Эйри;
 эллиптические функции и интегралы;
 функции Бесселя, их нули, производные и интегралы;
 сферические функции Бесселя;
 разнообразные статистические функции и распределения;
 гамма- и бета-функции;
 функция распределения (вероятности) ошибок;
 интегралы Френеля;
 функции Лежандра и присоединенные функции Лежандра;
 разнообразные ортогональные многочлены;
 гипергеометрические функции;

8.1 Физические константы и специальные функции  419
 функции параболического цилиндра;
 функции Матьё;
 сфероидальные функции.
Все функции подробно описаны в документации (https://docs.scipy.org/doc/
scipy/reference/special.html), а в этом разделе основное внимание сосредоточено
на нескольких характерных примерах их применения.
Большинство перечисленных вышеспециальных функций в библиотеке
SciPy реализованы как универсальные функции, т. е. они поддерживают бродкастинг и векторизацию (автоматический проход по массиву в цикле), поэтому вполне ожидаемо корректно работают с массивами NumPy.

8.1.1 Физические константы
Библиотека SciPy содержит рекомендованные международным Комитетом
по данным для науки и техники CODATA в выпуске 2018 г. значения многих
физических констант (https://physics.nist.gov/cuu/Constants/). Константы вместе
с единицами их измерения и допустимыми погрешностями хранятся в словаре scipy.constants.physical_constants, в котором ключом является строка
идентификации. Например:
In [x]: import scipy.constants as pc
In [x]: pc.physical_constants['Avogadro constant']
Out[x]: (6.022140857e+23, 'mol^-1', 7400000000000000.0)

Удобные методы value, unit и precision извлекают соответствующие свойства констант:
In [x]: pc.value('electron mass')
Out[x]: 9.1093837015e-31
In [x]: pc.unit('electron mass')
Out[x]: 'kg'
In [x]: pc.precision('electron mass')
3.0737534961217373e-10

Для удобства требуемое значение физической константы обычно присваивается переменной в начале программы, например:
In [x]: muB = pc.value('Bohr magneton')

Полный список констант и их названий приведен в официальной документации SciPy (https://docs.scipy.org/doc/scipy/reference/constants.html), но в табл. 8.1
перечислены наиболее важные значения. Значения некоторых особенно важных констант присвоены переменным в модуле scipy.constants (в единицах
СИ), поэтому их можно импортировать напрямую:
In [x]: from scipy.constants import c, R, k
In [x]: c, R, k # Скорость света, универсальная газовая постоянная, постоянная Больцмана.
Out[x]: (299792458.0, 8.314462618, 1.380649e-23)

420



Библиотека SciPy

Таблица 8.1. Некоторые физические константы из модуля scipy.constants
Строка идентификации константы

Переменная

Значение

Единицы измерения

‘atomic mass constant’

m_u

1.6605390666e-27

кг

‘Avogadro constant’

N_A

6.02214076e+23

1/моль

‘Bohr magneton’

9.2740100783e-24

Дж/Тл

‘Bohr radius’

5.29177210903e-11

м

‘Boltzmann constant’

k

1.380649e-23

Дж/К

‘electron mass’

m_e

9.1093837015e-31

кг

‘elementary charge’

e

1.602176634e-19

Кл

96485.33212

Кл/моль

‘Faraday constant’
‘fine-structure constant’

alpha

0.0072973525693

‘molar gas constant’

R

8.314462618

‘neutron mass’

m_n

1.67492749804e-27

кг

‘Newtonian constant of gravitation’

G

6.6743e-11

‘Planck constant’

h

6.62607015e-34

м3 / (с2 ∙ кг)

‘proton mass’

m_p

1.67262192369e-27

кг

‘Rydberg constant’

Rydberg

10973731.56816

1/м

‘speed of light in vacuum’

c

299792458.0

Дж / (К∙моль)

Дж ∙ с
м/с

Для приведенного выше примера имена переменных приведены в табл. 8.1.
Возможно, более удобным вам покажется использование значений из scipy.
constants, но при этом следует помнить о том, что после публикации новых
выпусков CODATA значений констант этот пакет может обновляться – это
значит, что ваш код может выдавать немного отличающиеся результаты
с различными версиями SciPy. Значения в табл. 8.1 взяты из версии 1.4 SciPy,
в которую включены новые определения основных единиц системы СИ, принятые в 2019 г.
Кроме того, в пакете scipy.constants определены полезные коэффициенты
и методы преобразования, которые также включают представления префиксов
системы СИ. Например:
In [x]:
In [x]:
Out[x]:
In [x]:
Out[x]:
In [x]:
Out[x]:
In [x]:
Out[x]:
In [x]:
Out[x]:

import scipy.constants
pc.atm
101325.0
pc.bar
100000.0
pc.torr
133.32236842105263
pc.zero_Celsius
273.15
pc.micro
1e-06

as pc
# 1 атм в Па.
# 1 бар в Па.
# 1 торр (мм рт.ст.) в Па.
# 0 градC в K.
# А также нано, пико, мега, гига и т. д.

8.1 Физические константы и специальные функции  421
Пример П8.1. Здесь используется словарь scipy.constants.physical_constants
для определения констант, которые считаются наименее точными. Для этого требуются относительные погрешности значений констант. Код в листинге 8.1 использует
структурированный массив для вычисления относительных погрешностей и выводит
константы, которые наименее точно определены.
Листинг 8.1. Наименее точно определенные физические константы
import numpy as np
from scipy.constants import physical_constants
def make_record(k, v):
"""
Return the record for this constant from the key and value of its entry
in the physical_constants dictionary.
"""
# """Возвращает запись для этой константы, сформированную из ключа и значения
#
соответствующего элемента словаря физических констант."""
name = k
val, units, abs_unc = v
# Вычисление относительной погрешности в ppm (в частях на миллион).
rel_unc = abs_unc / abs(val) * 1.e6
return name, val, units, abs_unc, rel_unc
dtype = [('name', 'S50'), ('val', 'f8'), ('units', 'S20'),
('abs_unc', 'f8'), ('rel_unc', 'f8')]
constants = np.array([make_record(k, v) for k, v in physical_constants.items()],
dtype=dtype)
constants.sort(order='rel_unc')
# Список из 10 констант с наибольшими относительными погрешностями.
for rec in constants[-10:]:
print('{:.0f} ppm: {:s} = {:g} {:s}'.format(rec['rel_unc'],
rec['name'].decode(), rec['val'], rec['units'].decode()))

Вывод результата выполнения этой программы:
90 ppm: tau Compton wavelength over 2 pi = 1.11056e-16 m
90 ppm: tau mass energy equivalent in MeV = 1776.82 MeV
193 ppm: W to Z mass ratio = 0.88153
348 ppm: deuteron rms charge radius = 2.12799e-15 m
428 ppm: proton mag. shielding correction = 2.5689e-05
428 ppm: proton magn. shielding correction = 2.5689e-05
829 ppm: shielding difference of t and p in HT = 2.414e-08
990 ppm: shielding difference of d and p in HD = 2.02e-08
1346 ppm: weak mixing angle = 0.2229
2258 ppm: proton rms charge radius = 8.414e-16 m

422

 Библиотека SciPy

8.1.2 Функции Эйри и Бесселя
Функции Эйри Ai(x) и Bi(x) – это линейно независимые решения дифференциального уравнения Эйри y" − xy = 0, которые используются в квантовой механике, оптике, электродинамике и других областях физики. Функции (Ai, Bi) и их
производные (Aip, Bip) возвращает функция scipy.special.airy. Единственный обязательный аргумент x может быть комплексным числом или массивом
NumPy:
In [x]: Ai, Aip, Bi, Bip = airy(0)
In [x]: Ai, Aip, Bi, Bip
(0.35502805388781722, -0.25881940379280682, 0.61492662744600068, 0.44828835735382638)

Первые nt нулей функций Эйри и их производные возвращает функция
scipy.special.ai_zeros(nt):
In [x]: a, ap, ai, aip = ai_zeros(2)
# Массивы для первых двух нулей функции Ai.
In [x]: a[1], ap[1], ai[1], aip[1]
# Содержимое второго нуля:
Out[x]: ( -4.0879494441309721, -3.248197582179837, -0.41901547803256406,
-0.80311136965486463)
In [x]: airy(a[1])[0]
# Функция Ai(a) должна = 0.
Out[x]: 1.2774882441379295e-15
# Достаточно близко к нулю.
In [x]: airy(ap[1])[1]
# Производная Aip(ap) должна = 0.
Out[x]: -3.2322209157744908e-16
# Достаточно близко к нулю.
In [x]: airy(ap[1])[0]
# Ai(ap) возвращается как ai выше.
Out[x]: -0.41901547803256395
In [x]: airy(a[1])[1]
# Aip(a) возвращается как aip выше.
Out[x]: -0.80311136965486396

Пример П8.2. Рассмотрим частицу массой m, перемещающуюся в постоянном гравитационном поле, таком, что потенциальная энергия частицы на высоте z над поверхностью равна mgz. Если частица совершает упругие прыжки по поверхности, то
классическая плотность вероятности, соответствующая положению частицы, записывается формулой
Pcl ( z ) =

1
zmax ( zmax − z )

,

где zmax – максимальная высота, достигаемая частицей.
Поведение по законам квантовой механики этой системы можно описать
в виде решения независимого от времени (стационарного) уравнения Шрёдингера:


 d 2ψ
+ mgzψ = E ψ,
2m dz 2

которое упрощается при изменении масштаба координат q = z/α, где
α = (  / 2m2 g )1/ 3:

8.1 Физические константы и специальные функции  423
d 2ψ
E
− ( q − qE )ψ = 0, где qE =
.
mg α
dq 2

Решениями этого дифференциального уравнения являются функции Эйри.
Граничное условие ψ(z) → 0 как z → ∞ в частном случае приводит к следующей
форме записи:
ψ(q) = NEAi(q − qE),

где NE – константа нормализации.
Второе граничное условие ψ(q = 0) = 0 приводит к квантованию в понятиях
квантового числа n = 1, 2, 3, …, с масштабируемыми значениями энергии qE,
определяемыми по нулям функции Эйри: Ai(−qE) = 0.
Программа в листинге 8.2 вычисляет и строит графики классических и квантовых распределений вероятностей Pcl(z) и |ψ(z)|2 при n = 1 и n = 16 (см. рис. 8.1).
0.8
0.7

n=1

n = 16

0.25

Классическое
Квантовое

Классическое
Квантовое
0.20

0.6

0.4
0.3

0.15
|ψ(q)|2

|ψ(q)|2

0.5

0.10

0.2
0.05
0.1
0.0
0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0
q

0.00
0

5

10
q

15

20

Рис. 8.1. Сравнение классических и квантовых распределений вероятностей для частицы,
перемещающейся в постоянном гравитационном поле на двух различных энергетических
уровнях
Листинг 8.2. Плотности распределения вероятностей для частицы в однородном гравитационном поле
# eg8-qm-gravfield.py
import numpy as np
from scipy.special import airy , ai_zeros
import matplotlib.pyplot as plt

424



Библиотека SciPy

nmax = 16
# Поиск первых nmax нулей функции Ai(x).
a, _, _, _ = ai_zeros(nmax)
# Действительное граничное условие Ai(-qE) = 0 при q = 0, так что:
qE = -a



def prob_qm(n):
"""
Return the quantum mechanical probability density for a particle moving
in a uniform gravitational field.
Возвращает плотность распределения вероятностей по правилам квантовой механики для
частицы, перемещающейся в однородном гравитационном поле.
"""
# Волновая функция квантовой механики пропорциональна функции Эйри Ai(q - qE),
# где значение qE, соответствующее квантовому числу n, индексируется по n - 1.
psi, _, _, _ = airy(q-qE[n-1])

# Возвращает плотность распределения вероятностей после приближенной, но эффективной
# нормализации.
P = psi**2
return P / (sum(P) * dq)

def prob_cl(n):
"""
Return the classical probability density for a particle bouncing
elastically in a uniform gravitational field.
Возвращает классическую плотность распределения вероятностей для частицы, перемещающейся
упругими скачками в однородном гравитационном поле.
"""
# Классическая плотность распределения вероятностей уже нормализована.
return 0.5/np.sqrt(qE[n-1]*(qE[n-1]-q))
# Основное (стандартное) состояние n = 1.
q, dq = np.linspace(0, 4, 1000, retstep=True)
plt.plot(q, prob_cl(1), label='Classical')
plt.plot(q, prob_qm(1), label='Quantum')
plt.ylim(0, 0.8)
plt.legend()
plt.show()
# Возбужденное состояние n = 16.
q, dq = np.linspace(0, 20, 1000, retstep=True)
plt.plot(q, prob_cl(16), label='Classical')
plt.plot(q, prob_qm(16), label='Quantum')
plt.ylim(0, 0.25)
plt.legend(loc='upper left')
plt.show()
❶ Здесь используется функция scipy.special.ai_zeros для получения соб-

ственных значений n = 1 и n = 16.

❷ Функция scipy.special.airy находит соответствующие волновые функ-

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

8.1 Физические константы и специальные функции  425
Функции Бесселя – еще одна важная группа функций со множеством приложений в физике и инженерной деятельности. Библиотека SciPy предоставляет
несколько методов для вычисления функций Бесселя, их производных и нулей:
 jn(v, x) и jv(v, x) возвращают функцию Бесселя первого рода от x порядка v (Jv(x)). Значение v может быть действительным или целым числом;
 yn(n, x) и yv(v, x) возвращают функцию Бесселя второго рода от x целочисленного порядка n (Yn(x)) и действительного порядка v (Yv(x)) соответственно;
 in(n, x) и iv(v, x) возвращают модифицированную функцию Бесселя
первого рода от x целочисленного порядка n (In(x)) и действительного порядка v (Iv(x)) соответственно;
 kn(n, x) и kv(v, x) возвращают модифицированную функцию Бесселя
второго рода от x целочисленного порядка n (Kn(x)) и действительного
порядка v (Kv(x)) соответственно;
 функции jvp(v, x), yvp(v, x), ivp(v, x) и kvp(v, x) возвращают производ­
ные перечисленных выше функций. По умолчанию возвращается первая
производная. Чтобы получить n-ю производную, необходимо определить дополнительный аргумент n;
 некоторые функции можно использовать для получения нулей функций
Бесселя. Вероятно, самыми полезными являются функции jn_zeros(n,
nt), jnp_zeros(n, nt), yn_zeros(n, nt) и ynp_zeros(n, nt), которые возвращают первые nt нулей Jn(x), Jn’(x), Yn(x) и Yn’(x).
Пример П8.3. Вибрации тонкой круговой мембраны, натянутой на жесткий круговой каркас (как на барабане (ударном инструменте)), можно описать как собственную
(нормальную) форму колебаний, записанную с использованием функций Бесселя:
z(r, θ; t) = AJn(kr) sin nθ cos kvt,

где (r, θ) описывает положение в полярных координатах с началом в центре
мембраны, t – время, v – константа, зависящая от силы натяжения и плотности
поверхности барабана. Формы колебаний помечаются целочисленными значениями n = 0, 1, … и m = 1, 2, 3, …, где k – m-й ноль функции Jn.
Программа в листинге 8.3 создает график колебаний мембраны в n = 3, m = 2
собственной (нормальной) форме в момент времени t = 0 (см. рис. 8.2).

426



Библиотека SciPy
1.0

0.5

0.0

− 0.5

− 1.0
− 1.0

− 0.5

0.0

0.5

1.0

Рис. 8.2. Собственная (нормальная) форма n = 3, m = 2
вибрации круговой мембраны барабана
Листинг 8.3. Собственные (нормальные) формы вибрации круговой мембраны барабана
# eg8-drum-normal-modes.py
import numpy as np
from scipy.special import jn, jn_zeros
import matplotlib.pyplot as plt
# Разрешить вычисления до предельного значения m = mmax.
mmax = 5
def displacement(n, m, r, theta):
"""
Calculate the displacement of the drum membrane at (r, theta; t = 0)
in the normal mode described by integers n >= 0, 0 < m = 0, 0 < m 1: population grows'
else:
msg = 'Population does not grow'
# Уравнение Эйлера-Лотки: поиск единственного действительного корня в r.
def func(r):
return np.sum(f * np.exp(-r * x)) - 1
# Локализация корня и решение с использованием метода scipy.optimize.brentq.
a, b = 0, 10
r = brentq(func, a, b)
print('R0 = {:.3f} ({})'.format(R0, msg))
print('r = {:.5f} (lambda = {:.5f})'.format(r, np.exp(r)))

Вывод результата выполнения программы показан ниже:
R0 = 5.904 (R0 > 1: population grows)
r = 0.08742 (lambda = 1.09135)

Это значение r можно сравнить с приближенным значением, полученным
Лесли и Рэнсоном и сопровожденным их комментарием:
«Искомый корень равен 0.087703, он немного превышает оценку значения r, к которому стремится эта последовательность. Это значение находится между 0.0861 (третья степень приближения) и 0.0877, но ближе к последнему, чем к первому. Вероятно, погрешность возникла в последнем
десятичном знаке».

500



Библиотека SciPy

Пример П8.27. Метод Ньютона–Рафсона для поиска корней функции принимает
начальную предпосылку для корня x0 и выполняет процедуру поиска посредством
постепенно улучшаемых приближений по формуле:
xn+1 = xn − f(xn)/f ’(xn).
Таким образом, на каждой итерации корень приближенно вычисляется как
xn+1, координата по оси x точки пересечения касательной к графику функции
в точке f(xn). Если алгоритм применяется к функциям комплексной переменной z, то метод можно использовать для создания фракталов любопытной
формы, рассматривая сходимость корня к множеству чисел на комплексной
плоскости. Код в листинге 8.23 генерирует изображение фрактала (рис. 8.28),
выделяя цветом соответствующие точки на комплексной плоскости, используемые как начальные предпосылки при поиске корня.

Рис. 8.28. Фрактал Ньютона для функции f(z) = z4 − 1. Замысловатые самоподобные структуры
наблюдаются для начальных предпосылок z0 в интервале между корнями (−1, 1, −i, i)
Листинг 8.23. Создание изображения фрактала Ньютона
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
# Список цветов для обозначения различных корней.
colors = ['b', 'r', 'g', 'y']
TOL = 1.e-8
def newton(z0, f, fprime, MAX_IT=1000):
"""The Newton-Raphson method applied to f(z).
Returns the root found, starting with an initial guess, z0, or False
if no convergence to tolerance TOL was reached within MAX_IT iterations.

8.4 Оптимизация, подгонка данных и численные методы решения уравнений  501
Метод Ньютона-Рафсона, применяемый к функции f(z).
Возвращает найденный корень, начиная с исходной предпосылки z0, или False,
если не достигнута сходимость в пределах допустимой погрешности TOL за MAX_IT итераций.
"""
z = z0
for i in range(MAX_IT):
dz = f(z)/fprime(z)
if abs(dz) < TOL:
return z
z -= dz
return False
def plot_newton_fractal(f, fprime, n=200, domain=(-1, 1, -1, 1)):
"""Plot a Newton Fractal by finding the roots of f(z).
The domain used for the fractal image is the region of the complex plane
(xmin, xmax, ymin, ymax) where z = x + iy, discretized into n values along
each axis.
Создание изображения фрактала Ньютона по найденным корням функции f(z).
Для изображения фрактала используется область комплексной плоскости
(xmin, xmax, ymin, ymax), где z = x + iy с дискретизацией по n значениям
на каждой оси.
"""
roots = []
m = np.zeros((n, n))
def get_root_index(roots, r):
"""Get the index of r in the list roots.
If r is not in roots, append it to the list.
Определение индекса r в списке корней.
Если r нет в списке корней, то добавить его в этот список.
"""
try:
return np.where(np.isclose(roots, r, atol=TOL))[0][0]
except IndexError:
roots.append(r)
return len(roots) - 1
xmin, xmax, ymin, ymax = domain
for ix, x in enumerate(np.linspace(xmin, xmax, n)):
for iy, y in enumerate(np.linspace(ymin, ymax, n)):
z0 = x + y*1j
r = newton(z0, f, fprime)
if r is not False:
ir = get_root_index(roots, r)
m[iy, ix] = ir
nroots = len(roots)
if nroots > len(colors):
# Использование "непрерывной" цветовой схемы, если найдено слишком много корней.
cmap = 'hsv'
else:
# Использование списка цветов для цветовой схемы: по одному цвету для каждого корня.
cmap = ListedColormap(colors[:nroots])
plt.imshow(m, cmap=cmap, origin='lower')

502

 Библиотека SciPy

plt.axis('off')
plt.show()
f = lambda z: z**4 - 1
fprime = lambda z: 4*z**3
plot_newton_fractal(f, fprime, n=500)

8.4.4 Упражнения
Вопросы
В8.4.1. Использовать метод scipy.optimize.brentq для поиска решений уравнения
x + 1 = 1 / (x − 3)3.
В8.4.2. Метод scipy.optimize.newton ошибается при поиске корней перечисленных ниже функций с заданной начальной точкой x0. Объяснить, почему это происходит, и найти корни, изменив вызов метода newton или используя другой метод.
а)
б)
в)
г)

f(x) = x3 − 5x, x0 = 1.
f(x) = x3 − 3x + 1, x0 = 1.
f(x) = 2 − x5, x0 = 0.01.
f(x) = x4 − (4.29)x2 − 5.29, x0 = 0.8.

В8.4.3. Траектория снаряда в плоскости xz, запущенного из исходной точки
под углом θ0 с начальной скоростью v0 = 25 м/с, описывается формулой
z = x tg θ0 − (g / 2v02 cos2 θ0) x2.

Если известно, что снаряд пролетел через точку с координатами (5, 15), то
с помощью метода Брента определить возможные значения угла θ0.

Задачи

З8.4.1. Для прямоугольной области площадью A = 10 000 м2, примыкающей
к прямому участку реки, необходимо построить ограду (границу у реки огораживать не нужно). При каких размерах области a, b длина ограды будет минимальной? Проверить и убедиться, что алгоритм минимизации с ограничениями дает тот же результат, что и алгебраическое решение.
З.8.4.2. Найти все корни функции
f(x) = 1/5 + x cos(3/x),
используя: а) метод scipy.optimize.brentq, б) метод scipy.optimize.newton.
З8.4.3. Закон смещения Вина утверждает, что длина волны максимального излучения абсолютно черного тела, описываемого законом излучения Планка,
пропорциональна 1/T:

8.4 Оптимизация, подгонка данных и численные методы решения уравнений  503
λmaxT = b,

где b – постоянная смещения Вина. Рассматривая распределение Планка для
плотности излучаемой энергии как функцию от длины волны
u(λ, T) = (8π2hc / λ5) (1 / (ehc/λkBT − 1)),

определить значение постоянной смещения Вина b, используя метод scipy.
optimize.minimize_scalar для поиска максимума функции u(λ,T) при температурах в диапазоне 500 К ≤ T ≤ 6000 К и подогнать λmax к прямой относительно
1/T. Сравнить полученный результат с «точным» значением постоянной смещения Вина, доступным в пакете scipy.constants (см. раздел 8.1.1).
◊ З8.4.4. Рассмотреть одномерный случай «ямы с бесконечными стенками» –
модели квантовой механики для частицы, заключенной в «ящике» определенной формы (−1 ≤ x ≤ 1), описываемой уравнением Шрёдингера
−d2ψ/dx2 = Eψ

в единицах измерения энергии, для которых ħ2/(2m) = 1, где m – масса частицы.
Точное решение для основного состояния этой системы записывается в виде
ψ = cos(πx/2), E = π2/4.

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

ψtrial =

∑a φ (x)
n=0

n

n

с учетом коэффициентов an. Считая, что базисные функции имеют форму симметричного многочлена:
φn = (1 − x)N−n+1(x + 1)n+1,

использовать методы scipy.optimize.minimize и scipy.integrate.quad для поиска оптимального значения для ожидаемой величины энергии (отношение
Рэлея–Рица):

ε=

ˆ ψ
ψ trial H
trial
ψtrial | ψtrial

1

=

d2
ψ dx
dx 2 trial
.
1
∫ ψ trial ψtrial dx

∫−1 ψtrial
−1

Сравнить оцениваемую величину энергии ℇ с точным значением при N = 1,
2, 3, 4. (Совет: рекомендуется использовать объекты np.polynomial.Polynomial
для представления базисной и контрольной волновой функции.)

Глава

9
Анализ данных
с помощью pandas

9.1 Введение в pandas
9.1.1 Что такое pandas
pandas – это широко распространенная библиотека на языке Python с открытым исходным кодом для обработки и анализа данных. В отличие от основной
структуры данных библиотеки NumPy, главный объект pandas DataFrame может
содержать неоднородные типы данных (числа с плавающей точкой, целые числа, строки, даты и т. д.), которые можно структурировать в виде иерархии и индексировать. Библиотека предоставляет большое количество векторизованных
функций для очистки, преобразования и агрегации данных эффективными
способами с использованием характерных приемов, аналогичных применяемым в библиотеке NumPy. Имя библиотеки образовано от английского словосочетания «panel data» (известного в другом варианте как «longitudinal data» –
«данные многомерного временно́го ряда при долговременном наблюдении»),
которое обозначает наборы данных нескольких переменных, наблюдаемых
в течение нескольких (многих) интервалов времени для одного объекта.
На домашней странице pandas https://pandas.pydata.org/ содержится по­
дробная информация о самой последней доступной версии библиотеки и инструкции по ее скачиванию и установке. В этой главе мы будем следовать общепринятому соглашению по импорту pandas как псевдонима pd:
import pandas as pd

Основными структурами данных pandas являются Series и DataFrame, представляющие одномерную последовательность значений и таблицу данных соответственно. В этом вводном разделе будут подробно описаны их главные свойства и практическое применение. В следующих разделах рассматриваются более
продвинутые функциональные возможности и примеры приложений. pandas –
большая и сложная библиотека с огромным спектром функциональных возможностей, но в этой главе описаны только базовые функции и варианты использования. Более подробные примеры представлены на веб-сайте книги.

9.1 Введение в pandas  505

9.1.2 Объект Series
В самой простой форме объект Series можно создать точно так же, как одномерный массив NumPy:
In [x]: river_lengths = pd.Series([6300, 6650, 6275, 6400])
In [x]: river_lengths
Out[x]:
0
6300
1
6650
2
6275
3
6400
dtype: int64

Объекту Series можно присвоить строку имени и тип данных dtype:
In [x]: river_lengths = pd.Series([6300, 6650, 6275, 6400], name='Length /km', dtype=float)
Out[x]:
0
6300.0
1
6650.0
2
6275.0
3
6400.0
Name: Length /km, dtype: float64

Но, в отличие от массива NumPy, каждый элемент в последовательности Series библиотеки pandas связан с индексом. Так как в приведенном выше примере индекс не был установлен явно, для индексирования по умолчанию используется последовательность целых чисел (начиная с 0):
In [x]: river_lengths.index
Out[x]: RangeIndex(start=0, stop=4, step=1)

RangeIndex – это объект pandas, работающий с эффективным использованием памяти, подобно встроенной функции Python range, предоставляющей
монотонную последовательность целых чисел. Часто с помощью этого объекта
удобно ссылаться на строки объекта Series с некоторыми другими метками,
отличающимися от целочисленного индекса. Явную индексацию элементов
можно обеспечить, передавая последовательность как аргумент index, или при
создании объекта Series из словаря:
In [x]: river_lengths = pd.Series(data=[6300, 6650, 6275, 6400],
...:
index=['Yangtze', 'Nile', 'Mississippi', 'Amazon'],
...:
name='Length /km')

или:
In [x]: river_lengths = pd.Series(data={'Yangtze': 6300, 'Nile': 6650,
...:
'Mississippi': 6275, 'Amazon': 6400},
...:
name='Length /km')
In [x]: river_lengths
Out[x]:
Yangtze
6300

506

 Анализ данных с помощью pandas

Nile
6650
Mississippi
6275
Amazon
6400
Name: Length /km, dtype: int64

Это обеспечивает весьма выразительный и удобный способ ссылки на элементы последовательности Series с использованием меток индекса вместо целых чисел, в том числе и на отдельные элементы последовательности:
In [x]: river_lengths['Nile']
Out[x]: 6650

вместо river_lengths[1] или для представления элементов в другом порядке:
In [x]: river_lengths[['Amazon', 'Nile', 'Yangtze']]
Out[x]:
Amazon
6400
Nile
6650
Yangtze
6300
Name: Length /km, dtype: int64

вместо river_lengths[ [3, 1, 0] ]. Вырезание элементов в стиле Python также
работает, как и ожидается:
In [x]: river_lengths[2::-1]
Out[x]:
Mississippi
6275
Nile
6650
Yangtze
6300
Name: Length /km, dtype: int64

Возможно даже использование нотации, похожей на стиль вырезания, но
следует обратить особое внимание на то, что в данном случае указанный конечный элемент включается в вырезаемую группу:
In [x]: river_lengths['Nile':'Amazon']
Out[x]:
Nile
6650
Mississippi
6275
Amazon
6400
Name: Length /km, dtype: int64

Определенная метка индекса является корректным идентификатором языка Python, поэтому можно обращаться к строке данных как к атрибуту последовательности Series:
In [x]: river_lengths.Mississippi
Out[x]: 6275

Разумеется, возможно выполнение числовых операций с данными Series
с использованием векторизации, как и для массивов NumPy:

9.1 Введение в pandas  507
In [x]: KM_TO_MILES = 0.621371
In [x]: river_lengths *= KM_TO_MILES
In [x]: river_lengths.name = 'Length /miles'
In [x]: river_lengths
Out[x]:
Yangtze
3914.637300
Nile
4132.117150
Mississippi
3899.103025
Amazon
3976.774400
Name: Length /miles, dtype: float64

В приведенном выше примере для обновления был также выбран атрибут name
объекта Series. Обратите внимание: атрибут dtype был тоже изменен соответствующим образом с int64 на float64, чтобы соответствовать значениям нового типа.
Операции сравнения и фильтрации объекта Series с помощью логических
операций создают новый объект Series:
In [x]: river_lengths > 4000
Out[x]:
Nile
True
Amazon
False
Yangtze
False
Mississippi
False
Name: Length /miles, dtype: bool
In [x]: river_lengths[river_lengths = 1800)]
Type
Stratovolcano
Caldera
Stratovolcano
Caldera
Stratovolcano

VEI Deaths
date
7.0 11000.0 10/4/1815
6.0 2000.0 27/8/1883
6.0 2500.0 25/10/1902
6.0
2.0
6/9/1912
6.0
350.0 15/6/1991

9.6 Примеры  561
Чтобы определить 10 самых мощных взрывных извержений, можно отфильтровать данные с исключением записей с неизвестными значениями VEI
перед сортировкой:
In [x]: df[pd.notnull(df['VEI'])].sort_values('VEI').tail(10)[
...:
['date', 'Name', 'Type', 'Country', 'VEI']]
Out[x]:
date
Name
Type
Country
29
653
Dakataua
Caldera Papua New Guinea
25
450
Ilopango
Caldera
El Salvador
22
240
Ksudach Stratovolcano
Russia
21
230
Taupo
Caldera
New Zealand
18
60 Bona-Churchill Stratovolcano
United States
99 19/2/1600
Huaynaputina Stratovolcano
Peru
1
1750 BCE
Veniaminof Stratovolcano
United States
40
1000
Changbaishan Stratovolcano
North Korea
218 10/4/1815
Tambora Stratovolcano
Indonesia
3
1610 BCE
Santorini Shield volcano
Greece

VEI
6.0
6.0
6.0
6.0
6.0
6.0
6.0
7.0
7.0
7.0

Но существует много записей со значением VEI, равным 6, и их порядок
здесь не вполне понятен. Более удачным подходом может оказаться предварительная сортировка сначала по значению VEI, потом по числу жертв. Необходимо установить параметр na_position='first', чтобы все нулевые (неизвестные) значения располагались перед числовыми значениями (это позволит
эффективно определить ранг по минимальным значениям):
In [x]: df.sort_values(['VEI', 'Deaths'], na_position='first').tail(10)[
...:
['date', 'Name', 'Type', 'Country', 'VEI', 'Deaths']]
Out[x]:
date
Name
Type
Country VEI Deaths
386
6/9/1912
Novarupta
Caldera
United States 6.0
2.0
650 15/6/1991
Pinatubo
Stratovolcano
Philippines 6.0
350.0
99
19/2/1600 Huaynaputina
Stratovolcano
Peru 6.0 1500.0
120
1660 Long Island Complex volcano Papua New Guinea 6.0 2000.0
322 27/8/1883
Krakatau
Caldera
Indonesia 6.0 2000.0
365 25/10/1902 Santa Maria
Stratovolcano
Guatemala 6.0 2500.0
25
450
Ilopango
Caldera
El Salvador 6.0 30000.0
3
1610 BCE
Santorini Shield volcano
Greece 7.0
NaN
40
1000 Changbaishan
Stratovolcano
North Korea 7.0
NaN
218 10/4/1815
Tambora
Stratovolcano
Indonesia 7.0 11000.0

Также можно создать несколько гистограмм с объединенными данными из
нескольких столбцов (см. рис. 9.10):
fig, axes = plt.subplots(nrows=2, ncols=2)
df['Day'].hist(bins=31, ax=axes[0][0], grid=False)
axes[0][0].set_xlabel('(a) Day')
df['Month'].hist(bins=np.arange(1, 14) - 0.5, ax=axes[0][1], grid=False)
axes[0][1].set_xticks(range(1, 13))
axes[0][1].set_xlabel('(b) Month')
df[df['Year']>1600]['Year'].hist(ax=axes[1][0], grid=False)
axes[1][0].set_xlabel('(c) Year')
df['Elevation'].hist(ax=axes[1][1], grid=False)

562



Анализ данных с помощью pandas

axes[1][1].set_xlabel('(d) Elevation /m')
plt.tight_layout()
plt.show()

50

20
0

0
1

10

20

30

1 3

(a) День

6

9

12

(б) Месяц
200

200

0

0
1600

1800
(в) Год

2000

0

2000 4000 6000

(г) Высота, м

Рис. 9.10. Диаграммы, объединяющие данные об извержениях вулканов из нескольких
столбцов: а) по дню месяца; б) по месяцу года; в) по частоте по годам с 1600 г. – можно
надеяться, что точность записей об извержениях вулканов постоянно улучшалась с 1600 г.
и частота извержений в действительности не увеличивалась; г) высота вулканов

Глава

10

Общие положения
научного программирования
10.1 Арифметика с плавающей точкой
10.1.1 Представление действительных чисел
Действительные числа, такие как 1.2, −0.36, π, 4 и 13256.625, можно мысленно представить как точки на непрерывной бесконечной числовой прямой132.
Некоторые действительные числа (включая сами целые числа) можно представить в виде отношения двух целых чисел, например 5/8 и 1/3. Такие числа
называются рациональными. Другие числа, такие как π, e и √2, невозможно
представить в таком виде, поэтому они называются иррациональными.
Существует несколько возможных способов записи действительных чисел
в зависимости от того, к какой категории они относятся, но не все способы способны точно отображать числовое значение (ручки и карандаши имеют ограниченный ресурс использования). Например, рациональное число 5/8 можно
точно записать как разложение в десятичную дробь:
5/8 = 6/10 + 2/100 + 5/1000,
но число 1/3 невозможно записать с помощью конечного количества знаков
после запятой в аналогичном разложении:
1/3 = 3/10 + 3/100 + 3/1000 + … = 0.333…
При записи 1/3 в виде разложения в десятичную дробь непременно приходится в каком-то месте обрывать бесконечную последовательность троек.
Иррациональные числа могут быть точно описаны (с помощью некоторых
предполагаемых геометрических или каких-либо других знаний), например
число π – это отношение длины окружности к ее диаметру, √2 – длина гипотенузы прямоугольного треугольника с длинами катетов 1. Но для численного
132

Очевидно, что целое число, такое как 4, представляет собой всего лишь особый случай действительного числа.

564



Общие положения научного программирования

представления или сохранения таких значений необходим определенный уровень приближения. Например, дробь 355/113 представляет собой общеизвестное рациональное приближенное значение числа π. Десятичная дробь, более
точно представляющая π: 3.14159265358979. Но при разложении в десятичную
дробь133 требуется бесконечное количество знаков после запятой, чтобы точно
выразить значение π, точно так же, как необходимо бесконечное количество
троек при разложении в десятичную дробь числа 1/3.
Компьютеры хранят числа в двоичной (бинарной) форме, и некоторые условия, ограничивающие точность десятичного представления действительного
числа, применяются и к его двоичному представлению.
Например, числу 5/8 соответствует точное двоичное представление с по­
мощью конечного числа битов:
5/8 = 1/2 + 0/4 + 1/8 = 0.1012,
но двоичное представление
1/10 = 0.000110011001100110011. . .2
является бесконечно повторяющейся последовательностью. Можно сохранить
только конечное число знаков после десятичной точки, поэтому такая усеченная последовательность битов после обратного преобразования в десятичную
дробь дает результат
1/10 ≈ 0.100000000000000006
при использовании так называемого стандарта двойной точности (double-precision), общепринятого для большинства языков программирования в большинстве операционных систем. Это наиболее близкое (точное) числовое представление дроби 1/10.
Формат представления чисел с плавающей точкой двойной точности определяется международным стандартом IEEE-754. Представление числа, хранящееся в 64 битах (8 байтах), разделяется на три части: один знаковый бит,
11-битовый показатель степени и 52-битовая мантисса (значащая часть числа).
Это становится более понятным на конкретном примере: десятичную дробь
13 256.625 можно записать в научном формате:
13 256.625 = +1.3256625 × 104
и сохранить со знаковым битом, соответствующим знаку +, как мантиссу, равную 13256625 (где подразумевается, что десятичная точка расположена после
первого знака), и показатель степени 4. Такая форма записи называется «с пла-

133

Следует отметить, что разложение в десятичную дробь – это просто рациональное число, в знаменателе которого записывается некоторая степень числа 10:
3.14159265358979 = 314 159 265 358 979 / 100 000 000 000 000.

10.1 Арифметика с плавающей точкой  565
вающей точкой» (floating point), потому что десятичная точка134 смещается на
количество знаков, определяемое показателем степени.
Представление чисел с плавающей точкой в двоичном виде работает аналогичным образом, но используются, разумеется, только цифры 0 и 1. Это позволяет воспользоваться хитроумным приемом: двоичная точка числа (равнозначная десятичной точке в системе счисления с основанием 10) сдвигается так,
чтобы мантисса не содержала начальных нулей, т. е. начиналась с 1. Поскольку
все мантиссы, нормализованные таким способом, непременно начинаются с 1,
нет необходимости хранить эту начальную единицу, так что в 52-битовой мантиссе можно сохранить число с эффективной точностью 53 бита135. Опущенный
бит иногда называют скрытым битом (hidden bit).
В рассматриваемом здесь примере число 13 256.625 в действительности
можно точно представить как следующее двоичное число:
13256.62510 ≡ 11001111001000.1012.

Следовательно, нормализованная форма мантиссы: 11001111001000101,
а показатель степени равен 13, таким образом:
11001111001000.1012 = 1.1001111001000101 × 213.
В соответствии с описанным выше приемом первый знак нормализованной
мантиссы, который всегда равен 1, опускается, и мантисса сохраняется в следующем виде:
1001111001000101000000000000000000000000000000000000

Для обеспечения возможности использования чисел с отрицательными степенями (чисел, меньших 1) показатель степени сохраняется с так называемым
сдвигом (смещением – bias): к действительному показателю степени прибавляется 1023. Таким образом, реальные показатели степени в диапазоне от −1023
до +1023 сохраняются как числа от 1 до 2046. В рассматриваемом здесь примере
11-битовое поле показателя степени содержит значение 13 + 1023 = 1036:
10000001100

Здесь знаковый бит равен 0, обозначающему положительное число. Полное
64-битовое представление числа с плавающей точкой 13 256.625 (для удобства
части представления разделены пробелами):
0 10000001100 1001111001000101000000000000000000000000000000000000

и это точное представление десятичной дроби. Но представление дроби 0.1
0 01111111011 1001100110011001100110011001100110011001100110011010
134

135

Более общее название: точка (запятая) в позиционном представлении чисел (radix
point) в системах счисления, отличающихся от привычной системы с основанием 10.
Следует отметить, что этот прием работает только в двоичной системе.

566

 Общие положения научного программирования

не является точным (из-за описанного выше усечения и округления бесконечно повторяющейся последовательности 0011…) – после обратного преобразования в десятичную дробь получается число
0.100000000000000005551115123126

В общем случае 53 бита (включая скрытый бит) мантиссы обеспечивают точность, приблизительно равную 15 значащим цифрам: log10(253) = 15.95. Любое
вычисление с бо́льшим количеством значащих цифр дает результат с ошибкой
округления. Верхняя граница относительной ошибки из-за округления называется машинным эпсилоном (не путать с машинным нулем) ε. В языке Python:
In [x]:
In [x]:
In [x]:
Out[x]:

import sys
eps = sys.float_info.epsilon
eps
2.220446049250313e-16

Можно показать, что максимальный интервал между двумя нормализованными числами с плавающей точкой равен 2ε. Таким образом, условное выражение x + 2*eps == x всегда вычисляется с неизменным результатом False.

10.1.2 Сравнение чисел с плавающей точкой
Из-за ограниченной точности представления чисел с плавающей точкой для
(большинства) действительных чисел весьма опасно сравнивать два объекта
типа float на равенство. Например, рассмотрим операцию возведения в квад­
рат числа 0.1:
In [x]: (0.1)**2
Out[x]: 0.010000000000000002

Как нам уже известно, результат не равен в точности 0.01, но полученный
результат даже не является наиболее близким представлением числа 0.01,
потому что в действительности квадратом этого числа являлось значение
0.100000000000000006. Печальным следствием этого факта становится неудачное сравнение:
In [x]: (0.1)**2 == 0.01
Out[x]: False

Библиотека NumPy предоставляет методы isclose и allclose (см. раздел 6.1.12) для сравнения двух чисел с плавающей точкой или массивов таких
чисел с заданным или установленным по умолчанию пределом допустимой
погрешности:
In [x]: np.isclose(0.1**2, 0.01)
Out[x]: True

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

10.1 Арифметика с плавающей точкой  567
In [x]: a, b, c = 1e14, 25.44, 0.74
In [x]: (a + b) + c
Out[x]: 100000000000026.17
In [x]: a + (b + c)
Out[x]: 100000000000026.19

Кроме того, в общем случае умножение чисел с плавающей точкой не всегда
является дистрибутивным относительно сложения:
In [x]: a, b, c = 100, 0.1, 0.2
In [x]: a*b + a*c
Out[x]: 30.0
In [x]: a * (b + c)
Out[x]: 30.000000000000004

10.1.3 Потеря значащих разрядов
Большинство операций с числами с плавающей точкой (такие как сложение
и вычитание) приводят к потере значащих разрядов. Это означает, что количество значащих разрядов (цифр) в полученном результате может быть меньшим, чем в исходных числах (операндах), используемых при вычислении. Для
наглядной демонстрации рассмотрим предполагаемое представление числа
с плавающей точкой, используемое в десятичном виде с шестью значащими
разрядами мантиссы, и выполним следующее вычисление, записанное в точной форме:
1.2345432 − 1.23451 = 0.0000332.
Наша воображаемая система не способна сохранить первый операнд с его
полной точностью, но может обеспечить приближение в виде числа 1.23454.
Тогда вычитание чисел с плавающей точкой даст результат:
1.23454 − 1.23451 = 0.00003.
Исходные числа имели точность до шестой значащей цифры, но результат
обеспечивает точность только в первой значащей цифре. Следует отметить,
что это не тот случай, когда точный результат невозможно представить во
всех доступных разрядах нашей воображаемой архитектуры с плавающей точкой: число 0.0000332 ≡ 3.32 × 10−5 содержит только три значащие цифры, т. е.
не выходит за предел шести доступных разрядов. Критическая потеря значащих разрядов произошла исключительно из-за чрезвычайно малой разности
между двумя исходными числами. Об этом явлении, которое иногда называют
«катастрофическим взаимоуничтожением» (catastrophic cancellation), следует
всегда помнить при вычитании двух чисел с близкими значениями.
Аналогичная потеря значащих разрядов может происходить при вычитании
или сложении малого числа со значительно бо́льшим числом:

568



Общие положения научного программирования

12 345.6 + 0.123456 = 12 345.72345 (точный результат),
12 345.6 + 0.123456 = 12 345.7 (в системе с мантиссой с шестью значащими
разрядами).
Даже несмотря на то, что 15 значащих разрядов представления числа с плавающей точкой двойной точности могут показаться обеспечивающими вполне
достаточную точность при одном отдельном вычислении, необходимо всегда
помнить о том, что многократное выполнение таких вычислений может увеличивать неизбежную ошибку округления до критической величины, если используемые числа невозможно представить точно. Рассмотрим следующий пример:
In [x]: for i in range(10000000):
....:
a += 0.1
....:
In [x]: a
Out[x]: 999999.9998389754

Разность между вычисленным приближенным значением и известным точным значением 1 000 000 составляет более 1.61 × 10−4.
В модуле Python math есть функция fsum, использующая методику под названием алгоритм (компенсационного суммирования) Шевчука для компенсации
ошибок округления и потери значащих разрядов. Сравним две реализации ранее вычисленной в простом цикле суммы с использованием выражения генератора:
In [x]:
Out[x]:
In [x]:
Out[x]:

sum((0.1 for i in range (10000000)))
999999.9998389754
math.fsum((0.1 for i in range (10000000)))
1000000.0

10.1.4 Обращение в машинный ноль и переполнение
Еще одним последствием способа компьютерной обработки чисел с плавающей запятой является ограничение минимальной и максимальной величин чисел, которые могут быть сохранены. Например, при байесовском анализе часто
требуется перемножение весьма малых значений вероятностей, поскольку
каждое значение вероятности представлено числом от 0 до 1. При большом
количестве таких вероятностей их произведение может достигать значения,
которое слишком мало для представления, в результате чего происходит обращение в машинный ноль (underflow):
In [x]: P =
In [x]: for
....:
....:
1
0.0005
2.5e-07
1.25e-10

1
i in range(101):
print(P)
P *= 5.e-4

10.1 Арифметика с плавающей точкой  569
6.250000000000001e-14
...
1.0097419586828971e-307
5.0487097934146e-311
2.5243548965e-314
1.2621776e-317
6.31e-321
5e-324
0.0
0.0

# Начинается денормализация.

# Обращение в машинный ноль.

Ниже этого значения Python начинает жертвовать некоторой степенью точности и работает с измененным представлением числа (денормализованное
или субнормальное (по существу – ошибочное) число). Такой процесс называется постепенное обращение в машинный ноль. Как бы то ни было, в итоге
число, представляемое машинным нулем, становится неотличимым от настоящего нуля. Минимальным числом, которое можно представить полностью по
стандарту IEEE-754 с двойной точностью, является
In [x]: import sys
In [x]: sys.float_info.min
Out[x]: 2.2250738585072014e-308

Существует несколько возможных методик решения проблемы обращения
в машинный ноль (помимо использования чисел с большей степенью точности, например np.float128, если это доступно). В приведенном выше примере часто применяется вычисление суммы логарифмов вероятностей, которые
имеют более приемлемые величины, вместо непосредственного вычисления
произведения вероятностей. Другой вариант: в приведенном выше коде нужно начать со значения P = 1.e100 и обрабатывать полученные в результате числа с учетом этого постоянного множителя.
Переполнение (overflow) чисел с плавающей точкой – это проблема, возникающая на другом конце числовой шкалы: наибольшее число с двойной точностью, которое можно представить в Python:
In [x]: sys.float_info.max
Out[x]: 1.7976931348623157e+308

В библиотеке NumPy для чисел, вышедших за границу переполнения, устанавливаются специальные значения inf или -inf в зависимости от знака:
In [x]: f = 1
In [x]: for x in range(1, 40, 4):
...:
print('exp({}) = {}'.format(x**2, np.exp(x**2)))
...:
exp(1) = 2.718281828459045
exp(25) = 72004899337.38588
exp(81) = 1.5060973145850306e+35
exp(169) = 2.487524928317743e+73
exp(289) = 3.2441824460394912e+125
exp(441) = 3.340923407659982e+191

570



Общие положения научного программирования

exp(625) = 2.7167594696637367e+271
exp(841) = inf
exp(1089) = inf
exp(1369) = inf

Это может приводить к некоторым странным отношениям между числами,
которые слишком велики для корректного представления:
In [x]:
In [x]:
Out[x]:
In [x]:
Out[x]:

a, b = 1.e500, 1.e1000
a == b
True
a, b
(inf, inf)

Существует еще одно специальное значение nan (NaN – Not a Number –
не число), которое возвращают некоторые операции, работающие с «переполненными» числами:
In [x]: a / b
Out[x]: nan

В библиотеке NumPy также реализованы собственные специальные значения numpy.nan и numpy.inf (см. раздел 6.1.4).
Никогда не проверяйте объект на значение nan с помощью оператора ==,
потому что nan не равно даже самому себе136:
In [x]: c = a / b
In [x]: c == c
Out[x]: False

Для объектов типа int в языке Python не существует проблемы переполнения, так как Python автоматически выделяет память для их хранения с полной
точностью (ограничение существует только по доступной оперативной памяти). Но массивы NumPy целых чисел, которые отображаются во внутренние
структуры данных языка C, сохраняются в фиксированном количестве байтов
(см. табл. 6.2), поэтому переполнение возможно. Например:
In [x]:
In [x]:
In [x]:
Out[x]:

a = np.zeros(3, dtype=np.int16)
a[:] = -30000, 30000, 40000
a
array([-30000, 30000, -25536], dtype=int16)

In [x]:
In [x]:
In [x]:
Out[x]:

b = np.zeros(3, dtype=np.uint16)
b[:] = -30000, 40000, 70000
b
array([35536, 40000, 4464], dtype=uint16)

Для знаковых 16-битных целых чисел определен диапазон от −32 768 до
32 767, т. е. от −215 до (215 − 1). Из-за такого способа хранения в приведенном
136

Это означает, что оператор == не можетбыть отношением равенства для чисел с плавающей точкой, так как он не является рефлексивным (возвратным).

10.1 Арифметика с плавающей точкой  571
выше примере попытка присваивания элементу массива a[2] числа 40 000
привела к присваиванию вместо этого значения 40 000 − 216 = −25 536. Аналогично беззнаковые 16-битовые целые числа ограничены значениями от 0 до
65 535, т. е. от 0 до (216 − 1). Отрицательные числа вообще не могут быть каклибо представлены, поэтому при попытке присваивания b[0] = -30000 происходит преобразование в −30 000 mod 216 = 35536, а присваивание b[2] = 70000
приводит к переполнению, и в итоге получается значение 70 000 mod 216 = 4464.

10.1.5 Материалы для дальнейшего изучения
 Документация по языку Python: Floating-Point Arithmetic: Issues and Limitations. Доступно здесь: https://docs.python.org/tutorial/floatingpoint.html.
 Статья «What Every Computer Scientist Should Know About Floating-Point
Arithmetic» Дэвида Голдберга (David Goldberg) (Computing Surveys, March
1991) уже стала классической. В ней представлена строгая методика использования арифметики с плавающей точкой. Настоятельно рекомендуется для изучения. Статья доступна здесь: https://docs.oracle.com/cd/
E19957-01/806-3568/ncg_goldberg.html.
 S. Oliveira and D. Stewart. Writing Scientific Software: A Guide to Good Style,
Cambridge University Press, Cambridge (2006).
 N. J. Higham. Accuracy and Stability of Numerical Algorithms, 2nd edn., Society for Industrial and Applied Mathematics, Philadelphia, PA (2002).
 Модуль Standard Library decimal поддерживает операции с десятичными
числами с фиксированной точкой и с корректно округленными числами с плавающей точкой, но эти вычисления в общем случае выполняются намного медленнее, чем вычисления со встроенным типом данных
float. Более подробную информацию см. здесь: https://docs.python.org/3/
library/decimal.html.

10.1.6 Упражнения
Вопросы
В10.1.1. Десятичное представление некоторых действительных чисел не является однозначным. Например, докажите математически, что 0.9 ≡ 0.9999... ≡ 1.
В10.1.2. Выражение tg (π ) = 0 строго определено математически, но тогда почему при выполнении следующего вычисления возникает критический сбой
из-за ошибки в области действия модуля math?
In [x]: math.sqrt(math.tan(math.pi))
--------------------------------------------------------------------------ValueError
Traceback (most recent call last)
in ()
----> 1 math.sqrt(math.tan(math.pi))

В10.1.3. Великая теорема Ферма утверждает, что не существует трех положительных целых чисел x, y и z, являющихся корнями уравнения xn + yn − zn = 0 для
любого целого n > 2. Объясните результат выполнения примера, противоречащего этой теореме:

572



Общие положения научного программирования

In [x]: 844487.**5 + 1288439.**5 - 1318202.**5
Out[x]: 0.0

В10.1.4. Функции f(x) = (1 − cos2x)/x2 и g(x) = sin2x/x2 неразличимы с математической точки зрения, но при построении их графиков с использованием Python
в области −0/001 ≤ x ≤ 0.001 наблюдается существенное различие. Объяснить
причину этого различия.
В10.1.5. Как можно определить, представлено ли число с плавающей точкой
значением nan или нет, без использования методов math.isnan и numpy.isnan?
В10.1.6. Определить и объяснить результаты вычисления следующих инструкций:
а) 1e1001 > 1e1000
б) 1e350/1.e100 == 1e250
в) 1e250 * 1.e-250 == 1e150 * 1.e-150
г) 1e350 * 1.e-350 == 1e450 * 1.e-450
д) 1 / 1e250 == 1e-250
е) 1 / 1e350 == 1e-350
ж) 1e450/1e350 != 1e450 * 1e-350
з) 1e250/1e375 == 1e-125
и) 1e35 / (1e1000 - 1e1000) == 1 / (1e1000 - 1e1000)
к) 1e1001 > 1e1000 or 1e1001 < 1e1000
л) 1e1001 > 1e1000 or 1e1001 0 с учетом граничного условия y(0) = 1. Эту простую задачу можно решить аналитически:
y = e−αx,
но предположим, что необходимо решить ее в численном виде. Самым простым
является явный одношаговый метод Эйлера первого порядка точности: выбирается размер шага h, определяющий сетку значений x, т. е. xi = xi−1 + h, затем выполняется приближенное вычисление соответствующих значений y по формуле:
yi = yi−1 + h|dy/dx|x

i−1

= yi−1 − hαyi−1 = yi−1(1 − αh).

Возникает вопрос: какое значение необходимо выбрать для h? Малое значение h минимизирует погрешность, возникающую при аппроксимации, описанной выше, при которой значения y просто соединяются отрезками прямой138,
но если значение h слишком мало, то возникает погрешность с потерей значащих разрядов («катастрофическое взаимоуничтожение») из-за ограниченной
точности представления чисел, используемых при вычислении139.
Код в листинге 10.1 реализует явный одношаговый алгоритм Эйлера для решения приведенного выше дифференциального уравнения. Наибольшее значение h (здесь h = α/2 = 1) явно делает алгоритм нестабильным (см. рис. 10.1).
138
139

То есть это ряд Тейлора, усеченный в окрестности точки yi–1 по линейному члену h.
В экстремальном случае, если выбранное значение h меньше машинного эпсилона,
обычно приблизительно равного 2×10−16, то получаем xi = xi−1, следовательно, сетка
точек вообще отсутствует.

574



Общие положения научного программирования
1.0
h = 0.01
h = 0.2
h =1

0.5

0.0

0.5

1.0

0

2

4

6

8

10

Рис. 10.1. Нестабильность явного одношагового решения Эйлера для дифференциального
уравнения dy/dx = − αy при большом размере шага h

Листинг 10.1. Сравнение различных размеров шагов h для численного решения дифференциального уравнения y’ = −αy явным одношаговым методом Эйлера
import numpy as np
import matplotlib.pyplot as plt
alpha, y0, xmax = 2, 1, 10
def euler_solve(h, n):
""" Solve dy/dx = -alpha.y by forward Euler method for step size h."""
""" Решение ДУ dy/dx = -alpha.y явным одношаговым методом Эйлера при размере шага h."""
y = np.zeros(n)
y[0] = y0
for i in range(1, n):
y[i] = (1 - alpha * h) * y[i-1]
return y
def plot_solution(h):
x = np.arange(0, xmax, h)
y = euler_solve(h, len(x))
plt.plot(x, y, label='$h={}$'.format(h))
for h in (0.01, 0.2, 1):
plot_solution(h)
plt.legend()
plt.show()

10.2 Стабильность и обусловленность алгоритма  575
Пример П10.2. Для интеграла
1

In = ∫ xnexdx при n = 0, 1, 2, …
0

предлагается рекурсивное отношение, полученное при интегрировании по
час­тям:
1

In = [xnex] − n ∫ xnexdx = e − nIn−1,
1

0

0

завершающееся при I0 = e − 1. Но этот алгоритм, применяемый «в направлении
вперед», т. е. при увеличении значения n, является численно нестабильным,
так как малые погрешности (такие как погрешности округления чисел с плавающей точкой) увеличиваются на каждом шаге вычислений: если погрешность
для In равна εn, так что оцениваемое значение I’n = In + εn, то
εn = I’n − In = (e − nI’n−1) − (e − nIn−1) = n(In−1 − I’n−1) = −nεn−1,

следовательно, |εn| = n!ε0. Даже если значение погрешности в ε0 мало, то в εn погрешность увеличивается, умножаясь на n!, т. е. результат может стать огромным.
В этом случае численно стабильным решением является применение рекурсии в обратном порядке, т. е. в сторону уменьшения n:
In−1 = 1/n(e − In) ⇒ εn−1 = −εn/n.

Таким образом, погрешности в In уменьшаются на каждом шаге рекурсии.
Можно даже начать выполнение алгоритма при I’N = 0, и при обеспечении достаточного количества шагов между N и требуемым n достигается сходимость
к верному значению In.
1

Листинг 10.2. Сравнение стабильности алгоритма при вычислении интеграла In = ∫ xnexdx
0

# eg9-integral-stability.py
import numpy as np
import matplotlib.pyplot as plt
def Iforward(n):
if n == 0:
return np.e - 1
return np.e - n * Iforward(n-1)
def Ibackward(n):
if n >= 99:
return 0
return (np.e - Ibackward(n+1)) / (n+1)
N = 35
Iforward = [np.e - 1]
for n in range(1, N+1):
Iforward.append(np.e - n * Iforward[n-1])

576



Общие положения научного программирования

Ibackward = [0] * (N+1)
for n in range(N-1,-1,-1):
Ibackward[n] = (np.e - Ibackward[n+1]) / (n+1)
n = range(N+1)
plt.plot(n, Iforward, label='Forward algorithm')
plt.plot(n, Ibackward, label='Backward algorithm')
plt.ylim(-0.5, 2)
plt.xlabel('$n$')
plt.ylabel('$I(n)$')
plt.legend()
plt.show()

На рис. 10.2 показано, что алгоритм, применяемый в прямом направлении,
становится чрезвычайно нестабильным при n > 16 и начинает колебаться между весьма большими положительными и отрицательными значениями. В противоположность ему алгоритм, применяемый в обратном направлении, ведет
себя корректно.
2.0
Алгоритм в прямом направлении
Алгоритм в обратном направлении

1.5

I (n)

1.0

0.5

0.0

0.5

0

5

10

15

n

20

25

30

35

Рис. 10.2. Нестабильность явного одношагового алгоритма Эйлера, применяемого в прямом
1

направлении для вычисления интеграла In = ∫ xnexdx
0

10.2.2 Хорошо обусловленные и плохо
обусловленные задачи
В численном анализе существует еще одно различие между задачами, которые хорошо или плохо обусловлены. Хорошо обусловленной является задача,
для которой малые относительные погрешности в исходных данных приводят
к малым относительным погрешностям в решении. Плохо обусловленной яв-

10.2 Стабильность и обусловленность алгоритма  577
ляется задача, для которой малые погрешности в исходных данных приводят
к большим погрешностям в решении. Обусловленность (число обусловленности) является свойством самой задачи, а не алгоритма, и в этом заключается
отличие от проблемы стабильности: вполне возможно использовать нестабильный алгоритм для решения хорошо обусловленной задачи и получить
ошибочные результаты.

Пример П10.3. Рассмотрим две прямые, заданные уравнениями:
y = x,
y = mx + c.
Эти прямые пересекаются в точке (x⋆, y⋆) = (c/(1 − m), c/(1 − m)). Поиск точки
пересечения представляет собой плохо обусловленную задачу при m ≈ 1 (т. е.
прямые почти параллельны).
Например, прямые y = x и y = (1.01)x + 2 пересекаются в точке (x⋆, y⋆) =
(−200, −200). Если немного изменить значение m на величину δm = 0.001, т. е.
m’ = m + δm = 1.011, то точкой пересечения станет (x’⋆, y’⋆) = (−181.8182, −181.8182).
Таким образом, относительная погрешность δm/m ≈ 0.001 в значении m привела к относительной погрешности результата |(x’⋆ − x⋆)/x⋆| ≈ 0.091, что почти
в 100 раз больше.
Напротив, если прямые имеют значительно отличающиеся коэффициенты
угла наклона, то задача является хорошо обусловленной. Например, примем
m = −1 (перпендикулярные прямые), тогда точка их пересечения (1, 1) при
том же небольшом изменении m’ = m + δm = −0.999 становится точкой (1.0005,
1.0005), т. е. приводит к относительной погрешности 0.0005, которая действительно меньше относительной погрешности значения m.

Пример П10.4. Общеизвестно, что задача поиска корней многочлена плохо обу­
словлена. Одним из широко известных примеров является многочлен Уилкинсона:

20
P(x) = ∏(x − i) = (x − 1)(x − 2)…(x − 20) =
i=1

20
19
= x − 210x + 20 615x18 + … + 2 432 902 008 176 640 000.

Вполне очевидно, что корнями являются числа 1, 2, …, 20. Но Уилкинсон показал, что небольшое уменьшение коэффициента при x19 с −210 до −210 − 2−23 ≈
−210.000000119209 оказывает критическое воздействие на многие из корней,
причем некоторые корни становятся комплексными. Например, корень x = 20
смещается к x = 20.8 – изменение на 4 % при весьма малом отклонении единственного коэффициента, всего лишь на одну миллиардную долю (также см.
задачу З10.2.2).

578



Общие положения научного программирования

10.2.3 Упражнения
Задачи
З10.2.1. Самый простой (и наименее точный) способ вычисления первой производной функции просто использует определение самой производной:
f ‘(x) = lim (f(x + h) − f(x) / h).
h→0

Если принять h как некоторое весьма малое (бесконечно малое) значение, то
получим приближенную формулу:
f ‘(x) ≈ (f(x + h) − f(x)) / h.
Если рассматривать функцию f(x) = ex, то при каком значении h (при использовании арифметики с двойной точностью наиболее близкое к степени 10) получается самое точное приближение к f ‘(1) = e?
З10.2.2. Использовать класс Polynomial из библиотеки NumPy (см. раздел 6.4)
для генерации объекта, представляющего многочлен Уилкинсона, по его корням с доступной численной точностью. Затем найти корни этого представления многочлена Уилкинсона.

10.3 Методики программирования и разработка
программного обеспечения

10.3.1 Общие замечания
Комментирование исходного кода
В этой книге я попытался комментировать примеры исходного кода и решения упражнений с наибольшей пользой. Это правильный подход даже для коротких скриптов, но эффективное использование комментариев – это совсем
не простая задача. Ниже приводятся некоторые общие советы:
 в общем случае размещайте комментарии на отдельных строках, а не
в конце строк кода (т. е. до или после строки комментируемого кода):
# Объем додекаэдра с длиной стороны a.
V = (15 + 7 * np.sqrt(5)) / 4 * a**3

Это лучше, чем:
V = (15 + 7 * np.sqrt(5)) / 4 * a**3 # Объем додекаэдра с длиной стороны a.

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

10.3 Методики программирования и разработка программного обеспечения  579
В этой книге используются комментарии в конце строк кода для объяснения особенностей синтаксиса, поэтому они, вероятнее всего, не нужны в «настоящем промышленном» исходном коде Python;
 необходимо не просто описывать, что делает код, но и объяснять, почему он это делает. Предположим, что некто читает ваш код и уже хорошо
знает синтаксис языка. В таком случае
# Увеличение i на 10:
i += 10

является абсолютно бесполезным комментарием, ничего не добавляющим к смыслу строки кода, которую он намеревается объяснить. С другой стороны,
# Пропуск следующих 10 точек данных.
i += 10

по крайней мере обеспечивает некоторое понимание причины использования этой команды;
 необходимо поддерживать актуальность комментариев и соответствие
их коду, который они описывают. Слишком часто встречаются случаи изменения исходного кода без соответствующего исправления комментариев. Это может привести к ситуации, в которой наличие неактуального
комментария гораздо хуже, чем полное его отсутствие:
# Пропуск следующих 10 точек данных.
i += 20

Что здесь написано правильно? Правильный комментарий, объясняющий намерение программиста, а строка кода ошибочна, или строка кода была изменена по какой-либо причине, а комментарий остался
неисправленным? Если в вашем коде весьма часто требуются подобные
изменения, то следует рассмотреть вариант с определением отдельной
дополнительной переменной, содержащей значение изменения для переменной i:
DATA_SKIP = 10
...
# Пропуск следующих DATA_SKIP точек данных.
i += DATA_SKIP

На практике некоторые программисты являются приверженцами минимизации количества комментариев при тщательном выборе осмысленных имен идентификаторов. Например, если переименовать индексную переменную, то, возможно, комментарий вообще не потребуется:
data_index += DATA_SKIP

580



Общие положения научного программирования

 подробно описывайте определяемые функции с использованием строк документации docstring. В языке Python все функции имеют атрибут __doc__,
которому присваивается строка docstring, записанная в определении функции (см. раздел 2.7.1). Строка документации docstring – это обычно текст на
нескольких строках в тройных кавычках, представляющий объяснение того,
что делает функция, какие аргументы принимает, а также объяснение смысла возвращаемого значения (или нескольких значений), если это необходимо. В интерактивной командной оболочке при вводе команды help(имя_
функции) выводится более подробная информация об использовании
указанной функции, включающая также и строку документации docstring.

Пример П10.5. Здесь показан пример правильно комментированной функции (вычисляющей объем тетраэдра).
Листинг 10.3. Функция, вычисляющая объем тетраэдра
# eg9-tetrahedron.py
import numpy as np
def tetrahedron_volume(vertexes=None, sides=None):
"""
Return the volume of the tetrahedron with given vertexes or side lengths.
If vertexes are given they must be in an array with shape (4, 3): the
position vectors of the four vertexes in three dimensions; if the six sides are
given, they must be an array of length 6. If both are given, the sides
will be used in the calculation.
Raises a ValueError if the vertexes do not form a tetrahedron (e.g.
because they are coplanar, colinear or coincident).
"""
"""
Возвращает объем тетраэдра при заданных вершинах или длинах сторон.
Если заданы вершины, то они должны передаваться в массиве формы (4, 3):
положения векторов четырех вершин в трех измерениях. Если заданы шесть сторон,
то они должны передаваться в массиве длиной 6. Если определены оба аргумента,
то при вычислении будут использоваться длины сторон.
Генерирует исключение ValueError, если заданные вершины не образуют тетраэдр
(например, если они лежат в одной плоскости, на одной прямой или совпадают).
"""
#
#
#
#
#
#
#
#

Этот метод реализует формулу Тартальи, используя определитель Кэли-Менгера:
|0 1
1
1
1 |
|1 0 s1^2 s2^2 s3^2|
288 V^2 = |1 s1^2 0 s4^2 s5^2|
|1 s2^2 s4^2 0 s6^2|
|1 s3^2 s5^2 s6^2 0 |
где s1, s2, ..., s6 - длины сторон тетраэдра.
Внимание: этот алгоритм не проверялся на численную стабильность.

# Индексы строк в массиве вершин соответствуют всем
# возможным парам вершин.
vertex_pair_indexes = np.array(((0, 1), (0, 2), (0, 3),
(1, 2), (1, 3), (2, 3)))

10.3 Методики программирования и разработка программного обеспечения  581
if sides is None:
# Если длины сторон не заданы, то их необходимо вычислить по вершинам.
vertexes = np.asarray(vertexes)
if vertexes.shape != (4, 3):
raise TypeError('vertexes must be a numpy array with shape (4, 3)')
# Получить все квадраты всех длин сторон из разностей между шестью
# различными парами положений вершин.
vertex1, vertex2 = vertex_pair_indexes.T
sides_squared = np.sum((vertexes[vertex1] - vertexes[vertex2])**2, axis=-1)
else:
# Проверка: длины сторон представлены как корректный массив.
# Вычислить квадраты длин сторон.
sides = np.asarray(sides)
if sides.shape != (6,):
raise TypeError('sides must be an array with shape (6,)')
sides_squared = sides**2



# Создать определитель Кэли-Менгера.
M = np.zeros((5, 5))
# Заполнить верхнюю треугольную область матрицы.
M[0, 1:] = 1
# Элементы, содержащие квадраты длин сторон, можно проиндексировать, используя индексы пар
# вершин (сравнить с определителем, показанным выше).
M[tuple(zip(*(vertex_pair_indexes + 1)))] = sides_squared
# Эта матрица симметрична, поэтому можно заполнить нижнюю треугольную область,
# применив операцию транспонирования.
M = M + M.T
# Вычислить определитель и проверить, получено ли положительное значение (отрицательное
# значение или ноль означает, что вершины не образуют тетраэдр).
det = np.linalg.det(M)
if det >>
>>>
>>>
3
>>>
>>>
>>>
1

a = 2
b = 6
3 * (a**3*b - a*b**3) % 7
a = 3
b = 5
3 * (a**3*b - a*b**3) % 7

В2.2.8. Толщина листа бумаги, сложенного пополам n раз, равна 2nt, поэтому
потребуется 2nt ≥ d ⇒ nmin = log2(d/t):
>>> d = 384_400 * 1.e3
>>> t = 1.e-4
>>> math.log(d / t, 2)
41.805745474760016

# Расстояние до Луны, м
# Толщина листа бумаги, м
# Логарифм по основанию 2.

Следовательно, лист бумаги необходимо сложить пополам 42 раза, чтобы его
толщина стала равна расстоянию до Луны (x обозначает округление с избытком (в большую сторону) значения x: наименьшее целое число, не меньшее x).
В2.2.10. Оператор ^ не возводит число в заданную степень (это делает оператор
**). Это оператор «побитовое исключающее или», поэтому в двоичной записи
10^2 выглядит как 1010 xor 0010 = 1000, т. е. 8 в десятичной системе счисления.
В2.3.1. Необходимо выполнить вырезание из строки s = 'seehemewe', как показано ниже (в некоторых случаях возможны и другие решения):
а) s[:3]
б) s[3:5]
в) s[5:7]

592



Приложение A. Решения

г) s[7:]
д) s[3:6]
е) s[5:2:-1]
ж) s[-2::-3]
В2.3.2. Нужно просто выполнить вырезание строки в обратном направлении
и сравнить результат с исходной строкой:
>>> s
>>> s
False
>>> s
>>> s
True

= 'banana'
== s[::-1]
= 'deified'
== s[::-1]

В2.3.5. Это некорректный способ проверки на равенство строки s значению
'ham' или 'eggs'. Выражение ('eggs' or 'ham') логическое, в нем оба аргумента, являющиеся непустыми строками, вычисляются как значения True. Выражение вычисляется по укороченной схеме до первого эквивалента значения
True, и возвращается соответствующий операнд (см. раздел 2.2.4), т. е. выражение ('eggs' or 'ham') возвращает 'eggs'. Поскольку s, несомненно, содержит
строку ‘eggs’, сравнение на равенство возвращает значение True. Но если поменять местами операнды, то логический оператор or снова выполнит вычисление по укороченной схеме до первого эквивалента значения True, которым
теперь является 'ham', и вернет эту строку. Сравнение на равенство с s завершается неудачей, и возвращается результат False.
Существуют два корректных способа проверки того, что s является одной из
двух и более строк:
>>> s = 'eggs'
>>> s == 'ham' or s == 'eggs'
True
>>> s in ('ham', 'eggs')
True

(Более подробное описание синтаксиса второй команды см. в разделе 2.4.2.)
В2.4.2. Проблема заключается в том, что enumerate по умолчанию возвращает
индексы и элементы переданного массива, а индексы нумеруются, начиная с 0.
Передаваемый массив является срезом P[1:] = [5, 0, 2], а enumerate, в свою
очередь, генерирует кортежи (0, 5), (1, 0) и (2, 2). Но для вычисляемой здесь
производной необходимы индексы из исходного списка P, т. е. (1, 5), (2, 0)
и (3, 2). Здесь возможны два варианта: передача дополнительного аргумента
start=1 в enumerate или прибавление 1 к индексу по умолчанию:
>>>
>>>
>>>
...

P = [4, 5, 0, 2]
dPdx = []
for i, c in enumerate(P[1:], start=1):
dPdx.append(i*c)

Приложение A. Решения 

593

>>> dPdx
[5, 0, 6]
>>>
>>>
>>>
...
>>>
[5,

P = [4, 5, 0, 2]
dPdx = []
for i, c in enumerate(P[1:]):
dPdx.append((i+1)*c)
dPdx
0, 6]

В2.4.3. Одно из возможных решений:
>>>
>>>
>>>
...
...
>>>
[1,

scores = [87, 75, 75, 50, 32, 32]
ranks = []
for score in scores:
ranks.append(scores.index(score) + 1)
ranks
2, 2, 4, 5, 5]

В2.4.4. Ниже показано вычисление значения π до 10 знаков после (десятичной) запятой.
>>> import math
>>> pi = 0
>>> for k in range(20):
...
pi += pow(-3, -k) / (2*k+1)
...
>>> pi *= math.sqrt(12)
>>> print('pi = ', pi)
pi = 3.1415926535714034
>>> print('error = ', abs(pi - math.pi))
error = 1.8389734179891093e-11



❶ Встроенная функция pow(x, j) равнозначна выражению (x)**j.

В2.4.5. Результатом выражения any(x) and not all(x) является True, если хотя
бы один элемент в x равнозначен True, но не все элементы:
>>> x1, x2,
>>> any(x1)
False
>>> any(x2)
False
>>> any(x3)
True

x3 = [False, False], [1, 2, 3, 4], [1, 2, 3, 0]
and not all(x1)
and not all(x2)
and not all(x3)

В2.4.6. Необходимо вспомнить о том, что оператор * распаковывает кортеж
в список позиционных аргументов, передаваемых в функцию. Поэтому если
z = zip(a, b) является (итерируемой) последовательностью: (a0, b0), (a1, b1),
(a2, b2), …, то распаковка этой последовательности при вызове zip(*z) равнозначна вызову zip с этими кортежами как аргументами:

594



Приложение A. Решения

zip((a0, b0), (a1, b1), (a2, b2), ...)

Метод zip по очереди берет первый и второй элементы из каждого кортежа,
воспроизводя исходные последовательности:
(a0, a1, a2, ...), (b0, b1, b2, ...)

В2.4.7. Необходимо просто применить метод zip к спискам значений солнечных часов и имен месяцев, затем отсортировать полученный список кортежей
в обратном порядке:
>>> months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
...
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
>>> sun = [44.7, 65.4, 101.7, 148.3, 170.9, 171.4,
...
176.7, 186.1, 133.9, 105.4, 59.6, 45.8]
>>> for s, m in sorted(zip(sun, months), reverse=True):
...
print('{}: {:.1f} hrs'.format(m, s))
...
Aug: 186.1 hrs
Jul: 176.7 hrs
Jun: 171.4 hrs
May: 170.9 hrs
Apr: 148.3 hrs
Sep: 133.9 hrs
Oct: 105.4 hrs
Mar: 101.7 hrs
Feb: 65.4 hrs
Nov: 59.6 hrs
Dec: 45.8 hrs
Jan: 44.7 hrs

В2.5.1. Необходимо нормализовать список:
>>> a = [2, 4, 10, 6, 8, 4]
>>> amin, amax = min(a), max(a)
>>> for i, val in enumerate(a):
...
a[i] = (val - amin) / (amax - amin)
...
>>> a
[0.0, 0.25, 1.0, 0.5, 0.75, 0.25]

В2.5.2. Приведенный ниже код вычисляет постоянную Гаусса до 14 знаков пос­
ле запятой.
>>>
>>>
>>>
>>>
...
...
>>>
G =

import math
tol = 1.e-14
an, bn = 1., math.sqrt(2)
while abs(an - bn) > tol:
an, bn = (an + bn) / 2, math.sqrt(an * bn)
print('G = {:.14f}'.format(1/an))
0.83462684167407

Приложение A. Решения 

595

В2.5.3. Приведенный ниже код вычисляет первые 100 чисел «Fizzbuzz».
nmax = 100
for n in range(1, nmax + 1):
message = ''
if not n % 3:
message = 'fizz'
if not n % 5:
message += 'buzz'
print(message or n)



❶ Обратите внимание: если n не делится ни на 3, ни на 5, то сообщением

message будет пустая строка, которая вычисляется как False в этом логическом выражении, поэтому вместо сообщения выводится значение n.

В2.5.4. Здесь показано одно из решений, использующее в качестве примера
stoich = 'C8H18':
Листинг A.1. Структурная формула алкана с прямой (неразветвленной) цепью
# qn2-5-c-alkane-a.py
stoich = 'C8H18'
fragments = stoich.split('H')
nC = int(fragments [0][1:])
nH = int(fragments[1])
if nH != 2*nC + 2:
print('{} is not an alkane!'.format(stoich))
else:
print('H3C', end='')
for i in range(nC -2):
print('-CH2', end='')
print('-CH3')

Вывод результата:
H3C-CH2-CH2-CH2-CH2-CH2-CH2-CH3

В2.7.1. Только варианты б) и е) ведут себя так, как предполагалось:
а) При отсутствии явной инструкции return функция line возвращает значение None. Поскольку None невозможно присоединить к строке, возникает
ошибка:
my_sum = '\n'.join([' 56', ' +44', line , ' 100', line])
...
TypeError: sequence item 2: expected str instance, NoneType found

б) Код работает правильно.

596



Приложение A. Решения

в) Функция line возвращает строку, как и требуется, но она не вызывается как
line() – без скобок line ссылается непосредственно на объект функции, который невозможно присоединить к строке, поэтому возникает ошибка:
my_sum = '\n'.join([' 56', ' +44', line , ' 100', line])
...
TypeError: sequence item 2: expected str instance, function found

г) В этом коде ошибка не возникает, но выводится строка представления (объекта) функции вместо строки, возвращаемой при правильном вызове этой
функции:
56
+44

100


д) Этот код выводит неожидаемое значение None:
56
+44
----None
100
----None

Это происходит потому, что инструкция print(line()) вызывает функцию
line, которая выводит строку дефисов, но, кроме того, выводит свое возвращаемое значение (т. е. None, так как явно не возвращает что-либо другое).
е) Код работает правильно.
В2.7.2. Проблема возникает внутри функции add_interest:
def add_interest(balance, rate):
balance += balance * rate / 100

Здесь создается новый объект типа float balance, который является локальным в этой функции и не зависит от исходного объекта balance. При выходе из
функции локальный объект balance уничтожается, а исходный объект balance
не обновляется никогда. Один из способов устранения этой проблемы – явный
возврат из функции обновленного значения баланса:
>>> balance = 100
>>> def add_interest(balance, rate):
...
balance += balance * rate / 100
...
return balance
...
>>> for year in range(4):

Приложение A. Решения 
...
...
...
Balance
Balance
Balance
Balance

597

balance = add_interest(balance, 5)
print('Balance after year {}: ${:.2f}'.format(year + 1, balance))
after
after
after
after

year
year
year
year

1:
2:
3:
4:

$105.00
$110.25
$115.76
$121.55

В2.7.3. Проблема возникает из-за того, что функция digit_sum не возвращает
сумму цифр n, которая была вычислена. При отсутствии явной инструкции return функция Python возвращает значение None, но это недопустимый объект
для использования при вычислении остатка от деления, поэтому генерируется
исключение TypeError.
Проблему легко устранить, добавив нужную инструкцию return dsum:
def digit_sum(n):
""" Find and return the sum of the digits of integer n. """
""" Вычисление и возврат суммы цифр целого числа n. """
s_digits = list(str(n))
dsum = 0
for s_digit in s_digits:
dsum += int(s_digit)
return dsum
def is_harshad(n):
return not n % digit_sum(n)

Теперь код работает, как предполагается:
>>> is_harshad(21)
True

В2.7.4. Этот код выводит:
[1, 2, 'a']
[1, 2, 'a']

потому что новый список создается только один раз при определении функции, и это тот список, который добавляется и возвращается при каждом вызове
функции. Таким образом, lst1 и lst2 – это один и тот же объект, в чем можно
убедиться:
print(lst1 is lst2)
True

В4.1.1. Правильный практический прием – сохранять размер блока try настолько малым, насколько это возможно, чтобы предотвратить захват лишних
исключений, отличающихся от требуемого. Например, в примере П4.5 предполагается чтение файла после его открытия в одном блоке try:

598

 Приложение A. Решения

try:
fi = open(filename, 'r')
lines = fi.readlines()
except IOError:
...

В этом случае имеем две потенциальные ошибки, которые могут привес­
ти к генерации исключения IOError: критический сбой при открытии файла
и критический сбой при чтении его строк. Ветвь except предназначена для обработки только первого случая, но будет также выполняться и во втором случае, хотя более правильным решением была бы отдельная обработка критических сбоев при чтении строк (или другой вариант: оставить этот случай без
обработки, тогда программа просто прекратит выполнение).
В4.1.2. Проблема в блоке finally в примере П4.5 заключается в том, что инструкции в этом блоке начинают выполняться до того, как происходит возврат
из функции. Если бы строка
print('

Done with file {}'.format(filename))

была перемещена в позицию после блока try, то она бы не выполнялась, если
было бы сгенерировано исключение IOError (потому что возврат из функции
в вызвавший ее блок кода произошел бы до перехода к этой инструкции print).
В4.2.1. Это легко сделать с помощью метода set. Вызов из строки s:
set(s.lower()) >= set('abcdefghijklmnopqrstuvwxyz')

возвращает результат True, если строка является панграммой. Например:
>>> s = 'The quick
>>> set(s.lower())
True
>>> s = 'The quick
>>> set(s.lower())
False

brown fox jumps over the lazy dog'
>= set('abcdefghijklmnopqrstuvwxyz')
brown fox jumped over the lazy dog'
>= set('abcdefghijklmnopqrstuvwxyz')

В4.2.2. Эту функцию можно использовать для удаления повторяющихся элементов из упорядоченного списка:
>>> def remove_dupes(l):
...
return sorted(set(l))
...
>>> remove_dupes([1, 1, 2, 3, 4, 4, 4, 5, 7, 8, 8, 9])
[1, 2, 3, 4, 5, 7, 8, 9]

Следует отметить, что хотя множества не имеют какого-либо определенного порядка, они являются итерируемыми объектами и могут передаваться во
встроенный метод sorted(), который возвращает список (list).

Приложение A. Решения 

599

В4.2.3. Необходимо выполнить следующие команды в интерактивном интерпретаторе Python:
>>> set('hellohellohello')
{'h', 'o', 'l', 'e'}
>>> set(['hellohellohello'])
{'hellohellohello'}
>>> set(('hellohellohello'))
{'h', 'o', 'l', 'e'}
>>> set(('hellohellohello',))
{'hellohellohello'}
>>> set(('hello', 'hello', 'hello'))
{'hello'}
>>> set(('hello', ('hello', 'hello')))
{'hello', ('hello', 'hello')}
>>> set(('hello', ['hello', 'hello']))
Traceback (most recent call last):
File "", line 1, in
TypeError: unhashable type: 'list'

Обратите особое внимание на различие между инициализацией множества
set списком (list) объектов и попыткой добавления списка как объекта в множество.
В4.2.4. Обратите внимание: инструкция
>> a |= {2, 3, 4, 5}

не изменяет frozenset, а создает новый объект, объединяя старое множество
и set {2, 3, 4, 5}. (То же самое наблюдалось, когда после создания объекта int
i присваивание i = i + 1 заново связывало метку (имя) i с новым целочисленным объектом со значением i + 1, а не изменяло значение неизменяемого объекта типа int, ранее связанного с i).
В4.2.5. Приведенный ниже фрагмент кода должен быть добавлен после определения text – не забудьте импортировать defaultdict из модуля collections.
words_by_length = defaultdict(list)
for word in text.split():
words_by_length[len(word)].append(word)
for length in sorted(words_by_length.keys()):
print(f'{length}: {words_by_length[length]}')

Вывод результата:
1:
2:
3:
4:
5:
6:

['a']
['on', 'in', 'to']
['and', 'ago', 'our', 'new', 'and', 'the', 'all', 'men', 'are']
['four', 'this', 'that']
['score', 'seven', 'years', 'forth', 'equal']
['nation']

600



Приложение A. Решения

7: ['fathers', 'brought', 'liberty', 'created']
9: ['continent', 'conceived', 'dedicated']
11: ['proposition']

В4.3.1. Генератор списков
>>> flist = [lambda x, i=i: x**i for i in range(4)]

создает такой же список анонимных функций, как в примере П4.11.
Следует отметить, что необходимо передавать каждое значение i в лямбдафункцию явно, иначе правила замыкания Python приведут к тому, что каждая
лямбда-функция станет равнозначной выражению x**3 (3 – это последнее значение i в цикле).
В4.3.2. Приведенный в вопросе фрагмент кода выводит первые nmax+1 строк
треугольника Паскаля:
[1]
[1,
[1,
[1,
[1,
[1,

1]
2,
3,
4,
5,

1]
3, 1]
6, 4, 1]
10, 10, 5, 1]

В присваивании генератора списков
x = [([0] + x)[i] + (x + [0])[i] for i in range(n+1)]

добавляются элементы двух списков. Эти два списка формируются из списка,
представляющего предыдущую строку: в первом случае добавлением 0 в начало списка, во втором случае добавлением 0 в конец списка. При таком подходе
сумма вычисляется по соседним парам чисел, а начальное и конечное числа
остаются неизменными. Например, если x содержит [1, 3, 3, 1], то следующая
строка формируется с помощью суммирования элементов в списках
[0, 1, 3, 3, 1]
[1, 3, 3, 1, 0]

что в итоге дает требуемую строку [1, 4, 6, 4, 1].
В4.3.3.
а) Индекс элементов a, использующий элементы b:
>>> [a[x] for x in b]
['E', 'C', 'G', 'B', 'F', 'A', 'D']

б) Индекс элементов a, использующий отсортированные элементы b. В этом
случае возвращаемый список – это просто (копия) a:

Приложение A. Решения 

601

>>> [a[x] for x in sorted(b)]
['A', 'B', 'C', 'D', 'E', 'F', 'G']

в) Индекс элементов a, использующий элементы b, проиндексированный по
элементам b(!):
>>> [a[b[x]] for x in b]
['F', 'G', 'D', 'C', 'A', 'E', 'B']

г) Каждый элемент b связывается с соответствующим элементом a в последовательности кортежей [(4, 'A'), (2, 'B'), (6, 'C'), ...], которая затем сор­
тируется, – этот метод используется для возврата элементов a, соответствующих упорядоченным элементам b.
>>> [x for (y,x) in sorted(zip(b,a))]
['F', 'D', 'B', 'G', 'A', 'E', 'C']

В4.3.4. Для возвращения отсортированного списка пар (ключ, значение) из
словаря:
>>> d = {'five': 5, 'one': 1, 'four': 4, 'two': 2, 'three': 3}
>>> d
{'four': 4, 'one': 1, 'five': 5, 'two': 2, 'three': 3}
>>> sorted([(k, v) for k, v in d.items()])
[('five', 5), ('four', 4), ('one', 1), ('three', 3), ('two', 2)]

Обратите внимание: сортировка списка кортежей (ключ, значение) требует,
чтобы все ключи имели типы данных, которые можно осмысленно упорядочить. Такой подход не работает, например, если в ключах объединены целые
значения и строки, так как (в Python 3) не существует определенного порядка для совместной сортировки этих типов – будет сгенерировано исключение
TypeError: unorderable types: int() < str().
Для сортировки по значению можно отсортировать список кортежей (значение, ключ), но для сохранения возвращаемого списка с парами (ключ, значение) использовать следующий код:
>>> sorted([(k, v) for k, v in d.items()], key=lambda item: item[1])
[('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5)]

Аргумент key для метода sorted определяет, как интерпретировать каждый
элемент в списке для упорядочения: здесь требуется упорядочение по второму
элементу (item[1]) в каждом кортеже (k, v) для упорядочения по значению.
В4.3.5. В приведенном ниже коде шифруется (и расшифровывается) номер
телефона, хранящийся как строка с использованием метода «прыжок через 5».
''.join(['5987604321'[int(i)] if i.isdigit() else '-' for i in '555-867-5309'])

602

 Приложение A. Решения

В4.3.6. Одно из решений: создать кортеж из двух элементов для каждого элемента в сортируемом списке: первый элемент кортежа – логическое значение,
обозначающее, является ли элемент списка значением None или нет, второй
элемент кортежа – само значение из списка. При сортировке этих кортежей
первый элемент дает результат False для всех чисел (которые могут сравниваться как второй элемент) и True для всех значений None. Поскольку False при
сравнении всегда считается значением, «меньшим, чем» True, здесь нет необходимости сравнивать различные типы:
In [x]:
In [x]:
In [x]:
Out[x]:

lst = [4, 2, 6, None, 0, 8, None, 3]
lst.sort(key=lambda e: (e is None, e))
lst
[0, 2, 3, 4, 6, 8, None, None]

В4.3.7. Здесь можно предложить два решения:
а) использование выражения присваивания для кортежа:
>>> t = 1, 1
>>> while (t := (t[0] + t[1], t[0])) < (5000, 0):
...
continue
...
>>> t[0]
6765

б) в условие цикла while включается выражение присваивания, содержащее
вызов метода input:
>>> while (s := input("> ").lower()) != "exit":
...
print(s)
...
> hello
hello
> bye
bye
> quit
quit
> :q
:q
> exit
>>>

В6.1.1. Массив np.ndarray – это класс NumPy для представления многомерных
массивов в Python. В этой книге мы часто ссылаемся на экземпляры данного класса
просто как на объекты массива. np.array – это функция, создающая такие объекты
из передаваемых в нее аргументов (обычно это последовательность значений).
В6.1.2. Для создания двумерного массива в метод array() необходимо передать последовательность последовательностей как один аргумент, а в приведенном в вопросе вызове вместо этого передаются как отдельные аргументы
три последовательности. Правильный вызов:

Приложение A. Решения 

603

>>> np.array( ((1, 0, 0), (0, 1, 0), (0, 0, 1)) , dtype=float)

В6.1.3. np.array([0, 0, 0]) создает одномерный массив с тремя элементами.
np.array([[0, 0, 0]]) создает двумерный массив 1×3 (т. е. a[0] – это одномерный массив, созданный в первом примере).
В6.1.4. Изменение типа массива с помощью непосредственной установки dtype
не изменяет данные на уровне байтов, а влияет только на интерпретацию данных как чисел, строк и т. д. Известно, что байтовое представление нуля одинаково для целых чисел (int64) и чисел с плавающей точкой (float64), по­этому
результат установки типа dtype будет ожидаемым. Но 8 байт, представляющие
1.0, преобразуются в целое значение 4602678819172646912. Для корректного
преобразования типа данных необходимо использовать метод astype(), который возвращает новый массив (с собственными данными):
In [x]: a = np.ones((3,))
In [x]: a
Out[x]: array([ 1., 1., 1.])
In [x]: a.astype('int')
In [x]: a
Out[x]: array([1, 1, 1])

В6.1.5. Индексация и вырезание массива NumPy:
а) a[1, 0, 3]
б) a[0, 2, :] (или просто a[0, 2])
в) a[2, ...] (или a[2, :, :], или a[2])
г) a[:, 1, :2]
д) a[2, :, :1:-1] («в третьем блоке для каждой строки взять (в обратном порядке) элементы из всех столбцов, кроме первого»).
е) a[:, ::-1, 0] («для каждого блока пройти по строкам в обратном направлении и взять элемент из первого столбца каждой строки»).
ж) Определить три индексных массива 2×2 для блоков, строк и столбцов, позиционирующих заданные элементы, как показано ниже:
ia = np.array([[0, 0], [2, 2]])
ja = np.array([[0, 0], [3, 3]])
ka = np.array([[0, 3], [0, 3]])

a[ia, ja, ka] возвращает требуемый результат.
В6.1.6. Например:
In [x]: a = np.array([0, -1, 4.5, 0.5, -0.2, 1.1])
In [x]: a[abs(a) 0)

Приложение A. Решения 

605

В6.1.11. В первом случае создается один объект требуемого типа dtype и умножается на скаляр (обычный тип int Python). Python выполняет «приведение
типа с повышением» для возврата результата типа dtype, который может содержать это значение:
In [x]: x = np.uint8(250)
In [x]: type(x*2)
Out[x]: numpy.int64

Но поскольку для ndarray жестко задан размер в байтах, для него нельзя выполнить приведение типа с повышением таким способом: его собственный
dtype имеет приоритет над типом скаляра, на который умножается массив,
поэтому умножение выполняется по модулю 256.
Сравните с результатом умножения двух скалярных значений одинакового
типа dtype:
In [x]: np.uint8(250) * np.uint8(2)
Out[x]: 244
# Тип np.uint8.

(Возможно, будет выведено предупреждающее сообщение: RuntimeWarning:
overflow encountered in ubyte_scalars.)
В6.4.1. Метод deriv класса Polynomial возвращает объект Polynomial (в рассмат­
риваемом здесь случае с одним элементом – коэффициентом x0, равным 18).
Этот объект не равен объекту целого числа со значением 18.
В6.4.2. Используйте объект numpy.polynomial.Polynomial:
In [x]: p1 = Polynomial([-11, 1, 1])
In [x]: p2 = Polynomial([-7, 1, 1])
In [x]: p = p1**2 + p2**2
In [x]: dp = p.deriv()
# Первая производная.
In [x]: stationary_points = dp.roots()
In [x]: ddp = dp.deriv()
# Вторая производная.
In [x]: minima = stationary_points[ddp(stationary_points) > 0]
In [x]: maxima = stationary_points[ddp(stationary_points) < 0]
In [x]: inflections = stationary_points[np.isclose(ddp(stationary_points),0)]
In [x]: print(np.array((minima, p(minima))).T)
[[-3.54138127 8.
]
[ 2.54138127 8.
]]
In [x]: print(np.array((maxima, p(maxima))).T)
[[ -0.5 , 179.125]]
In [x]: print(np.array((inflections, p(inflections))).T)
[]

Таким образом, функция имеет два минимума:
f(−3.54138127) = 8,
f(2.54138127) = 8

606



Приложение A. Решения

и один максимум:
f(−0.5) = 179.125
при отсутствии точек перегиба/волнообразности.
В6.5.1. Без излишнего усложнения:
In [x]: pauli_matrices = np.array((
((0, 1), (1, 0)),
((0, -1j), (1j, 0)),
((1, 0), (0, -1))
))
In [x]: I2 = np.eye(2)
In [x]: for sigma in pauli_matrices:
...:
print(np.allclose(sigma.T.conj().dot(sigma), I2))
True
True
True

В6.5.2. Код в листинге A.2 подбирает коэффициенты заданного квадратного
уравнения. Следует отметить, что это линейная подгонка методом наименьших квадратов, даже притом, что сама функция является нелинейной во времени, потому что она линейная по отношению к коэффициентам.
Листинг A.2. Подгонка методом наименьших квадратов к функции x = x0 + v0t + 1/2gt2
# qn6-9-b-quadratic-fit-a.py
import numpy as np
import matplotlib.pyplot as plt
Polynomial = np.polynomial.Polynomial
x = np.array([1.3, 6.0, 20.2, 43.9, 77.0, 119.6, 171.7, 233.2, 304.2,
384.7, 474.7, 574.1, 683.0, 801.3, 929.2, 1066.4, 1213.2,
1369.4, 1535.1, 1710.3, 1894.9])
dt, n = 0.1, len(x)
tmax = dt * (n-1)
t = np.linspace(0, tmax, n)
A = np.vstack((np.ones(n), t, t**2)).T
coefs, resid, _, _ = np.linalg.lstsq(A, x)
# Начальное положение (см) и скорость (см/с), ускорение свободного падения (м/с2).
x0, v0, g = coefs[0], coefs[1], coefs[2] * 2 / 100
print('x0 = {:.2f} cm, v0 = {:.2f} cm.s-1, g = {:.2f} m.s-2'.format(x0, v0, g))
xfit = Polynomial(coefs)(t)
plt.plot(t, x, 'ko')
plt.plot(t, xfit, 'r')
plt.xlabel('Time (sec)')
plt.ylabel('Distance (cm)')
plt.show()

Приложение A. Решения 

607

Результат подгонки к заданной функции показан на рис. A.1.
2000

Расстояние, см

1500

1000

500

0
0.0

0.5

1.0
Время, с

1.5

2.0

Рис. A.1. Подгонка методом наименьших квадратов к функции x = x0 + v0t + 1/2gt2

В6.6.1. В первом случае
In [x]: a = np.array([6, 6, 6, 7, 7, 7, 7, 7, 7])
In [x]: a[np.random.randint(len(a), size=5)]
array([7, 7, 7, 6, 7])
# (Как пример).

выполняются случайные выборки из массива a с заменой: для каждого выбираемого элемента вероятность выбора 6 равна 1/3, а вероятность выбора 7
равна 2/3.
Во втором случае
In [x]: np.random.randint(6, 8, 5)
array([6, 6, 7, 7, 7])
# (Как пример).

числа выбираются из последовательности [6, 7] на равных условиях, поэтому
вероятности выбора каждого числа равны 1/2.
В6.6.2. Функция np.random.randint выполняет равновероятную выборку из
полуоткрытого интервала [low, high), поэтому для получения поведения, равнозначного поведению метода np.random.random_integers, в примере П6.18
необходим следующий код:
In [x]: a, b, n = 0.5, 3.5, 4
In [x]: a + (b - a) * (np.random.randint(1, n + 1, size=10) - 1) / (n - 1)
Out[x]: array([ 0.5, 1.5, 0.5, 3.5, 1.5, 3.5, 2.5, 0.5, 1.5, 1.5])

608



Приложение A. Решения

В6.6.3. Вероятность выигрыша в одном из вариантов:
 70  25  70 ⋅69⋅68⋅ 67⋅ 66
⋅25 = 302 575 350.
   =
1⋅2⋅3⋅ 4 ⋅5
 5  1 

Исходный код для выбора пяти случайных чисел от 1 до 70 и одного числа
от 1 до 25:
In [x]: (sorted(np.random.choice(np.arange(1, 71), 5, replace=False)),
np.random.randint(25) + 1)
([23, 45, 51, 52, 67], 11)

В6.6.4. Здесь приводится более общее решение предложенной задачи. Определяется распределение опечаток в книге из биномиального распределения
с использованием метода np.random.binomial и подсчитывается, на скольких
страницах имеется более q опечаток. Для сравнения с распределением Пуассона по числу опечаток на странице X необходимо вычислить
Pr(X >= q) = 1 − Pr(X < q) = 1 − (Pr(X = 0) + Pr(X = 1) + … + Pr(X = q − 1)):
Листинг A.3. Вычисление вероятности нахождения q и более опечаток на заданной странице книги
# qn6-7-d-misprints-a.py
import numpy as np
n, m = 500, 400
q = 2
ntrials = 100
errors_per_page = np.random.binomial(m, 1/n, (ntrials , n))
av_ge_q = np.sum(errors_per_page >=q) / n / ntrials
print('Probability of {} or more misprints on a given page'.format(q))
print('Result from {} trials using binomial distribution: {:.6f}'
.format(ntrials, av_ge_q))
# Теперь вычисляется то же самое количество с использованием распределения Пуассона:
# Pr(X>=q) = 1 - exp(-lam)[1 + lam + lam^2/2! + ... + lam^(q-1}/(q-1)!]
lam = m/n
poisson = 1
term = 1
for k in range(1, q):
term *= lam/k
poisson += term
poisson = 1 - np.exp(-lam) * poisson
print('Result from Poisson distribution: {:.6f}'.format(poisson))

Пример вывода результата:
Probability of 2 or more misprints on a given page
Result from 100 trials using binomial distribution: 0.190200
Result from Poisson distribution: 0.191208

Приложение A. Решения 

609

В6.7.1. Для этих двух методов вычисления дискретного преобразования Фурье
(ДПФ) можно измерить время их выполнения, используя магическую функцию
IPython %timeit:
In
In
In
In

[x]:
[x]:
[x]:
[x]:

import numpy as np
n = 512
# Функция ввода - это просто случайные числа.
f = np.random.rand(n)

In [x]: # Время выполнения алгоритма NumPy ДПФ (алгоритм Кули-Тьюки).
In [x]: %timeit np.fft.fft(f)
100000 loops, best of 3: 13.1 us per loop
In [x]: # Теперь вычисляется ДПФ прямым суммированием.
In [x]: k = np.arange(n)
In [x]: m = k.reshape((n, 1))
In [x]: w = np.exp(-2j * np.pi * m * k / n)
In [x]: %timeit np.dot(w, f)
1000 loops, best of 3: 354 us per loop
In [x]:
In [x]:
In [x]:
In [x]:
Out[x]:

# Проверка совпадения результатов, полученных этими двумя методами.
ftfast = np.fft.fft(f)
ftslow = np.dot(w, f)
np.allclose(ftfast, ftslow)
True

Здесь можно видеть, что алгоритм Кули–Тьюки почти в 30 раз быстрее, чем
метод прямого суммирования. В действительности этот алгоритм характеризуется сложностью вычислений O(n log n) по сравнению со сложностью O(n2)
метода прямого суммирования.
В8.1.1. Необходимо простозаменить строку
for rec in constants[-10:]:

на строку
for rec in constants[constants['rel_unc'] > 0][:10]:

а также спецификатор формата в строке вывода на ':g' (так как значения неопределенностей меньше, чем 1 на миллион). Константа с наиболее точным
известным значением – g-фактор электрона.
1.74797e-07
1.79792e-07
1.90811e-06
1.91096e-06
...

ppm:
ppm:
ppm:
ppm:

electron g factor = -2.00232
electron mag. mom. to Bohr magneton ratio = -1.00116
hertz-hartree relationship = 1.51983e-16 E_h
Rydberg constant times hc in eV = 13.6057 eV

В8.1.2. Вычисление N/V = p/kBT при заданных условиях можно выполнить, используя только константы из модуля scipy.constants:

610



Приложение A. Решения

In [x]: scipy.constants.atm / scipy.constants.k / scipy.constants.zero_Celsius
Out[x]: 2.686780501003883e+25

Это постоянная Лошмидта, определяемая стандартами 2010 CODATA и включенная в модуль scipy.constants (более подробно см. документацию):
In [x]: from scipy import constants
In [x]: constants.value('Loschmidt constant (273.15 K, 101.325 kPa)')
Out[x]: 2.6867805e+25

В8.2.1. При численном интегрировании подразумевается результат 3:
In [x]:
In [x]:
In [x]:
In [x]:
Out[x]:

from scipy.integrate import quad
import numpy as np
func = lambda x: np.floor(x) - 2*np.floor(x/2)
quad(func, 0, 6)
(2.999964948683555,0.0009520766614606472)

В8.2.2. В приведенных ниже фрагментах кода подразумевается выполнение
следующих инструкций импорта:
In [x]: import numpy as np
In [x]: from scipy.integrate import quad

а) In [x]: f1 = lambda x: x**4 * (1 - x)**4/(1 + x**2)
In [x]: quad(f1, 0, 1)
Out[x]: (0.0012644892673496185, 1.1126990906558069e-14)
In [x]: 22/7 - np.pi
Out[x]: 0.0012644892673496777
б) In [x]: f2 = lambda x: x**3/(np.exp(x) - 1)
In [x]: quad(f2, 0, np.inf)
Out[x]: (6.49393940226683, 2.628470028924825e-09)
In [x]: np.pi**4 / 15
Out[x]: 6.493939402266828
в) In [x]: f3 = lambda x: x**-x
In [x]: quad(f3, 0, 1)
Out[x]: (1.2912859970626633, 3.668398917966442e-11)
In [x]: np.sum(n**-n for n in range(1, 20))
Out[x]: 1.2912859970626636
г) In [x]: from scipy.misc import factorial
In [x]: f4 = lambda x, p: np.log(1/x)**p
In [x]: for p in range(10):
...:
print(quad(f4, 0, 1, args=(p,))[0], factorial(p))
...:

Приложение A. Решения 

611

1.0 1.0
0.9999999999999999 1.0
1.9999999999999991 2.0
6.000000000000064 6.0
24.000000000000014 24.0
119.9999999999327 120.0
719.9999999989705 720.0
5039.99999945767 5040.0
40320.00000363255 40320.0
362880.00027390465 362880.0
д)







In [x]:
In [x]:
In [x]:
In [x]:
In [x]:
In [x]:
Out[x]:

from scipy.special import i0
z = np.linspace(0, 2, 100)
y1 = 2 * np.pi * i0(z)
f5 = lambda theta, z: np.exp(z*np.cos(theta))
y2 = np.array([quad(f5, 0, 2*np.pi, args=(zz,))[0] for zz in z])
np.max(abs(y2-y1))
2.1863399979338283e-11

В8.2.3. Для приближенного вычисления значения π с помощью интегрирования постоянной функции f(x, y) = 4 в четверти круга с единичным радиусом
в квадранте x > 0, y > 0:
In [x]: from scipy.integrate import dblquad
In [x]: dblquad(lambda y, x: 4, 0, 1, lambda x: 0, lambda x: np.sqrt(1 - x**2))
Out[x]: (3.1415926535897922, 3.533564552071766e-10)

В8.2.4. Вычисляемый интеграл:
1

∫∫



0 0

r d θ dr = π.

Обратите внимание: внутренний интеграл вычисляется по θ, а внешний –
по r. Следовательно, при вызове метода dblquad должна вызываться функция
f(r, θ) = r как lambda theta, r: r (обратите особое внимание на порядок аргументов).
In [x]: dblquad(lambda theta, r: r, 0, 1, lambda r: 0, lambda r: 2*np.pi)
Out[x]: (3.141592653589793, 3.487868498008632e-14)

Другой вариант с изменением порядка интегрирования:
dblquad(lambda r, theta: r, 0, 2*np.pi, lambda theta: 0, lambda theta: 1)
(3.141592653589793, 3.487868498008632e-14)

В8.4.1. Необходимо переписать уравнение в следующем виде:
f(x) = x + 1 + (x − 3)−3 = 0.
Для этой функции легко построить график, а искомые корни можно ограничить интервалами (−2, −0.5) и (0, 2.99) (избегая сингулярности в точке x = 3).

612



Приложение A. Решения

In [x]: f = lambda x: x + 1 + (x-3)**-3
In [x]: brentq(f, -2, -0.5), brentq(f, 0, 2.99)
Out[x]: ( -0.984188231211512, 2.3303684533047426)

В8.4.2. Некоторые примеры поиска корней уравнений, для которых не подходит алгоритм Ньютона–Рафсона, и другие способы их решения.
а) In [x]: newton(lambda x: x**3 - 5*x, 1, lambda x: 3*x**2 - 5)
...
RuntimeError: Failed to converge after 50 iterations, value is 1.0

Здесь алгоритм Ньютона–Рафсона входит в бесконечный цикл перебора
повторяющихся значений x:
x0 = 1 : x1 = x0 − f(x0)/f'(x0) = −1,
x1 = −1 : x2 = x1 − f(x1)/f'(x1) = 1,
x2 = 1 : x3 = x2 − f(x2)/f'(x2) = −1.

Выбор других начальных точек обеспечивает сходимость к корню. Даже
весьма малое смещение от x = 0 обеспечит сходимость:
In [x]:
Out[x]:
In [x]:
Out[x]:
In [x]:
Out[x]:
б) In [x]:
In [x]:
Out[x]:
In [x]:
Out[x]:

newton(lambda x: x**3 - 5*x, 1.0001, lambda x: 3*x**2 - 5)
2.23606797749979
newton(lambda x: x**3 - 5*x, 1.1, lambda x: 3*x**2 - 5)
-2.23606797749979
newton(lambda x: x**3 - 5*x, 0.5, lambda x: 3*x**2 - 5)
0.0
f, fp = lambda x: x**3 - 3*x + 1, lambda x: 3*x**2 - 3
newton(f, 1, fp)
1.0
f(1.0)
-1

Алгоритм сходится, но не к корню. К сожалению, градиентом этой функции является ноль в выбранной начальной точке, поэтому возникающая
здесь ошибка округления не приводит к генерации исключения ZeroDivisionError. Чтобы найти истинные корни, необходимо выбрать другие начальные точки, например f'(x0) ≠ 0, или воспользоваться другим методом
после ограничения интервалов искомых корней и исследования графика
функции:
In [x]: brentq(f, -0.5, 0.5), brentq(f, -2, -1.5), brentq(f, 1, 2)
Out[x]: (0.34729635533386066, -1.879385241571423, 1.532088886237956)

в) Ф
 ункция f(x) = 2 − x5 на графике имеет плоское плато в окрестности точки
f(0) = 2, поэтому малый градиент в этом интервале приводит к медленной
сходимости к корню:

Приложение A. Решения 

613

In [x]: newton(f, 0.01, fp)
...
RuntimeError: Failed to converge after 50 iterations, value is ...

Для вычисления корня методом newton необходимо либо переместить начальную точку ближе к корню, либо увеличить максимальное число итераций:
In [x]: newton(f, 0.01, fp, maxiter=100)
Out[x]: 1.148698354997035

г) Е
 ще один пример функции, которая порождает бесконечный цикл перебора
значений при использовании метода Ньютона–Рафсона:
In [x]: f = lambda x: x**4 - 4.29 * x**2 - 5.29
In [x]: fp = lambda x: 4*x**3 - 8.58 * x
In [x]: newton(f, 0.8, fp)
...
RuntimeError: Failed to converge after 50 iterations, value is ...

В отличие от функции в случае а) область 0.6 ≤ x0 ≤ 1.1 вызывает такое зацикленное поведение, поэтому необходимо инициализировать алгоритм вне
этого интервала, чтобы вычислить корни ±2.3. Например:
In [x]: newton(f, 1.2, fp)
Out[x]: -2.3

В8.4.3. В общем случае существуют два (физически различных) возможных
значения угла θ0, соответствующих прохождению маятника через заданную
точку (x1, y1) = (5, 15) – при движении вверх и при движении вниз. Эти значения
являются корнями в интервале (0, π/2) для следующей функции:
f(θ0; x1, z1) = x1 tg θ0 − (gx12) / (2v02 cos2θ0) − z1.

После ограничения интервала искомых корней с помощью приближенного
графика функции f(θ0) можно воспользоваться методом brentq:
In [x]: g = 9.81
In [x]: v0, x1, z1 = 25, 5, 15
In [x]: f = lambda theta0, x1, z1: x1 * np.tan(theta0) - g / 2\
* (x1 / v0 / np.cos(theta0))**2 - z1
In [x]: th1 = brentq(f, 1, 1.4, args=(x1,z1))
In [x]: th2 = brentq(f, 1.5, 1.6, args=(x1,z1))
In [x]: np.degrees(th1), np.degrees(th2)
Out[x]: (74.172740936822834, 87.392310240255171)

Таким образом, θ0 = 74.2° или θ0 = 87.4°.

В10.1.1. Пусть x = 0.9999…. Тогда

10x = 9.9999… = 9 + x ⇒ 9x = 9 ⇒ x = 1.

614



Приложение A. Решения

В10.1.2. Это происходит, потому что math.pi – это только лишь приближенное
значение (число с плавающей точкой двойной точности) π, поэтому тангенс
этого приближенного значения (непредвиденно) становится отрицательным:
In [x]: math.tan(math.pi)
Out[x]: -1.2246467991473532e-16

Попытка извлечения квадратного корня из этого значения приводит к арифметической ошибке.
В10.1.3. Здесь проблема, разумеется, состоит в том, что выражение записано
с использованием чисел с плавающей точкой двойной точности, а разность
между суммой первых двух членов и третьим меньше предела точности этого
представления. Использование точного представления в целочисленной арифметике
In [x]:
Out[x]:
In [x]:
Out[x]:

844487**5 + 1288439**5
3980245235185639013055619497406
1288439**5
3980245235185639013290924656032

дает разность
In [x]: 844487**5 + 1288439**5 - 1318202**5
Out[x]: -235305158626

Но ограниченная точность используемого ранее представления чисел с плавающей точкой отсекает знаки после десятичной точки до вычисления разности:
In [x]:
Out[x]:
In [x]:
Out[x]:

844487.**5 + 1288439.**5
3.980245235185639e+30
1318202.**5
3.980245235185639e+30

Это пример «катастрофического взаимоуничтожения» – потери значащих
разрядов.
В10.1.4. Выражение 1 - np.cos(x)**2 становится некорректным из-за потери
значащих разрядов («катастрофического взаимоуничтожения») при значении,
близком к x = 0, приводящем к критической потере точности и весьма резким
непредсказуемым колебаниям на графике функции f(x) (см. рис. A.2). Например, рассмотрим значение x = 1.e-9: в этом случае разность 1 - np.cos(x)**2
неотличима от нуля (в представлении с двойной точностью), поэтому f(x) возвращает 0. В другом случае значение np.sin(x)**2 неотличимо от x**2, поэтому g(x) корректно возвращает 1.0.

Приложение A. Решения 

615

+9.999×10-1
0.00014

0.00012

0.00010

0.00008

0.00006
− 0.00010

− 0.00005

0.00000

0 .00005

0 .00010

Рис. A.2. Сравнение поведения в численном представлении функций f(x) = (1 − cos2x)/x2
и g(x) = sin2x/x2 при значениях, близких к x = 0
Листинг A.4. Сравнение поведения в численном представлении функций f(x) = (1 − cos2x)/x2
и g(x) = sin2x/x2 при значениях, близких к x = 0
# qn9-1-c-cos-sin-a.py
import numpy as np
import matplotlib.pyplot as plt
f = lambda x: (1 - np.cos(x)**2)/x**2
g = lambda x: (np.sin(x)/x)**2
x = np.linspace(-0.0001, 0.0001, 10000)
plt.plot(x, f(x))
plt.plot(x, g(x))
plt.ylim(0.99995, 1.00005)
plt.show()

В10.1.5. В этом случае нельзя выполнять сравнение с помощью оператора ==,
так как специальное значение nan не равно самому себе. Но это всего лишь число с плавающей точкой, которое не равно самому себе, поэтому вместо сравнения на равенство можно использовать оператор !=:
In [x]: c = 0 * 1.e1000
In [x]: c != c
Out[x]: True

# 0 * inf дает результат nan.
# c не равно самому себе, поэтому его значением должно быть nan.

Приложение B.
Различия между версиями
Python 2 и 3
B.0.1 Целые числа и целочисленная арифметика
В версии Python 2 существовали два типа целых чисел: «простые» (системнозависимые, но обычно хранимые в 32 или 64 битах) и «длинные» (любого размера), обозначаемые суффиксом L. В версии Python 3 все проще: существует
только один тип целого числа, величина которого может быть любой (ограничение только по доступной оперативной памяти на конкретном компьютере).
В Python 2 оператор деления / всегда должен был выполнять целочисленное
деление (с округлением в сторону уменьшения), если оба операнда являлись
целыми числами. В противоположность этому в Python 3 оператор / всегда возвращает число с плавающей точкой типа float. В обеих версиях можно использовать оператор // для принудительного выполнения целочисленного деления.
Подводя итог, отметим следующие факты:
Python 2:
>>> 8 / 4
2
# Только в Python 2: результат int.
>>> 8 // 4
2
>>> 7.7 / 2
3.85
>>> 7.7 // 2
3.0
# Наибольшее целое, не превышающее 3.85.
>>> -7.7 // 2
-4.0
# Наибольшее целое, не превышающее -3.85.

Python 3:
>>> 8 / 4
2.0
>>> 8 // 4
2
>>> 7.7 / 2
3.85
>>> 7.7 // 2
3.0
>>> -7.7 // 2
-4.0

# Результат типа float, даже если оба операнда 8 и 4 типа int.

Приложение B. Различия между версиями Python 2 и 3

 617

Встроенная функция round() работает немного по-разному в версиях 2 и 3.
При округлении до нуля знаков после десятичной точки Python 3 применяет
метод округления банкира: если число находится точно посередине между
двумя целыми значениями, то возвращается четное целое типа int. В Python 2
функция round() округляет в сторону, противоположную направлению к нулю,
и всегда возвращает значение типа float.
Python 2:
>>> round(-4.5)
-5.0
>>> round(6.5)
7.0

Python 3:
>>> round(-4.5)
-4
>>> round(6.5)
6

B.0.2 Операции сравнения
В Python 2 были разрешены сравнения объектов различных типов. При сравнении числового и нечислового типов числовой тип всегда был меньше нечислового. Другие объекты различных типов упорядочивались последовательно, но
в произвольном порядке (таким образом, различные интерпретаторы Python
могли создавать различные варианты упорядоченности, например в CPython
объекты упорядочивались по имени типа, т. е. все объекты типа dict были
«меньше, чем» объекты типа str):
>>> '2' > 5
True
>>> {} > 'a'
False

# (CPython).

В Python 3 не допускается сравнение объектов различных типов:
>>> '2' > 5
TypeError
...
----> 1 '2' > 5
TypeError: unorderable types: str() > int()

Traceback (most recent call last)

B.0.3 Ключевые слова
Зарезервированные ключевые слова Python, которые нельзя использовать как
имена переменных (идентификаторы), приведены в табл. 2.4. В соответствующем списке для Python 2.7 исключены ключевые слова async, await, nonlocal,
True, False, None, но включены слова exec и print.

618



Приложение B. Различия между версиями Python 2 и 3

Для новых пользователей одним из наиболее заметных различий между Python 2 и Python 3 является то, что в Python 2 print был оператором (ключевым
словом), а в Python 3 print() – это (встроенная) функция:
Python 2:
>>> print '2 + 2 =', 4
2 + 2 = 4
>>> print >>fo, 'A string to write to file with open handle fo'

Python 3:
>>> print('2 + 2 =', 4)
2 + 2 = 4
>>> print('A string to write to file with open handle fo', file=fo)

Также обращает на себя внимание тот факт, что поскольку True и False не являлись ключевыми словами в Python 2, они могли использоваться как идентификаторы имен переменных. Из-за этого возникали «смысловые» проблемы,
например:
Python 2:
>>> True = False
>>> True
False

B.0.4 Строки и Unicode
В Python 2 различались строки в кодировке Unicode (таким строковым литералам предшествовал символ u, например u'El Niño') и строки в 8-битовой
кодировке (содержащие только 7-битовые ASCII-символы). Это становилось
источником многочисленных проблем, в том числе весьма неприятной ошибки UnicodeEncodeError, возникающей при смешивании строк в разных кодировках. Например, при попытке преобразования строки в кодировке Unicode
в строку с 8-битовой кодировкой:
>>> s1 = 'I live in'
>>> s2 = u'Saint Étienne'
>>> '{} {}'.format(s1, s2)
----> 1 '{} {}'.format(s1, s2)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-6:
ordinal not in range(128)

Все строки текста в Python 3 являются строками в кодировке Unicode и имеют тип str:
>>> s1 = 'I live in'
>>> s2 = 'Saint Étienne'
>>> '{} {}'.format(s1, s2)
'I live in Saint Étienne'

Приложение B. Различия между версиями Python 2 и 3

 619

Если действительно необходимо использовать строку 8-битовых значений
данных, то для этого существует строка байтов типа bytes. Бинарные литералы
данных можно определить с помощью синтаксиса b'…' или выполнить явное
приведение типа других совместимых объектов, как показано ниже:
>>>
>>>
>>>
>>>

b1
b2
b3
b4

=
=
=
=

b'ABC'
b'ABC\xff'
bytes([65, 66, 67, 255])
bytes([65, 66, 67, 0xff])

Последние три инструкции генерируют один и тот же объект типа bytes с конечным байтом, определяемым различными способами. При попытке присваивания объекту типа bytes значения, превышающего 255, возникает ошибка,
так как байт по определению содержит 8 бит:
>>> b5 = bytes([65, 66, 67, 256])
----> 1 b5 = bytes([65, 66, 67, 256])
ValueError: bytes must be in range(0, 256)

B.0.5 Итераторы и списки
В Python 2 встроенная функция range возвращала список, а память выделялась
для каждого элемента:
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Это создавало очевидные проблемы для весьма больших последовательностей, поэтому существовала отдельная встроенная функция xrange, возвращающая итерируемый объект, который должен был последовательно генерировать каждое значение арифметической прогрессии без сохранения всех
элементов.
В Python 3 функция range возвращает итерируемый объект, подобно устаревшей функции xrange, а xrange больше не существует. В Python 3 range (но
не xrange из Python 2) достаточно интеллектуально определяет, является ли
целое число членом последовательности без итеративного прохода по ней, поэтому следующая инструкция выполняется чрезвычайно быстро:
>>> 999999999998 in range(0, 10**12, 2)
True

# Все четные числа меньше 1 триллиона.

>>> 999999999997 in range(0, 10**12, 2)
False

В Python 2 методы словарей keys(), values() и items() также возвращали
списки соответствующих значений:

620



Приложение B. Различия между версиями Python 2 и 3

>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d.keys()
['a', 'c', 'b']
>>> d.items()
[('a', 1), ('c', 3), ('b', 2)]

В Python 3 возвращаются специальные итерируемые объекты. Если требуется список list, то необходимо обязательное явное приведение типа:
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d.keys()
dict_keys(['a', 'b', 'c'])
>>> list(d.keys())
['a', 'b', 'c']
# Внимание: в Python 3.6+: словари выводятся в порядке вставки элементов.

Очевидно, что это более эффективно для больших словарей.
Следующий пример демонстрирует особенности использования встроенной
функции zip.
Python 2:
>>> zip(['a', 'b', 'c'], [1, 2, 3])
[('a', 1), ('b', 2), ('c', 3)]

# Список кортежей.

Python 3:
>>> zip(['a','b','c'], [1, 2, 3])

# Специальный итерируемый объект zip...
>>> list(zip(['a','b','c'], [1, 2, 3]))
[('a', 1), ('b', 2), ('c', 3)] # …который можно преобразовать в список, если необходимо.

Приложение C.
Механизм решения обыкновенных
дифференциальных уравнений
odeint в библиотеке SciPy
В разделе 8.2.3 описан метод solve_ivp из библиотеки SciPy для решения обыкновенных дифференциальных уравнений (ОДУ): эта функция предоставляет
доступ к комплекту механизмов решения ОДУ через универсальный интерфейс и в настоящий момент является рекомендуемой методикой для большинства ситуаций, с которыми встречаются пользователи библиотеки SciPy.
Более старая функция scipy.integrate.odeint145 остается доступной в версии
SciPy 1.4, так как от нее, вероятно, зависит весьма большой объем ранее написанного кода. В этом приложении описано практическое использование этой
функции. Работа odeint основана на тщательно протестированной программе
Fortran LSODA, которая может автоматически переключаться между алгоритмами решений жестких и нежестких систем ОДУ.

Одно обыкновенное дифференциальное уравнение первого порядка
Простейший вариант использования – решение одного ОДУ первого порядка:
dy/dt = f(y, t).
Функция odeint принимает три аргумента: объект функции, возвращающей
dy/dt, начальное условие y0 и последовательность значений t, в которых вычисляется решение y(t).
Вернемся к примеру химической реакции первого порядка A → P с учетом
концентрации реагента y = [A], которой соответствует дифференциальное
уравнение
dy/dt = −ky.
Необходимо определить функцию:
def dydt(y, t):
return -k * y
145

Функция odeint является упрощенным интерфейсом к более продвинутому методу
scipy.integrate.ode, который предоставляет набор разнообразных механизмов численного интегрирования, включая алгоритмы Рунге-Кутты и поддержку переменных с комплексными значениями.

622



Приложение C. Механизм решения обыкновенных дифференциальных...

Обратите внимание: порядок аргументов противоположен порядку, требуемому для метода solve_ivp. При использовании odeint можно предложить
следующее решение, равнозначное решению, приведенному в разделе 8.2.3:
from scipy.integrate import odeint
# Постоянная скорости реакции первого порядка, 1/с.
k = 0.2
# Начальное условие для y: 100 % реагента имеется в момент времени t = 0.
y0 = 100
# Правильно подобранная сетка точек времени для этой реакции.
t = np.linspace(0, 20, 21)
y = odeint(dydt, y0, t)

Обратите внимание: по умолчанию odeint возвращает только решение y как
функцию на заданной сетке точек времени146.

Одно обыкновенное дифференциальное уравнение второго порядка
При решении ОДУ второго порядка, как и при решении задачи простого генератора гармонических колебаний из раздела 8.2.3, требуется разложение в систему ОДУ первого порядка:
# Частота гармонического осциллятора (1/с).
omega = 0.9
# Начальные условия для x1 = x и x2 = dx/dt в момент времени t = 0.
x0 = 3, 0 # cm, cm.s-1
def dxdt(x, t, omega):
""" Return dx/dt = f(x,t) at time t for the harmonic oscillator."""
""" Возвращает dx/dt = f(x,t) в момент времени t для генератора гармонических колебаний. """
x1, x2 = x
dx1dt = x2
dx2dt = -omega**2 * x1
return dx1dt, dx2dt
# Интегрирование дифференциальных уравнений с помощью odeint.
x1, x2 = odeint(dxdt, x0, t, args=(omega ,)).T

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

146

Для дополнительного аргумента full_output можно установить значение True, чтобы
вывести еще и словарь infodict со статистическими данными о том, насколько хорошо (или плохо) было выполнено интегрирование.

Словарь терминов
IPython – интерактивная рабочая среда на основе интерфейса командной строки
для Python, предоставляющая обширное разнообразие удобных функциональных возможностей, в том числе интроспекцию, хронологию команд, интерактивную визуализацию и автоматическое завершение ввода по клавише Tab.
Jupyter Notebook – интерактивная вычислительная блокнотная (виртуальная)
среда на основе браузера для создания совместно используемых документов с включением в них мультимедийных фрагментов. В этой среде можно
объединять исходный код, вывод результатов выполнения кода и текстовые описания в упорядоченной последовательности редактируемых ячеек.
Matplotlib – бесплатная библиотека с открытым исходным кодом, предназначенная для создания разнообразных двумерных и трехмерных графиков
и диаграмм, удобная для визуализации данных, которые можно использовать в печатных изданиях и в режиме онлайн.
NumPy – бесплатная библиотека с открытым исходным кодом, поддерживающая быстрые численные операции с большими многомерными массивами,
содержащими элементы одного типа данных.
pandas – бесплатная библиотека с открытым исходным кодом, предназначенная для обработки и анализа данных с поддержкой табличных данных разнородных типов, а также функциональность высокого уровня для группирования, агрегации и очистки наборов данных.
PEP – Python Enhancement Proposals – комплект рекомендаций по использованию, описаний стандартов и регулирующих процедур для разработчиков
на языке Python. Новые функциональные возможности языка предлагаются
и описываются в коротких документах, которые рецензируются координационным советом и помещаются в репозиторий www.python.org/dev/peps.
pip – менеджер пакетов для программной среды языка Python для установки
пакетов из Python Package Index (https://pypi.org/), репозитория бесплатных
программных библиотек для расширения функциональных возможностей
языка.
SciPy – бесплатная библиотека с открытым исходным кодом, предназначенная для научных вычислений и включающая алгоритмы численного интег­
рирования, интерполяции, оптимизации и решения дифференциальных
уравнений.
Unicode – международный стандарт для кодирования и представления текста,
отображаемого почти во всех системах записи мира. Стандарт Unicode присваивает числовую кодовую точку (code point) каждому символу и определяет, как это числовое значение должно выражаться в байтах, с учетом

624



Словарь терминов

правил, относящихся к направленности текста, подобию (сопоставлению)
символов и т. д.
Аргумент (argument) – значение, передаваемое в вызываемую функцию. Позиционные аргументы идентифицируются по их порядку в последовательности, заданной в определении функции (например, complex(1, -2)). Именованные аргументы связаны с конкретным идентификатором при вызове
функции (например, complex(real=1, imag=2)).
Атрибут (attribute) – в Python объект (элемент данных, функция и т. д.), связанный («принадлежащий») с другим объектом и доступный через точку:
object.attribute. Например, функция upper является атрибутом объекта
str: при создании экземпляра строки, скажем 'python', атрибут становится
доступным как ‘python’.upper и вызывается как 'python'.upper().
Байт-код (bytecode) – промежуточный язык, в который исходный код компилируется интерпретатором Python для выполнения виртуальной машиной
Python как машинного кода, понятного процессору компьютера.
Векторизация (vectorization) – пакетное (комплексное) выполнение одной
операции с массивом в целом без необходимости явного применения цикла Python – это улучшает и скорость, и удобство чтения. Библиотека NumPy
поддерживает векторизацию для объекта типа ndarray посредством реализации многих из подобных операций в виде предварительно скомпилированного кода на языке C.
Встроенный (built-in) – любой из типов и функций, предварительно определенных интерпретатором Python и всегда доступных без явного импорта
из какого-либо другого модуля. Примеры: print(), float и range().
Выражение (expression) – синтаксически корректное сочетание допустимых
в Python идентификаторов, литералов и операторов, вычисляющее некоторое значение. Например, (предполагается, что значение x определено)
x + 2 – корректное выражение Python, вычисляющее сумму значения x и
целого числа 2.
Генератор псевдослучайных чисел (pseudorandom-number generator –
PRNG) – алгоритм, генерирующий последовательность чисел, которая приближенно имитирует свойства случайных чисел. Генератор псевдослучайных чисел может инициализироваться (seeded) фиксированным значением
для повторного воспроизведения последовательности (это детерминированная аппроксимация), которая в конце концов окажется повторяющейся
(хотя и с достаточно длинным периодом).
Генератор списков (list comprehension) – синтаксическая конструкция Python
для создания списка в одной лаконичной, но удобочитаемой строке кода без
явного использования метода append, например [x**2 for x in range(5)].
Динамическая проверка типа (dynamic type-checking) – определение соответствия объекта выполняемой над ним операции во время выполнения,
а не перед выполнением программы (статическая проверка типа). Python –
это язык с динамической типизацией.

Словарь терминов 

625

Изменяемые и неизменяемые объекты (mutable and immutable objects) – неизменяемый объект не может быть изменен после его определения: объектам этого типа выделяется область памяти (связанная
с некоторой идентификационной характеристикой, которую можно
мысленно интерпретировать как «адрес» объекта в памяти), и содержимое этой области памяти невозможно изменить, пока существует объект
(хотя имя, связанное с этим объектом, может быть «переприсвоено» другому объекту). Изменяемый объект можно изменять непосредственно:
его свойства могут изменяться без изменения идентификационной характеристики. Примеры некоторых типов неизменяемых объектов: int,
float, str, tuple. Объекты типа list, dict и set являются изменяемыми.
Имя-идентификатор (identifier name) – символьное имя, связанное с объектом, которое используется для обращения к этому объекту в исходном
коде программы.
Инструкция (statement) – одна строка (или несколько строк) кода Python, состоящая из выражений, которые в общем случае оказывают воздействие на
состояние выполняемой программы.
Интроспекция (introspection) – способность программы или сеанса интер­
активной командной оболочки предоставлять информацию об объекте во
время выполнения. Например, выражение type(x) возвращает тип объекта, идентифицируемого по имени x.
Исключение (exception) – ошибка, возникающая во время выполнения
Python-программы. Если исключение не обрабатывается, то программа
прекращает выполнение. Общепринятая практика: предвосхищать и перехватывать (catch) с целью обработки конкретные исключения в блоке try…
except (см. описание парадигмы EAFP). Кроме того, можно использовать
и генерировать исключения, определенные пользователем.
Итерируемый (объект) (iterable) – объект является итерируемым, если он содержит или порождает последовательность значений, по которой можно
проходить поочередно при выполнении любого цикла. Примеры: списки
(list), кортежи (tuple) и строки (str).
«Катастрофическое взаимоуничтожение» (catastrophic cancellation) –
критическая потеря значимых разрядов при использовании арифметики
с плавающей точкой, когда выполняется вычитание очень близких друг
к другу значений.
Класс (class) – шаблон для создания (экземпляров) объектов, определяющий
их атрибуты, интерфейсы и поведение. Классы могут быть связаны между
собой механизмом наследования.
Командная оболочка (shell) – пользовательский интерфейс в компьютерной
системе, точнее интерактивный интерфейс командной строки, в которой
вводятся и выполняются команды.
«Легче попросить прощения, чем разрешения» («Easier to Ask Forgiveness
than to seek Permission» – EAFP) – характерный для Python стиль напи-

626



Словарь терминов

сания кода, при котором пытаются выполнить обработку данных с намерением аккуратно обработать любые возникающие ошибки (исключения).
Этот подход противоположен парадигме «Look Before You Leap» (посмотри,
прежде чем прыгнуть; семь раз отмерь, один отрежь), принятой в других
языках, где данные тщательно проверяются на соответствие их типа перед
попыткой выполнения операции с ними.
Литерал (literal) – в Python литерал – это прямая спецификация объекта при
синтаксическом разборе (парсинге) интерпретатором (в противоположность ссылке на объект по имени соответствующей переменной). Например, 'parrot' – строковый литерал.
Лямбда-функция (lambda) – анонимная функция: не связанная с каким-либо именем-идентификатором. В Python лямбда-функции обычно определяются непосредственно в строке (inline) другого кода и могут содержать
только одно выражение.
Магическое число (magic number) – постоянное числовое значение, используемое как литерал в программе вместо присваиваемого осмысленного
имени переменной. Рекомендуется избегать использования магических
чисел, чтобы исходный код оставался легко читаемым и удобно сопровож­
даемым.
Метод (method) – функция, связанная с объектом.
Методы с двойным подчеркиванием (double-underscore methods) – спе­
циальные методы (dunder-методы) обозначаются в Python именами, начинающимися и заканчивающимися двумя символами подчеркивания.
Такие методы обычно реализуют операцию, вызываемую некоторыми специальными синтаксическими конструкциями или встроенными функциями. Например, индексирование списка x[i] – это по существу равнозначно
вызову x.__getitem__(i). В Python возможно переопределение этих методов аналогично перегрузке операторов.
Модуль (module) – комплект кодов Python (определений объектов, включая
классы и функции, и выполняемого кода) в одном или нескольких файлах,
который можно импортировать для многократного использования в других программах.
Наследование (inheritance) – реализация иерархических связей между классами в объектно-ориентированной программе. Класс, определяемый как
основанный на другом классе (производный класс или подкласс), наследует атрибуты этого базового класса, которые можно изменять и добавлять
новые. Наследование способствует многократному использованию исходного кода, а представляемая этим механизмом иерархия часто неотделима
от внутренних концепций, реализованных в классах.
Не число (Not a Number – NaN) – специализированное значение для типа
данных с плавающей точкой, представляющее неопределенные или непредставимые числа.

Словарь терминов 

627

Область видимости (scope) – характеристика доступности переменной (имени) в каком-либо блоке кода. Поскольку одно имя-идентификатор может
быть связано с различными объектами в разных частях программы, правила разрешения области видимости определяют, какой объект разрешено использовать с рассматриваемым идентификатором. Например, если
происходит обращение к переменной внутри функции, то интерпретатор
Python в первую очередь определяет, присвоено ли это имя объекту внутри
функции (в локальном пространстве имен). Если объекта с таким именем
нет, то рассматривается блок кода, включающий эту функцию, и т. д. – до
глобального пространства имен программы (или модуля). Конечным является собственное пространство встроенных имен Python (содержащее
предварительно определенные имена объектов, таких как функция print).
Обусловленность (численной задачи) (conditioning (of a numerical problem)) – задача, решение которой является относительно независимым от
ошибок и неопределенностей в исходных данных, называется хорошо обу­
словленной (well-conditioned). Задача, в которой относительно малые погрешности в исходных данных значительно искажают результат, называется плохо обусловленной (ill-conditioned).
Объект (object) – абстрактная концепция сущности, реализованная в компьютерной программе как структура данных, которая может содержать и обрабатывать собственные данные (атрибуты), включая методы и другие объекты.
Объектно-ориентированное программирование (object-oriented programming – OOP) – парадигма программирования, в соответствии с которой компоненты системы идентифицируются как отдельные сущности,
определяемые в коде объектами, описываемые шаблонами («схемами») –
классами. Такой концептуальный подход может помочь при решении задач посредством разделения их на небольшие взаимосвязанные части
с простым индивидуальным поведением, взаимодействующие определенным и управляемым способом.
Оптимизация (математическая) (optimization (mathematics) – процесс получения наилучшей (по некоторому критерию) параметризации задачи (возможно, связанной с некоторыми ограничениями). Например, поиск значений x и y, при которых значение функции f(x,y) минимально, – это задача
оптимизации. Поиск максимального значения той же функции по существу
является аналогичной задачей, так как она равнозначна минимизации −f(x,y).
Пакет (package) – иерархически структурированный набор модулей, хранящихся в каталоге, который создает для них пространство имен и позволяет
в крупных проектах определять способ их импортирования и использования.
Перегрузка оператора (operator overloading) – реализация оператора, используемого в одном классе, в другом классе: например, оператор + используется для сложения целых чисел (как в выражении 2 + 3), но еще и для
объединения объектов типа list: [1, 2] + [3, 4, 5] возвращает [1, 2, 3, 4, 5].
Это пример полиморфизма. В классах, определенных пользователем, можно перегрузить любой оператор, определив соответствующий ему метод
с двойным подчеркиванием.

628



Словарь терминов

Переменная (variable) – символьное имя, связываемое с объектом, по которому происходит обращение к этому объекту в коде программы. В Python
переменная в этом смысле иногда более формально называется именемидентификатором (identifier name): один объект может иметь более одного
такого идентификатора.
Переполнение и обращение в машинный ноль (overflow and underflow) –
состояние, когда результат вычислений больше или меньше значения, которое может быть представлено в памяти для используемого типа данных.
Например, вычисление math.exp(1000) приводит к переполнению при использовании арифметики с плавающей точкой двойной точности, потому
что результат больше, чем наибольшее представимое значение типа float,
использующее 64 бита (приблизительно 1.8×10308).
Подгонка методом наименьших квадратов (least-squares fitting) – в анализе переопределенных систем (систем с количеством точек данных,
бо́льшим, чем количество неизвестных параметров рассматриваемой модели) это процесс получения набора параметров, минимизирующих сумму
квадратов (наблюдаемых – моделируемых) остатков (невязок). При линейной подгонке методом наименьших квадратов функция модели зависит
линейно от своих параметров.
Полиморфизм (polymorphism) – свойство функции (метода), позволяющее
разумно выполнять некоторую операцию с данными различных типов. Например, оператор * является полиморфным, потому что может использоваться для умножения двух чисел (2 * 1.2 возвращает 2.4), но также для создания списка повторяющихся элементов ([0] * 4 возвращает [0, 0, 0, 0]).
Порядок следования байтов (endianness) – зависимый от платформы порядок байтов, представляющих число. Системы с порядком байтов от
старшего к младшему (с обратным порядком) (big-endian systems) первым размещают старший байт (по наименьшему адресу памяти). Системы
с порядком байтов от младшего к старшему (с прямым порядком) (littleendian systems) записывают старший байт последним. Название порядка
байтов происходит от фанатичной вражды лилипутов и блефусканцев по
поводу разбивания вареных яиц с тупого или острого конца («тупоконечники» и «остроконечники»), описанной Джонатаном Свифтом в романе
«Путешест­вия Гулливера» (1726).
Пространство имен (namespace) – отображение (словарь), устанавливаемое
между именами-идентификаторами и объектами, с которыми связаны имена.
Решение о выборе пространства имен для правильной идентификации имени
используемого объекта определяется областью видимости (scope) имени.
Синтаксический сахар (syntactic sugar) – программный синтаксис и конструкции, которые не являются абсолютно необходимыми для обеспечения функциональности языка, но делают исходный код более простым, понятным
и удобным для программирования, а иногда даже более эффективным (быст­
рым). Например, Python поддерживает комбинированное присваивание (допустим, a += 1 – это синоним присваивания a = a + 1) и генератор списков.

Словарь терминов 

629

Стабильность (устойчивость) алгоритма (stability of an algorithm) – алгоритм называют стабильным (устойчивым), если он относительно независим от ошибок (погрешностей) аппроксимации различного рода, которые
могут возникать при его выполнении или при вводе данных. Если такие
ошибки накапливаются (обычно это приводит к критическому отказу алгоритма, который не позволяет получить осмысленный результат), то алгоритм называется нестабильным (неустойчивым).
Стандартная библиотека (Standard Library) – большой набор модулей, содержащих методы выполнения общих задач, например математических,
операций ввода-вывода или отладки. Стандартная библиотека устанавливается автоматически вместе с большинством дистрибутивов Python. Дополнительные пакеты и модули доступны в репозитории Python Package
Index, их можно устанавливать с помощью утилиты pip.
Стек (программный) (stack (software)) – набор программных компонентов, которые работают совместно для создания платформы (или среды),
в которой может выполняться некоторый класс компьютерных задач.
Например, стеком SciPy иногда называют комплект, состоящий из интерпретатора Python, установленного вместе с библиотеками NumPy, SciPy
и Matplotlib.
Строгая и слабая типизация (strongly and weakly typed) – язык определяется как слабо типизированный, если он незаметно для пользователя выполняет преобразование объектов в требуемый тип, чтобы позволить функции работать с ним. Языки со строгой типизацией позволяют выполнять
только операции с объектами предварительно определенного набора типов (языки со статической типизацией) или с объектами, обладающими совместимыми свойствами (языки с динамической типизацией). Python является языком с относительно строгой типизацией: например, выражение
'hello' + 4 генерирует исключение (TypeError). Языки, подобные JavaScript,
являются языками с относительно слабой типизацией – в них автоматически выполняется преобразование целого числа 4 в строку, чтобы вернуть
литерал 'hello4'.
Строка документации (docstring) – строковый литерал, записанный как первая инструкция в определении функции, класса или модуля. Становится
атрибутом __doc__ своего объекта и документирует его поведение и функциональность.
Управление версиями (version control) – система управления изменениями в процессе разработки программного обеспечения, в которой часто
используются инструментальные средства, обеспечивающие совместную
работу, систему меток (тегов) выпускаемых версий и разветвление на параллельно развивающиеся версии исходного кода в процессе разработки.
Утиная (неявная) типизация (duck typing) – определение принадлежности
объекта к какому-либо типу с учетом выполняемой над ним операции по
методам и свойствам, которыми он обладает, вместо явного формального
объявления его типа данных. Python – это язык с утиной типизацией.

630



Словарь терминов

Функция (function) – набор инструкций, объединенных в блок программы для
выполнения некоторой задачи при вызове. В функции можно передавать аргументы (данные в форме ссылок на объекты Python). Функция также может
возвращать одно или несколько значений. Функции, принадлежащие какому-либо объекту, называются его методами. Но поскольку в Python все есть
объект, между терминами «метод» и «функция» нет почти никакой разницы.
Хеш (hash) – объект Python является хешируемым (hashable), если он может
возвращать из специального метода __hash__ хеш-значение, которое никогда не изменяется в течение своего жизненного цикла, и может сравниваться
с другими объектами на равенство. Это важно для таких структур данных,
как словари, которые отображают хеш-значения в объекты для обеспечения
эффективного хранения и высокопроизводительного доступа.
Число с плавающей точкой (floating-point number) – представление действительного числа, используемое в компьютерной арифметике, при котором число хранится в виде отдельных частей, представляющих значащие разряды и показатель степени, в фиксированном количестве битов,
в общем случае определяющем ограниченную точность.
Экземпляр (instance) – специальное представление объекта, созданное (instantiated) из класса с собственными конкретными данными и именем-идентификатором. В дополнение к значениям атрибутов, принадлежащих конкретному
экземпляру объекта, могут существовать данные, совместно используемые
всеми экземплярами рассматриваемого класса (атрибуты класса).

Предметный указатель
A

abs, встроенная функция 27
add, метод множества 149
allclose(a,b), метод 270
all(), встроенная функция 70
Anaconda, дистрибутив Python 18
Anaconda, дистрибутивный пакет 209
animation, модуль Matplotlib 411
any(), встроенная функция 70
append, метод списка 65
append(), метод списка 67
Artist, базовый класс 388
ax.add_patch(), метод 388
ax.annotate, метод 381
ax.axhline, метод 386
ax.axhspan, метод 386
ax.axvline, метод 386
ax.axvspan, метод 386
ax.bar, метод 371
ax.barh, метод 372
ax.clabel, метод 396
ax.contour, метод 396
ax.contourf, метод 396
ax.errorbar, метод 363
Axes, объект 349
ax.fill_between, метод 384
ax.grid(), метод 355
ax.hlines, метод 385
ax.imshow, метод 397
ax.invert_xaxis(), метод 351
ax.invert_yaxis(), метод 351
ax.legend(), метод 355
ax.matshow, метод 399
ax.pcolor, метод 400
ax.pcolormesh, метод 400
ax.pie, метод 375
ax.plot, метод 349, 385
ax.plot_surface, метод 407
ax.set_rlabel_position, метод 378
ax.set_rticks, метод 378
ax.set_xlabel, метод 355
ax.set_xlim, метод 350
ax.set_xticklabels, метод 359
ax.set_xticks, метод 358
ax.set_ylabel, метод 355

ax.set_ylim, метод 350
ax.set_yticklabels, метод 359
ax.set_yticks, метод 358
ax.tick_params, метод 361
ax.title, метод 355
ax.view_init, метод 408
ax.vlines, метод 385
ax.xaxis, объект 361
ax.yaxis, объект 361

B

BFGS, квазиньютоновский метод
(алгоритм) 482
break, команда 81

C

CamelCase
стиль именования классов 190
CamelCase, стиль имен переменных 32
clear, метод множества 149
close, файловый метод 90
complex, тип 22
continue, команда 82
ContourSet, объект 396
convert, метод 303
Cooley-Tukey algorithm 340
C, язык программирования 14

D

DataFrame, объект pandas 504, 510
datetime.datetime, объект 186
datetime.date, объект 184
datetime.time, объект 185
datetime, модуль 184
date.today, конструктор 184
dawsn(), метод 433
defaultdict, объект 147
def, ключевое слово 94
del, ключевое слово Python 517
dict, конструктор словаря 143
discard(), метод множества 149
docstring 580
Docstring 46, 95, 190
domain, атрибут многочлена 304
dtype, тип структурированного
массива 266
Dunder-метод 199

632



Предметный указатель

E

else, команда для циклов for и while 83
Endianness. См. Порядок следования
байтов
Enthought Deployment Manager (EDM),
дистрибутив Python 18
enumerate, встроенный метод 74
erf(), метод 433
erfc(), метод 433
erfcinv(), метод 433
erfcx(), метод 433
erfinv(), метод 433
Escape-последовательность 45
Esc-последовательность 45
Excel 525
extend, метод списка 65

F

fig.add_subplot, метод 365
fig.add_subplot(), метод 350
fig.colorbar, метод 401
fig.subplots_adjust(), метод 368
fig.suptitle, метод 355
file, тип объекта 90
filter, встроенный метод 163
float, тип 22
format, строковый метод 53
FORTRAN 77 QUADPACK 443
Fortran, язык программирования 17
for, цикл 71
frozenset, объект, неизменяемое
множество 151
FuncAnimation, класс Matplotlib 411
FuncAnimation, объект Matplotlib 414

G

geopandas, пакет 558
get(), метод словаря 144
Git, система управления версиями 586
global, ключевое слово 100

H

HITRAN, база данных 531
HTTP, протокол 183
запрос 183
команда
GET 183
POST 183

I

IDE integrated development
environment
интегрированная среда разработки
(IDE). См. Python
IEEE-754, стандарт чисел с плавающей
точкой 564
if…elif…else, конструкция 79
if, оператор проверки условия 79
image.imread, метод 397
insert, метод списка 66
int, тип 22
in, оператор 62
IPython 209
измерение времени выполнения
команды 218
командная оболочка 209
автоматическое дополнение
ключевых слов по клавише Tab 212
взаимодействие с операционной
системой 215
интроспекция 211
магическая функция
%history 213
промпт 210
справочная информация 210
хронология команд 213
магическая функция 216
%alias_magic 218
%automagic 216
%bookmark 218
%edit 224
%load 220
%lsmagic 216
%macro 220
%recall 220
%rerun 219
%run 221
%save 221
%sx 222, 225
%timeit 218, 225
секционная 216
строковая 216
макрокоманда 220
объект SList 222
fields, метод разбивки на поля 222
grep, метод поиска 222
sort, метод сортировки 222

Предметный указатель 

633

псевдоним команды 218
ядро 227
is, оператор 38
items, метод словаря 144

mplot3d (Matplotlib) 439
mpl_toolkits.mplot3d, модуль 406

J

NaN, значение «не число» 539
NaN Not a Number, не число 248
ndarray 238
None, специальное
значение 36, 144, 170
nonlocal, ключевое слово 100
Not a Number не число 116
np.abs(F)**2, величина спектра
мощности 340
np.abs(F), величина амплитудного
спектра 340
np.all, метод 269
np.allclose, метод 566
np.amax, метод 287
np.amin, метод 287
np.angle(F), величина фазочастотного
спектра 340
np.any, метод 269
np.arange 241
np.arctan2, функция 390
np.argmax, метод 261, 287
np.argmin, метод 261, 287
np.argsort, метод 264
np.array 239
np.asarray, метод 581
np.average, метод 288
np.bool_ 244
np.clip(), метод 544
np.complex_ 244
np.corrcoef, метод 290
np.cov, метод 289
np.cross, метод 268
np.diff, метод 272
np.dot, метод 268
np.dsplit, метод 252
np.dstack, метод 252
np.empty 240
np.empty_like 240
np.eye, метод 313
np.fft.fft2, метод 343
np.fft.fftfreq(n,d), метод 340
np.fft.fftn, метод 343
np.fft.fftshift, метод 340
np.fft.fft, метод 340, 341, 346

JPG, формат 370
JupyterLab, IDE на основе браузера 237
Jupyter Notebook 225
nbconverter, средство
преобразования в другие
форматы 235
ядро 227
ячейка ввода 227
HTML 231
LaTeX 234
MathJax 234
видеотег HTML5 video 235
код 227, 228
необработанный текст 228
текст с разметкой 228, 229

K

keys, метод словаря 144

L

LaTeX, язык разметки 119
LEGB, правило разрешения
конфликтов областей
видимости 99
len(), встроенный метод 50

M

map, встроенный метод 163
math.e, атрибут модуля math 28
math.fsum, метод 568
math.isclose, функция 34
math.pi, атрибут модуля math 28
math, модуль 27
MATLAB 348
MATLAB, коммерческий
программный пакет 111
Matplotlib, библиотека 111
matplotlib.cm, модуль 395
matplotlib.pyplot, модуль 348
matplotlib.ticker, модуль 359
Mercurial, система управления
версиями 586
Mersenne Twister 328

N

634



Предметный указатель

np.fft.ifft2, метод 343
np.fft.ifftn, метод 343
np.fft.ifftshift, метод 340
np.flatten, метод 250
np.float_ 244
np.fmax, метод 287
np.fmin, метод 287
np.fromfunction 242
np.genfromtxt,
метод 274, 278, 279, 281, 286
np.histogram, метод 292, 293, 295
np.hsplit, метод 252
np.hstack, метод 252, 253
np.inf, значение бесконечности 248
np.isclose, метод 566
np.isclose(a,b), метод 269
np.isclose(), метод 271
np.isfinite, метод 248
np.isinf, метод 248
np.isnan, метод 248
np.linalg.det, метод 314
np.linalg.eigh, метод 317
np.linalg.eigvalsh, метод 318
np.linalg.eigvals, метод 318
np.linalg.inv, метод 315
np.linalg.lstsq, метод 321, 328
np.linalg.matrix_rank, метод 314
np.linalg.norm, метод 314
np.linalg.solve, метод 321
np.linalg, метод 313
np.linspace 241
np.linspace, метод 114
np.loadtxt, метод 274, 276
np.load, метод 274
np.maximum, метод 287
np.max, метод 261, 287
np.mean, метод 288
np.meshgrid, метод 258, 394
np.minimum, метод 287
np.min, метод 261, 287
np.nan 248, 287
np.nanargmax, метод 287
np.nanargmin, метод 287
np.nanmax, метод 287
np.nanmin, метод 287
np.nanstd, метод 289
np.newaxis 257, 261
np.ones 240
np.ones_like 240
np.percentile, метод 287

np.random.binomial(n,p), метод 334
np.random.choice, метод 336
np.random.normal, метод 332
np.random.permutation, метод 337
np.random.poisson, метод 336
np.random.randint, метод 330, 338
np.random.randn, метод 332
np.random.random_integers,
метод 331, 338
np.random.random_sample, метод 329
np.random.seed, метод 328
np.random.shuffle, метод 337
np.ravel, метод 250
np.reshape, метод 251
np.resize, метод 251
np.savetxt, метод 283
np.save, метод 274
np.searchsorted, метод 264
np.sort, метод 263
np.std, метод 289
np.tile, метод 362
np.trace, метод 314
np.transpose, метод 251
np.uint8 272
np.uint16 244
np.vsplit, метод 252
np.vstack, метод 252
np.zeros 240
np.zeros_like 240
NumPy 238
numpy.fft, библиотека быстрых
преобразований Фурье 340
polynomial, пакет 296
random, модуль 328
массив 238
бродкастинг 259, 318
вырезание 254
двумерный 239
добавление оси 257
записей 264
индексация 254
индексация массивом логических
значений 256
интроспекция 242
как вектор 267
как сетка 257, 258
многомерный 239
ось 239
представление 250
ранг 239

Предметный указатель 

сложная индексация 255
совместимые измерения 259
сортировка 263
строка 246
строка кода типа 245
структурированный 264
структурированный,
сортировка 267
тип данных 239
транспонирование 251
форма 249
чтение и запись в файл 274
шаг вырезания 254
решение линейных уравнений 320
numpy.linalg.eig, метод 326
numpy.linalg.svd, метод 326
NumPy, библиотека 111, 114

O

open, файловый метод 90
openpyxl, модуль 526
os.getenv(), функция 171
os.path.basename, функция 172
os.path.exists, функция 172
os.path.getmtime, функция 172
os.path.join, функция 172
os.path.splitext, функция 172
os.path, модуль 172
os.uname(), функция 171
os, модуль 171

P

pandas, библиотека 504
pass, команда 82
pc.precision, метод 419
pc.unit, метод 419
pc.value, метод 419
pd.append, метод 516
pd.at, метод 514
pd.concat, метод 516
pd.cut, метод 542, 550
pd.date_range, метод 535
pd.df.dropna(), метод 539
pd.df.fillna(), метод 540
pd.df.head(), метод 518
pd.df.plot, метод 519
pd.df.size(), метод 557
pd.df.sort_index, метод 533
pd.df.unstack, метод 557
pd.df.unstack(), метод 534

635

pd.df.xs, метод 534
pd.drop, метод 517
pd.drop_duplicates(), метод 542
pd.dropna, метод 508
pd.duplicated(), метод 542
PDF, формат 370
pd.fillna, метод 509
pd.groupby, метод 551
pd.iat, метод 514
pd.idxmax, метод 519
pd.idxmin, метод 519
pd.iloc, метод 513
pd.loc, метод 513, 533
pd.map, метод 550
pd.MultiIndex.from_product, метод 532
pd.MultiIndex.from_tuples, метод 531
pd.read_csv, метод 520, 555
pd.read_excel, метод 525
pd.read_fwf, метод 523
pd.read_html, метод 528
pd.replace, метод 509, 541
pd.resample, метод 537
pd.Series.sort_index, метод 507
pd.Series.sort_values, метод 507
pd.to_csv, метод 524
pd.to_datetime, метод 535
pd.to_excel, метод 526
pd.value_counts, метод 543
PEP8, документ, определяющий стиль
кодирования Python 583
Period, объект pandas 536
Perl, язык программирования 15
pip, приложение установки
пакетов 178
p.linspace, метод 306
plt.figure, метод 348
plt.figure(), метод 370
plt.gca(), метод 378
plt.legend, метод 117
plt.plot, метод 112
plt.savefig(), метод 113, 370
plt.scatter(), метод 112
plt.show(), метод 112
plt.subplots, метод 365
plt.title, метод 118
plt.xlabel, метод 118
plt.xlim, метод 123
plt.ylabel, метод 118
plt.ylim, метод 123

636



Предметный указатель

PNG, формат 370
Polynomial.Chebyshev 301
Polynomial.deriv, метод 300
Polynomial.fit, метод 306
Polynomial.fromroots, метод 298
Polynomial.Hermite 301
Polynomial.HertmiteE 301
Polynomial.integ, метод 300
Polynomial.Laguerre 301
Polynomial.Legendre 301, 302
Polynomial, класс 296
pop, метод множества 149
pop(), метод списка 67
PostScript (EPS), формат 370
print, встроенная функция 52, 91
pyplot, интерфейс Matplotlib 348
pyplot.bar, метод 295
pyplot.contour, метод 394
pyplot.hist, метод 293, 295
pyplot.hist, функция 127
pyplot.polar, метод 127, 377
pyplot.scatter, метод 353
pyplot.twinx(), метод 128
pyplot, интерфейс 111
pytest, рабочая среда
тестирования 587
Python
динамическая типизация 30
интегрированная
среда разработки (IDE) 19
командная строка 19
установка 18
Linux 19
macOS 19
Windows 19
Python(x,y) 19

R

randint, метод 328
random.choice, метод 180
random.normalvariate(), метод 180
random.randint(), метод 180
random.random, метод 179
random.sample(), метод 181
random.seed, метод 179
random.shuffle, метод 180
random.uniform(), метод 180
random, модуль 179
range, встроенная функция Python 619
range, встроенный метод 73

RangeIndex, объект pandas 505
readlines(), файловый метод 91
readline(), файловый метод 91
read, файловый метод 91
remove, метод множества 149
remove, метод списка 66
Resampler, объект pandas 537
reversed(), встроенная функция 72
reverse(), метод списка 66
round, встроенная функция 27
Ruby, язык программирования 15

S

scipy.constants, пакет 418, 420
scipy.constants.physical_constants,
словарь 419
пример использования 421
scipy.integrate, модуль 445
scipy.integrate, пакет 442
scipy.integrate.dblquad, метод 467
scipy.integrate.nquad, метод 447
scipy.integrate.odeint, метод 449, 621
scipy.integrate.quad,
метод 443, 466, 468
scipy.integrate.solve_ivp, метод 449, 472
scipy.integrate.solv_ivp, метод 471
scipy.interpolate.griddata, метод 476
scipy.interpolate.interp1d, метод 472
scipy.interpolate.RectBivariateSpline,
объект 475
scipy.interpolation, пакет 472
scipy.interpolation.interp2d, метод 474
scipy.optimize, модуль 496
scipy.optimize, пакет 478
scipy.optimize.bisect, метод 496
scipy.optimize.brenth, метод 496
scipy.optimize.brentq, метод 496, 502
scipy.optimize.curve_fit, метод 494
scipy.optimize.leastsq, метод 490
scipy.optimize.minimize_scalar,
метод 486
scipy.optimize.newton, метод 497, 502
scipy.optimize.ridder, метод 496
scipy.special, модуль 433, 435
scipy.special, пакет 418
scipy.special.airy, метод 422
scipy.special.ai_zeros(), метод 422
scipy.special.binom, метод 440
scipy.special.lambertw, метод 472
Series, объект pandas 505

Предметный указатель 

set_ticks_position, метод 361
sinc, функция 116
sorted, встроенный метод 160
sorted(), встроенный метод 66
sort, метод списка 160
sort(), метод списка 66
sp.cobyla, метод 485
sp.dblquad, метод 445
sp.ellipe(), метод 431
sp.ellipk(), метод 431
sp.expi(), метод 436
sp.expl(), метод 437
sp.fresnel(), метод 435
sp.fresnel_zeros(), метод 435
sp.gamma(), метод 429
sp.gammaln(), метод 429
sp.jnp_zeros(), метод 425
sp.jn_zeros(), метод 425
sp.jvp(), метод 425
split(), строковый метод 67
sp.nquad, метод 445
sp.slsqp, метод 485
sp.solve_ivp, метод 461, 621
sp.tplquad, метод 445
sp.ynp_zeros(), метод 425
sp.yn_zeros(), метод 425
sp.yvp(), метод 425
str, встроенная функция 44
str, тип 43
Subversion (SVN), система управления
версиями 586
SVG (Scalable Vector Graphics), формат
графических файлов 175
sys.argv, список аргументов командной
строки 169
sys.exit, метод 170
sys, модуль 169

T

Timestamp, объект pandas 535

U

Unicode 46, 246
Unicode, кодировка символов 618
unittest, модуль 587
пример использования 588
urllib, пакет 183
UTF-8 246
UTF-8, кодировка 47
UTF-8, кодировка символов 584

637

V

values, метод словаря 144

W

while, цикл 80
window, атрибут многочлена 304
WinPython 19
with, ключевое слово 160
wofz(), метод 433
write, файловый метод 91

X

xlrd, пакет 525
xrange, встроенная
функция Python 2 619

Z

zip, встроенная функция 74

А

Адаптивная квадратура 443
Адрес памяти 29
Азбука Морзе 155
Алгоритм quicksort (быстрая
сортировка) 264
Алгоритм Евклида для поиска
наибольшего общего делителя двух
чисел 81
Алгоритм Луна 87
Алгоритм шнурования
(shoelace algorithm) 273
Анализ электрических цепей методом
контурных токов 315
Анонимная функция 159
Антикорреляция 290
Аподизация 346
Аргумент 97
именованный 97, 147
передача в функцию 102
позиционный 97
по умолчанию 97
Арифметико-геометрическое среднее
(АГС) значение 86
Арифметическое среднее 288
Атрибут 26, 188
Аффинное преобразование 399

Б

Байт-код 15
Бакминстерфуллерен C60 334
Баллестероса формула 530

638



Предметный указатель

Бернулли испытание 333
Бета-функция 430
Библиотека Matplotlib
трехмерный график 406
Бинарный оператор 24
Биномиальное распределение
вероятностей 333
Биномиальный коэффициент 436
Блиттинг 413
Брента метод решения
уравнений 496, 502
Бродкастинг 379, 419
Брюсселятор (Brusselator) 468
Бугера–Ламберта–Бера закон 322
Быстрое преобразование Фурье 340
Бюффона игла, задача 338

В

Ван дер Ваальса уравнение 311
Вариационный принцип 503
Веб-скрейпинг 528
Векторизация 115, 247, 259, 419
Векторное произведение 261, 267
Великая теорема Ферма 571
Взвешенная подгонка данных 364
Взвешенное среднее значение 288
Вид акул
пример составления словаря 155
Визуализация матриц 398
Визуализация сферических
гармонических функций 439
Визуальное представление тора 408
Википедия, парсинг страниц
с использованием pandas 529
Вина закон смещения 502
Виртуальный робот черепашка,
пример 84
Вихрь Мерсенна (ГПСЧ) 179
Возведение матрицы в
(целочисленную) степень 313
Волновая функция 433
Временной ряд 535
Всемирная геодезическая система
(сеть) (World Geodetic System),
стандарт WGS-84 43
Выборка случайных целых чисел 330
Вырезание из последовательности 48, 64
Высота полета снаряда как функция
от времени 527

Г

Гаверсинус 174, 286
Галлея метод поиска корней
уравнений 498
Гамма-функция 429
Гаусса функция 332
Гауссова функция 117, 273
Гауссово простое число 126
Гауссово целое число 126
Гауссов фильтр 344
Гвидо ван Россум
(Guido van Rossum) 14
Генератор 161, 225
Генератор гармонических
колебаний 455
Генератор псевдослучайных чисел
(ГПСЧ) 179, 328
Генерация генераторов 162
Герона формула (площадь
треугольника) 572
Герцшпрунга–Рассела диаграмма
(классификация звезд) 530
Гессе матрица 483
Гессе матрица (гессиан) 479
Гипотеза Коллатца 88
Гистограмма 127, 292
интервал 292
Главный момент инерции 326
Глобальная переменная 98
Граница графика (Matplotlib) 123
График в полярных координатах
(Matplotlib) 127
График Matplotlib
анимация 411
аннотация 380
со стрелками 381
граница 350
заголовок 355
легенда 355
линия сетки 354
логарифмическая шкала 355
маркер 352
многоугольник (патч) 391
надпись 355
окружность (патч) 389
перекрывающий прямоугольник 386
произвольная линия 385
прямоугольник (патч) 391
стиль линии 351

Предметный указатель 

639

шрифт, свойства 358
штриховая метка на оси 358
вспомогательная 360
основная 360
подпись 359
удаление 360
эллипс (патч) 389
Грегорианский календарь 80, 184
Гудермана функция 467
Дальность полета снаряда,
вычисление 109

Дискретное преобразование
Фурье 340
Диск (узор) Эйри 440
Дисперсия 288
Диспетчер контекста 160
Дифракционная картина (диаграмма)
рентгеновских лучей 427
Добсона единица (озоновый слой) 530
Дополнительная функция ошибок 433
Дуга большой окружности 174

Д

Евклида норма для одномерных
массивов 314

Данные
агрегация 551
анализ 551
группирование 551
категоризация 551
о 822 самых мощных вулканических
явлениях на Земле за период
с 1750 г. до н. э. по 2020 г. н. э.
анализ с использованием
pandas 559
обо всех ядерных взрывах с 1945
по 1998 г. 555
обработка промахов 544
отсутствующие значения 539
очистка 538
повторяющиеся значения 542
статистическая группировка
(биннинг) 542
Данные о выбросе в атмосферу
парниковых газов 375
Двойной интеграл 445
Двойной факториал 77
Двумерное быстрое преобразование
Фурье 343
Двумерное дискретное
преобразование Фурье 343
Дебая теория (модель) 466
Действительное число 563
Деление чисел с плавающей
точкой (/) 24
Денормализация числа с плавающей
точкой 569
Джейкоба, уравнение 442
Диаграмма в полярных
координатах 377
Диаграмма погрешностей 363
Диаграмма рассеяния 112

Е

Ж

Жесткая система ОДУ 460

З

Задача Коши 449
Закон Бенфорда
(закон первой цифры) 78
Закон излучения Планка 502
Закон Мура 123
Закон Ципфа 154
Зарезервированное ключевое слово 31
Знаковый бит 564
Значащая часть числа 564

И

Идентификатор объекта 30
Идентичность объектов 38
Избыточно определенная задача 321
Излучина реки (меандр), имитация 339
Изменяемость списка 62
Импорт из модуля 29
Имя-идентификатор 30
Имя переменной 31
Индексация последовательности 61
Индекс бигмака 392
Индексирование
последовательности 47
Индекс массы тела (ИМТ) 405
Индекс подобия Земле
(Earth Similarity Index ESI) 93
Интеграл Доусона 433
Интеграл Френеля 435
Интегрирование 442
Интегрированная среда разработки
(Integrated Development
Environment IDE) 585

640



Предметный указатель

Интерполяция 472
многомерная 474
неструктурированных данных 476
одномерная 472
Интерфейс командной строки 19
Инфиксная запись 155
Иррациональное число 563
Исключение 132, 134
AssertionError 138
FileNotFoundError 135
IndexError 135
KeyError 135, 147, 149
LinAlgError 315, 317, 321
NameError 134
RankWarning 307
SystemExit 136, 221
TypeError 135
ValueError 135
ZeroDivisionError 134
генерация
ключевое слово assert 138
ключевое слово raise 138
необработанное 137
обработка 137
EAFP, методика 137
блок else 138
блок except 137
блок finally 138
блок try 137
Итерируемый объект 69

К

Каркасная диаграмма (проволочная
модель) 407
Каталог звезд Yale Bright Star
Catalog 206
Катастрофическое взаимоуничтожение
(catastrophic cancellation) 567
Квадрат судоку, пример 257
Кеш (cache) общего пользования
для небольших целочисленных
объектов 39
Кирхгофа закон 315
Класс 188
абстрактный 189
атрибут 192
базовый 189, 193
конструктор 192
метод 192
наследование 189, 193

оператор 199
переменная класса 192
переменная экземпляра класса 192
подкласс 193
производный 189, 193
экземпляр 191
Клеточный автомат 167
Клотоида 436
Ключевое слово 31
Ковариационная матрица 290
Ковариация 289
Кодон 77, 224
Колебания маятника 430
Командная оболочка (shell) 20, 21
Комбинирование битовых карт
изображения 413
Комбинированное присваивание 39
Комитет по данным для науки
и техники CODATA 419
Комментарий 26
Комплексное число 22, 23
Консоль 19
Константа Гаусса 86
Конструктор 23
Контур Гаусса 434
Контур Лоренца 434
Контурная диаграмма 394
Конфигурация электронов в атоме 89
Копирование списка 69
Корпус (языка) Brown Corpus 168
Корреляция 291
Кортеж 67, 95
распаковка 156
Коэффициент направленного действия
(КНД) системы антенн 377
Кратный интеграл 445
Круговая (секторная) диаграмма 375
Кружковая (пузырьковая) диаграмма 393
Кули–Тьюки алгоритм ДПФ 340
Кэхэна формула (площадь
«игловидных» треугольников) 572

Л

Ламберта W-функция 472
Лежандра многочлен 301
Линейная алгебра
операция с матрицами 312
Линейная регрессия по методу
наименьших квадратов,
подгонка к прямой 207

Предметный указатель 

Линейный график 112
трехмерный 410
Линейный контур Фогта 434
Логический (boolean) объект 33
Логический оператор 34
Локальная переменная 98
Лоренца распределение 494
Луна алгоритм 206
Лямбда-функция 159, 242, 443, 448, 484

М

Магический квадрат 248
Магическое число (magic number) 581
Максвелла–Больцмана
распределение 203
Максимизация 479
Мантисса 564
Маркер (Matplotlib) 120
Масштабированная дополнительная
функция ошибок 433
Матрица коэффициентов
корреляции 290
Матрица тождественного
преобразования (единичная
матрица) 313
Матричное (векторное)
умножение 312
Машинный эпсилон 566
Медиана 288
Международная программа по оценке
образовательных достижений
учащихся (Programme for
International Student Assessment
PISA) 554
Международный номер банковского
счета IBAN (International Bank
Account Number)
проверка 168
Мерсенна вихрь, алгоритм ГПСЧ 328
Метод 26, 188
Метод Герона для вычисления
квадратного корня числа 87
Метод деформируемого
многогранника (амебы) 481
Метод Монте-Карло 89, 339
Метод наименьших квадратов 321, 490
Метод обработки строк 50
Метод округления банкира 27, 617
Метод округления банкира
(Bankers rounding) 27

641

Метод списка 65
Мечта второкурсника, интеграл 467
Милликена эксперимент с
заряженными каплями масла 545
Минимизация 479
функции одной переменной 486
Михаэлиса, константа 117
Михаэлиса–Ментен, уравнение 117
Многократное использование
кода 189
Многочлен 295
аппроксимация 303
дифференцирование 300
домен, интервал подгонки 303
интегрирование 300
качество подгонки 306
классический ортогональный 301
окно (при подгонке) 304
подгонка 303
Многочлен Лежандра 301
Множество 148
конструктор set() 148
мощность 150
Множество Жюлиа 406
Моби Дик, роман (автор Герман
Мелвилл) 154, 372
Модуль 176
Модульное тестирование 586
Молекулярная динамика сферических
частиц с равными массами, пример
имитации 203
Монотипный род деревьев
тихоокеанского побережья Секвойя
красная (Sequoia sempervirens) 92
Монти Холла парадокс (задача) 181
Морж-оператор
= 164
Морзе код 155

Н

Наблюдение за погодой, пример
обработки 291
Название графика (Matplotlib) 118
Найквиста частота 340
Наука нового типа, книга Стивена
Вольфрама 167
Научный формат записи чисел 564
Неизменяемость кортежа 67
Неизменяемость объекта 37
Нелдера–Мида алгоритм 481, 483

642



Предметный указатель

Нелинейная подгонка методом
наименьших квадратов 490
Необработанная (или
неформатируемая) строка 45
Несимметричное случайное
блуждание 339
Несобственный интеграл 443
Норма вектора 314
Нормальное распределение
вероятностей 332
Норма матрицы 314
Ньютона–Рафсона алгоритм поиска
корней уравнений 497
Ньютона фрактал 500

О

Область видимости 99
глобальная (global scope) 99
локальная (local scope) 99
Обмен значениями двух
переменных 156
Обмен значениями между двумя
переменными с использованием
кортежей 69
Обработка изображений ДПФ 344
Обратная польская запись 155
Обращение в машинный ноль 568
Обращенная матрица 315
Объект 26, 188
обобщенный тип 189
Объектно-ориентированное
программирование 187
Объект первого класса
(first class objects) 96
Объект типа bool 33
Объемная поверхностная
диаграмма 407
Обыкновенное дифференциальное
уравнение
второго порядка 455
первого порядка 449
Обыкновенное дифференциальное
уравнение 449, 621
второго порядка
решение 622
жесткая система 621
нежесткая система 621
первого порядка
решение 621
Обязательное смещение вправо
блоков кода 71

Оддо–Гаркинса правило
(распространенность химических
элементов) 530
Одномерный квантовый генератор
гармонических (синусоидальных)
колебаний 433
Ома закон 315
Операнд 24
Оператор деления по модулю (%) 25
Оператор сравнения объектов 32
@, оператор умножения матриц 247
Описание графика (легенда) 117
место расположения 117
Определитель матрицы 314
Оптимизация 478
с ограничениями 484
Ортогональный многочлен 438
Ортодромия 174
Ошибка
времени выполнения 134
синтаксическая 132
IndentationError 133
TabError 133
сообщение 132
сообщение SyntaxError 133
Ошибка округления 566, 568

П

Пакет 176
Палиндром 58, 59, 110
Панграмма 153
Папоротник Барнсли (фрактал) 399
Паули матрица 324
Переменная 30
Переменная среды 171
Переопределенная задача 321
Переподгонка 307
Переполнение чисел с плавающей
точкой 569
Перестановка содержимого массива
в случайном порядке 337
Планка единица измерения 324
Плохо обусловленная задача 576
Площадь простого
многоугольника 273
Подбор (аппроксимация) кривой 494
Поиск прямой наилучшего
соответствия 322
Показатель степени 564
Поле замены в строке 53
Полимер, пример моделирования 195

Предметный указатель 

Полиморфизм 27
Полицейский телесериал Прослушка
(The Wire)
шифрование номеров телефонов 166
Половинная ширина на уровне
половинной амплитуды
(HWHM) 494
Порядок следования байтов 243, 265
Последовательность
чисел-градин 88, 174
Постоянная смещения Вина 503
Постфиксная запись 155
Потенциал Леннарда–Джонса 130
Потеря значащих разрядов 567
Потеря точности 33
Поэлементное умножение матриц 312
Правила комментирования исходного
кода 578
Правила определения областей
видимости Python, пример 101
Правило Маделунга 89
Приближенное представление
многозвенной слабой кислоты 86
Приоритет арифметических
операторов 25
Простое число Мерсенна 152
Пространство имен 29
Процедурное программирование 187
Процентиль 287
Прямолинейная наилучшая
подгонка 307
Пуассона распределение
вероятностей 335
Пустая строка 44

Р

Равномерное распределение
вероятностей 179
Равномерное распределение
случайных чисел 329
Радау, метод (Radau IIA) 462
Разделение–обработка–объединение
(split-apply-combine) 551
Различие между оператором
сравнения == и оператором
присваивания = 33
Разложение числа в десятичную
дробь 563
Ранг матрицы 314
Распаковка кортежа
(tuple unpacking) 68, 70

643

Распространение светящейся области
(пламени), образовавшейся при
(ядерном) взрыве,
пример анализа 309
Расстояние Хэмминга 77
Рациональная операция
присваивания 156
Рациональное число 563
Регрессионное тестирование 587
Решение линейных уравнений 320
Решето Эратосфена, алгоритм 88
Риддерса метод решения
уравнений 497
Робертсона, автокаталитическая
химическая реакция 460
Руководство по стилю
кодирования PEP8 32
Рунге–Кутты, метод 460
Рунге–Кутты, метод решения ОДУ 459
Рэлея–Рица отношение 503
Ряд Мадхавы–Лейбница 76
Ряд Фибоначчи 73

С

Свертка (сверточная аппроксимация)
контура Гаусса 434
Сверхсоставное число 225
Сдвиг показателя степени 565
Семенная шапка подсолнечника,
моделирование 131
Сила сопротивления Стокса 457
Симплекс-метод (спуска) 481
Синглтон 68
Сингулярное разложение 326
Сингулярность функции 443
Синтаксис * 70
Синтаксический сахар 156
Система взаимосвязанных ОДУ
первого порядка 453
Система направленных антенн
(антенная решетка) 377
Система управления версиями 585
Скалярное произведение 267
Скалярное умножение матриц 312
Скрытый бит 565
След матрицы (сумма ее диагональных
элементов) 314
Словарь 142
значение 142
ключ 142
набор пар ключ-значение 142

644



Предметный указатель

Случайное блуждание 339
Случайный выбор элементов
из массива 336
Собственная (нормальная) форма
колебаний 425
Собственное значение 317
Собственный вектор 317
Соглашение по стилю именования
переменных 31
Создание матрицы вращения
на двумерной плоскости 313
Сопряженное транспонирование 326
Спецификатор формата в стиле
языка C 57
Спецификатор формата для чисел
с плавающей точкой 55
Спираль гауссовых простых чисел 126
Спираль Корню 436
Спираль Эйлера 436
Список 61
генерация 157
Сплайн 472
Сравнение чисел с плавающей
точкой 566
Стабильность алгоритма 573
Стандартное отклонение 288
Стек 67, 155
Степенное множество 168
Степенной ряд 295
Стивен Вольфрам (Stephen Wolfram) 167
Стиль верблюда (CamelCase) 584
Стиль кодирования
для языка Python 583
Стиль линии (Matplotlib) 121
Стокса, закон 457
Столбиковая диаграмма 371
Строка 43
Строка в тройных кавычках 46
Строковый литерал 43
Сфера, вычисление объема 447
Сферический снаряд, пример
вычисления траектории 463

Т

Тейса, метод (уравнение) 441
Тензор моментов инерции
совокупности масс 325
Тепловая карта 397
Терминал 19
Тетрация (tetration) 110

Тетраэдр, вычисление центра масс 448
Тикер-таймер 324
Толщина линии (Matplotlib) 121
Тор, вычисление объема 445
Точечная диаграмма 112, 353
трехмерная 410
Точка разрыва функции 443
Транспонирование матрицы 312
Трассировка стека в обратном
направлении 134, 136
Треугольник Паскаля 77, 86, 440
Треугольник Серпинского 405
Треугольное число 162
Триплет 77
Тройной интеграл 447
Туннельный эффект 433

У

Уилкинсона многочлен 577
Укороченная схема вычисления
логических выражений 36
Унарный (unary) оператор минус 40
Универсальная функция 247, 419
Упаковка кортежа (tuple packing) 68
Уравнение адвекции в двумерном
пространстве 405
Уравнение диффузии в двумерном
пространстве 402
Уравнение колебаний маятника 469
Уравнение одномерной диффузии 366
Уравнение Шрёдингера 422, 503
Уравнение эллипса 432
Условное присваивание 156
Утиная типизация (duck typing) 30

Ф

Файловый ввод/вывод 90
Факториал 77, 105, 429
Фермент эндонуклеазы EcoRI 336
Фибоначчи ряд 327
Физическая константа 419
Форматирование строк 53
Форматирование числовых
значений 54
Форматированный строковый
литерал, f-строка 55
Формула Герона (площадь
треугольника) 32
Формула Полиньяка 87
Фробениуса норма
для двумерных массивов 314

Предметный указатель 

Функциональное программирование 159
Функция 26, 94
передача аргумента 102
рекурсивная 105
Функция Бесселя 425, 440
Функция (мультипликативная)
Эйлера 88
Функция, обратная дополнительной
функции ошибок 433
Функция, обратная функции
ошибок 433
Функция ошибок 433
Функция Планка 394
Функция прямоугольной волновой
формы 346
Функция распределения вероятности
ошибок 433
Функция с утверждениями (assertion
function) 588
Функция Фаддеевой 433, 434
Функция Эйри 422

Х

Ханойская башня, задача 105
Хемотаксис, имитация процесса 339
Хеш-таблица 142
Химмельблау функция 479, 483, 485
Хорошо обусловленная задача 576

Ц

Цветовая карта (colormap) 395
Цвет (Matplotlib) 120
Цветовые обозначения сопротивлений
резисторов 153
Целое число 22, 243
Целочисленное деление (//) 24
Центральное многоугольное число
(Lazy caterers sequence) 104
Цепочное индексирование
(в pandas) 512
Ципфа закон 154

645

Ч

Чепмена цикл (образования озона в
атмосфере) 469
Численное интегрирование 443
Числовое значение (код) символа 47
Число с плавающей точкой 22, 243, 564
научный формат записи 22
сравнение 269
точность 22
Число Фибоначчи 327
Число харшад (число Нивена) 108

Ш

Шаг (stride) числовой
последовательности 72
Шаг вырезания (stride)
из последовательности 49
Шевчука алгоритм компенсационного
суммирования 568
Шифр с заменой ROT13 166
Шкала вулканической активности
(Volcanic Explosivity Index VEI) 559

Э

Эйлера–Лотки
уравнение (экология) 498
Эйлера–Маскерони, постоянная 442
Экспоненциальный период
радиоактивного распада 359
Электромагнитный спектр 387
Эллипсоид 441
Эллиптический интеграл 431
Энергия ионизации атома 522
Эрмитова сопряженность 326
Эффект Струпа (психология),
пример 281

Я

Явный одношаговый метод Эйлера
первого порядка точности 573
Якоби матрица (якобиан) 479, 483, 492

Книги издательства «ДМК Пресс» можно заказать
в торгово-издательском холдинге «Планета Альянс» наложенным платежом,
выслав открытку или письмо по почтовому адресу:
115487, г. Москва, 2-й Нагатинский пр-д, д. 6А.
При оформлении заказа следует указать адрес (полностью),
по которому должны быть высланы книги;
фамилию, имя и отчество получателя.
Желательно также указать свой телефон и электронный адрес.
Эти книги вы можете заказать и в интернет-магазине: www.a-planeta.ru.
Оптовые закупки: тел. (499) 782-38-89.
Электронный адрес: books@alians-kniga.ru.

Кристиан Хилл
Научное программирование на Python
Главный редактор

Мовчан Д. А.

dmkpress@gmail.com

Зам. главного редактора
Перевод
Корректор
Верстка
Дизайн обложки

Сенченкова Е. А.
Снастин А. В.
Синяева Г. И.
Луценко С. В.
Мовчан А. Г.

Формат 70×100 1/16.
Гарнитура «PT Serif». Печать цифровая.
Усл. печ. л. 52,49. Тираж 200 экз.
Веб-сайт издательства: www.dmkpress.com