КулЛиб - Классная библиотека! Скачать книги бесплатно 

Python: Искусственный интеллект, большие данные и облачные вычисления [Харви Дейтел] (pdf) читать онлайн

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


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

Искусственный
И
скусственный и
интеллект,
нтеллект,
большие д
анные
большие
данные
блачные в
ычисления
ио
облачные
вычисления

2020

ББК 32.973.2-018.1
УДК 004.43
Д27

Дейтел Пол, Дейтел Харви
Д27 Python: Искусственный интеллект, большие данные и облачные вычисления. —
СПб.: Питер, 2020. — 864 с.: ил. — (Серия «Для профессионалов»).
ISBN 978-5-4461-1432-0
Пол и Харви Дейтелы предлагают по-новому взглянуть на Python и использовать уникальный
подход, чтобы быстро решить проблемы, стоящие перед современными айтишниками. Вы на практике познакомитесь с революционными вычислительными технологиями и программированием на
Python — одном из самых популярных языков.
В вашем распоряжении более пятисот реальных задач — от фрагментов до 40 больших сценариев
и примеров с полноценной реализацией. IPython с Jupyter Noteboos позволят быстро освоить современные идиомы программирования Python. Главы 1–5 и фрагменты глав 6–7 сделают понятными
примеры решения задач искусственного интеллекта из глав 11–16. Вы познакомитесь с обработкой
естественного языка, анализом эмоций в Twitter®, когнитивными вычислениями IBM® Watson™,
машинным обучением с учителем в задачах классификации и регрессии, машинным обучением
без учителя в задачах кластеризации, распознавания образов с глубоким обучением и сверточными
нейронными сетями, рекуррентными нейронными сетями, большими данными с Hadoop®, Spark™
и NoSQL, IoT и многим другим. Вы поработаете (напрямую или косвенно) с облачными сервисами,
включая Twitter, Google Translate™, IBM Watson, Microsoft® Azure®, OpenMapQuest, PubNub и др.

16+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.)

ББК 32.973.2-018.1
УДК 004.43
Права на издание получены по соглашению с Pearson Education, Inc. Все права защищены. Никакая часть
данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может
гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные
ошибки, связанные с использованием книги. Издательство не несет ответственности за доступность материалов, ссылки на которые вы можете найти в этой книге. На момент подготовки книги к изданию все ссылки на
интернет-ресурсы были действующими.
ISBN 978-0135224335 англ.

ISBN 978-5-4461-1432-0

Authorized translation from the English language edition, entitled PYTHON
FOR PROGRAMMERS, 1st Edition by PAUL DEITEL; HARVEY DEITEL,
published by Pearson Education, Inc, publishing as Prentice Hall,
© 2019 Pearson Education, Inc.
© Перевод на русский язык ООО Издательство «Питер», 2020
© Издание на русском языке, оформление ООО Издательство «Питер»,
2020
© Серия «Для профессионалов», 2020

Краткое содержание
Предисловие.......................................................................................... 22
Приступая к работе.................................................................................45
Глава 1. Компьютеры и Python...................................................................51
Глава 2. Введение в программирование Python.......................................95
Глава 3. Управляющие команды..............................................................121
Глава 4. Функции.......................................................................................151
Глава 5. Последовательности: списки и кортежи................................... 194
Глава 6. Словари и множества.................................................................246
Глава 7. NumPy и программирование, ориентированное
на массивы..................................................................................277
Глава 8. Подробнее о строках...................................................................322
Глава 9. Файлы и исключения..................................................................358

6  

Краткое содержание

Глава 10. Объектно-ориентированное программирование................... 395
Глава 11. Обработка естественного языка (NLP).................................... 479
Глава 12. Глубокий анализ данных Twitter.............................................. 519
Глава 13. IBM Watson и когнитивные вычисления................................. 577
Глава 14. Машинное обучение: классификация,
регрессия и кластеризация......................................................611
Глава 15. Глубокое обучение.....................................................................692
Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT....................... 758

Оглавление
От издательства.............................................................................................................. 20

Предисловие...................................................................................................... 22
Спрос на квалификацию в области data science......................................................... 23
Модульная структура...................................................................................................... 23
Ключевые особенности.................................................................................................. 24
Ответы на вопросы......................................................................................................... 39
Поддержка Jupyter......................................................................................................... 40
Приложения.................................................................................................................... 40
Как связаться с авторами книги.................................................................................... 41
Благодарности................................................................................................................ 41
Об авторах....................................................................................................................... 43
О компании Deitel® & Associates, Inc........................................................................... 44

Приступая к работе............................................................................................ 45
Загрузка примеров кода................................................................................................ 45
Структура папки examples............................................................................................. 46
Установка Anaconda........................................................................................................ 46
Обновление Anaconda.................................................................................................... 47
Менеджеры пакетов....................................................................................................... 47
Установка программы статического анализа кода Prospector................................... 48
Установка jupyter-matplotlib ........................................................................................ 48

8  

Оглавление

Установка других пакетов.............................................................................................. 48
Получение учетной записи разработчика Twitter........................................................ 49
Необходимость подключения к интернету в некоторых главах.................................. 49
Различия в выводе программ....................................................................................... 49
Получение ответов на вопросы..................................................................................... 50

Глава 1. Компьютеры и Python............................................................................... 51
1.1. Введение.................................................................................................................. 52
1.2. Основы объектных технологий............................................................................... 53
1.3. Python....................................................................................................................... 57
1.4. Библиотеки............................................................................................................... 59
1.4.1. Стандартная библиотека Python................................................................... 60
1.4.2. Библиотеки data science................................................................................ 61
1.5. Первые эксперименты: использование IPython и Jupyter Notebook.................. 63
1.5.1. Использование интерактивного режима IPython как калькулятора............. 63
1.5.2. Выполнение программы Python с использованием
интерпретатора IPython................................................................................. 65
1.5.3. Написание и выполнение кода в Jupyter Notebook.................................... 68
1.6. Облачные вычисления и «интернет вещей»......................................................... 73
1.6.1. Облачные вычисления................................................................................... 73
1.6.2. «Интернет вещей»......................................................................................... 75
1.7. Насколько велики большие данные?..................................................................... 76
1.7.1. Анализ больших данных................................................................................ 83
1.7.2. Data Science и большие данные изменяют ситуацию:
практические примеры.................................................................................. 84
1.8. Практический пример: использование больших данных
в мобильном приложении....................................................................................... 87
1.9. Введение в data science: искусственный интеллект —
на пересечении компьютерной теории и data science......................................... 89
1.10. Итоги....................................................................................................................... 93

Глава 2. Введение в программирование Python................................................... 95
2.1. Введение.................................................................................................................. 96
2.2. Переменные и команды присваивания................................................................. 96
2.3. Арифметические операторы................................................................................... 97
2.4. Функция print и строки, заключенные в одинарные и двойные кавычки........ 103

Оглавление   9
2.5. Строки в тройных кавычках.................................................................................. 105
2.6. Получение ввода от пользователя....................................................................... 107
2.7. Принятие решений: команда if и операторы сравнения.................................... 109
2.8. Объекты и динамическая типизация................................................................... 116
2.9. Введение в data science: основные описательные статистики......................... 117
2.10. Итоги..................................................................................................................... 120

Глава 3. Управляющие команды.......................................................................... 121
3.1. Введение................................................................................................................ 122
3.2. Управляющие команды......................................................................................... 122
3.3. Команда if............................................................................................................... 123
3.4. Команды if…else и if…elif…else............................................................................. 125
3.5. Команда while........................................................................................................ 129
3.6. Команда for............................................................................................................ 130
3.6.1. Итерируемые объекты, списки и итераторы............................................. 131
3.6.2. Встроенная функция range......................................................................... 132
3.7. Расширенное присваивание................................................................................ 132
3.8. Повторение, управляемое последовательностью;
отформатированные строки.................................................................................. 133
3.9. Повторение, управляемое контрольным значением.......................................... 135
3.10. Подробнее о встроенной функции range........................................................... 137
3.11. Использование типа Decimal для представления денежных сумм................. 138
3.12. Команды break и continue................................................................................... 143
3.13. Логические операторы and, or или not ............................................................. 144
3.14. Введение в data science: параметры, характеризующие положение
центра распределения, — математическое ожидание, медиана и мода........ 148
3.15. Итоги..................................................................................................................... 150

Глава 4. Функции................................................................................................... 151
4.1. Введение................................................................................................................ 152
4.2. Определение функций........................................................................................... 152
4.3. Функции с несколькими параметрами................................................................. 156
4.4. Генератор случайных чисел.................................................................................. 158
4.5. Практический пример: игра «крэпс»................................................................... 161
4.6. Стандартная библиотека Python........................................................................... 165
4.7. Функции модуля math........................................................................................... 167

10  

Оглавление

4.8. Использование автозаполнения IPython............................................................. 168
4.9. Значения параметров по умолчанию................................................................... 170
4.10. Ключевые аргументы.......................................................................................... 171
4.11. Произвольные списки аргументов..................................................................... 172
4.12. Методы: функции, принадлежащие объектам................................................... 173
4.13. Правила области видимости............................................................................... 174
4.14. Подробнее об импортировании.......................................................................... 177
4.15. Подробнее о передаче аргументов функциям................................................... 179
4.16. Рекурсия............................................................................................................... 183
4.17. Программирование в функциональном стиле................................................... 187
4.18. Введение в data science: дисперсионные характеристики.............................. 190
4.19. Итоги..................................................................................................................... 192

Глава 5. Последовательности: списки и кортежи............................................... 194
5.1. Введение................................................................................................................ 195
5.2. Списки.................................................................................................................... 195
5.3. Кортежи.................................................................................................................. 201
5.4. Распаковка последовательностей........................................................................ 204
5.5. Сегментация последовательностей..................................................................... 207
5.6. Команда del............................................................................................................ 210
5.7. Передача списков функциям................................................................................ 211
5.8. Сортировка списков.............................................................................................. 213
5.9. Поиск в последовательностях.............................................................................. 214
5.10. Другие методы списков....................................................................................... 217
5.11. Моделирование стека на базе списка............................................................... 220
5.12. Трансформации списков..................................................................................... 221
5.13. Выражения-генераторы...................................................................................... 223
5.14. Фильтрация, отображение и свертка................................................................. 224
5.15. Другие функции обработки последовательностей............................................ 227
5.16. Двумерные списки............................................................................................... 230
5.17. Введение в data science: моделирование и статические
визуализации....................................................................................................... 232
5.17.1. Примеры диаграмм для 600, 60 000 и 6 000 000 бросков..................... 233
5.17.2. Визуализация частот и процентов......................................................... 236
5.18. Итоги..................................................................................................................... 244

Оглавление   11

Глава 6. Словари и множества............................................................................. 246
6.1. Введение................................................................................................................ 247
6.2. Словари.................................................................................................................. 247
6.2.1. Создание словаря........................................................................................ 248
6.2.2. Перебор по словарю.................................................................................... 249
6.2.3. Основные операции со словарями............................................................ 249
6.2.4. Методы keys и values................................................................................... 252
6.2.5. Сравнения словарей................................................................................... 254
6.2.6. Пример: словарь с оценками студентов.................................................... 254
6.2.7. Пример: подсчет слов ................................................................................ 255
6.2.8. Метод update................................................................................................ 258
6.2.9. Трансформации словарей........................................................................... 258
6.3. Множества.............................................................................................................. 259
6.3.1. Сравнение множеств................................................................................... 262
6.3.2. Математические операции с множествами............................................... 263
6.3.3. Операторы и методы изменяемых множеств............................................ 265
6.3.4. Трансформации множеств.......................................................................... 267
6.4. Введение в data science: динамические визуализации..................................... 267
6.4.1. Как работает динамическая визуализация............................................... 268
6.4.2. Реализация динамической визуализации................................................ 271
6.5. Итоги....................................................................................................................... 275

Глава 7. NumPy и программирование, ориентированное на массивы............. 277
7.1. Введение................................................................................................................ 278
7.2. Создание массивов на основе существующих данных...................................... 279
7.3. Атрибуты array....................................................................................................... 280
7.4. Заполнение array конкретными значениями...................................................... 282
7.5. Создание коллекций array по диапазонам.......................................................... 283
7.6. Сравнение быстродействия списков и array....................................................... 285
7.7. Операторы array..................................................................................................... 288
7.8. Вычислительные методы NumPy ........................................................................ 290
7.9. Универсальные функции....................................................................................... 292
7.10. Индексирование и сегментация......................................................................... 295
7.11. Представления: поверхностное копирование................................................... 296
7.12. Глубокое копирование......................................................................................... 299

12   Оглавление
7.13. Изменение размеров и транспонирование....................................................... 300
7.14. Введение в data science: коллекции Series и DataFrame
библиотеки pandas.............................................................................................. 303
7.14.1. Коллекция Series..................................................................................... 304
7.14.2. DataFrame................................................................................................. 309
7.15. Итоги..................................................................................................................... 319

Глава 8. Подробнее о строках............................................................................... 322
8.1. Введение................................................................................................................ 323
8.2. Форматирование строк.......................................................................................... 324
8.2.1. Типы представлений................................................................................... 324
8.2.2. Ширины полей и выравнивание............................................................... 326
8.2.3. Форматирование чисел.............................................................................. 327
8.2.4. Метод format................................................................................................ 328
8.3. Конкатенация и повторение строк....................................................................... 329
8.4. Удаление пропусков из строк............................................................................... 330
8.5. Изменение регистра символов............................................................................. 331
8.6. Операторы сравнения для строк.......................................................................... 331
8.7. Поиск подстрок...................................................................................................... 332
8.8. Замена подстрок.................................................................................................... 334
8.9. Разбиение и объединение строк.......................................................................... 334
8.10. Символы и методы проверки символов............................................................. 337
8.11. Необработанные строки...................................................................................... 338
8.12. Знакомство с регулярными выражениями........................................................ 339
8.12.1. Модуль re и функция fullmatch .............................................................. 341
8.12.2. Замена подстрок и разбиение строк...................................................... 345
8.12.3. Другие функции поиска, обращение к совпадениям............................ 346
8.13. Введение в data science: pandas, регулярные выражения
и первичная обработка данных.......................................................................... 350
8.14. Итоги..................................................................................................................... 356

Глава 9. Файлы и исключения.............................................................................. 358
9.1. Введение................................................................................................................ 359
9.2. Файлы..................................................................................................................... 360
9.3. Обработка текстовых файлов................................................................................ 361
9.3.1. Запись в текстовый файл: команда with.................................................... 361
9.3.2. Чтение данных из текстового файла.......................................................... 363

Оглавление   13
9.4. Обновление текстовых файлов............................................................................. 364
9.5. Сериализация в формат JSON ............................................................................. 367
9.6. Вопросы безопасности: сериализация и десериализация pickle..................... 370
9.7. Дополнительные замечания по поводу файлов.................................................. 371
9.8. Обработка исключений......................................................................................... 372
9.8.1. Деление на нуль и недействительный ввод.............................................. 372
9.8.2. Команды try.................................................................................................. 373
9.8.3. Перехват нескольких исключений в одной секции except....................... 377
9.8.4. Какие исключения выдают функция или метод?...................................... 377
9.8.5. Какой код должен размещаться в наборе try?.......................................... 377
9.9. Секция finally.......................................................................................................... 378
9.10. Явная выдача исключений................................................................................. 380
9.11. Раскрутка стека и трассировка (дополнение)................................................... 381
9.12. Введение в data science: работа с CSV-файлами.............................................. 384
9.12.1. Модуль csv стандартной библиотеки Python......................................... 384
9.12.2. Чтение CSV-файлов в коллекции DataFrame
библиотеки pandas....................................................................................387
9.12.3. Чтение набора данных катастрофы «Титаника»................................... 389
9.12.4. Простой анализ данных на примере набора данных
катастрофы «Титаника»........................................................................... 391
9.12.5. Гистограмма возраста пассажиров.................................................................. 392
9.13. Итоги..................................................................................................................... 393

Глава 10. Объектно-ориентированное программирование............................... 395
10.1. Введение.............................................................................................................. 396
10.2. Класс Account ...................................................................................................... 399
10.2.1. Класс Account в действии........................................................................ 399
10.2.2. Определение класса Account.................................................................. 401
10.2.3. Композиция: ссылка на объекты как компоненты классов.................. 404
10.3. Управление доступом к атрибутам..................................................................... 404
10.4. Использование свойств для доступа к данным................................................ 405
10.4.1. Класс Time в действии............................................................................ 405
10.4.2. Определение класса Time....................................................................... 408
10.4.3. Замечания по проектированию определения класса Time.................. 412
10.5. Моделирование «приватных» атрибутов........................................................... 414

14  

Оглавление

10.6. Практический пример: моделирование тасования и сдачи карт.................... 416
10.6.1. Классы Card и DeckOfCards в действии................................................. 416
10.6.2. Класс Card — знакомство с атрибутами класса..................................... 418
10.6.3. Класс DeckOfCards .................................................................................. 421
10.6.4. Вывод изображений карт средствами Matplotlib.................................. 423
10.7. Наследование: базовые классы и подклассы................................................... 426
10.8. Построение иерархии наследования. Концепция полиморфизма.................. 429
10.8.1. Базовый класс CommissionEmployee ................................................... 430
10.8.2. Подкласс SalariedCommissionEmployee................................................ 433
10.8.3. Полиморфная обработка CommissionEmployee
и SalariedCommissionEmployee.............................................................. 438
10.8.4. Объектно-базированное и объектно-ориентированное
программирование.................................................................................. 439
10.9. Утиная типизация и полиморфизм..................................................................... 439
10.10. Перегрузка операторов..................................................................................... 441
10.10.1. Класс Complex в действии.................................................................. 443
10.10.2. Определение класса Complex............................................................. 444
10.11. Иерархия классов исключений и пользовательские исключения................ 446
10.12. Именованные кортежи...................................................................................... 448
10.13. Краткое введение в новые классы данных Python 3.7................................... 449
10.13.1. Создание класса данных Card............................................................ 450
10.13.2. Использование класса данных Card.................................................. 454
10.13.3. П
 реимущества классов данных перед именованными
кортежами............................................................................................. 456
10.13.4. П
 реимущества класса данных перед традиционными
классами............................................................................................... 456
10.14. Модульное тестирование с doc-строками и doctest........................................ 457
10.15. Пространства имен и области видимости........................................................ 462
10.16. Введение в data science: временные ряды и простая
линейная регрессия.......................................................................................... 466
10.17. Итоги................................................................................................................... 477

Глава 11. Обработка естественного языка (NLP)................................................ 479
11.1. Введение.............................................................................................................. 480
11.2. TextBlob................................................................................................................. 481
11.2.1. Создание TextBlob.................................................................................... 484

Оглавление   15
11.2.2. Разбиение текста на предложения и слова........................................... 484
11.2.3. Пометка частей речи................................................................................ 485
11.2.4. Извлечение именных конструкций........................................................ 486
11.2.5. Анализ эмоциональной окраски с использованием
анализатора TextBlob по умолчанию...................................................... 487
11.2.6. Анализ эмоциональной окраски с использованием
NaiveBayesAnalyzer.................................................................................. 489
11.2.7. Распознавание языка и перевод............................................................ 490
11.2.8. Формообразование: образование единственного
и множественного числа......................................................................... 492
11.2.9. Проверка орфографии и исправление ошибок..................................... 493
11.2.10. Нормализация: выделение основы и лемматизация......................... 494
11.2.11. Частоты слов........................................................................................... 495
11.2.12. Получение определений, синонимов и антонимов из WordNet......... 496
11.2.13. Удаление игнорируемых слов............................................................... 498
11.2.14. n-граммы................................................................................................ 500
11.3. Визуализация частот вхождения слов с использованием гистограмм
и словарных облаков........................................................................................... 501
11.3.1. Визуализация частот вхождения слов средствами Pandas................. 501
11.3.2. Визуализация частот слов в словарных облаках.................................. 505
11.4. Оценка удобочитаемости с использованием Textatistic................................... 508
11.5. Распознавание именованных сущностей с использованием spaCy............... 511
11.6. Выявление сходства средствами spaCy............................................................ 513
11.7. Другие библиотеки и инструменты NLP............................................................ 514
11.8. Машинное обучение и NLP-приложения с глубоким обучением.................... 515
11.9. Наборы данных естественных языков............................................................... 516
11.10. Итоги................................................................................................................... 517

Глава 12. Глубокий анализ данных Twitter.......................................................... 519
12.1. Введение.............................................................................................................. 520
12.2. Обзор Twitter APIs................................................................................................ 522
12.3. Создание учетной записи Twitter....................................................................... 524
12.4. Получение регистрационных данных Twitter — создание приложения......... 525
12.5. Какую информацию содержит объект Tweet?.................................................... 527
12.6. Tweepy................................................................................................................... 532
12.7. Аутентификация Twitter с использованием Tweepy ......................................... 533

16  

Оглавление

12.8. Получение информации об учетной записи Twitter........................................ 535
12.9. Введение в курсоры Tweepy: получение подписчиков и друзей
учетной записи................................................................................................... 537
12.9.1. Определение подписчиков учетной записи......................................... 538
12.9.2. Определение друзей учетной записи................................................... 540
12.9.3. Получение недавних твитов пользователя.......................................... 541
12.10. Поиск недавних твитов..................................................................................... 542
12.11. Выявление тенденций: Twitter Trends API....................................................... 545
12.11.1. Места с актуальными темами.............................................................. 546
12.11.2. Получение списка актуальных тем..................................................... 547
12.11.3. Создание словарного облака по актуальным темам......................... 549
12.12. Очистка / предварительная обработка твитов для анализа........................... 550
12.13. Twitter Streaming API......................................................................................... 553
12.13.1. Создание подкласса StreamListener.................................................. 553
12.13.2. Запуск обработки потока..................................................................... 557
12.14. Анализ эмоциональной окраски твитов........................................................... 559
12.15. Геокодирование и вывод информации на карте............................................. 564
12.15.1. Получение твитов и нанесение их на карту....................................... 566
12.15.2. Вспомогательные функции tweetutilities.py....................................... 571
12.15.3. Класс LocationListener......................................................................... 573
12.16. Способы хранения твитов................................................................................. 574
12.17. Twitter и временные ряды................................................................................. 575
12.18. Итоги................................................................................................................... 575

Глава 13. IBM Watson и когнитивные вычисления............................................. 577
13.1. Введение: IBM Watson и когнитивные вычисления......................................... 578
13.2. Учетная запись IBM Cloud и консоль Cloud....................................................... 580
13.3. Сервисы Watson................................................................................................... 581
13.4. Другие сервисы и инструменты.......................................................................... 586
13.5. Watson Developer Cloud Python SDK................................................................... 588
13.6. Практический пример: приложение-переводчик............................................. 589
13.6.1. Перед запуском приложения................................................................... 590
13.6.2. Пробный запуск приложения.................................................................. 592
13.6.3. Сценарий SimpleLanguageTranslator.py................................................. 594
13.7. Ресурсы Watson.................................................................................................... 607
13.8. Итоги..................................................................................................................... 610

Оглавление   17

Глава 14. Машинное обучение: классификация, регрессия
и кластеризация.................................................................................... 611
14.1. Введение в машинное обучение........................................................................ 612
14.1.1. Scikit-learn............................................................................................... 613
14.1.2. Типы машинного обучения..................................................................... 615
14.1.3. Наборы данных, включенные в поставку scikit-learn.......................... 618
14.1.4. Последовательность действий в типичном исследовании
data science.............................................................................................. 619
14.2. Практический пример: классификация методом k ближайших
соседей и набор данных Digits, часть 1............................................................. 620
14.2.1. Алгоритм k ближайших соседей............................................................ 622
14.2.2. Загрузка набора данных......................................................................... 623
14.2.3. Визуализация данных............................................................................. 627
14.2.4. Разбиение данных для обучения и тестирования................................ 629
14.2.5. Создание модели..................................................................................... 631
14.2.6. Обучение модели..................................................................................... 631
14.2.7. Прогнозирование классов для рукописных цифр................................ 632
14.3. Практический пример: классификация методом k ближайших
соседей и набор данных Digits, часть 2............................................................. 634
14.3.1. Метрики точности модели...................................................................... 634
14.3.2. K-проходная перекрестная проверка.................................................... 639
14.3.3. Выполнение нескольких моделей для поиска наилучшей.................. 641
14.3.4. Настройка гиперпараметров.................................................................. 643
14.4. Практический пример: временные ряды и простая
линейная регрессия............................................................................................ 644
14.5. Практический пример: множественная линейная регрессия
с набором данных California Housing................................................................. 651
14.5.1. Загрузка набора данных......................................................................... 651
14.5.2. Исследование данных средствами Pandas........................................... 654
14.5.3. Визуализация признаков........................................................................ 656
14.5.4. Разбиение данных для обучения и тестирования................................ 661
14.5.5. Обучение модели..................................................................................... 661
14.5.6. Тестирование модели.............................................................................. 663
14.5.7. Визуализация ожидаемых и прогнозируемых цен............................... 664
14.5.8. Метрики регрессионной модели............................................................ 665
14.5.9. Выбор лучшей модели...................................................................................... 666

18  

Оглавление

14.6. Практический пример: машинное обучение без учителя,
часть 1 — понижение размерности.................................................................... 667
14.7. Практический пример: машинное обучение без учителя,
часть 2 — кластеризация методом k средних................................................... 672
14.7.1. Загрузка набора данных Iris................................................................... 674
14.7.2. Исследование набора данных Iris: описательная статистика
в Pandas.................................................................................................... 676
14.7.3. Визуализация набора данных функцией pairplot................................. 678
14.7.4. Использование оценщика KMeans......................................................... 682
14.7.5. Понижение размерности методом анализа главных компонент......... 684
14.7.6. Выбор оптимального оценщика для кластеризации............................ 687
14.8. Итоги..................................................................................................................... 690

Глава 15. Глубокое обучение................................................................................. 692
15.1. Введение.............................................................................................................. 693
15.1.1. Практическое применение глубокого обучения.................................... 696
15.1.2. Демонстрационные приложения глубокого обучения.......................... 696
15.1.3. Ресурсы Keras.......................................................................................... 697
15.2. Встроенные наборы данных Keras..................................................................... 697
15.3. Нестандартные среды Anaconda........................................................................ 699
15.4. Нейронные сети................................................................................................... 701
15.5. Тензоры................................................................................................................. 704
15.6. Сверточные нейронные сети для распознавания образов;
множественная классификация с набором данных MNIST.............................. 706
15.6.1. Загрузка набора данных MNIST.............................................................. 709
15.6.2. Исследование данных............................................................................. 709
15.6.3. Подготовка данных.................................................................................. 712
15.6.4. Создание нейронной сети....................................................................... 715
15.6.5. Обучение и оценка модели...................................................................... 726
15.6.6. Сохранение и загрузка модели............................................................... 733
15.7. Визуализация процесса обучения нейронной сети в TensorBoard................. 734
15.8. ConvnetJS: глубокое обучение и визуализация в браузере............................. 738
15.9. Рекуррентные нейронные сети для последовательностей;
анализ эмоциональной окраски с набором данных IMDb................................ 740
15.9.1. Загрузка набора данных IMDb................................................................ 741
15.9.2. Исследование данных............................................................................. 742

Оглавление   19
15.9.3. Подготовка данных................................................................................. 746
15.9.4. Создание нейронной сети..................................................................... 747
15.9.5. Обучение и оценка модели.................................................................... 750
15.10. Настройка моделей глубокого обучения.......................................................... 752
15.11. Модели сверточных нейронных сетей с предварительным
обучением на ImageNet..................................................................................... 753
15.12. Итоги................................................................................................................... 755

Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT................................... 758
16.1. Введение.............................................................................................................. 759
16.2. Реляционные базы данных и язык структурированных запросов (SQL)..........765
16.2.1. База данных books................................................................................... 767
16.2.2. Запросы SELECT...................................................................................... 773
16.2.3. Секция WHERE......................................................................................... 773
16.2.4. Условие ORDER BY................................................................................... 774
16.2.5. Слияние данных из нескольких таблиц: INNER JOIN........................... 776
16.2.6. Команда INSERT INTO.............................................................................. 777
16.2.7. Команда UPDATE...................................................................................... 778
16.2.8. Команда DELETE FROM .......................................................................... 780
16.3. Базы данных NoSQL и NewSQL: краткое введение.......................................... 781
16.3.1. Базы данных NoSQL «ключ-значение»................................................. 782
16.3.2. Документные базы данных NoSQL......................................................... 782
16.3.3. Столбцовые базы данных NoSQL........................................................... 783
16.3.4. Графовые базы данных NoSQL............................................................... 784
16.3.5. Базы данных NewSQL.............................................................................. 785
16.4. Практический пример: документная база данных MongoDB........................... 786
16.4.1. Создание кластера MongoDB Atlas......................................................... 787
16.4.2. Потоковая передача твитов в MongoDB................................................. 789
16.5. Hadoop.................................................................................................................. 801
16.5.1. Обзор Hadoop........................................................................................... 801
16.5.2. Получение статистики по длине слов в «Ромео и Джульетте»
с использованием MapReduce................................................................ 805
16.5.3. Создание кластера Apache Hadoop в Microsoft Azure HDInsight.......... 805
16.5.4. Hadoop Streaming..................................................................................... 808
16.5.5. Реализация сценария отображения....................................................... 809
16.5.6. Реализация сценария свертки............................................................... 810

20  

Оглавление

16.5.7. Подготовка к запуску примера MapReduce............................................ 811
16.5.8. Выполнение задания MapReduce........................................................... 812
16.6. Spark..................................................................................................................... 816
16.6.1. Краткий обзор Spark................................................................................ 816
16.6.2. Docker и стеки Jupyter Docker................................................................ 818
16.6.3. Подсчет слов с использованием Spark.................................................. 823
16.6.4. Подсчет слов средствами Spark в Microsoft Azure................................ 827
16.7. Spark Streaming: подсчет хештегов Twitter с использованием
стека Docker pyspark-notebook.......................................................................... 831
16.7.1. Потоковая передача твитов в сокет........................................................ 832
16.7.2. Получение сводки хештегов и Spark SQL.............................................. 836
16.8. «Интернет вещей»............................................................................................... 844
16.8.1. Публикация и подписка........................................................................... 846
16.8.2. Визуализация живого потока PubNub средствами Freeboard............. 846
16.8.3. Моделирование термостата, подключенного к интернету,
в коде Python............................................................................................ 849
16.8.4. Создание информационной панели с Freeboard.io............................... 853
16.8.5. Создание подписчика PubNub в коде Python........................................ 854
16.9. Итоги..................................................................................................................... 860

От издательства
Некоторые иллюстрации для лучшего восприятия нужно смотреть в цветном
варианте. Мы снабдили их QR-кодами, перейдя по которым, вы можете ознакомиться с цветной версией рисунка.
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.
com (издательство «Питер», компьютерная редакция). Мы будем рады узнать
ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию
о наших книгах.

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

Предисловие
«Там золото в этих холмах!»1

Перед вами книга «Python: Искусственный интеллект, большие данные и облачные вычисления». В ней вы займетесь практическим освоением самых
интересных, самых революционных вычислительных технологий, а также
программированием на Python — одном из самых популярных языков программирования в мире, лидирующем по темпам развития.
Python обычно сразу приходится по нраву разработчикам. Они ценят Python
за выразительность, удобочитаемость, компактность и интерактивную природу. Разработчикам нравится мир разработки с открытым кодом, который
порождает стремительно растущую базу программного обеспечения для невероятно широкого спектра прикладных областей.
Уже много десятилетий в мире действуют определенные тенденции. Компьютерное оборудование становится быстрее, дешевле и компактнее. Скорость доступа к интернету растет и дешевеет. Качественное программное обеспечение
становится более массовым и практически бесплатным (или почти бесплатным) благодаря движению «открытого кода». Вскоре «интернет вещей» объединит десятки миллиардов устройств любых видов, которые только можно
представить. Они порождают колоссальные количества данных на быстро
растущих скоростях и объемах.
1

Источник неизвестен, часто ошибочно приписывается Марку Твену.

Спрос на квалификацию в области data science   23

В современных вычислениях большинство последних новшеств связано
с данными — data science, аналитика данных, большие данные, реляционные
базы данных (SQL), базы данных NoSQL и NewSQL… Все эти темы будут
рассматриваться в книге в сочетании с инновационным подходом к программированию на Python.

Спрос на квалификацию в области data science
В 2011 году Глобальный институт McKinsey опубликовал отчет «Большие данные: новый рубеж для инноваций, конкуренции и производительности». В отчете было сказано: «Только Соединенные Штаты сталкиваются с нехваткой от
140 тысяч до 190 тысяч специалистов, обладающих глубокими аналитическими
познаниями, а также 1,5 миллиона менеджеров и аналитиков, которые бы анализировали большие данные и принимали решения на основании полученных
результатов»1. Такое положение дел сохраняется. В отчете за август 2018 года
«LinkedIn Workforce Report» сказано, что в Соединенных Штатах существует
нехватка более 150 тысяч специалистов в области data science2. В отчете IBM,
Burning Glass Technologies и Business-Higher Education Forum за 2017 года
говорится, что к 2020 году в Соединенных Штатах будут существовать сотни
тысяч вакансий, требующих квалификации в области data science3.

Модульная структура
Модульная структура книги обеспечивает потребности разных профессиональных аудиторий.
Главы 1–10 посвящены программированию на языке Python. Каждая из этих
глав содержит краткий раздел «Введение в data science»; в этих разделах будут
представлены такие темы, как искусственный интеллект, основные характеристики описательной статистики, метрики, характеризующие положение
центра распределения и разброс, моделирование, статические и динамические
визуализации, работа с файлами CSV, применение Pandas для исследования
и первичной обработки данных, временные ряды и простая линейная регрес1

2
3

https://www.mckinsey.com/~/media/McKinsey/Business%20Functions/McKinsey%20Digital/Our%20
Insights/Big%20data%20The%20next%20frontier%20for%20innovation/MGI_big_data_full_report.
ashx (с. 3).
https://economicgraph.linkedin.com/resources/linkedin-workforce-report-august-2018.
https://www.burning-glass.com/wp-content/uploads/The_Quant_Crunch.pdf (с. 3).

24   Предисловие
сия. Эти разделы подготовят вас к изучению data science, искусственного
интеллекта, больших данных и облачных технологий в главах 11–16, в которых вам представится возможность применить реальные наборы данных
в полноценных практических примерах.
После описания Python в главах 1–5 и некоторых ключевых частей глав 6–7
вашей подготовки будет достаточно для основных частей практических примеров в главах 11–16. Раздел «Зависимость между главами» данного предисловия поможет преподавателям спланировать свои профессиональные курсы
в контексте уникальной архитектуры книги.
Главы 11–16 переполнены занимательными, современными примерами. В них
представлены практические реализации по таким темам, какобработка естественного языка, глубокий анализ данных Twitter, когнитивные вычисления на
базе IBM Watson, машинное обучение с учителем для решения задач классификации и регрессии, машинное обучение без учителя для решения задач кластеризации, глубокое обучение на базе сверточных нейронных сетей, глубокое
обучение на базе рекуррентных нейронных сетей, большие данные с Hadoop,
Spark и баз данных NoSQL, «интернет вещей» и многое другое. Попутно вы
освоите широкий спектр терминов и концепций data science, от кратких определений до применения концепций в малых, средних и больших программах.
Подробное оглавление книги даст вам представление о широте изложения.

Ключевые особенности
ØØПростота: в каждом аспекте книги мы ставили на первое место простоту

и ясность. Например, для обработки естественного языка мы используем простую и интуитивную библиотеку TextBlob вместо более сложной
библиотеки NLTK. При описании глубокого обучения мы отдали предпочтение Keras перед TensorFlow. Как правило, если для решения какойлибо задачи можно было воспользоваться несколькими разными библиотеками, мы выбирали самый простой вариант.
ØØКомпактность: большинство из 538 примеров этой книги невелики — они

состоят всего из нескольких строк кода с немедленным интерактивным
откликом от IPython. Также в книгу включены 40 больших сценариев
и подробных практических примеров.
ØØАктуальность: мы прочитали множество книг о программировании

Python и data science; просмотрели или прочитали около 15 000 статей,
исследовательских работ, информационных документов, видеороликов,
публикаций в блогах, сообщений на форумах и документов. Это позво-

Спрос на квалификацию в области data science   25

лило нам «держать руку на пульсе» сообществ Python, компьютерных
технологий, data science, AI, больших данных и облачных технологий.

Быстрый отклик: исследования и эксперименты с IPython
ØØЕсли вы хотите учиться по этой книге, лучше всего читать текст и парал-

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

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

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

Основы программирования на Python
ØØПрежде всего в книге достаточно глубоко и подробно излагаются основы

Python.
ØØВ ней рассматриваются модели программирования на языке Python —

процедурное программирование, программирование в функциональном
стиле и объектно-ориентированное программирование.
ØØМы стараемся наглядно выделять текущие идиомы.
ØØПрограммирование в функциональном стиле используется везде, где это

уместно. На диаграмме в главе 4 перечислены ключевые средства программирования в функциональном стиле языка Python с указанием глав,
в которых они впервые рассматриваются.

538 примеров кода
ØØУвлекательное, хотя и непростое введение в Python подкрепляется 538 ре-

альными примерами — от небольших фрагментов до основательных прак-

26   Предисловие
тических примеров из области компьютерной теории, data science, искусственного интеллекта и больших данных.
ØØМы займемся нетривиальными задачами из области искусственного ин-

теллекта, больших данных и облачных технологий, такими как обработка
естественного языка, глубокий анализ данных Twitter, машинное обучение, глубокое обучение, Hadoop, MapReduce, Spark, IBM Watson, ключевые библиотеки data science (NumPy, pandas, SciPy, NLTK, TextBlob,
spaCy, Textatistic, Tweepy, Scikit-learn, Keras), ключевые библиотеки
визуа­лизации (Matplotlib, Seaborn, Folium) и т. д.

Объяснения вместо математических выкладок
ØØМы стараемся сформулировать концептуальную сущность математиче-

ских вычислений и использовать ее в своих примерах. Для этого применяются такие библиотеки, как statistics, NumPy, SciPy, pandas и многие
другие, скрывающие математические сложности от пользователя. Таким
образом, вы сможете пользоваться такими математическими методами,
как линейная регрессия, даже не владея математической теорией, на которой они базируются. В примерах машинного обучения мы стараемся
создавать объекты, которые выполнят все вычисления за вас.

Визуализации
ØØ67 статических, динамических, анимированных и интерактивных визуа-

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

граммирование, мы сосредоточимся на высокоуровневых визуализациях,
построенных средствами Matplotlib, Seaborn, pandas и Folium (для интерактивных карт).
ØØВизуализации используются как учебный инструмент. Например, закон

больших чисел наглядно демонстрируется динамической моделью бросков кубиков и построением гистограммы. С увеличением количества
бросков процент выпадений каждой грани постепенно приближается
к 16,667% (1/6), а размеры столбцов, представляющих эти проценты, постепенно выравниваются.
ØØВизуализации чрезвычайно важны при работе с большими данными: они

упрощают исследование данных и получение воспроизводимых результа-

Спрос на квалификацию в области data science   27

тов исследований, когда количество элементов данных может достигать
миллионов, миллиардов и более. Часто говорят, что одна картинка стоит
тысячи слов1 — в мире больших данных визуализация может стоить миллиардов, триллионов и даже более записей в базе данных. Визуализации
позволяют взглянуть на данные «с высоты птичьего полета», увидеть их
«в перспективе» и составить о них представление. Описательные статистики полезны, но иногда могут увести в ошибочном направлении. Например, квартет Энскомба2 демонстрирует посредством визуализаций,
что серьезно различающиеся наборы данных могут иметь почти одинаковые показатели описательной статистики.
ØØМы приводим код визуализаций и анимаций, чтобы вы могли реализовать

собственные решения. Также анимации предоставляются в виде файлов
с исходным кодом и документов Jupyter Notebook, чтобы вам было удобно настраивать код и параметры анимаций, заново выполнить анимации
и понаблюдать за эффектом изменений.

Опыт работы с данными
ØØВ разделах «Введение в data science» и практических примерах из глав 11–

16 вы получите полезный опыт работы с данными.
ØØМы будем работать со многими реальными базами данных и источниками

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

пулярные наборы данных для экспериментов.
ØØВ книге мы рассмотрим действия, необходимые для получения данных

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

GitHub
ØØGitHub — превосходный ресурс для поиска открытого кода, который вы

сможете интегрировать в свои проекты (а также поделиться своим кодом
1
2

https://en.wikipedia.org/wiki/A_picture_is_worth_a_thousand_words.
https://ru.wikipedia.org/wiki/Квартет_Энскомба.

28   Предисловие
с сообществом). Также GitHub является важнейшим элементом арсенала
разработчика с функциональностью контроля версий, которая помогает
командам разработчиков управлять проектами с открытым (и закрытым)
кодом.
ØØМы будем использовать множество разнообразных библиотек Python

и data science, распространяемых с открытым кодом, а также программных
продуктов и облачных сервисов — бесплатных, имеющих пробный период
и условно-бесплатных. Многие библиотеки размещаются на GitHub.

Практические облачные вычисления
ØØБольшая часть аналитики больших данных выполняется в облачных сре-

дах, позволяющих легко динамически масштабировать объем аппаратных
и программных ресурсов, необходимых вашему приложению. Мы будем
работать с различными облачными сервисами (напрямую или опосредованно), включая Twitter, Google Translate, IBM Watson, Microsoft Azure,
OpenMapQuest, geopy, Dweet.io и PubNub.
ØØМы рекомендуем пользоваться бесплатными, имеющими пробный пе-

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

Базы данных, большие данные и инфраструктура
больших данных
ØØПо данным IBM (ноябрь 2016 года), 90% мировых данных было создано

за последние два года1. Факты показывают, что скорость создания данных
стремительно растет.
ØØПо данным статьи AnalyticsWeek за март 2016 года, в течение 5 лет к ин-

тернету будет подключено более 50 миллиардов устройств, а к 2020 году
в мире будет ежесекундно производиться 1,7 мегабайт новых данных на
каждого человека2!
1

2

https://public.dhe.ibm.com/common/ssi/ecm/wr/en/wrl12345usen/watson-customer-engagementwatson-marketing-wr-other-papers-and-reports-wrl12345usen-20170719.pdf.
https://analyticsweek.com/content/big-data-facts/.

Спрос на квалификацию в области data science   29
ØØВ книге рассматриваются основы работы с реляционными базами данных

и использования SQL с SQLite.
ØØБазы данных — критический элемент инфраструктуры больших данных

для хранения и обработки больших объемов информации. Реляционные
базы данных предназначены для обработки структурированных данных —
они не приспособлены для неструктурированных и полуструктурированных данных в приложениях больших данных. По этой причине с развитием больших данных были созданы базы данных NoSQL и NewSQL
для эффективной работы с такими данными. Мы приводим обзор NoSQL
и NewSQL, а также практический пример работы с документной базой
данных MongoDB в формате JSON. MongoDB — самая популярная база
данных NoSQL.
ØØОборудование и программная инфраструктура больших данных рассма-

триваются в главе 16.

Практические примеры из области искусственного интеллекта
ØØВ практических примерах глав 11–15 представлены темы искусственного

интеллекта, включая обработку естественного языка, глубокий анализ данных Twitter для анализа эмоциональной окраски, когнитивные вычисления
на базе IBM Watson, машинное обучение с учителем, машинное обучение
без учителя и глубокое обучение. В главе 16 представлено оборудование
больших данных и программная инфраструктура, которые позволяют специалистам по компьютерным технологиям и теоретикам data science реализовать ультрасовременные решения на базе искусственного интеллекта.

Встроенные коллекции: списки, кортежи, множества, словари
ØØКак правило, в наши дни самостоятельная реализация структур дан-

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

Программирование с использованием массивов NumPy
и коллекций pandas Series/DataFrame
ØØМы также уделили особое внимание трем ключевым структурам данных

из библиотек с открытым кодом: массивам NumPy, коллекциям pandas
Series и pandas DataFrame. Эти коллекции находят широкое применение

30   Предисловие
в data science, компьютерной теории, искусственном интеллекте и больших данных. NumPy обеспечивает эффективность на два порядка выше,
чем у встроенных списков Python.
ØØВ главу 7 включено подробное описание массивов NumPy. Многие би-

блиотеки, например pandas, построены на базе NumPy. В разделах «Введение в data science» в главах 7–9 представлены коллекции pandas Series
и DataFrame, которые хорошо работают в сочетании с массивами NumPy,
а также используются в оставшихся главах.

Работа с файлами и сериализация
ØØВ главе 9 рассказывается об обработке текстовых файлов, а затем показано,

как сериализовать объекты в популярном формате JSON (JavaScript Object
Notation). JSON часто используется в части, посвященной data science.
ØØМногие библиотеки data science предоставляют встроенные средства для

работы с файлами и загрузки наборов данных в программы Python. Кроме
простых текстовых файлов, мы также займемся обработкой файлов в популярном формате CSV (значения, разделенные запятыми) с использованием модуля csv стандартной библиотеки Python и средств библиотеки
data science pandas.

Объектно-базированное программирование
ØØМы стараемся использовать многочисленные классы, упакованные со-

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

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

Объектно-ориентированное программирование
ØØРазработка собственных классов — важнейшая составляющая объектно-

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

Спрос на квалификацию в области data science   31

физмом и утиной типизацией. Эти составляющие рассматриваются в главе 10.
ØØВ главе 10 рассматривается модульное тестирование с использованием

doctest и интересного моделирования процесса тасования и раздачи карт.
ØØДля целей глав 11–16 хватает нескольких простых определений пользо-

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

Воспроизводимость результатов
ØØВ науке вообще и в data science в частности существует потребность в вос-

произведении результатов экспериментов и исследований, а также эффективном распространении этих результатов. Для этого обычно рекомендуется применять документы Jupyter Notebook.
ØØВоспроизводимость результатов рассматривается в книге в контексте ме-

тодов программирования и программных средств, таких как документы
Jupyter Notebook и Docker.

Эффективность
ØØВ нескольких примерах используется средство профилирования %timeit

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

Большие данные и параллелизм
В этой книге вместо написания собственного кода параллелизации мы поручим таким библиотекам, как Keras на базе TensorFlow, и таким инструментам
больших данных, как Hadoop и Spark, провести параллелизацию за вас. В эру
больших данных/искусственного интеллекта колоссальные требования к вычислительным мощностям приложений, работающих с большими массивами
данных, заставляют нас задействовать полноценный параллелизм, обеспечиваемый многоядерными процессорами, графическими процессорами (GPU),
тензорными процессорами (TPU) и гигантскими компьютерными кластерами
в облаке. Некоторые задачи больших данных могли требовать параллельной
работы тысяч процессоров для быстрого анализа огромных объемов данных.

32   Предисловие

Зависимость между главами
Допустим, вы — преподаватель, составляющий план лекций для профессиональных учебных курсов, или разработчик, решающий, какие главы следует
читать в первую очередь. Тогда этот раздел поможет вам принять оптимальные решения. Главы лучше всего читать (или использовать для обучения)
по порядку. Тем не менее для большей части материала разделов «Введение
в data science» в конце глав 1–10 и практических примеров в главах 11–16
необходимы только главы 1–5 и небольшие части глав 6–10.

Часть 1: Основы Python
Мы рекомендуем читать все главы по порядку:
ØØВ главе 1 «Компьютеры и Python» представлены концепции, которые

закладывают фундамент для программирования на языке Python в главах 2–10 и практических примеров больших данных, искусственного
интеллекта и облачных сервисов в главах 11–16. В главе также приведены результаты пробных запусков интерпретатора IPython и документов
Jupyter Notebook.
ØØВ главе 2 «Введение в программирование Python» изложены основы про-

граммирования Python с примерами кода, демонстрирующими ключевые
возможности языка.
ØØВ главе 3 «Управляющие команды» представлены управляющие команды

Python и простейшие возможности обработки списков.
ØØВ главе 4 «Функции» представлены пользовательские функции, мето-

ды моделирования с генерированием случайных чисел и основы работы
с кортежами.
ØØВ главе 5 «Последовательности: списки и кортежи» встроенные списки

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

Часть 2: Структуры данных Python, строки и файлы
Ниже приведена сводка зависимостей между главами для глав 6–9; предполагается, что вы уже прочитали главы 1–5.
ØØГлава 6 «Словари и множества» — раздел «6.4. Введение в data science»

этой главы не зависит от материала главы.

Спрос на квалификацию в области data science   33
ØØГлава 7 «NumPy и программирование, ориентированное на массивы» —

для раздела «7.14. Введение в data science» необходимо знание словарей
(глава 6) и массивов (глава 7).
ØØГлава 8 «Подробнее о строках» — для раздела «8.13. Введение в data

science» необходимо знание необработанных строк и регулярных выражений (разделы 8.11–8.12), а также коллекций pandas Series и DataFrame из
раздела 7.14.
ØØГлава 9 «Файлы и исключения» — для изучения сериализации JSON по-

лезно знать основы работы со словарями (раздел 6.2). Кроме того, раздел «9.12. Введение в data science» требует знания встроенной функции open и коман­ды with (раздел 9.3), а также коллекций pandas Series
и DataFrame из раздела 7.14.

Часть 3: Нетривиальные аспекты Python
Ниже приведена сводка зависимостей между главами для глав 10; предполагается, что вы уже прочитали главы 1–5.
ØØГлава 10 «Объектно-ориентированное программирование» — раздел

«Вве­дение в data science» требует знания возможностей DataFrame из раздела 7.14. Преподаватели, которые намерены ограничиваться рассмотрением только классов и объектов, могут изложить материал разделов 10.1–
10.6. Для преподавателей, которые собираются изложить более сложные
темы (наследование, полиморфизм, утиная типизация), могут представлять интерес разделы 10.7–10.9. В разделах 10.10–10.15 изложены дополнительные перспективы.

Часть 4: Искусственный интеллект, облачные технологии
и практические примеры больших данных
Ниже приведена сводка зависимостей между главами для глав 11–16; предполагается, что вы уже прочитали главы 1–5. Большинство глав 11–16 также
требует знания словарей из раздела 6.2.
ØØВ главе 11 «Обработка естественного языка» используются возможности

pandas DataFrame из раздела 7.14.
ØØВ главе 12 «Глубокий анализ данных Twitter» используются возможности

pandas DataFrame из раздела 7.14, метод строк join (раздел 8.9), основы
работы с JSON (раздел 9.5), TextBlob (раздел 11.2) и словарные облака

34   Предисловие
(раздел 11.3). Некоторые примеры требуют определения классов с наследованием (глава 10).
ØØВ главе 13 «IBM Watson и когнитивные вычисления» используется встро-

енная функция open и команда with (раздел 9.3).
ØØВ главе 14 «Машинное обучение: классификация, регрессия и класте-

ризация» используются основные средства работы с массивами NumPy
и метод unique (глава 7), возможности pandas DataFrame из раздела 7.14,
а также функция subplots библиотеки Matplotlib (раздел 10.6).
ØØВ главе 15 «Глубокое обучение» используются основные средства работы

с массивами NumPy (глава 7), метод строк join (раздел 8.9), общие концепции машинного обучения из главы 14 и функциональность из практического примера главы 14 «Классификация методом k ближайших соседей и набор данных Digits».
ØØВ главе 16 «Большие данные: Hadoop, Spark, NoSQL и IoT» использует-

ся метод строк split (раздел 6.2.7), объект Matplotlib FuncAnimation из
раздела 6.4, коллекции pandas Series и DataFrame из раздела 7.14, метод
строк join (раздел 8.9), модуль JSON (раздел 9.5), игнорируемые слова
NLTK (раздел 11.2.13), аутентификация Twitter из главы 12, класс Tweepy
StreamListener для потоковой передачи твитов, а также библиотеки geopy
и folium. Некоторые примеры требуют определения классов с применением наследования (глава 10), но вы можете просто повторить наши определения классов без чтения главы 10.

Документы Jupyter Notebook
Для вашего удобства мы предоставили примеры кода книги в файлах с исходным кодом Python (.py) для использования с интерпретатором командной
строки IPython, а также файлы Jupyter Notebook (.ipynb), которые можно загрузить в браузере и выполнить.
Jupyter Notebook — бесплатный проект с открытым кодом, который позволяет
объединять текст, графику, аудио, видео и функциональность интерактивного
программирования для быстрого и удобного ввода, редактирования, выполнения, отладки и изменения кода в браузере. Фрагмент статьи «Что такое
Jupyter?»:
«Jupyter стал фактическим стандартом для научных исследований и анализа данных. Вычисления упаковываются вместе с аргументами, позволяя
вам строить “вычислительные нарративы”; …это упрощает проблему

Спрос на квалификацию в области data science   35

распространения работоспособного кода между коллегами и участниками
сообщества»1.
Наш опыт показывает, что эта среда прекрасно подходит для обучения и быстрой прототипизации. По этой причине мы используем документы Jupyter
Notebook вместо традиционных интегрированных сред (IDE), таких как
Eclipse, Visual Studio, PyCharm или Spyder. Ученые и специалисты уже широко
применяют Jupyter для распространения результатов своих исследований.
Поддержка Jupyter Notebook предоставляется через традиционные механизмы сообщества с открытым кодом2 (см. раздел «Поддержка Jupyter» в этом
предисловии). За подробным описанием установки обращайтесь к разделу
«Приступая к работе» после предисловия, а информация о запуске примеров
книги приведена в разделе 1.5.

Совместная работа и обмен результатами
Работа в команде и распространение результатов исследований играют важную
роль для разработчиков, которые занимают или собираются занять должность,
связанную с аналитикой данных, в коммерческих, правительственных или
образовательных организациях:
ØØСозданные вами документы Notebook удобно распространять среди

участников команды простым копированием файлов или через GitHub.
ØØРезультаты исследований, включая код и аналитику, могут публиковать-

ся в виде статических веб-страниц при помощи таких инструментов, как
nbviewer (https://nbviewer.jupyter.org) и GitHub, — оба ресурса автоматически визуализируют документы Notebook в виде веб-страниц.

Воспроизводимость результатов: веский аргумент
в пользу Jupyter Notebook
В области data science и научных дисциплин вообще эксперименты и исследования должны быть воспроизводимыми. Об этом неоднократно упоминалось
в литературе:
ØØПубликация Дональда Кнута «Грамотное программирование» в 1992 году3.
1
2
3

https://www.oreilly.com/ideas/what-is-jupyter.
https://jupyter.org/community.

Knuth D. Literate Programming (PDF), The Computer Journal, British Computer Society,
1992.

36   Предисловие
ØØСтатья «Языково-независимый воспроизводимый анализ данных с при-

менением грамотного программирования»1, в которой сказано: «Lir-вы­
чис­ления (грамотные воспроизводимые вычисления) базируются на
концепции грамотного программирования, предложенной Дональдом
Кнутом».
По сути, воспроизводимость отражает полное состояние среды, использованной для получения результатов: оборудование, программное обеспечение,
коммуникации, алгоритмы (особенно код), данные и родословная данных (источник и линия происхождения).

Docker
В главе 16 используется Docker — инструмент для упаковки программного
кода в контейнеры, содержащие все необходимое для удобного, воспроизводимого и портируемого выполнения этого кода между платформами. Некоторые
программные пакеты, используемые в главе 16, требуют сложной подготовки
и настройки. Для многих из них можно бесплатно загрузить готовые контейнеры Docker. Это позволяет избежать сложных проблем установки и запускать программные продукты локально на настольном или портативном
компьютере. Docker предоставляет идеальную возможность быстро и удобно
приступить к использованию новых технологий.
Docker также помогает обеспечить воспроизводимость. Вы можете создавать
специализированные контейнеры Docker с нужными версиями всех программных продуктов и всех библиотек, использованных в исследовании.
Это позволит другим разработчикам воссоздать использованную вами среду,
а затем повторить вашу работу и получить ваши результаты. В главе 16 мы
используем Docker для загрузки и выполнения контейнера, заранее настроенного для программирования и запуска Spark-приложений больших данных
на базе Jupyter Notebook.

IBM Watson и когнитивные вычисления
На ранней стадии исследований, проводимых для этой книги, мы распознали
быстро растущий интерес к IBM Watson. Мы проанализировали предложения
конкурентов и обнаружили, что политика Watson «без ввода данных кредит-

1

http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0164023.

Спрос на квалификацию в области data science   37

ной карты» для бесплатных уровней является одной из самых удобных для
наших читателей.
IBM Watson — платформа когнитивных вычислений, применяемая в широком
спектре реальных сценариев. Системы когнитивных вычислений моделируют
функции человеческого мозга по выявлению закономерностей и принятию
решений для «обучения» с поглощением большего объема данных1,2,3. В книге
Watson уделяется значительное внимание. Мы используем бесплатный пакет
Watson Developer Cloud: Python SDK, который предоставляет различные API
для взаимодействия с сервисами Watson на программном уровне. С Watson
интересно работать, и эта платформа помогает раскрыть ваш творческий потенциал.

Сервисы Watson уровня Lite и практический пример Watson
Чтобы способствовать обучению и экспериментам, IBM предоставляет бесплатные lite-уровни для многих своих API4. В главе 13 будут опробованы
демонстрационные приложения для многих сервисов Watson5. Затем мы используем lite-уровни сервисов Watson Text to Speech, Speech to Text и Translate
для реализации приложения-переводчика. Пользователь произносит вопрос
на английском языке, приложение преобразует речь в английский текст, переводит текст на испанский язык и зачитывает испанский текст. Собеседник
произносит ответ на испанском языке (если вы не говорите на испанском, мы
предоставили аудиофайл, который вы можете использовать). Приложение
быстро преобразует речь в испанский текст, переводит текст на английский
и зачитывает ответ на английском. Круто!

Подход к обучению
«Python: Искусственный интеллект, большие данные и облачные вычисления»
содержит обширную подборку примеров, позаимствованных из многих областей. Мы рассмотрим некоторые интересные примеры с реальными наборами
данных. В книге основное внимание уделяется принципам качественного
1
2
3

4

5

http://whatis.techtarget.com/definition/cognitive-computing.
https://en.wikipedia.org/wiki/Cognitive_computing.
https://www.forbes.com/sites/bernardmarr/2016/03/23/what-everyone-should-know-about-cognitivecomputing.

Всегда проверяйте последние условия предоставления сервиса на сайте IBM, так как
условия и сервисы могут меняться со временем.
https://console.bluemix.net/catalog/.

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

538 примеров кода
538 примеров, приведенных в книге, содержат приблизительно 4000 строк
кода. Это относительно небольшой объем для книги такого размера, что отчасти объясняется выразительностью языка Python. Кроме того, наш стиль
программирования подразумевает, что мы по возможности используем полнофункциональные библиотеки классов; эти библиотеки берут на себя большую
часть работы.

160 таблиц/иллюстраций/визуализаций
В книгу включено множество таблиц, графиков, а также статических, динамических и интерактивных визуализаций.

Житейская мудрость программирования
В материал книги интегрируется житейская мудрость программирования,
основанная на девяти десятилетиях (в сумме) авторского опыта программирования и преподавания.
ØØХороший стиль программирования и общепринятые идиомы Python помо-

гают создавать более понятные, более четкие и простые в сопровождении
программы.
ØØОписание распространенных ошибок программирования снижает вероят-

ность того, что эти ошибки будут допущены читателями.
ØØСоветы по предотвращению ошибок с рекомендациями по выявлению де-

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

ускорения работы ваших программ или сокращения объема занимаемой
памяти.
ØØНаблюдения из области программирования, в которых выделяются ар-

хитектурные и проектировочные аспекты правильного построения программных продуктов (особенно для больших систем).

Ответы на вопросы   39

Программные продукты, используемые в книге
Программные продукты, используемые в книге, доступны для Windows,
macOS и Linux, и их можно бесплатно загрузить из интернета. Для написания примеров используется бесплатный дистрибутив Anaconda Python. Он
включает большую часть Python, библиотек визуализации и data science,
которые вам понадобятся, а также интерпретатор IPython, Jupyter Notebook
и Spyder — одну из самых лучших интегрированных сред Python для data
science. Для разработки программ, приведенных в книге, используется только
IPython и Jupyter Notebook. В разделе «Приступая к работе» после предисловия обсуждается установка Anaconda и других продуктов, необходимых для
работы с нашими примерами.

Документация Python
Следующая документация особенно пригодится вам во время работы с книгой:
ØØСправочник по языку Python:
https://docs.python.org/3/reference/index.html
ØØСтандартная библиотека Python:
https://docs.python.org/3/library/index.html
ØØСписок документации Python:
https://docs.python.org/3/

Ответы на вопросы
Несколько популярных форумов, посвященных Python и программированию
вообще:
ØØpython-forum.io
ØØhttps://www.dreamincode.net/forums/forum/29-python/
ØØStackOverflow.com

Кроме того, многие разработчики открывают форумы по своим инструментариям и библиотекам. Управление и сопровождение многих библиотек, используемых в книге, осуществляется через github.com. Для некоторых библиотек
поддержка предоставляется через вкладку Issues на странице GinHub этих

40   Предисловие
библиотек. Если вы не найдете ответ на свои вопросы, посетите веб-страницу
этой книги на сайте
http://www.deitel.com1

Поддержка Jupyter
Поддержка Jupyter Notebook предоставляется на следующих ресурсах:
ØØProject Jupyter Google Group:
https://groups.google.com/forum/#!forum/jupyter
ØØJupyter-чат в реальном времени:
https://gitter.im/jupyter/jupyter
ØØGitHub
https://github.com/jupyter/help
ØØStackOverflow:
https://stackoverflow.com/questions/tagged/jupyter
ØØJupyter for Education Google Group (для преподавателей, использующих

Jupyter в ходе обучения):
https://groups.google.com/forum/#!forum/jupyter-education

Приложения
Чтобы извлечь максимум пользы из материала, выполняйте каждый пример
кода параллельно с соответствующим описанием в книге. На веб-странице
книги на сайте
http://www.deitel.com

предоставляются:
ØØисходный код Python (файлы .py), подготовленный к загрузке, и документы Jupyter Notebook (файлы .ipynb) для примеров кода;
ØØвидеоролики, поясняющие использование примеров кода с IPython и доку-

ментами Jupyter Notebook. Эти инструменты также описаны в разделе 1.5;
1

Наш сайт сейчас проходит серьезную переработку. Если вы не найдете нужную информацию, обращайтесь к нам по адресу deitel@deitel.com.

Благодарности   41
ØØсообщения в блогах и обновления книги.

За инструкциями по загрузке обращайтесь к разделу «Приступая к работе»
после предисловия.

Как связаться с авторами книги
Мы ждем ваши комментарии, критические замечания, исправления и предложения по улучшению книги. С вопросами, найденными опечатками и предложениями обращайтесь по адресу: deitel@deitel.com.
Или ищите нас в соцсетях:
ØØFacebook® (http://www.deitel.com/deitelfan)
ØØTwitter® (@deitel)
ØØLinkedIn® (http://linkedin.com/company/deitel-&-associates)
ØØYouTube® (http://youtube.com/DeitelTV)

Благодарности
Спасибо Барбаре Дейтел (Barbara Deitel) за долгие часы, проведенные в интернете за поиском информации по проекту. Нам повезло работать с группой профессионалов из издательства Pearson. Мы высоко ценим все усилия и 25-летнее
наставничество нашего друга и профессионала Марка Л. Тауба (Mark L. Taub),
вице-президента издательской группы Pearson IT Professional Group. Марк
со своей группой работает над всеми нашими профессиональными книгами,
видеоуроками и учебными руководствами из сервиса Safari (https://learning.
oreilly.com/). Они также являются спонсорами наших обучающих семинаров
в Safari. Джули Наил (Julie Nahil) руководила выпуском книги. Мы выбрали
иллюстрацию для обложки, а дизайн обложки был разработан Чати Презертсит
(Chuti Prasertshith).
Мы хотим выразить свою благодарность своим редакторам. Патрисия Байрон-Кимболл (Patricia Byron-Kimball) и Меган Джейкоби (Meghan Jacoby)
подбирали научных рецензентов и руководили процессом рецензирования.
Держась в рамках жесткого графика, редакторы рецензировали нашу работу,
делились многочисленными замечаниями для повышения точности, полноты
и актуальности материала.

42   Предисловие

Научные редакторы
Книга

Ланс Брайант (Lance Bryant), доцент кафедры математики, Шиппенсбургский университет

Дэниел Чен (Daniel Chen), специалист по
data science, Lander Analytics

Дэниел Чен (Daniel Chen), специалист по
data science, Lander Analytics

Гаррет Дансик (Garrett Dancik), доцент кафедры компьютерных наук/биоинформатики, Университет Восточного Коннектикута

Гаррет Дансик (Garrett Dancik), доцент
кафедры компьютерных наук/биоинформатики, Университет Восточного Коннектикута

Праншу Гупта (Pranshu Gupta), доцент кафедры компьютерных наук, Университет
Десалс
Дэвид Куп (David Koop), доцент кафедры
data science, содиректор по учебным программам, Университет Массачусетса в Дарт­
муте

Марша Дэвис (Dr. Marsha Davis), декан
математического факультета, Университет
Восточного Коннектикута
Роланд ДеПратти (Roland DePratti), доцент
кафедры компьютерных наук, Университет
Восточного Коннектикута

Рамон Мата-Толедо (Ramon Mata-Toledo),
профессор кафедры компьютерных наук,
Университет Джеймса Мэдисона

Шьямал Митра (Shyamal Mitra), старший
преподаватель, Техасский университет
в Остине

Шьямал Митра (Shyamal Mitra), старший
преподаватель кафедры компьютерных
наук, Техасский университет в Остине

Марк Поли (Dr. Mark Pauley), старший
научный сотрудник на кафедре биоинформатики, школа междисциплинарной информатики, Университет штата Небраска
в Омахе

Элисон Санчес (Alison Sanchez), доцент кафедры экономики, Университет Сан-Диего
Хосе Антонио Гонсалес Секо (José Antonio
González Seco), IT-консультант
Джейми Уайтакер (Jamie Whitacre), независимый консультант в области data science
Элизабет Уикс (Elizabeth Wickes), преподаватель, школа информатики, Университет
штата Иллинойс

Черновик
Ирен Бруно (Dr. Irene Bruno), доцент кафедры информатики и информационных технологий, Университет Джорджа Мэйсона

Шон Рейли (Sean Raleigh), доцент кафедры
математики, заведующий кафедрой data
science, Вестминстерский колледж
Элисон Санчес (Alison Sanchez), доцент
кафедры экономики, Университет СанДиего
Харви Сай (Dr. Harvey Siy), доцент кафедры
компьютерных наук, информатики и информационных технологий, Университет
штата Небраска в Омахе
Джейми Уайтакр (Jamie Whitacre), независимый консультант в области data science

Мы будем благодарны за ваши комментарии, критику, исправления и предложения по улучшению. Если у вас возникают какие-либо вопросы, обращайтесь
по адресу deitel@deitel.com.

Об авторах   43

Добро пожаловать в увлекательный мир разработки Python с открытым кодом.
Надеемся, вам понравится эта книга, посвященная разработке современных
приложений Python с использованием IPython и Jupyter Notebook и затрагивающая вопросы data science, искусственного интеллекта, больших данных
и облачных технологий. Желаем успеха!
Пол и Харви Дейтелы

Об авторах
Пол Дж. Дейтел (Paul J. Deitel), генеральный и технический директор компании
Deitel & Associates, Inc., окончил Массачусетский технологический институт
(MIT), более 38 лет занимается компьютерами. Пол — один из самых опытных
преподавателей языков программирования, он ведет учебные курсы для разработчиков с 1992 года. Он провел сотни занятий по всему миру для корпоративных клиентов, включая Cisco, IBM, Siemens, Sun Microsystems (сейчас Oracle),
Dell, Fidelity, NASA (Космический центр имени Кеннеди), Национальный центр
прогнозирования сильных штормов, ракетный полигон Уайт-Сэндз, Rogue
Wave Software, Boeing, Nortel Networks, Puma, iRobot и многих других. Пол
и его соавтор, д-р Харви М. Дейтел, являются авторами всемирно известных
бестселлеров — учебников по языкам программирования, предназначенных для
начинающих и для профессионалов, а также видеокурсов.
Харви М. Дейтел (Dr. Harvey M. Deitel), председатель и главный стратег
компании Deitel & Associates, Inc., имеет 58-летний опыт работы в области
информационных технологий. Он получил степени бакалавра и магистра
Массачусетского технологического института и степень доктора философии
Бостонского университета — он изучал компьютерные технологии во всех этих
программах до того, как в них появились отдельные программы компьютерных наук. Харви имеет огромный опыт преподавания в колледже и занимал
должность председателя отделения информационных технологий Бостонского
колледжа. В 1991 году вместе с сыном — Полом Дж. Дейтелом — он основал компанию Deitel & Associates, Inc. Харви с Полом написали несколько
десятков книг и выпустили десятки видеокурсов LiveLessons. Написанные
ими книги получили международное признание и были изданы на японском,
немецком, русском, испанском, французском, польском, итальянском, упрощенном китайском, традиционном китайском, корейском, португальском,
греческом, турецком языках и на языке урду. Дейтел провел сотни семинаров
по программированию в крупных корпорациях, академических институтах,
правительственных и военных организациях.

44   Предисловие

О компании Deitel® & Associates, Inc.
Компания Deitel & Associates, Inc., основанная Полом Дейтелом и Харви Дейтелом, получила международное признание в области авторских разработок
и корпоративного обучения. Компания специализируется на языках программирования, объектных технологиях, интернете и веб-программировании.
В число клиентов компании входят многие ведущие корпорации, правительственные агентства, военные и образовательные учреждения. Компания
предоставляет учебные курсы, проводимые на территории клиента по всему
миру для многих языков программирования и платформ.
Благодаря своему 44-летнему партнерскому сотрудничеству с Pearson/Prentice
Hall, компания Deitel & Associates, Inc., публикует передовые учебники по программированию и профессиональные книги в печатном и электронном виде,
видеокурсы LiveLessons (доступны для покупки на https://www.informit.com),
Learning Paths и интерактивные обучающие семинары в режиме реального
времени в службе Safari (https://learning.oreilly.com) и интерактивные мультимедийные курсы Revel™.
Чтобы связаться с компанией Deitel & Associates, Inc. и авторами или запросить план-проспект или предложение по обучению, напишите: deitel@deitel.com
Чтобы узнать больше о корпоративном обучении Дейтелов, посетите
http://www.deitel.com/training.

Желающие приобрести книги Дейтелов, могут сделать это на
https://www.amazon.com.

Крупные заказы корпораций, правительства, военных и академических учреждений следует размещать непосредственно на сайте Pearson. Для получения
дополнительной информации посетите
https://www.informit.com/store/sales.aspx.

Приступая к работе
В этом разделе собрана информация, которую следует просмотреть перед
чтением книги. Обновления будут публиковаться на сайте http://www.deitel.com.

Загрузка примеров кода
Файл examples.zip с кодом примеров книги можно загрузить на нашей вебстранице книги на сайте:
http://www.deitel.com

Щелкните на ссылке Download Examples, чтобы сохранить файл на вашем компьютере. Многие браузеры помещают загруженный файл в папку Downloads
вашей учетной записи. Когда загрузка завершится, найдите файл в своей
системе и распакуйте папку examples в папку Documents вашей учетной записи:
ØØWindows: C:\Users\YourAccount\Documents\examples
ØØmacOS или Linux: ~/Documents/examples

В большинстве операционных систем имеется встроенная программа распаковки архивов. Также можно воспользоваться внешней программой-архиватором — например, 7-Zip (www.7-zip.org) или WinZip (www.winzip.com).

46   Приступая к работе

Структура папки examples
В этой книге приводятся примеры трех типов:
ØØотдельные фрагменты кода для интерактивной среды IPython;
ØØзаконченные приложения, называемые сценариями;
ØØдокументы Jupyter Notebook — удобной интерактивной среды для брау-

зера, в которой можно писать и выполнять код, а также чередовать код
с текстом, графикой и видео.
Все варианты продемонстрированы в примерах из раздела 1.5.
Каталог examples содержит одну вложенную папку для каждой главы. Этим
папкам присвоены имена вида ch##, где ## — двузначный номер главы от 01
до 16 — например, ch01. Кроме глав 13, 15 и 16, папка каждой главы содержит
следующие элементы:
ØØsnippets_ipynb — папка с файлами Jupyter Notebook этой главы;
ØØsnippets_py — папка с файлами с исходным кодом Python, в которых хра-

нятся все представленные фрагменты кода, разделенные пустой строкой.
Вы можете скопировать эти фрагменты в IPython или в созданные вами
новые документы Jupyter Notebook;
ØØфайлы сценариев и используемые ими файлы.

Глава 13 содержит одно приложение. Главы 15 и 16 объясняют, где найти
нужные файлы в папках ch15 и ch16 соответственно.

Установка Anaconda
В книге используется дистрибутив Anaconda Python, отличающийся простотой установки. В неговходит практически все необходимое для работы
с примерами, в том числе:
ØØинтерпретатор IPython;
ØØбольшинство библиотек Python и data science, используемых в книге;
ØØлокальный сервер Jupyter Notebook для загрузки и выполнения доку­

ментов;

Менеджеры пакетов   47
ØØдругие программные пакеты, такие как Spyder IDE (Integrated Development

Environment), — в книге используются только среды IPython и Jupyter
Notebook.
Программу установки Python 3.x Anaconda для Windows, macOS и Linux
можно загрузить по адресу:
https://www.anaconda.com/download/

Когда загрузка завершится, запустите программу установки и выполните
инструкции на экране. Чтобы установленная копия Anaconda работала правильно, не перемещайте ее файлы после установки.

Обновление Anaconda
Затем проверьте актуальность установки Anaconda. Откройте окно командной
строки в своей системе:
ØØВ macOS откройте приложение Terminal из подкаталога Utilities в каталоге Applications.
ØØВ Windows откройте командную строку Anaconda Prompt из меню Пуск. Когда

вы делаете это для обновления Anaconda (как в данном случае) или для установки новых пакетов (см. ниже), выполните Anaconda Prompt с правами администратора: щелкните правой кнопкой мыши и выберите команду Запуск
от имени администратора. (Если вы не можете найти команду Anaconda Prompt
в меню Пуск, найдите ее при помощи поля поиска в нижней части экрана.)
ØØВ Linux откройте терминал или командную оболочку своей системы (за-

висит от дистрибутива Linux).
В окне командной строки своей системы выполните следующие команды,
чтобы обновить установленные пакеты Anaconda до последних версий:
conda update conda
conda update --all

Менеджеры пакетов
Приведенная выше команда conda запускает менеджер пакетов conda — один
из двух основных менеджеров пакетов Python, используемых в книге (другой — pip). Пакеты содержат файлы, необходимые для установки отдельных

48   Приступая к работе
библиотек или инструментов Python. В книге conda будет использоваться для
установки дополнительных пакетов, если только не окажется, что эти пакеты
недоступны в conda; в этом случае будет использоваться pip. Некоторые разработчики предпочитают пользоваться исключительно pip, потому что эта
программа в настоящее время поддерживает больше пакетов. Если у вас возникнут проблемы с установкой пакетов из conda, попробуйте использовать pip.

Установка программы статического анализа
кода Prospector
Для анализа кода Python можно воспользоваться аналитической программой
Prospector, которая проверяет ваш код на наличие типичных ошибок и помогает улучшить его. Чтобы установить программу Prospector и используемые ею
библиотеки Python, выполните следующую команду в окне командной строки:
pip install prospector

Установка jupyter-matplotlib
В книге для построения некоторых анимаций используется библиотека визуализации Matplotlib. Чтобы использовать анимации в документах Jupyter
Notebook, необходимо установить программу ipympl. В терминале в командной
строке Anaconda или в оболочке, открытой ранее, последовательно выполните
следующие команды1:
conda install -c conda-forge ipympl
conda install nodejs
jupyter labextension install @jupyter-widgets/jupyterlab-manager
jupyter labextension install jupyter-matplotlib

Установка других пакетов
Дистрибутив Anaconda включает приблизительно 300 популярных пакетов
Python и data science, включая NumPy, Matplotlib, pandas, Regex, BeautifulSoup,
requests, Bokeh, SciPy, SciKit-Learn, Seaborn, Spacy, sqlite, statsmodels и многие
другие. Количество дополнительных пакетов, которые вам придется уста1

https://github.com/matplotlib/jupyter-matplotlib.

Различия в выводе программ   49

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

Получение учетной записи разработчика Twitter
Если вы намереваетесь использовать главу «Глубокий анализ данных Twitter»
и все примеры на базе Twitter в последующих главах, подайте заявку на получение учетной записи разработчика Twitter. Сейчас Twitter требует регистрации
для получения доступа к их API. Чтобы подать заявку на создание учетной
записи разработчика, заполните и отправьте форму по адресу:
https://developer.twitter.com/en/apply-for-access

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

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

Различия в выводе программ
При выполнении наших примеров вы можете заметить некоторые различия
между приведенными и вашими результатами:

50   Приступая к работе
ØØИз-за различий в выполнении вычислений в формате с плавающей точ-

кой (например, –123.45, 7.5 или 0.0236937) в разных операционных системах вы можете заметить незначительные различия в выводе, особенно
в младших разрядах дробной части.
ØØКогда мы приводим выходные данные, отображаемые в разных окнах, мы

обрезаем границы окон для экономии места.

Получение ответов на вопросы
На форумах в интернете вы сможете общаться с другими программистами
Python и получать ответы на свои вопросы. Некоторые популярные форумы
по тематике Python и программирования в целом:
ØØpython-forum.io
ØØStackOverflow.com
ØØhttps://www.dreamincode.net/forums/forum/29-python/

Кроме того, многие фирмы-разработчики предоставляют форумы, посвященные их инструментам и библиотекам. Управление большинством упоминаемых в книге библиотек и их сопровождение осуществляется на github.
com. Некоторые разработчики, занимающиеся сопровождением библиотек,
предоставляют поддержку на вкладке Issues страницы GitHub библиотеки.
Если вам не удастся найти ответ на свой вопрос в интернете, обращайтесь
к веб-странице книги на сайте1
http://www.deitel.com

Теперь все готово к чтению книги. Надеемся, она вам понравится!

1

В настоящее время наш сайт проходит глобальную реконструкцию. Если вы не найдете
какую-то необходимую информацию, напишите нам по адресу deitel@deitel.com.

1
Компьютеры и Python
В этой главе…
•• Наиболее интересные тенденции в области современных вычислительных
технологий.
•• Краткий обзор основ объектно-ориентированного программирования.
•• Сильные стороны Python.
•• Знакомство с важнейшими библиотеками Python и data science, используемыми в этой книге.
•• Интерактивный режим интерпретатора IPython и выполнение
кода Python.
•• Выполнение сценария Python для анимации гистограммы.
•• Создание и тестирование веб-оболочки Jupyter
Notebook для выполнения кода Python.
•• Большие данные: с каждым днем еще больше.
•• Анализ больших данных на примере популярного мобильного навигационного приложения.
•• Искусственный интеллект: пересечение компьютерной теории и data science.

52   Глава 1. Компьютеры и Python

1.1. Введение
Знакомьтесь: Python — один из наиболее широко используемых языков
программирования, а согласно индексу PYPL (Popularity of Programming
Languages) — самый популярный в мире1.
В этом разделе представлены терминология и концепции, закладывающие
основу для программирования на языке Python, которому будут посвящены
главы 2–10, и для практических примеров из области больших данных, искусственного интеллекта и облачных технологий, описываемых в главах 11–16.
Читатели узнают, почему язык Python стал таким популярным, познакомятся
со стандартной библиотекой Python и различными библиотеками data science,
благодаря которым не придется заново «изобретать велосипед». Эти библио­
теки используются для создания объектов. С их помощью можно решать
серьезные задачи при умеренном объеме программного кода.
Затем будут рассмотрены три примера, демонстрирующие различные способы
выполнения кода Python:
ØØВ первом примере вы будете выполнять команды Python в оболочке

IPython в интерактивном режиме и сразу же видеть результаты.
ØØВо втором примере мы выполним серьезное приложение Python, отобра-

жающее анимированную гистограмму результатов бросков шестигранного кубика. Это позволит увидеть «закон больших чисел» в действии.
Забегая чуть вперед, отметим, что в главе 6 это же приложение будет построено с использованием библиотеки визуализации Matplotlib.
ØØВ последнем примере будет продемонстрирована работа с документами

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

1

https://pypl.github.io/PYPL.html (на январь 2019 г.).

1.2. Основы объектных технологий   53

технологиях и концепции IoT (Internet of Things), закладывающих основу для
современных приложений (подробнее об этом в главах 11–16).
Вы, кроме того, узнаете, насколько велики большие данные и как они стремительно становятся еще больше. Затем будет представлен вариант анализа
больших данных на примере мобильного навигационного приложения Waze.
Это приложение многие современные технологии использует для построения динамических инструкций, помогающих быстро и безопасно добраться
до конечной точки маршрута. При описании этих технологий будет указано,
где многие из них используются в этой книге. Глава завершается разделом
«Введение в data science», посвященным важнейшей области пересечения компьютерной теории и дисциплины data science — искусственному интеллекту.

1.2. Основы объектных технологий
Потребность в новых, более мощных программах стремительно растет, поэтому
очень важно, чтобы программные продукты строились по возможности быстро,
корректно и эффективно. Объекты (точнее, классы, на основе которых строятся
объекты), по сути, представляют собой программные компоненты, пригодные
для повторного использования. Объектом может быть что угодно: дата, время,
аудио, видео, автомобиль, человек и т. д. Практически каждое существительное может быть адекватным образом представлено программным объектом,
обладающим атрибутами (например, имя, цвет и размер) и способностями
к выполнению определенных действий (например, вычисление, перемещение
и обмен данными). Разработчики программ используют модульную структуру и объектно-ориентированную методологию проектирования с большей
эффективностью, чем некогда популярные методологии вроде «структурного
программирования». Объектно-ориентированные программы зачастую более
понятны, их проще исправлять и модифицировать.

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

54   Глава 1. Компьютеры и Python
лью скрываются сложные механизмы, которые при приведении в действие
ускоряют автомобиль. Аналогично и педаль тормоза «скрывает» механизмы,
тормозящие автомобиль, руль «скрывает» механизмы, поворачивающие автомобиль, и т. д. Благодаря этому люди, не имеющие понятия о внутреннем
устройстве автомобиля, могут легко им управлять.
Невозможно готовить пищу на кухне, существующей лишь на листе бумаги;
точно так же нельзя водить автомобиль, существующий лишь в чертежах. Прежде чем вы сядете за руль машины, ее нужно воплотить в металл на основе
чертежей. Воплощенный в металле автомобиль имеет реальную педаль газа,
с помощью которой он может ускоряться, но (к счастью!) не может делать это
самостоятельно — нажимает на педаль газа водитель.

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

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

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

1.2. Основы объектных технологий   55

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

Сообщения и вызовы методов
Нажимая педаль газа, вы тем самым отсылаете автомобилю сообщение с запросом на выполнение определенной задачи (ускорение движения). Подобным
же образом отсылаются и сообщения объекту. Каждое сообщение реализуется
вызовом метода, который «сообщает» методу объекта о необходимости выполнения определенной задачи. Например, программа может вызвать метод
deposit объекта банковского счета для его пополнения.

Атрибуты и переменные экземпляра класса
Любой автомобиль не только способен выполнять определенные задачи,
но и обладает и характерными атрибутами: цвет, количество дверей, запас
топлива в баке, текущая скорость и пройденное расстояние. Атрибуты автомобиля, как и его возможности по выполнению определенных действий,
представлены на инженерных диаграммах как часть проекта (например, им
могут соответствовать одометр и указатель уровня бензина). При вождении
автомобиля его атрибуты существуют вместе с ним. Каждому автомобилю
присущ собственный набор атрибутов. Например, каждый автомобиль «знает»
о том, сколько бензина осталось в его баке, но ему ничего не известно о запасах
горючего в баках других автомобилей.
Объект, как и автомобиль, имеет собственный набор атрибутов, которые он
«переносит» с собой при использовании этого объекта в программах. Эти атрибуты определены в качестве части объекта класса. Например, объект банков-

56   Глава 1. Компьютеры и Python
ского счета bank-account имеет атрибут баланса, представляющий количество
средств на банковском счете. Каждый объект bank-account «знает» о количестве
средств на собственном счете, но ничего не «знает» о размерах других банковских
счетов. Атрибуты определяются с помощью других переменных экземпляра
класса. Между атрибутами и методами класса (и его объектов) существует
тесная связь, поэтому классы хранят вместе свои атрибуты и методы.

Наследование
С помощью наследования можно быстро и просто создать новый класс объектов. При этом новый класс (называемый подклассом) наследует характеристики существующего класса (называемого суперклассом) — возможно, частично
изменяя их или добавляя новые характеристики, уникальные для этого класса.
Если вспомнить аналогию с автомобилем, то «трансформер» является объектом более обобщенного класса «автомобиль» с конкретной особенностью:
у него может подниматься или опускаться крыша.

Объектно-ориентированный анализ и проектирование
Вскоре вы начнете писать программы на Python. Как же вы будете формировать код своих программ? Скорее всего, как и большинство других программистов, вы включите компьютер и начнете вводить с клавиатуры исходный
код программы. Подобный подход годится при создании маленьких программ
(вроде тех, что представлены в начальных главах книги), но что делать при
создании крупного программного комплекса, который, например, управляет
тысячами банкоматов крупного банка? А если вы руководите командой из
1000 программистов, занятых разработкой системы управления воздушным
движением следующего поколения? В таких крупных и сложных проектах
нельзя просто сесть за компьютер и строка за строкой вводить код.
Чтобы выработать наилучшее решение, следует провести процесс детального
анализа требований к программному проекту (то есть определить, что должна
делать система) и разработать проектное решение, которое будет соответствовать этим требованиям (то есть определить, как система будет выполнять поставленные задачи). В идеале вы должны тщательно проанализировать проект
(либо поручить выполнение этой задачи коллегам-профессионалам) еще до
написания какого-либо кода. Если этот процесс подразумевает применение
объектно-ориентированного подхода, то, значит, мы имеем дело с процессом
OOAD (object-oriented analysis and design, объектно-ориентированный анализ и проектирование). Языки программирования, подобные Python, тоже

1.3. Python   57

называются объектно-ориентированными. Программирование на таком
языке — объектно-ориентированное программирование (ООП) — позволяет
реализовать результат объектно-ориентированного проектирования в форме
работоспособной системы.

1.3. Python
Python — объектно-ориентированный сценарный язык, официально опубликованный в 1991 году. Он был разработан Гвидо ван Россумом (Guido
van Rossum) из Национального исследовательского института математики
и компьютерных наук в Амстердаме.
Python быстро стал одним из самых популярных языков программирования
в мире. Он пользуется особой популярностью в среде образования и научных
вычислений1, а в последнее время превзошел язык программирования R в качестве самого популярного языка обработки данных2,3,4. Назовем основные
причины популярности Python, пояснив, почему каждому стоит задуматься
об изучении этого языка5,6,7:
ØØPython — бесплатный общедоступный проект с открытым кодом, имею-

щий огромное сообщество пользователей.
ØØОн проще в изучении, чем такие языки, как C, C++, C# и Java, что позво-

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

программирования.
ØØОн широко применяется в образовательной области8.
ØØОн повышает эффективность труда разработчика за счет обширной под-

борки стандартных и сторонних библиотек с открытым кодом, так что
1
2
3

4
5
6
7
8

https://www.oreilly.com/ideas/5-things-to-watch-in-python-in-2017.
https://www.kdnuggets.com/2017/08/python-overtakes-r-leader-analytics-data-science.html.
https://www.r-bloggers.com/data-science-job-report-2017-r-passes-sas-but-python-leaves-them-bothbehind/.
https://www.oreilly.com/ideas/5-things-to-watch-in-python-in-2017.
https://dbader.org/blog/why-learn-python.
https://simpleprogrammer.com/2017/01/18/7-reasons-why-you-should-learn-python/.
https://www.oreilly.com/ideas/5-things-to-watch-in-python-in-2017.

Tollervey N. Python in Education: Teach, Learn, Program (O’Reilly Media, Inc., 2015).

58   Глава 1. Компьютеры и Python
программисты могут быстрее писать код и решать сложные задачи с минимумом кода (подробнее см. раздел 1.4).
ØØСуществует множество бесплатных приложений Python с открытым кодом.
ØØPython — популярный язык веб-разработки (Django, Flask и т. д.).
ØØОн поддерживает популярные парадигмы программирования — проце-

дурную, функциональную и объектно-ориентированную1. Обратите внимание: мы начнем описывать средства функционального программирования в главе 4, а потом будем использовать их в последующих главах.
ØØОн упрощает параллельное программирование — с asyncio и async/await

вы можете писать однопоточный параллельный код2, что существенно
упрощает сложные по своей природе процессы написания, отладки и сопровождения кода3.
ØØСуществует множество возможностей для повышения быстродействия

программ на языке Python.
ØØPython используется для построения любых программ от простых сцена-

риев до сложных приложений со множеством пользователей, таких как
Dropbox, YouTube, Reddit, Instagram или Quora4.
ØØPython — популярный язык для задач искусственного интеллекта, а эта

область в последнее время стремительно развивается (отчасти благодаря
ее особой связи с областью data science).
ØØОн широко используется в финансовом сообществе5.
ØØДля программистов Python существует обширный рынок труда во мно-

гих областях, особенно в должностях, связанных с data science, причем вакансии Python входят в число самых высокооплачиваемых вакансий для
программистов6,7. Ближайший конкурент Python — R, популярный язык
1
2
3
4

https://en.wikipedia.org/wiki/Python_(programming_language).
https://docs.python.org/3/library/asyncio.html.
https://www.oreilly.com/ideas/5-things-to-watch-in-python-in-2017.
https://www.hartmannsoftware.com/Blog/Articles_from_Software_Fans/Most-Famous-SoftwarePrograms-Written-in-Python.

5

Kolanovic M., Krishnamachari R. Big Data and AI Strategies: Machine Learning and Alternative
Data Approach to Investing (J. P. Morgan, 2017).

6

https://www.infoworld.com/article/3170838/developer/get-paid-10-programming-languages-to-learnin-2017.html.
https://medium.com/@ChallengeRocket/top-10-of-programming-languages-with-the-highest-salariesin-2017-4390f468256e.

7

1.4. Библиотеки   59

программирования с открытым кодом для разработки статистических
приложений и визуализации. Сейчас Python и R — два наиболее широко
применяемых языка data science.

Anaconda
Мы будем использовать дистрибутив Python Anaconda — он легко устанавливается в Windows, macOS и Linux, поддерживая последние версии Python, интерпретатора IPython (раздел 1.5.1) и Jupyter Notebooks (раздел 1.5.3). Anaconda
также включает другие программные пакеты и библиотеки, часто используемые
в программировании Python и data science, что позволяет разработчикам сосредоточиться на коде Python и аспектах data science, не отвлекаясь на возню
с проблемами установки. Интерпретатор IPython1 обладает интересными возможностями, позволяющими проводить исследования и эксперименты с Python,
стандартной библиотекой Python и обширным набором сторонних библиотек.

Дзен Python
Мы придерживаемся принципов из статьи Тима Петерса (Tim Peters) «The Zen
of Python», в которой приведена квинтэссенция идеологии проектирования
от создателя Python Гвидо ван Россума. Этот список также можно просмотреть в IPython, воспользовавшись командой import this. Принципы «дзена
Python» определяются в предложении об улучшении языка Python (PEP,
Python Enhancement Proposal) 20, согласно которому «PEP — проектный
документ с информацией для сообщества Python или с описанием новой возможности Python, его процессов или окружения»2.

1.4. Библиотеки
В этой книге мы будем по возможности пользоваться существующими библио­
теками (включая стандартные библиотеки Python, библиотеки data science
и некоторые сторонние библиотеки) — их применение способствует повышению эффективности разработки программных продуктов. Так, вместо того
чтобы писать большой объем исходного кода (дорогостоящий и длительный
процесс), можно просто создать объект уже существующего библиотечного
класса посредством всего одной команды Python. Библиотеки позволяют
решать серьезные задачи с минимальным объемом кода.
1
2

https://ipython.org/.
https://www.python.org/dev/peps/pep-0001/.

60   Глава 1. Компьютеры и Python

1.4.1. Стандартная библиотека Python
Стандартная библиотека Python предоставляет богатую функциональность
обработки текстовых/двоичных данных, математических вычислений, программирования в функциональном стиле, работы с файлами/каталогами,
хранения данных, сжатия/архивирования данных, криптографии, сервисных
функций операционной системы, параллельного программирования, межпроНЕКОТОРЫЕ МОДУЛИ СТАНДАРТНОЙ БИБЛИОТЕКИ PYTHON,
ИСПОЛЬЗУЕМЫЕ В КНИГЕ
collections — дополнительные структуры данных помимо списков, кортежей,

словарей и множеств.

csv — обработка файлов с данными, разделенными запятыми.
datetime, time — операции с датой и временем.
decimal — вычисления с фиксированной и плавающей точкой, включая

финансовые вычисления.

doctest — простое модульное тестирование с использованием проверочных

тестов и ожидаемых результатов, закодированных в doc-строках.

json — обработка формата JSON (JavaScript Object Notation) для использо-

вания с веб-сервисами и базами данных документов NoSQL.

math — распространенные математические константы и операции.
os — взаимодействие с операционной системой.
queue — структура данных, работающая по принципу «первым зашел, первым

вышел» (FIFO).

random — псевдослучайные числа.
re — регулярные выражения для поиска по шаблону.
sqlite3 — работа с реляционной базой данных SQLite.
statistics — функции математической статистики (среднее, медиана, дисперсия, мода и т. д.).
string — обработка строк.
sys — обработка аргументов командной строки; потоки стандартного ввода,

стандартного вывода; стандартный поток ошибок.
timeit — анализ быстродействия.

1.4. Библиотеки   61

цессных взаимодействий, сетевых протоколов, работы с JSON/XML/другими
форматами данных в интернете, мультимедиа, интернационализации, построения графического интерфейса, отладки, профилирования и т. д. В таблице
выше перечислены некоторые модули стандартной библиотеки Python, которые будут использоваться в примерах.

1.4.2. Библиотеки data science
У Python сформировалось огромное и стремительно развивающееся сообщество
разработчиков с открытым кодом во многих областях. Одним из самых серьезных факторов популярности Python стала замечательная подборка библиотек,
разработанных сообществом с открытым кодом. Среди прочего мы стремились
создавать примеры и практические примеры, которые бы послужили увлекательным и интересным введением в программирование Python, но при этом
одновременно познакомили читателей с практическими аспектами data science,
ключевыми библиотеками data science и т. д. Вы не поверите, какие серьезные
задачи можно решать всего в нескольких строках кода. В следующей таблице
перечислены некоторые популярные библиотеки data science. Многие из них
будут использоваться нами в примерах. Для визуализации будут использоваться
Matplotlib, Seaborn и Folium, но существует и немало других (хорошая сводка
библиотек визуализации Python доступна по адресу http://pyviz.org/).
ПОПУЛЯРНЫЕ БИБЛИОТЕКИ PYTHON, ИСПОЛЬЗУЕМЫЕ В DATA SCIENCE
Научные вычисления и статистика
NumPy (Numerical Python) — в Python нет встроенной структуры данных массива. В нем используются списки — удобные, но относительно медленные.
NumPy предоставляет высокопроизводительную структуру данных ndarray
для представления списков и матриц, а также функции для обработки таких
структур данных.
SciPy (Scientific Python) — библиотека SciPy, встроенная в NumPy, добавляет
функции для научных вычислений: интегралы, дифференциальные уравнения, расширенная обработка матриц и т. д. Сайт scipy.org обеспечивает
поддержку SciPy и NumPy.
StatsModels — библиотека, предоставляющая функциональность оценки
статистических моделей, статистических критериев и статистического анализа данных.

62   Глава 1. Компьютеры и Python

Анализ и управление данными
pandas — чрезвычайно популярная библиотека для управления данными.
В pandas широко используется структура ndarray из NumPy. Ее две ключевые
структуры данных — Series (одномерная) и DataFrames (двумерная).
Визуализация
Matplotlib — библиотека визуализации и построения диаграмм с широкими
возможностями настройки. Среди прочего в ней поддерживаются обычные
графики, точечные диаграммы, гистограммы, контурные и секторные диаграммы, графики поля направлений, полярные системы координат, трехмерные диаграммы и текст.
Seaborn — высокоуровневая библиотека визуализации, построенная на базе
Matplotlib. Seaborn добавляет более качественное оформление и дополнительные средства визуализации, а также позволяет строить визуализации
с меньшим объемом кода.
Машинное обучение, глубокое обучение и обучение
с подкреплением
scikit-learn — ведущая библиотека машинного обучения. Машинное обучение является подмножеством области искусственного интеллекта, а глубокое
обучение — подмножеством машинного обучения, ориентированным на
нейронные сети.
Keras — одна из самых простых библиотек глубокого обучения. Keras
­работает на базе TensorFlow (Google), CNTK (когнитивный инструментарий Microsoft для глубокого обучения) или Theano (Монреальский университет).
TensorFlow — самая популярная библиотека глубокого обучения от Google.
TensorFlow использует графические процессоры или тензорные процессоры Google для повышения быстродействия. TensorFlow играет важную
роль в областях искусственного интеллекта и аналитики больших данных
с их огромными требованиями к вычислительным мощностям. Мы будем
использовать версию Keras, встроенную в TensorFlow.
OpenAI Gym — библиотека и среда для разработки, тестирования и сравнения алгоритмов обучения с подкреплением.

1.5. Первые эксперименты: использование IPython и Jupyter Notebook   63

Обработка естественного языка (NLP, Natural Language Processing)
NLTK (Natural Language Toolkit) — используется для задач обработки естественного языка (NLP).
TextBlob — объектно-ориентированная NLP-библиотека обработки текста,
построенная на базе библиотек NLTK и NLP с паттернами. TextBlob упрощает
многие задачи NLP.
Gensim — похожа на NLTK. Обычно используется для построения индекса для коллекции документов и последующего определения его схожести
с остальными документами в индексе.

1.5. Первые эксперименты: использование
IPython и Jupyter Notebook
В этом разделе мы опробуем интерпретатор IPython1 в двух режимах:
ØØВ интерактивном режиме будем вводить небольшие фрагменты кода

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

ширением .py (сокращение от Python). Такие файлы, называемые сценариями или программами, обычно имеют большую длину, чем фрагменты
кода, используемые в интерактивном режиме.
Затем вы научитесь использовать браузерную среду Jupyter Notebook для
написания и выполнения кода Python2.

1.5.1. Использование интерактивного режима IPython
как калькулятора
Попробуем использовать интерактивный режим IPython для вычисления
простых арифметических выражений.
1

2

Прежде чем читать этот раздел, выполните инструкции из раздела «Приступая к работе»
и установите дистрибутив Python Anaconda, содержащий интерпретатор IPython.
Jupyter поддерживает многие языки программирования, для чего следует установить соответствующее «ядро». За дополнительной информацией обращайтесь по адресу https://
github.com/jupyter/jupyter/wi.

64   Глава 1. Компьютеры и Python

Запуск IPython в интерактивном режиме
Сначала откройте окно командной строки в своей системе:
ØØВ macOS откройте Терминал из папки Utilities в папке Applications.
ØØВ Windows запустите командную строку Anaconda из меню Пуск.
ØØВ Linux откройте Терминал или командный интерпретатор своей системы

(зависит от дистрибутива Linux).
В окне командной строки введите команду ipython и нажмите Enter (или
Return). На экране появится сообщение вроде (зависит от платформы и версии IPython):
Python 3.7.0 | packaged by conda-forge | (default, Jan 20 2019, 17:24:52)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]:

Текст "In [1]:" — приглашение, означающее, что IPython ожидает вашего ввода. Вы можете ввести ? для получения справки или перейти непосредственно
к вводу фрагментов, чем мы сейчас и займемся.

Вычисление выражений
В интерактивном режиме можно вычислять выражения:
In [1]: 45 + 72
Out[1]: 117

In [2]:

После того как вы введете 45 + 72 и нажмете Enter, IPython читает фрагмент,
вычисляет его и выводит результат в Out[1]1. Затем IPython выводит приглашение In [2], означающее, что среда ожидает от вас ввода второго фрагмента.
Для каждого нового фрагмента IPython добавляет 1 к числу в квадратных
скобках. Каждое приглашение In [1] в книге указывает на то, что мы начали
новый интерактивный сеанс. Обычно мы будем делать это для каждого нового раздела главы.
1

В следующей главе вы увидите, что в некоторых ситуациях приглашение Out[] не выводится.

1.5. Первые эксперименты: использование IPython и Jupyter Notebook   65

Создадим более сложное выражение:
In [2]: 5 * (12.7 - 4) / 2
Out[2]: 21.75

В Python звездочка (*) обозначает операцию умножения, а косая черта (/) —
операцию деления. Как и в математике, круглые скобки определяют порядок
вычисления, так что сначала будет вычислено выражение в круглых скобках
(12.7 - 4) и получен результат 8.7. Затем вычисляется результат 5 * 8.7,
который равен 43.5. После этого вычисляется выражение 43.5 / 2, а полученный результат 21.75 выводится средой IPython в Out[2]. Числа, такие как
5, 4 и 2, называются целыми числами. Числа с дробной частью, такие как 12.7,
43.5 и 21.75, называются числами с плавающей точкой.

Выход из интерактивного режима
Чтобы выйти из интерактивного режима, можно воспользоваться одним из
нескольких способов:
ØØВвести команду exit в приглашении In [] и нажать Enter для немедлен-

ного выхода.
ØØНажать клавиши +d (или +d). На экране появляется пред-

ложение подтвердить выход "Do you really want to exit ([y]/n)?".
Квадратные скобки вокруг y означают, что этот ответ выбирается по
умолчанию — нажатие клавиши Enter отправляет ответ по умолчанию
и завершает интерактивный режим.
ØØНажать +d (или +d) дважды (только в macOS и Linux).

1.5.2. Выполнение программы Python
с использованием интерпретатора IPython
В этом разделе выполним сценарий с именем RollDieDynamic.py, который будет
написан в главе 6. Расширение .py указывает на то, что файл содержит исходный код Python. Сценарий RollDieDynamic.py моделирует бросок шестигранного
кубика. Он отображает цветную диаграмму с анимацией, которая в динамическом режиме отображает частоты выпадения всех граней.

66   Глава 1. Компьютеры и Python

Переход в папку с примерами этой главы
Сценарий находится в папке ch01 исходного кода книги. В разделе «Приступая
к работе» папка examples была распакована в папку Documents вашей учетной
записи пользователя. У каждой главы существует папка с исходным кодом
этой главы. Этой папке присвоено имя ch##, где ## — номер главы от 01 до
16. Откройте окно командной строки вашей системы и введите команду cd
(«change directory»), чтобы перейти в папку ch01:
ØØВ macOS/Linux введите команду cd
жмите Enter.

~/Documents/examples/ch01 и на-

ØØВ Windows введите команду cd
C:\Users\YourAccount\Documents\examples\ch01

и нажмите Enter.

Выполнение сценария
Чтобы выполнить сценарий, введите следующую команду в командной строке
и нажмите Enter:
ipython RollDieDynamic.py 6000 1

Сценарий открывает окно, в котором отображается его визуализация. Числа
6000 и 1 сообщают сценарию, сколько бросков должно быть сделано и сколько
кубиков нужно бросать каждый раз. В данном случае диаграмма будет обновляться 6000 раз по одному кубику за раз.
Частота для 51 броска

Частота для 4207 бросков

,

,

,

,

,

,

,

,
,
,

Значение каждой грани

Частота

Частота

,

Значение каждой грани

,

1.5. Первые эксперименты: использование IPython и Jupyter Notebook   67

Для шестигранного кубика значения от 1 до 6 выпадают с равной вероятностью — вероятность каждого результата равна 1/6, или около 16,667%. Если
бросить кубик 6000 раз, каждая грань выпадет около 1000 раз. Бросок кубиков,
как и бросок монетки, является случайным процессом, поэтому некоторые
грани могут выпасть менее 1000 раз, а другие — более 1000 раз. Мы сделали
несколько снимков экрана во время выполнения сценария. Сценарий использует случайно сгенерированные значения, поэтому ваши результаты будут
другими. Поэкспериментируйте со сценарием; попробуйте заменить значение
1 на 100, 1000 и 10 000. Обратите внимание: с увеличением количества бросков
частоты сходятся к величине 16,667%. В этом проявляется действие «закона
больших чисел».

Создание сценария
Обычно исходный код Python создается в редакторе для ввода текста. Вы
вводите программу в редакторе, вносите все необходимые исправления и сохраняете ее на своем компьютере. Интегрированные среды разработки (IDE,
Integrated Development Environments) предоставляют средства, обеспечивающие
поддержку всего процесса разработки программного обеспечения: редакторы,
отладчики для поиска логических ошибок, нарушающих работу программы,
и т. д. Среди популярных интегрированных сред Python можно выделить
Spyder (входящий в комплект Anaconda), PyCharm и Visual Studio Code.

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

68   Глава 1. Компьютеры и Python

1.5.3. Написание и выполнение кода
в Jupyter Notebook
Дистрибутив Anaconda, установленный вами в разделе «Приступая к работе», включает Jupyter Notebook — интерактивную браузерную среду, в которой можно писать и выполнять код, а также комбинировать его с текстом,
изображениями и видео. Документы Jupyter Notebook широко применяются
в сообществе data science в частности и в более широком научном сообществе
в целом. Они рассматриваются как предпочтительный механизм проведения
аналитических исследований данных и распространения воспроизводимых
результатов. Среда Jupyter Notebook поддерживает все больше языков программирования.
Для вашего удобства весь исходный код книги также предоставляется в формате документов Jupyter Notebook, которые вы можете просто загружать
и выполнять. В этом разделе используется интерфейс JupyterLab, который
позволяет управлять файлами документов Notebook и другими файлами,
используемыми в них (например, графическими изображениями и видео­
роликами). Как вы убедитесь, JupyterLab также позволяет легко писать
код, выполнять его, просматривать результаты, вносить изменения и снова
выполнять его.
Вы увидите, что программирование в Jupyter Notebook имеет много общего
с работой в IPython, более того, документы Jupyter Notebooks используют
IPython по умолчанию. В этом разделе вы создадите документ Notebook, добавите в него код из раздела 1.5.1 и выполните этот код.

Открытие JupyterLab в браузере
Чтобы открыть JupyterLab, перейдите в папку примеров ch01 архива примеров
в терминале, окне командной строки или приглашении Anaconda (раздел 1.5.2),
введите следующую команду и нажмите Enter (или Return):
jupyter lab

Команда запускает на вашем компьютере сервер Jupyter Notebook и открывает
JupyterLab в браузере по умолчанию; содержимое папки ch01 отображается на
вкладке File Browser
в левой части интерфейса JupyterLab:

1.5. Первые эксперименты: использование IPython и Jupyter Notebook   69

Сервер Jupyter Notebook позволяет загружать и выполнять документы Jupyter
Notebook в браузере. На вкладке JupyterLab Files вы можете сделать двойной
щелчок на файле, чтобы открыть его в правой части окна, где в настоящее
время отображается вкладка Launcher. Каждый файл, который вы открываете,
отображается на отдельной вкладке в этой части окна. Если вы случайно закроете браузер, то сможете повторно открыть JupyterLab — для этого достаточно
ввести в браузере следующий адрес:
http://localhost:8888/lab

Создание нового документа Notebook Jupyter
На вкладке Launcher в Notebook щелкните на кнопке Python 3, чтобы создать
новый документ Jupyter Notebook с именем Untitled.ipynb. В этом документе ­можно вводить и выполнять код Python 3. Расширение файла .ipynb является
сокращением от «IPython Notebook» — исходное название Jupyter Notebook.

70   Глава 1. Компьютеры и Python

Переименование документа Notebook
Переименуйте Untitled.ipynb в TestDrive.ipynb:
1. Щелкните правой кнопкой мыши на вкладку Untitled.ipynb и выберите
Rename Notebook.
2. Измените имя на TestDrive.ipynb и нажмите RENAME.
Верхняя часть JupyterLab должна выглядеть так:

Вычисление выражений
Рабочей единицей в документе Notebook является ячейка, в которой вводятся
фрагменты кода. По умолчанию новый документ Notebook состоит из одной
ячейки (прямоугольник в документе TestDrive.ipynb), но вы можете добавить
в нее и другие ячейки. Обозначение []: слева от ячейки показывает, где Jupyter
Notebook будет выводить номер фрагмента ячейки после ее выполнения.
Щелк­ните в ячейке и введите следующее выражение:
45 + 72

Чтобы выполнить код текущей ячейки, нажмите Ctrl + Enter (или control +
Enter). JupyterLab выполняет код в IPython, после чего выводит результаты
под ячейкой:

1.5. Первые эксперименты: использование IPython и Jupyter Notebook   71

Добавление и выполнение дополнительной ячейки
Попробуем вычислить более сложное выражение. Сначала щелкните на кнопке
+ на панели инструментов над первой ячейкой документа — она добавляет
новую ячейку под текущей:

Щелкните в новой ячейке и введите выражение
5 * (12.7 - 4) / 2

и выполните код ячейки комбинациейклавиш Ctrl + Enter (или control + Enter):

Сохранение документа Notebook
Если ваш документ Notebook содержит несохраненные изменения, то знак X
на вкладке заменяется на ●. Чтобы сохранить документ Notebook, откройте
меню File в JupyterLab (не в верхней части окна браузера) и выберите команду
Save Notebook.

72   Глава 1. Компьютеры и Python

Документы Notebook с примерами каждой главы
Для удобства примеры каждой главы также предоставляются в форме готовых к исполнению документов Notebook без вывода. Вы сможете проработать
их фрагмент за фрагментом и увидеть результат при выполнении каждого
фрагмента.
Чтобы показать, как загрузить существующий документ Notebook и выполнить
его ячейки, выполним сброс блокнота TestDrive.ipynb, чтобы удалить выходные
данные и номера фрагментов. Документ вернется в исходное состояние, аналогичное состоянию примеров последующих глав. В меню Kernel выберите коман­
ду Restart Kernel and Clear All Outputs…, затем щелкните на кнопке RESTART. Эта
команда также полезна в тех случаях, когда вы захотите повторно выполнить
фрагменты документа Notebook. Документ должен выглядеть примерно так:

В меню File выберите команду Save Notebook. Щелкните на кнопке X на вкладке
TestDrive.ipynb, чтобы закрыть документ.

Открытие и выполнение существующего документа Notebook
Запустив JupyterLab из папки примеров конкретной главы, вы сможете открывать документы Notebook из любой папки или любой из его вложенных
папок. Обнаружив нужный документ, откройте его двойным щелчком. Снова
откройте файл TestDrive.ipynb. После того как документ Notebook будет открыт,
вы сможете выполнить каждую его ячейку по отдельности, как это делалось
ранее в этом разделе, или же выполнить всю книгу сразу. Для этого откройте
меню Run и выберите команду Run All Cells. Все ячейки выполняются по порядку,
а выходные данные каждой ячейки выводятся под этой ячейкой.

Закрытие JupyterLab
Завершив работу с JupyterLab, закройте вкладку в браузере, а затем в терминале, командном интерпретаторе или приглашении Anaconda, из которого
была запущена оболочка JupyterLab, дважды нажмите Ctrl + c (или control + c).

1.6. Облачные вычисления и «интернет вещей»   73

Советы по работе с JupyterLab
Возможно, следующие советы пригодятся вам при работе с JupyterLab:
ØØЕсли требуется вводить и выполнять много фрагментов, то можно выполнить текущую ячейку и добавить новую комбинацией клавиш Shift + Enter
вместо Ctrl + Enter (или control + Enter).
ØØВ более поздних главах отдельные фрагменты, которые вы вводите в до-

кументах Jupyter Notebook, будут состоять из многих строк кода. Чтобы
в каждой ячейке выводились номера строк, откройте в JupyterLab меню
View и выберите команду Show line numbers.

О работе с JupyterLab
JupyterLab содержит много возможностей, которые будут полезны в вашей
работе. Мы рекомендуем ознакомиться с вводным курсом JupyterLab от создателей Jupyter по адресу:
https://jupyterlab.readthedocs.io/en/stable/index.html

Краткий обзор открывается по ссылке Overview в разделе GETTING STARTED.
В разделе USER GUIDE приведены вводные уроки по работе с интерфейсом
JupyterLab, работе с файлами, текстовым редактором, документами Notebook
и др.

1.6. Облачные вычисления и «интернет вещей»
1.6.1. Облачные вычисления
В наши дни все больше вычислений осуществляется «в облаке», то есть выполняется в распределенном виде в интернете по всему миру. Многие приложения, используемые в повседневной работе, зависят от облачных сервисов,
использующих масштабные кластеры вычислительных ресурсов (компьютеры, процессоры, память, дисковое пространство и т. д.) и баз данных, взаимодействующих по интернету друг с другом и с приложениями, которыми
вы пользуетесь. Сервис, доступ к которому предоставляется по интернету,
называется веб-сервисом. Как вы увидите, использование облачных сервисов
в Python часто сводится к простому созданию программных объектов и взаимодействию с ними. Созданные объекты используют веб-сервисы, которые
связываются с облаком за вас.

74   Глава 1. Компьютеры и Python
В примерах глав 11–16 вы будете работать со многими сервисами на базе
облака:
ØØВ главах 12 и 16 веб-сервисы Twitter будут использоваться (через библио-

теку Python Tweepy) для получения информации о конкретных пользователях Twitter, поиска твитов за последние семь дней и получения потока
твитов в процессе их появления (то есть в реальном времени).
ØØВ главах 11 и 12 библиотека Python TextBlob применяется для перевода

текста с одного языка на другой. Во внутренней реализации TextBlob использует веб-сервис Google Translate для выполнения перевода.
ØØВ главе 13 будут использоваться сервисы IBM Watson: Text to Speech,

Speech to Text и Translate. Мы реализуем приложение-переводчик, позволяющее зачитать вопрос на английском языке, преобразовать речь
в текст, переводить текст на испанский и зачитывать испанский текст. Затем пользователь произносит ответ на испанском (если вы не говорите на
испанском, используйте предоставленный аудиофайл), приложение преобразует речь в текст, переводит текст на английский и зачитывает ответ
на английском. Благодаря демонстрационным приложениям IBM Watson
мы также поэкспериментируем в главе 13 с некоторыми другими облачными сервисами Watson.
ØØВ главе 16 мы поработаем с сервисом Microsoft Azure HDInsight и други-

ми веб-сервисами Azure в ходе реализации приложений больших данных
на базе технологий Apache Hadoop и Spark. Azure — группа облачных сервисов от компании Microsoft.
ØØВ главе 16 веб-сервис Dweet.io будет использоваться для моделирования

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

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

1.6. Облачные вычисления и «интернет вещей»   75

Гибриды
Методология разработки гибридных приложений позволяет быстро разрабатывать мощные продукты за счет объединения веб-сервисов (часто бесплатных)
и других форм информационных поставок — как мы поступим при разработке
приложения-переводчика на базе IBM Watson.
Одно из первых гибридных приложений объединяло списки продаваемых
объектов недвижимости, предоставляемые сайтом http://www.craigslist.org, с картографическими средствами Google Maps; таким образом строились карты
с местоположением домов, продаваемых или сдаваемых в аренду в заданной
области.
На сайте ProgrammableWeb (http://www.programmableweb.com/) представлен каталог более 20 750 веб-сервисов и почти 8000 гибридных приложений. Также
здесь имеются учебные руководства и примеры кода работы с веб-сервисами
и создания собственных гибридных приложений. По данным сайта, к числу
наиболее часто используемых веб-сервисов принадлежат Facebook, Google
Maps, Twitter и YouTube.

1.6.2. «Интернет вещей»
Интернет уже нельзя рассматривать как сеть, объединяющую компьютеры, —
сегодня уже говорят об «интернете вещей», или IoT (Internet of Things). Вещью
считается любой объект, обладающий IP-адресом и способностью отправлять,
а в некоторых случаях и автоматически получать данные по интернету. Вот
несколько примеров:
ØØавтомобиль с транспондером для оплаты дорожных сборов;
ØØмониторы доступности места для парковки в гараже;
ØØкардиомониторы в человеческом организме;
ØØмониторы качества воды;
ØØинтеллектуальный датчик, сообщающий о расходе энергии;
ØØдатчики излучения;
ØØустройства отслеживания товаров на складе;
ØØмобильные приложения, способные отслеживать ваше местонахождение

и перемещения;

76   Глава 1. Компьютеры и Python
ØØумные термостаты, регулирующие температуру в комнате на основании

прогнозов погоды и текущей активности в доме;
ØØустройства для умных домов.

По данным statista.com, в наши дни уже используются свыше 23 миллиардов
IoT-устройств, а к 2025 году их число может превысить 75 миллиардов1.

1.7. Насколько велики большие данные?
Для специалистов по компьютерной теории и data science данные играют не
менее важную роль, чем написание программ. По данным IBM, в мире ежедневно создаются приблизительно 2,5 квинтиллиона байт (2,5 экзабайта)
данных2, а 90% мировых данных были созданы за последние два года3. По
материалам IDC, к 2025 году ежегодный объем глобального генерирования
данных достигнет уровня 175 зеттабайт (175 триллионов гигабайт, или
175 миллиардов терабайт) 4. Рассмотрим наиболее популярные единицы
объема данных.

Мегабайт (Мбайт)
Один мегабайт составляет приблизительно 1 миллион (а на самом деле 220)
байт. Многие файлы, используемыми нами в повседневной работе, занимают
один или несколько мегабайтов. Примеры:
ØØАудиофайлы в формате MP3 — минута качественного звука в формате

MP3 занимает от 1 до 2,4 Мбайт5.
ØØФотографии — фотография в формате JPEG, сделанная на цифровую ка-

меру, может занимать от 8 до 10 Мбайт.
ØØВидеоданные — на камеру смартфона можно записывать видео с разным

разрешением. Каждая минута видео может занимать много мегабайтов.
Например, на одном из наших iPhone приложение настроек камеры со1
2
3

4

5

https://www.statista.com/statistics/471264/iot-number-of-connected-devices-worldwide/.
https://www.ibm.com/blogs/watson/2016/06/welcome-to-the-world-of-a-i/.
https://public.dhe.ibm.com/common/ssi/ecm/wr/en/wrl12345usen/watson-customer-engagementwatson-marketing-wr-other-papers-and-reports-wrl12345usen-20170719.pdf.
https://www.networkworld.com/article/3325397/storage/idc-expect-175-zettabytes-of-data-worldwideby-2025.html.
https://www.audiomountain.com/tech/audio-file-size.html.

1.7. Насколько велики большие данные?   77

общало, что видео в разрешении 1080p при 30 кадрах в секунду (FPS) занимает 130 Мбайт/мин, а видео в разрешении 4K при 30 FPS занимает
350 Мбайт/мин.

Гигабайт (Гбайт)
Один гигабайт составляет приблизительно 1000 мегабайт (а точнее, 230 байт).
Двуслойный диск DVD способен вмещать до 8,5 Гбайт1, что соответствует:
ØØдо 141 часа аудио в формате MP3;
ØØприблизительно 1000 фотографий на 16-мегапиксельную камеру;
ØØприблизительно 7,7 минуты видео 1080p при 30 FPS, или
ØØприблизительно 2,85 минуты видео 4K при 30 FPS.

Современные диски наивысшей емкости Ultra HD Blu-ray вмещают до
100 Гбайт видеоданных2. Потоковая передача фильма в разрешении 4K может
происходить со скоростью от 7 до 10 Гбайт (с высокой степенью сжатия).

Терабайт (Тбайт)
Один терабайт составляет около 1000 гигабайт (а точнее, 240 байт). Емкость
современного диска для настольных компьютеров достигает 15 Тбайт3, что
соответствует:
ØØприблизительно 28 годам аудио в формате MP3;
ØØприблизительно 1,68 миллиона фотографий на 16-мегапиксельную ка­

меру;
ØØприблизительно 226 часов видео 1080p при 30 FPS;
ØØприблизительно 84 часов видео 4K при 30 FPS.

У Nimbus Data наибольший объем диска SSD составляет 100 Тбайт; он вмещает в 6,67 раза больше данных, чем приведенные выше примеры аудио-,
фото- и видеоданных для 15-терабайтных дисков4.

1
2
3
4

https://en.wikipedia.org/wiki/DVD.
https://en.wikipedia.org/wiki/Ultra_HD_Blu-ray.
https://www.zdnet.com/article/worlds-biggest-hard-drive-meet-western-digitals-15tb-monster/.
https://www.cinema5d.com/nimbus-data-100tb-ssd-worlds-largest-ssd/.

78   Глава 1. Компьютеры и Python

Петабайт, экзабайт и зеттабайт
Почти 4 миллиарда людей в интернете ежедневно создают около 2,5 квинтиллиона байт данных1, то есть 2500 петабайт (1 петабайт составляет около
1000 терабайт) или 2,5 экзабайта (1 экзабайт составляет около 1000 петабайт).
По материалам статьи из AnalyticsWeek за март 2016 года, через пять лет
к интернету будет подключено свыше 50 миллиардов устройств (в основном
через «интернет вещей», который рассматривается в разделах 1.6.2 и 16.8),
а к 2020 году на каждого человека на планете ежесекундно будет производиться 1,7 мегабайта новых данных2. Для современного уровня населения
(приблизительно 7,7 миллиарда людей3) это соответствует приблизительно:
ØØ13 петабайт новых данных в секунду;
ØØ780 петабайт в минуту;
ØØ46 800 петабайт (46,8 экзабайт) в час;
ØØ1123 экзабайта в день, то есть 1,123 зеттабайта (ЗБ) в день (1 зеттабайт

составляет около 1000 экзабайт).
Это эквивалентно приблизительно 5,5 миллиона часов (более 600 лет) видео
в разрешении 4K в день, или приблизительно 116 миллиардам фотографий
в день!

Дополнительная статистика по большим данным
На сайте https://www.internetlive-stats.com доступна различная занимательная
статистика, в том числе количество:
ØØпоисков в Google;
ØØтвитов;
ØØпросмотров роликов на YouTube;
ØØзагрузок фотографий на Instagram за сегодняшний день.

Вы можете получить доступ и к иной интересующей вас статистической информации. Например, по данным сайта видно, что за 2018 год создано более
250 миллиардов твитов.
1

2
3

https://public.dhe.ibm.com/common/ssi/ecm/wr/en/wrl12345usen/watson-customer-engagementwatson-marketing-wr-other-papers-and-reports-wrl12345usen-20170719.pdf.
https://analyticsweek.com/content/big-data-facts/.
https://en.wikipedia.org/wiki/World_population.

1.7. Насколько велики большие данные?   79

Приведем еще несколько интересных фактов из области больших данных:
ØØЕжечасно пользователи YouTube загружают 24 000 часов видео, а за день

на YouTube просматривается почти 1 миллиард часов видео1.
ØØКаждую секунду в интернете проходит объем трафика 51 773 Гбайт

(или 51,773 Тбайт), отправляется 7894 твита, проводится 64 332 поиска
в Google2.
ØØНа Facebook ежедневно ставятся 800 миллионов лайков3, отправляются

60 миллионов эмодзи4 и проводятся свыше 2 миллиардов поисков в более
чем 2,5 триллиона постов Facebook, накопившихся с момента появления
сайта5.
ØØКомпания Planet использует 142 спутника, которые раз в сутки фотографи-

руют весь массив суши на планете. Таким образом, ежедневно к имеющимся данным добавляется 1 миллион изображений и 7 Тбайт новых данных.
Компания вместе с партнерами применяет машинное обучение для повышения урожайности, контроля численности кораблей в портах и контроля
за исчезновением лесов. В том, что касается потери леса в джунглях Амазонки, Уилл Маршалл (Will Marshall), исполнительный директор Planet,
сказал: «Прежде мы могли обнаружить большую дыру на месте амазонских
джунглей лишь спустя несколько лет после ее образования. Сегодня возможно каждый день подсчитывать число деревьев, растущих на планете»6.
У Domo, Inc. есть хорошая инфографика «Данные никогда не спят 6.0», которая наглядно показывает, сколько данных генерируется каждую минуту7:
ØØ473 400 твитов;
ØØ2 083 333 фотографии публикуются в Snapchat;
ØØ97 222 часа видео просматриваются в сервисе Netflix;
ØØотправляются 12 986 111 текстовых сообщений;
ØØ49 380 сообщений добавляется в Instagram;
1
2
3
4
5
6

7

https://www.brandwatch.com/blog/youtube-stats/.
http://www.internetlivestats.com/one-second.
https://newsroom.fb.com/news/2017/06/two-billion-people-coming-together-on-facebook.
https://mashable.com/2017/07/17/facebook-world-emoji-day/.
https://techcrunch.com/2016/07/27/facebook-will-make-you-talk/.
https://www.bloomberg.com/news/videos/2017-06-30/learning-from-planet-s-shoe-boxed-sizedsatellites-video, 30 июня 2017 г.
https://www.domo.com/learn/data-never-sleeps-6.

80   Глава 1. Компьютеры и Python
ØØпо Skype проходят 176 220 звонков;
ØØв Spotify прослушиваются 750 000 песен;
ØØв Google проводятся 3 877 140 поисков;
ØØв YouTube просматриваются 4 333 560 видеороликов.

Непрерывный рост вычислительной мощности
Данных становится все больше и больше, а вместе с ними растет вычислительная мощность, необходимая для их обработки. Производительность современных компьютеров часто измеряется в количестве операций с плавающей
точкой в секунду (FLOPS). Вспомним, что в начале и середине 1990-х скорость
самых быстрых суперкомпьютеров измерялась в гигафлопсах (109 FLOPS).
К концу 1990-х компания Intel выпустила первые суперкомпьютеры с производительностью уровня терафлопс (1012 FLOPS). В начале и середине 2000-х
скорости достигали сотен терафлопс, после чего в 2008 году компания IBM
выпустила первый суперкомпьютер с производительностью уровня петафлопс (1015 FLOPS). В настоящее время самый быстрый суперкомпьютер IBM
Summit, расположенный в Оукриджской национальной лаборатории Министерства энергетики США, обладает производительностью в 122,3 петафлопс1.
Распределенные вычисления могут связывать тысячи персональных компьютеров в интернете для достижения еще более высокой производительности.
В конце 2016 года сеть Folding@home — распределенная сеть, участники
которой выделяют ресурсы своих персональных компьютеров для изучения
заболеваний и поиска новых лекарств2, — была способна достигать суммарной
производительности свыше 100 петафлопс. Такие компании, как IBM, в настоящее время работают над созданием суперкомпьютеров, способных достигать
производительности уровня экзафлопс (1018 FLOPS3).
Квантовые компьютеры, разрабатываемые в настоящее время, теоретически
могут функционировать на скорости, в 18 000 000 000 000 000 000 раз превышающей скорость современных «традиционных» компьютеров4! Это число
настолько огромно, что квантовый компьютер теоретически может выполнить
за одну секунду неизмеримо больше вычислений, чем было выполнено всеми
1
2
3
4

https://en.wikipedia.org/wiki/FLOPS.
https://en.wikipedia.org/wiki/Folding@home.
https://www.ibm.com/blogs/research/2017/06/supercomputing-weather-model-exascale/.
https://medium.com/@n.biedrzycki/only-god-can-count-that-fast-the-world-of-quantum-computing406a0a91fcf4.

1.7. Насколько велики большие данные?   81

компьютерами мира с момента появления первого компьютера. Эта почти
невообразимая вычислительная мощность ставит под угрозу технологию
криптовалют на базе технологии блокчейна (например, Bitcoin). Инженеры
уже работают над тем, как адаптировать блокчейн для столь значительного
роста вычислительных мощностей1.
Мощь суперкомпьютеров постепенно проникает из исследовательских лабораторий, где для достижения такой производительности тратятся огромные
суммы денег, в бюджетные коммерческие компьютерные системы и даже настольные компьютеры, ноутбуки, планшеты и смартфоны.
Стоимость вычислительной мощности продолжает снижаться, особенно с применением облачных вычислений. Прежде люди спрашивали: «Какой вычислительной мощностью должна обладать моя система, чтобы справиться с пиковой
нагрузкой?». Сегодня этот подход заменился другим: «Смогу ли я быстро получить в облаке ресурсы, временно необходимые для выполнения моих наиболее
насущных вычислительных задач?». Таким образом, все чаще вы платите только
за то, что фактически используется вами для решения конкретной задачи.

Обработка данных требует значительных затрат
электроэнергии
Объем данных от устройств, подключенных к интернету, стремительно растет,
а обработка этих данных требует колоссальных объемов энергии. Так, энергозатраты на обработку данных в 2015 году росли на 20% в год и составляли
приблизительно от 3 до 5% мирового производства энергии. Но к 2025 году
общие затраты на обработку данных должны достичь 20%2.
Другим серьезным потребителем электроэнергии является криптовалюта
Bitcoin на базе блокчейна. На обработку всего одной транзакции Bitcoin
расходуется примерно столько же энергии, сколько расходуется средним
американским домом за неделю! Расходы энергии обусловлены процессом,
который используется «майнерами» Bitcoin для подтверждения достоверности данных транзакции3.
1

2

3

https://singularityhub.com/2017/11/05/is-quantum-computing-an-existential-threat-to-blockchaintechnology/.
https://www.theguardian.com/environment/2017/dec/11/tsunami-of-data-could-consume-fifth-globalelectricity-by-2025.
https://motherboard.vice.com/en_us/article/ywbbpm/bitcoin-mining-electricity-consumption-ethereumenergy-climate-change.

82   Глава 1. Компьютеры и Python
По некоторым оценкам, за год транзакции Bitcoin расходуют больше энергии,
чем экономика многих стран1. Так, в совокупности Bitcoin и Ethereum (другая
популярная криптовалюта и платформа на базе блокчейна) расходуют больше
энергии, чем Израиль, и почти столько же, сколько расходует Греция2.
В 2018 году компания Morgan Stanley предсказывала, что «затраты электроэнергии на создание криптовалют в этом году могут превзойти прогнозируемые компанией глобальные затраты на электротранспорт в 2025 году»3.
Такая ситуация становится небезопасной, особенно с учетом огромного
интереса к блокчейновым технологиям даже за пределами стремительно
развивающихся криптовалют. Сообщество блокчейна работает над решением
проблемы4,5.

Потенциал больших данных
Вероятно, в ближайшие годы в области больших данных будет наблюдаться экспоненциальный рост. В перспективе количество вычислительных
устройств достигнет 50 миллиардов, и трудно представить, сколько еще их
появится за несколько ближайших десятилетий. Очень важно, чтобы всеми
этими данными могли пользоваться не только бизнес, правительственные
организации, военные, но и частные лица.
Интересно, что некоторых из лучших работ по поводу больших данных, data
science, искусственного интеллекта и т. д. были написаны в авторитетных
коммерческих организациях: J. P. Morgan, McKinsey и др. Привлекательность
больших данных для большого бизнеса бесспорна, особенно если принять
во внимание стремительно развивающиеся результаты. Многие компании
осуществляют значительные инвестиции в области технологий, описанных
в книге (большие данные, машинное обучение, глубокое обучение, обработка
естественных языков, и т. д.), и добиваются ценных результатов. Это заставляет конкурентов также вкладывать средства, что стремительно повышает
спрос на профессионалов с опытом работы в области больших данных и тео­
рии вычислений. По всей вероятности, эта тенденция сохранится в течение
многих лет.
1
2
3
4

5

https://digiconomist.net/bitcoin-energy-consumption.
https://digiconomist.net/ethereum-energy-consumption.
https://www.morganstanley.com/ideas/cryptocurrencies-global-utilities.
https://www.technologyreview.com/s/609480/bitcoin-uses-massive-amounts-of-energy-but-theres-aplan-to-fix-it/.
http://mashable.com/2017/12/01/bitcoin-energy/

1.7. Насколько велики большие данные?   83

1.7.1. Анализ больших данных
Аналитическая обработка данных — зрелая, хорошо проработанная научная
и профессиональная дисциплина. Сам термин «анализ данных» появился
в 1962 году1, хотя люди применяли статистику для анализа данных в течение
тысяч лет, еще со времен Древнего Египта2. Анализ больших данных следует
считать более современным явлением — так, термин «большие данные» появился около 2000 года3. Назовем четыре основные характеристики больших
данных, обычно называемые «четырьмя V»4,5:
1. Объем (Volume) — количество данных, производимых в мире, растет
с экспоненциальной скоростью.
2. Скорость (Velocity) — темпы производства данных, их перемещения
между организациями и изменения данных стремительно растут6,7,8.
3. Разнообразие (Variety) — некогда данные в основном были алфавитно-цифровыми (то есть состояли из символов алфавита, цифр, знаков
препинания и некоторых специальных знаков). В наши дни они также
включают графику, аудио, видео и данные от стремительно растущего
числа датчиков «интернета вещей», установленных в жилищах, коммерческих организациях, транспортных средствах, городах и т. д.
4. Достоверность (Veracity) — характеристика, указывающая на то, являются ли данными полными и точными, и, как следствие, позволяющая
ответить на вопросы вроде: «Можно ли на них полагаться при принятии
критических решений?», «Насколько реальна информация?» и пр.
Большая часть данных сейчас создается в цифровой форме, относится к самым
разным типам, достигает невероятных объемов и перемещается с потрясающей скоростью. Закон Мура и связанные с ним наблюдения позволяют нам
организовать экономичное хранение данных, ускорить их обработку и перемещение — и все это на скоростях, экспоненциально растущих со временем.
Хранилища цифровых данных обладают настолько огромной емкостью, низ1
2
3
4
5

6
7
8

https://www.forbes.com/sites/gilpress/2013/05/28/a-very-short-history-of-data-science/.
https://www.flydata.com/blog/a-brief-history-of-data-analysis/.
https://bits.blogs.nytimes.com/2013/02/01/the-origins-of-big-data-an-etymological-detective-story/.
https://www.ibmbigdatahub.com/infographic/four-vs-big-data.

Во многих статьях и публикациях в этот список добавляется много других «слов на
букву V».
https://www.zdnet.com/article/volume-velocity-and-variety-understanding-the-three-vs-of-big-data/.
https://whatis.techtarget.com/definition/3Vs.
https://www.forbes.com/sites/brentdykes/2017/06/28/big-data-forget-volume-and-variety-focus-on-velocity.

84   Глава 1. Компьютеры и Python
кой стоимостью и выдающейся компактностью, что мы сейчас можем удобно
и экономично хранить все создаваемые цифровые данные1. Таковы наиболее
существенные характеристики больших данных. Но давайте двинемся дальше.
Следующая цитата Ричарда Хемминга (Richard W. Hamming), пусть и относящаяся к 1962 году, задает тон для всей этой книги:
«Целью вычислений является понимание сути, а не числа»2.
Область data science производит новую, более глубокую, неочевидную и более
ценную аналитическую информацию в потрясающем темпе. Инфраструктура больших данных рассматривается в главе 16 на практических примерах
использования баз данных NoSQL, программирования Hadoop MapReduce,
Spark, потокового программирования IoT и т. д. Чтобы получить некоторое
представление о роли больших данных в промышленности, правительственных и научных организациях, взгляните на следующий график3:
http://mattturck.com/wp-content/uploads/2018/07/Matt_Turck_FirstMark_Big_Data_
Landscape_2018_Final.png

1.7.2. Data Science и большие данные изменяют
ситуацию: практические примеры
Область data science стремительно развивается, потому что она производит
важные результаты, которые действительно влияют на ситуацию. Некоторые
примеры практического применения data science и больших данных перечислены в таблице. Надеемся, эти примеры, а также некоторые другие примеры
из книги вдохновят читателей на поиск новых сценариев использования данных в работе. Аналитика больших данных приводила к повышению прибыли,
улучшению отношений с клиентами и даже к повышению процента побед
у спортивных команд при сокращении затрат на игроков4,5,6.
1

http://www.lesk.com/mlesk/ksg97/ksg.html. [К статье Майкла Леска (Michael Lesk) нас привела
статья: https://www.forbes.com/sites/gilpress/2013/05/28/a-very-short-history-of-data-science/.]

2

Hamming, R. W., Numerical Methods for Scientists and Engineers (New York, NY., McGraw
Hill, 1962). [К книге Хемминга и его цитате нас привела следующая статья: https://www.
forbes.com/sites/gilpress/2013/05/28/a-very-short-history-of-data-science/.]
Turck, M., and J. Hao, «Great Power, Great Responsibility: The 2018 Big Data & AI Landscape»,
http://mattturck.com/bigdata2018/.
Sawchik, T., Big Data Baseball: Math, Miracles, and the End of a 20-Year Losing Streak (New
York, Flat Iron Books, 2015).
Ayres, I., Super Crunchers (Bantam Books, 2007), с. 7–10.
Lewis, M., Moneyball: The Art of Winning an Unfair Game (W. W. Norton & Company, 2004).

3

4

5
6

1.7. Насколько велики большие данные?   85

Некоторые примеры практического применения data science
и больших данных
автоматизированное генерирование
подписей к изображениям
автоматизированное генерирование
субтитров
автоматизированные инвестиции
автоматизированные суда
автономные автомобили
агенты по обслуживанию клиентов
адаптированная диета
анализ пространственных данных
анализ социальных графов
анализ тональности высказываний
беспилотные летательные аппараты
борьба с похищением личных
данных
борьба с фишингом
ведение электронных историй
болезни
визуализация данных
визуальный поиск продуктов
выявление мошенничества
выявление рыночных тенденций
выявление спама
выявление сходства
генерирование музыки
геномика и здравоохранение
географические информационные
системы (GIS)
глубокий анализ данных
голосовой поиск
диагностика рака груди
диагностика сердечных заболеваний
диагностика/лечение рака
диагностическая медицина

динамическое построение маршрута
динамическое ценообразование
дистанционная медицина
игры
идентификация вызывающего
абонента
идентификация жертв стихийных
бедствий
иммунотерапия
индивидуализированная медицина
интеллектуальные системы управления
движением
«интернет вещей» (IoT) и мониторинг
медицинских устройств
«интернет вещей» (IoT) и прогнозы
погоды
картирование головного мозга
картография
качество обслуживания клиентов
кибербезопасность
классификация рукописного текста
личные помощники
маркетинг
маркетинговая аналитика
минимизация рисков
наем и тренерская работа в спорте
обнаружение аномалий
обнаружение вредоносных
программ
обнаружение новых вирусов
обнаружение эмоций
обслуживание клиентов
оценка заемщика
оценка недвижимости
оценка успеваемости студентов

86   Глава 1. Компьютеры и Python
перевод естественного языка
перевод иностранных языков
персонализация лекарственных
средств
персонализация покупок
повышение урожайности
поиск новых препаратов
поиск попутчиков
помощь инвалидам
построение рекомендуемых
маршрутов
построение сводки текста
предиктивный анализ
предотвращение вспышек болезней
предотвращение злоупотреблений
опиатами
предотвращение краж
предотвращение оттока клиентов
предотвращение террористических
актов
преступления: предиктивная
полицейская деятельность
преступления: предотвращение
преступления: прогнозирование
места
преступления: прогнозирование
рецидивизма
прогнозирование биржевого рынка
прогнозирование вспышек болезней
прогнозирование выживания
при раке
прогнозирование погоды
прогнозирование продаж, зависящих
от погоды
прогнозирование регистрации абитуриентов
прогнозирование результатов лечения
прогнозирование рисков автострахования

программы лояльности
профилактическая медицина
распознавание выражения лица
распознавание голоса
распознавание изображений
редактирование генома CRISPR
рекомендательные системы
роботизированные финансовые
советники
секвенирование человеческого
генома
системы GPS
складской учет
снижение частоты повторной госпитализации
сокращение выбросов углерода
сокращение загрязнения окружающей
среды
сокращение избыточного резервирования
сокращение энергопотребления
социальная аналитика
удержание клиентов
удовлетворение запросов потребителей
улучшение безопасности
улучшение результатов лечения
умные города
умные датчики
умные дома
умные помощники
умные термостаты
услуги на основе определения местоположения
установление страховых тарифов
фитнес-мониторинг
чтение языка знаков
экономика совместного потребления

1.8. Практический пример   87

1.8. Практический пример: использование
больших данных в мобильном приложении
Навигационное GPS-приложение Google Waze с его 90 миллионами активных
пользователей в месяц1 стало одним из самых успешных приложений, использующих большие данные. Ранние устройства и приложения GPS-навигации
использовали статические карты и координаты GPS для определения оптимального маршрута к конечной точке, но при этом не могли динамически
адаптироваться к изменяющейся дорожной обстановке.
Waze обрабатывает огромные объемы краудсорсинговых данных, то есть данных, непрерывно поставляемых пользователями и их устройствами по всему
миру. Поступающие данные анализируются для определения оптимального
маршрута к месту назначения за минимальное время. Для решения этой задачи
Waze использует подключение вашего смартфона к интернету. Приложение
автоматически отправляет обновленную информацию о местоположении
на свои серверы (при условии что вы разрешите это делать). Данные используются для динамического изменения маршрута на основании текущей
дорожной обстановки и для настройки карт. Пользователи также сообщают
другую информацию: о пробках, строительстве, препятствиях, транспорте на
аварийных полосах, полицейских постах, ценах на бензин и др. Обо всем этом
Waze затем оповещает других водителей.
Для предоставления своего сервиса Waze использует множество технологий. Ниже приведен список технологий, которые с большой вероятностью
используются этим сервисом (подробнее о некоторых из них см. главы 11–
16). Итак:
ØØМногие приложения, создаваемые в наши дни, используют и некоторые

программные продукты с открытым кодом. В этой книге мы будем пользоваться многими библиотеками и служебными программами с открытым кодом.
ØØWaze передает информацию через интернет. В наши дни такие данные

часто передаются в формате JSON (JavaScript Object Notation); мы представим этот формат в главе 9 и будем использовать его в последующих
главах. Данные JSON обычно скрываются от вас используемыми библио­
теками.

1

https://www.waze.com/brands/drivers/.

88   Глава 1. Компьютеры и Python
ØØWaze использует синтез речи, для того чтобы передавать пользователю

рекомендации и сигналы, и распознавание речи для понимания речевых
команд. Мы поговорим о функциональности синтеза и распознавания
речи IBM Watson в главе 13.
ØØПосле того как приложение Waze преобразует речевую команду на есте-

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

и карты). Также Waze предоставляет возможность взаимодействия с картами: перемещения, увеличения/уменьшения и т. д. Мы рассмотрим динамические визуализации (с использованием Matplotlib и Seaborn) и отображением интерактивных карт (с использованием Folium) в главах 12
и 16.
ØØWaze использует ваш мобильный телефон / смартфон как устройство по-

токовой передачи IoT. Каждое подобное устройство представляет собой
GPS-датчик, постоянно передающий данные Waze по интернету. В главе 16 мы представим IoT и будем работать с моделируемыми потоковыми
датчиками IoT.
ØØWaze получает IoT-потоки от миллионов устройств мобильной связи

одновременно. Приложение должно обрабатывать, сохранять и анализировать эти данные для обновления карт на ваших устройствах, для
отображения и речевого воспроизведения оповещений и, возможно,
для обновления указаний для водителя. Все это требует массово-параллельных возможностей обработки данных, реализованных на базе
компьютерных кластеров в облаке. В главе 16 мы представим различные инфраструктурные технологии больших данных для получения потоковых данных, сохранения этих больших данных в соответствующих
базах данных и их обработки с применением программ и оборудования,
обладающего массово-параллельными вычислительными возможностями.
ØØWaze использует возможности искусственного интеллекта для решения

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

1.9. Введение в data science   89
ØØВероятно, Waze хранит свою маршрутную информацию в графовой базе

данных. Такие базы данных способны эффективно вычислять кратчайшие
маршруты. Графовые базы данных, такие как Neo4J, будут представлены
в главе 16.
ØØМногие машины в наше время оснащаются устройствами, позволяющими

им «видеть» другие машины и препятствия. Например, они используются для реализации автоматизированных систем торможения и являются
ключевым компонентом технологии автономных («необитаемых») автомобилей. Вместо того чтобы полагаться на пользователей, сообщающих
о препятствиях и машинах, стоящих на обочине, навигационные приложения пользуются камерами и другими датчиками, применяют методы
распознавания изображений с глубоким обучением для анализа изображений «на ходу» и автоматической передачи информации об обнаруженных объектах. Глубокое обучение в области распознавания изображений
рассматривается в главе 15.

1.9. Введение в data science: искусственный
интеллект — на пересечении компьютерной
теории и data science
Когда ребенок впервые открывает глаза, «видит» ли он лица своих родителей?
Имеет ли он представление о том, что такое лицо, — или хотя бы о том, что
такое форма? Как бы то ни было, ребенок должен «познать» окружающий мир.
Именно этим занимается искусственный интеллект (AI, Artificial Intelligence).
Он обрабатывает колоссальные объемы данных и обучается по ним. Искусственный интеллект применяется для игр, для реализации широкого спектра
приложений распознавания изображений, управления автономными машинами, обучения роботов, предназначенных для выполнения новых операций,
диагностики медицинских состояний, перевода речи на другие языки практически в реальном времени, создания виртуальных собеседников, способных отвечать на произвольные вопросы на основании огромных баз знаний,
и многих других целей. Кто бы мог представить всего несколько лет назад,
что автономные машины с искусственным интеллектом появятся на дорогах,
став вскоре обыденным явлением? А сегодня в этой области идет острая конкуренция! Конечной целью всех этих усилий является общий искусственный
интеллект, то есть искусственный интеллект, способный действовать разумно
на уровне человека. Многим эта мысль кажется пугающей.

90   Глава 1. Компьютеры и Python

Вехи развития искусственного интеллекта
Несколько ключевых точек в развитии искусственного интеллекта особенно
сильно повлияли на внимание и воображение людей. Из-за них общественность начала думать о том, что искусственный интеллект становится реальностью, а бизнес стал искать возможности коммерческого применения AI:
ØØВ 1997 году в ходе шахматного поединка между компьютерной системой

IBM DeepBlue и гроссмейстером Гарри Каспаровым DeepBlue стал первым
компьютером, победившим действующего чемпиона мира в условиях турнира1. Компания IBM загрузила в DeepBlue сотни тысяч записей партий
гроссмейстеров2. Вычислительная мощь DeepBlue позволяла оценивать
до 200 миллионов ходов в секунду3! Это классический пример использования больших данных. К слову, по окончании состязания компания IBM
получила премию Фредкина, учрежденную Университетом Карнеги —
Меллона, который в 1980 году предложил 100 000 долларов создателю
первого компьютера, который победит действующего чемпиона мира по
шахматам4.
ØØВ 2011 году суперкомпьютер IBM Watson победил двух сильнейших игро-

ков в телевизионной викторине Jeopardy! в матче с призовым фондом
в 1 000 000 долларов. Watson одновременно использовал сотни методов
анализа языка для поиска правильных ответов в 200 миллионах страницах контента (включая всю «Википедию»), для хранения которых требовалось 4 терабайта56. Для тренировки Watson использовались методы машинного обучения и глубокого обучения7. В главе 13 обсуждается IBM
Watson, а в главе 14 — машинное обучение.
ØØГо — настольная игра, созданная в Китае тысячи лет назад8, — обычно счи-

талась одной из самых сложных из когда-либо изобретенных игр с 10170
потенциально возможными позициями на доске9. Чтобы представить, на1
2

https://en.wikipedia.org/wiki/Deep_Blue_versus_Garry_Kasparov.
https://ru.wikipedia.org/wiki/Deep_Blue.

3

Ibid.

4

https://articles.latimes.com/1997/jul/30/news/mn-17696.
https://www.techrepublic.com/article/ibm-watson-the-inside-story-of-how-the-jeopardy-winningsupercomputer-was-born-and-what-it-wants-to-do-next/.
https://en.wikipedia.org/wiki/Watson_(computer).
https://www.aaai.org/Magazine/Watson/watson.php, AI Magazine, осень 2010 г.
http://www.usgo.org/brief-history-go.
https://www.pbs.org/newshour/science/google-artificial-intelligence-beats-champion-at-worlds-mostcomplicated-board-game.

5

6
7
8
9

1.9. Введение в data science   91

сколько огромно это число, поясним, что, по некоторым оценкам, известная часть Вселенной содержит (всего лишь) от 1078 до 1087 атомов1,2! Программа AlphaGo, созданная группой DeepMind (принадлежит Google),
использовала методы глубокого обучения с двумя нейронными сетями
и одержала победу над чемпионом Европы Фан Хуэем. Заметим, го считается намного более сложной игрой, чем шахматы. Нейронные сети и глубокое обучение рассматриваются в главе 15.
ØØПозднее компания Google обобщила искусственный интеллект AlphaGo

для создания AlphaZero — игрового искусственного интеллекта, который
самостоятельно обучается играть в другие игры. В декабре 2017 года программа AlphaZero узнала правила и научилась играть в шахматы менее чем
за 4 часа, используя методы обучения с подкреплением. Затем программа
победила шахматную программу Stockfish 8, которая являлась чемпионом
мира, в матче из 100 партий, причем все партии завершились ее победой
или ничьей. После обучения го в течение всего 8 часов AlphaZero смогла
играть со своим предшественником AlphaGo и победила в 60 партиях из
1003.

Личные воспоминания
Один из авторов этой книги, Харви Дейтел, будучи студентом бакалавриата
Массачусетского технологического университета в середине 1960-х проходил
магистерский курс у Марвина Мински (Marvin Minsky), одного из основателей дисциплины искусственного интеллекта (AI). Вот что вспоминает об
этом Харви:
Профессор Мински раздавал курсовые проекты. Он предложил нам поразмышлять над тем, что такое интеллект и как заставить компьютер сделать что-то разумное. Наша оценка по этому курсу будет почти полностью
зависеть от этого проекта. Полная свобода выбора!
Я исследовал стандартизированные IQ-тесты, проводимые в учебных заведениях для оценки интеллектуальных способностей учеников. Будучи
математиком по своей природе, я решил взяться за часто встречающуюся
в IQ-тестах задачу по предсказанию следующего числа в серии чисел произ1
2
3

https://www.universetoday.com/36302/atoms-in-the-universe/.
https://en.wikipedia.org/wiki/Observable_universe#Matter_content.
https://www.theguardian.com/technology/2017/dec/07/alphazero-google-deepmind-ai-beatschampion-program-teaching-itself-to-play-four-hours.

92   Глава 1. Компьютеры и Python
вольной длины и сложности. Я использовал интерактивный Lisp, работающий на одной из ранних моделей DEC PDP-1, и моя программа предсказания
чисел справлялась с довольно сложными задачами, выходившими далеко за
рамки тех, что встречались в IQ-тестах. Возможности Lisp по рекурсивной
обработке списков произвольной длины были именно тем, что было нужно
для выполнения требований проекта. Замечу, Python также поддерживает
рекурсию и обобщенную работку со списками (глава 5).
Я опробовал программу на многих сокурсниках из MIT. Они выдумывали
числовые серии и вводили их в моей программе. PDP-1 некоторое время «думала» (иногда довольно долго) и почти всегда выдавала правильный ответ.
Но потом возникли трудности. Один из моих сокурсников ввел последовательность 14, 23, 34 и 42. Программа взялась за работу. PDP-1 долгое время
размышляла, но так и не смогла предсказать следующее число. Я тоже не
мог этого сделать. Сокурсник предложил мне немного подумать и пообещал открыть ответ на следующий день; он утверждал, что это простая
последовательность. Все мои усилия были напрасными.
На следующий день сокурсник сообщил, что следующее число — 57, но я не
понял почему. Он снова предложил подумать до завтра, а на следующий
день заявил, что следующее число — 125. Это нисколько не помогло, я был
в замешательстве. Видя это, сокурсник пояснил, что последовательность
состояла... из номеров сквозных улиц с двусторонним движением на Манхэттене. Я закричал: «Нечестно!», но он парировал — задача соответствует
заданному критерию. Я смотрел на мир с точки зрения математика — его
взгляд был шире. За прошедшие годы я опробовал эту последовательность на
друзьях,родственниках и коллегах. Несколько жителей Манхэттена дали
правильный ответ. Для таких задач моей программе было нужно нечто
большее, чем математические знания, — ей требовались (потенциально
огромные) знания о мире.
Watson и Big Data открывают новые возможности
Харви продолжает:
Когда мы с Полом начали работать над этой книгой о Python, нас сразу же
привлекла история о том, как суперкомпьютер IBM Watson использовал
большие данные и методы искусственного интеллекта (в частности, обработку естественного языка (NLP) и машинное обучение), чтобы одержать
победу над двумя сильнейшими игроками в Jeopardy! Мы поняли, что Watson,
вероятно, сможет решать такие задачи, потому что в его память были
загружены планы городов мира и много других подобных данных. Все это

1.10. Итоги   93

усилило наш интерес к глубокому изучению больших данных и современным
методам искусственного интеллекта и помогло сформировать структуру
глав 11–16 этой книги.
Следует заметить, что все практические примеры реализации data science
в главах 11–16 либо уходят корнями к технологиям искусственного интеллекта
либо описывают программы и оборудование больших данных, позволяющие
специалистам по компьютерной теории и data science эффективно реализовать
революционные решения на базе AI.

AI: область, в которой есть задачи, но нет решений
В течение многих десятилетий искусственный интеллект рассматривался как
область, в которой есть задачи, но нет решений. Дело в том, что после того,
как конкретная задача была решена, люди начинают говорить: «Что ж, это не
интеллект, а просто компьютерная программа, которая указывает компьютеру,
что нужно делать». Однако благодаря методам машинного обучения (глава 14)
и глубокого обучения (глава 15) мы уже не программируем компьютер для
решения конкретных задач. Вместо этого мы предлагаем компьютерам решать
задачи, обучаясь по данным, и обычно по очень большим объемам данных.
Для решения многих самых интересных и сложных задач применялись методы
глубокого обучения. Компания Google (одна из многих подобных себе) ведет
тысячи проектов глубокого обучения, причем их число быстро растет1,2. По
мере изложения материала книги мы расскажем вам о многих ультрасовременных технологиях искусственного интеллекта, больших данных и облачных
технологиях.

1.10. Итоги
В этой главе представлена терминология и концепции, закладывающие фундамент для программирования на языке Python, которому будут посвящены
главы 2–10, а также практические примеры применения больших данных,
искусственного интеллекта и облачных технологий (подробнее об этом
в главах 11–16). Мы обсудили концепции объектно-ориентированного программирования и выяснили, почему язык Python стал настолько популярным.
Также в этой главе рассмотрена стандартная библиотека Python и различные
1
2

http://theweek.com/speedreads/654463/google-more-than-1000-artificial-intelligence-projects-works.
https://www.zdnet.com/article/google-says-exponential-growth-of-ai-is-changing-nature-of-compute/.

94   Глава 1. Компьютеры и Python
библиотеки data science, избавляющие программистов от необходимости «изобретать велосипед». В дальнейшем эти библиотеки будут использоваться для
создания программных объектов, с которыми вы будете взаимодействовать
для решения серьезных задач при умеренном объеме кода. В главе рассмотрены и три примера, показывающие, как выполнять код Python с помощью
интерпретатора IPython и Jupyter Notebook. Мы представили облачные технологии и концепцию «интернета вещей» (IoT), закладывающие основу для
современных приложений, которые будут разрабатываться в главах 11–16.
Также мы обсудили, насколько велики «большие данные» и с какой скоростью
они становятся еще больше; рассмотрели пример использования больших
данных в мобильном навигационном приложении Waze, в котором многие
современные технологии задействованы для генерирования динамических
указаний по выбору маршрута, по возможности быстро и безопасно приводящих вас к конечной точке маршрута. Мы пояснили, в каких главах книги
используются многие из этих технологий. Глава завершается разделом «Введение в data science», в котором рассматривается ключевая область на стыке
компьютерной теории и data science — искусственный интеллект.

2
Введение
в программирование
Python
В этой главе…
•• Ввод фрагментов кода и немедленный просмотр результатов в интерактивном режиме IPython.
•• Написание простых команд и сценариев Python.
•• Создание переменных для хранения данных.
•• Знакомство со встроенными типами данных.
•• Арифметические операторы и операторы сравнения, их приоритеты.
•• Строки в одинарных, двойных и тройных кавычках.
•• Использование встроенной функции print для вывода текста.
•• Использование встроенной функции input для запроса данных с клавиатуры и получения этих данных для использования в программе.
•• Преобразование текста в целочисленные значения встроенной функцией int.
•• Использование операторов сравнения и команды if для принятия решений
о выполнении команды или группы команд.
•• Объекты и динамическая типизация в Python.
•• Получение типа объекта встроенной функцией type.

96   Глава 2. Введение в программирование Python

2.1. Введение
В этой главе представлены основы программирования на Python, а также
приведены примеры, демонстрирующие ключевые возможности языка. Предполагается, что вы прочитали раздел с описанием экспериментов с IPython
в главе 1, где представлен интерпретатор IPython и продемонстрированы его
возможности для вычисления простых арифметических выражений.

2.2. Переменные и команды присваивания
Напомним, мы использовали интерактивный режим IPython как калькулятор
для выражений:
In [1]: 45 + 72
Out[1]: 117

Теперь создадим переменную x для хранения целого числа 7:
In [2]: x = 7

Фрагмент [2] является командой (statement). Каждая команда определяет
выполняемую операцию. Предыдущая команда создает x и использует знак
равенства (=), для того чтобы назначить x значение. Большинство команд
завершается в конце строки, хотя команда также может охватывать сразу несколько строк. Следующая команда создает переменную y и присваивает ей
значение 3:
In [3]: y = 3

Теперь значения x и y могут использоваться в выражениях:
In [4]: x + y
Out[4]: 10

Вычисления в командах присваивания
Следующая команда складывает значения переменных x и y, присваивая результат переменной total, значение которой затем выводится:
In [5]: total = x + y
In [6]: total
Out[6]: 10

2.3. Арифметические операторы   97

Знак = не является оператором. Часть справа от знака = всегда выполняется
первой, после чего результат присваивается переменной слева от знака.

Стиль Python
Руководство по стилю для кода Python1 помогает писать код, соответствующий общепринятым соглашениям об оформлении кода Python. Руководство
рекомендует вставлять по одному пробелу с каждой стороны от знака присваивания = и бинарных операторов (таких как +), чтобы код лучше читался.

Имена переменных
Имя переменной, такое как x, является идентификатором. Каждый идентификатор может состоять из букв, цифр и символов подчеркивания (_), но не
может начинаться с цифры. В языке Python учитывается регистр символов,
так что идентификаторы Number и number считаются различными, потому что
один начинается с прописной буквы, а другой — со строчной.

Типы
Каждое значение в Python обладает типом, который сообщает, какие данные
представляет это значение. Чтобы получить информацию о типе значения,
воспользуйтесь встроенной функцией Python type:
In [7]: type(x)
Out[7]: int
In [8]: type(10.5)
Out[8]: float

Переменная x содержит целое число 7 (из фрагмента [2]), поэтому Python
выводит int (сокращение от «integer»). Значение 10.5 является числом с плавающей точкой, поэтому Python выводит строку float.

2.3. Арифметические операторы
В табл. 2.1 перечислены арифметические операторы, среди которых встречаются некоторые знаки, не используемые в алгебре.
1

https://www.python.org/dev/peps/pep-0008/.

98   Глава 2. Введение в программирование Python
Таблица 2.1. Арифметические операторы Python
Операция Python

Арифметический оператор

Алгебраическое
выражение

Выражение
Python

Сложение

+

f+7

f + 7

Вычитание



p–c

p - c

Умножение

*

b⋅m

b * m

Возведение в степень

**

xy

x ** y

Деление

/

x/y, или

Целочисленное
деление

//

Остаток от деления

%

x
y

, или x ÷ y

x / y

x

x // y

[x/y], или   , или [x ÷ y]
y
r mod s

r % s

Умножение (*)
В Python в качестве оператора умножения используется знак * (звездочка):
In [1]: 7 * 4
Out[1]: 28

Возведение в степень (**)
Оператор возведения в степень (**) возводит одно значение в степень, заданную другим значением:
In [2]: 2 ** 10
Out[2]: 1024

Для вычисления квадратного корня можно воспользоваться показателем
степени 1/2 (то есть 0.5):
In [3]: 9 ** (1 / 2)
Out[3]: 3.0

Деление (/) и деление с округлением (//)
Оператор деления (/) делит числитель на знаменатель; результатом является
число с плавающей точкой:

2.3. Арифметические операторы   99
In [4]: 7 / 4
Out[4]: 1.75

Операция целочисленного деления (//) делит числитель на знаменатель; результатом является наибольшее целое число, не превышающее результат. Python
отсекает дробную часть:
In [5]: 7 // 4
Out[5]: 1
In [6]: 3 // 5
Out[6]: 0
In [7]: 14 // 7
Out[7]: 2

При обычном делении деление –13 на 4 дает результат -3.25:
In [8]: -13 / 4
Out[8]: -3.25

Целочисленное деление дает ближайшее целое число, не большее -3.25, то
есть –4:
In [9]: -13 // 4
Out[9]: -4

Исключения и трассировка
Деление на нуль оператором / или // запрещено, а при попытке выполнения
такой операции происходит исключение (подробнее об исключениях см. главу 9) — признак возникшей проблемы:
In [10]: 123 / 0
------------------------------------------------------------------------ZeroDivisionError
Traceback (most recent call last)
in ()
----> 1 123 / 0
ZeroDivisionError: division by zero

Сообщая об исключении, Python выдает трассировку стека. В трассировке
указано, что произошло исключение типа ZeroDivisionError, — большинство
имен исключений заканчивается суффиксом Error. В интерактивном режиме
номер фрагмента, вызвавшего исключения, задается числом 10 в строке
in ()

100   Глава 2. Введение в программирование Python
Строка, начинающаяся с ---->, содержит код, приведший к исключению.
Иногда фрагменты содержат более одной строки кода — 1 справа от ---->
означает, что исключение возникло в строке 1 внутри фрагмента. Последняя
строка содержит имя возникшего исключения, за которым следует двоеточие (:) и сообщение об ошибке с расширенной информацией об исключении:
ZeroDivisionError: division by zero

Исключение также происходит при попытке использования еще не созданной
переменной. Следующий фрагмент пытается прибавить 7 к неопределенной
переменной z, что приводит к исключению NameError:
In [11]: z + 7
-----------------------------------------------------------------------NameError
Traceback (most recent call last)
in ()
----> 1 z + 7
NameError: name 'z' is not defined

Оператор вычисления остатка от деления
Оператор вычисления остатка от деления в языке Python (%) получает остаток от целочисленного деления левого операнда на правый:
In [12]: 17 % 5
Out[12]: 2

В данном случае при делении 17 на 5 мы получаем частное 3 и остаток 2. Этот
оператор чаще всего используется с целыми числами, но также может использоваться с другими числовыми типами:
In [13]: 7.5 % 3.5
Out[13]: 0.5

Линейная форма
Алгебраическая запись вида
a
b

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

2.3. Арифметические операторы   101

с использованием операторов Python. Приведенное выше выражение должно
быть записано в виде a / b (или a//b для целочисленного деления), чтобы все
операторы и операнды выстраивались в одну прямую линию.

Группировка выражений с использованием круглых скобок
Круглые скобки используются для группировки выражений Python, как это
происходит в алгебраических выражениях. Например, следующий код умножает на 10 результат выражения 5 + 3:
In [14]: 10 * (5 + 3)
Out[14]: 80

Без круглых скобок результат будет другим:
In [15]: 10 * 5 + 3
Out[15]: 53

Круглые скобки избыточны, если при их удалении будет получен тот же
результат.

Правила приоритета операторов
Python применяет операторы в арифметических выражениях с соблюдением
правил приоритета операторов. Обычно эти правила совпадают с правилами,
действующими в алгебре:
1. Выражения в круглых скобках вычисляются первыми, так что при помощи
круглых скобок можно обеспечить любой нужный вам порядок вычисления. Круглые скобки обладают наивысшим уровнем приоритета. В выражениях с вложенными круглыми скобками, например (a / (b - c)), сначала
выполняются внутренние выражения в круглых скобках, то есть (b - c).
2. Затем выполняются операции возведения в степень. Если выражение
содержит несколько операций возведения в степень, Python выполняет
их справа налево.
3. Затем выполняются операции умножения, деления и вычисления остатка. Если выражение содержит несколько операций умножения, деления,
целочисленного деления и вычисления остатка, Python применяет их
слева направо. Операции умножения, деления и вычисления остатка
имеют «одинаковые уровни приоритета».

102   Глава 2. Введение в программирование Python
4. Операции сложения и вычитания выполняются в последнюю очередь.
Если выражение содержит несколько операций сложения и вычитания,
Python применяет их слева направо. Сложение и вычитание тоже имеют
одинаковые уровни приоритета.
За полным списком операторов и их приоритетов (по возрастанию) обращайтесь по адресу:
https://docs.python.org/3/reference/expressions.html#operator-precedence

Группировка операторов
Говоря о том, что Python применяет некоторые операторы слева направо, мы
подразумеваем группировку операторов. Например, в выражении
a + b + c

операторы сложения (+) применяются слева направо, как если бы в выражении присутствовали круглые скобки (a + b) + c. Все операторы Python
с одинаковым приоритетом группируются слева направо, кроме операторов
возведения в степень (**), которые группируются справа налево.

Избыточные круглые скобки
Избыточные круглые скобки могут использоваться для группировки подвыражений, чтобы смысл выражения стал более понятным. Например, в квадратном многочлене
y = a * x ** 2 + b * x + c

можно для наглядности расставить круглые скобки:
y = (a * (x ** 2)) + (b * x) + c

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

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

2.4. Функция print и строки, заключенные в одинарные и двойные кавычки   103

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

2.4. Функция print и строки, заключенные
в одинарные и двойные кавычки
Встроенная функция print выводит свой аргумент(-ы) в строке текста:
In [1]: print('Welcome to Python!')
Welcome to Python!

В этом случае аргументом 'Welcome to Python!' является строка — последовательность символов, заключенная в одинарные кавычки ('). В отличие
от вычисления выражений в интерактивном режиме, перед выводимым
текстом не ставится префикс Out[1]. Кроме того, print не выводит кавычки,
в которые заключена строка, хотя мы скоро покажем, как выводить кавычки
в строках.
Строка также может быть заключена в двойные кавычки ("):
In [2]: print("Welcome to Python!")
Welcome to Python!

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

Вывод списка элементов, разделенных запятыми
Функция print также может получать список аргументов, разделенных запятыми:
In [3]: print('Welcome', 'to', 'Python!')
Welcome to Python!

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

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

Вывод многострочного текста одной командой
Если в строке встречается обратный слеш (\), она является управляющим
символом, а в сочетании с непосредственно следующим за ней символом образует управляющую последовательность. Так, комбинация \n представляет
управляющую последовательность для символа новой строки, который приказывает функции print переместить курсор вывода на следующую строку.
В следующем фрагменте для создания многострочного вывода используются
три символа новой строки:
In [4]: print('Welcome\nto\n\nPython!')
Welcome
to
Python!

Другие управляющие последовательности
В табл. 2.2 перечислены часто используемые управляющие последовательности.
Таблица 2.2. Наиболее часто используемые управляющие
последовательности Python
Управляющая
последовательность

Описание

\n

Вставляет в строку символ новой строки. При выводе для
каждого символа новой строки экранный курсор перемещается в начало следующей строки

\t

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

\\

Вставляет символ обратного слеша

\"

Вставляет символ двойной кавычки

\'

Вставляет символ одиночной кавычки

2.5. Строки в тройных кавычках   105

Игнорирование разрывов строк
Очень длинную строку (или длинную команду) также можно разбить при
выводе; если строка завершается символом \, то разрыв строки игнорируется:
In [5]: print('this is a longer string, so we \
...: split it over two lines')
this is a longer string, so we split it over two lines

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

Вывод значения в выражении
Вычисления также можно выполнять прямо в командах print:
In [6]: print('Sum is', 7 + 3)
Sum is 10

2.5. Строки в тройных кавычках
Ранее мы представили строки, заключаемые в одинарные кавычки (') или
в двойные кавычки ("). Строки в тройных кавычках начинаются и завершаются тремя двойными кавычками (""") или тремя одинарными кавычками
('''). Руководство по стилю для кода Python рекомендует использовать три
двойные кавычки ("""). Используйте их для создания:
ØØмногострочных строк;
ØØстрок, содержащих одинарные или двойные кавычки;
ØØdoc-строк — рекомендуемого способа документирования целей некото-

рых компонентов.

Включение кавычек в строки
Строка, заключенная в одинарные кавычки, можно содержать символы двойных кавычек:
In [1]: print('Display "hi" in quotes')
Display "hi" in quotes

106   Глава 2. Введение в программирование Python
но не одинарные кавычки:
In [2]: print('Display 'hi' in quotes')
File "", line 1
print('Display 'hi' in quotes')
^
SyntaxError: invalid syntax

если только вы не используете управляющую последовательность \':
In [3]: print('Display \'hi\' in quotes')
Display 'hi' in quotes

Во фрагменте [2] синтаксическая ошибка происходит из-за того, что в строке,
заключенной в одинарные кавычки, встречается одинарная кавычка. IPython
выводит информацию о строке кода, которая стала причиной синтаксической
ошибки, а позиция ошибки обозначается символом ^. Также выводится сообщение о недопустимом синтаксисе (SyntaxError: invalid syntax). Строка, заключенная в двойные кавычки, может содержать символы одинарных кавычек:
In [4]: print("Display the name O'Brien")
Display the name O'Brien

но не двойные кавычки, если только вы не используете управляющую последовательность \":
In [5]: print("Display \"hi\" in quotes")
Display "hi" in quotes

Чтобы обойтись без использования последовательностей \' и \" внутри строк,
такие строки можно заключить в тройные кавычки:
In [6]: print("""Display "hi" and 'bye' in quotes""")
Display "hi" and 'bye' in quotes

Многострочные строки
Следующий фрагмент присваивает многострочную строку, заключенную
в тройные кавычки, переменной triple_quoted_string:
In [7]: triple_quoted_string = """This is a triple-quoted
...: string that spans two lines"""

IPython знает, что строка остается незавершенной, пока до нажатия Enter
не будет введена закрывающая последовательность """. По этой причине

2.6. Получение ввода от пользователя   107

IPython выводит приглашение ...: , в котором можно ввести следующую
часть многострочной строки. Это продолжается до тех пор, пока вы не введете
последовательность """ и не нажмете Enter. Следующий фрагмент выводит
triple_quoted_string:
In [8]: print(triple_quoted_string)
This is a triple-quoted
string that spans two lines

Python хранит многострочные строки со встроенными символами новой строки. Если вы используете переменную triple_quoted_string для вычисления,
вместо того чтобы выводить ее, IPython выводит строку в одинарных кавычках
с символом \n в той позиции, где во фрагменте [7] была нажата клавиша Enter.
Кавычки, которые выводит IPython, показывают, что triple_quoted_string
является строкой, — они не входят в содержимое строки:
In [9]: triple_quoted_string
Out[9]: 'This is a triple-quoted\nstring that spans two lines'

2.6. Получение ввода от пользователя
Встроенная функция input запрашивает данные у пользователя и получает их:
In [1]: name = input("What's your name? ")
What's your name? Paul
In [2]: name
Out[2]: 'Paul'
In [3]: print(name)
Paul

Выполнение этого фрагмента происходит так:
ØØСначала функция input выводит свой строковый аргумент (подсказку),

чтобы пользователь знал, какие данные ему следует ввести, и ожидает ответа пользователя. Мы ввели строку Paul и нажали Enter. Полужирный
шрифт использован для того, чтобы отличить ввод пользователя от выводимого текста подсказки.
ØØЗатем функция input возвращает эти символы в виде строки, которая мо-

жет использоваться в программе. В данном случае строка присваивается
переменной name.

108   Глава 2. Введение в программирование Python
Фрагмент [2] выводит значение name. При вычислении name значение переменной выводится в одинарных кавычках в виде 'Paul', потому что это строка.
При выводе name (в фрагменте [3]) строка отображается без кавычек. Если
пользователь ввел кавычки, они становятся частью строки:
In [4]: name = input("What's your name? ")
What's your name? 'Paul'
In [5]: name
Out[5]: "'Paul'"
In [6]: print(name)
'Paul'

Функция input всегда возвращает строку
Рассмотрим следующие фрагменты, которые пытаются прочитать два числа
и сложить их:
In [7]: value1 = input('Enter first number: ')
Enter first number: 7
In [8]: value2 = input('Enter second number: ')
Enter second number: 3
In [9]: value1 + value2
Out[9]: '73'

Вместо того чтобы сложить числа 7 и 3 и получить 10, Python «складывает»
строковые значения '7' и '3', получая строку '73'. Такое слияние называется конкатенацией строк. Эта операция создает новую строку, состоящую
из значения левого операнда, за которым следует значение правого операнда.

Получение целого числа от пользователя
Если вам понадобится целое число, преобразуйте строку в число функцией int:
In [10]: value = input('Enter an integer: ')
Enter an integer: 7
In [11]: value = int(value)
In [12]: value
Out[12]: 7

2.7. Принятие решений: команда if и операторы сравнения   109

Код во фрагментах [10] и [11] можно объединить:
In [13]: another_value = int(input('Enter another integer: '))
Enter another integer: 13
In [14]: another_value
Out[14]: 13

Переменные value и another_value теперь содержат целые числа. При их суммировании будет получен целочисленный результат (вместо конкатенации):
In [15]: value + another_value
Out[15]: 20

Если строка, переданная int, не может быть преобразована в целое число, то
происходит ошибка ValueError:
In [16]: bad_value = int(input('Enter another integer: '))
Enter another integer: hello
------------------------------------------------------------------------ValueError
Traceback (most recent call last)
in ()
----> 1 bad_value = int(input('Enter another integer: '))
ValueError: invalid literal for int() with base 10: 'hello'

Функция int также может преобразовать значение с плавающей точкой
в целое число:
In [17]: int(10.5)
Out[17]: 10

Для преобразования строк в числа с плавающей точкой используется встроенная функция float.

2.7. Принятие решений: команда if
и операторы сравнения
Условие представляет собой логическое выражение со значением «истина»
(True) или «ложь» (False). Следующее условие сравнивает числа 7 и 4 и проверяет, какое из них больше:

110   Глава 2. Введение в программирование Python
In [1]: 7 > 4
Out[1]: True
In [2]: 7 < 4
Out[2]: False

True и False — ключевые слова Python. Использование ключевого слова в качестве идентификатора приводит к ошибке SyntaxError. Каждое из ключевых
слов True и False начинается с буквы верхнего регистра.

В табл. 2.3 перечислены операторы сравнения, часто используемые в условиях.
Таблица 2.3. Операторы сравнения, часто используемые Python в условиях
Алгебраический
оператор

Оператор Python

Пример условия

Смысл

>

>

x > y

x больше y

<

<

x < y

x меньше y



>=

x >= y

x больше или
равно y



= 4
^
SyntaxError: invalid syntax

Другая синтаксическая ошибка происходит при изменении порядка сим­
волов в операторах !=, >= и
и = number2:
print(number1, 'is greater than', number2)
if number1 = number2:
print(number1, 'is greater than or equal to', number2)

Enter
Enter
Enter
37 is
37 is
37 is

two integers and I will tell you the relationships they satisfy.
first integer: 37
second integer: 42
not equal to 42
less than 42
less than or equal to 42

Enter two integers and I will tell you the relationships they satisfy.
Enter first integer: 7
Enter second integer: 7
7 is equal to 7
7 is less than or equal to 7
7 is greater than or equal to 7
Enter
Enter
Enter
54 is
54 is
54 is

two integers and I will tell you the relationships they satisfy.
first integer: 54
second integer: 17
not equal to 17
greater than 17
greater than or equal to 17

Комментарии
Строка 1 начинается с символа # (решетка), который указывает, что остаток
строки представляет собой комментарий:
# fig02_01.py

2.7. Принятие решений: команда if и операторы сравнения   113

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

Doc-строки
В «Руководстве по стилю для кода Python» указано, что каждый сценарий
должен начинаться с doc-строки, объясняющей назначение сценария, как
в строке 2 приведенного листинга:
"""Сравнение целых чисел командами if и операторами сравнения."""

В более сложных сценариях doc-строка часто занимает несколько строк. В последующих главах doc-строки будут использоваться для описания компонентов сценариев, определяемых вами, — например, новых функций и новых типов, называемых классами. Также мы покажем, как обращаться к doc-строкам
из справочной системы IPython.

Пустые строки
Строка 3 остается пустой. Пустые строки и пробелы упрощают чтение кода.
Пустые строки, пробелы и символы табуляции совместно называются пропусками (white space). Python игнорирует большинство пропусков, — хотя,
как вы увидите, некоторые отступы необходимы.

Разбиение длинной команды по строкам
Строки 4–5
print('Enter two integers, and I will tell you',
'the relationships they satisfy.')

выводят инструкции для пользователя. Эти инструкции не помещаются в одной строке, поэтому мы разбили их на две части. Напомним, что print может
выводить несколько значений, которые передаются при вызове в списке, разделенном запятыми, — print отделяет каждое значение от следующего пробелом.
Обычно команды записываются по одной на строку. Длинную команду можно разбить на несколько строк при помощи символа продолжения \. Python
также позволяет разбивать длинные строки с круглыми скобками без использования символов продолжения (как в строках 4–5). Этот способ разбиения

114   Глава 2. Введение в программирование Python
строк считается предпочтительным согласно «Руководству по стилю для кода
Python». Всегда выбирайте точки разбивки, которые выглядят логично, — например, после запятой в предшествующем вызове print или перед оператором
в длинном выражении.

Получение целых значений от пользователя
Затем строки 8 и 11 используют встроенные функции input и int, для того
чтобы запросить и прочитать два целочисленных значения от пользователя.

Команды if
Команда if в строках 13–14
if number1 == number2:
print(number1, 'is equal to', number2)

использует оператор сравнения == для определения того, равны ли значения
переменных number1 и number2. Если они равны, то условие истинно (True),
и строка 14 выводит строку текста, которая сообщает, что значения равны.
Если какие-либо условия остальных команд if истинны (строки 16, 19, 22, 25
и 28), то соответствующая команда print выводит строку текста.
Каждая команда if состоит из ключевого слова if, проверяемого условия
и двоеточия (:), за которым следует снабженное отступом тело команды,
которое называется набором (suite). Каждый набор должен состоять из одной или нескольких команд. Заметьте, что пропущенное двоеточие (:) после
условия — весьма распространенная синтаксическая ошибка.

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

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

2.7. Принятие решений: команда if и операторы сравнения   115

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

Сцепленные сравнения
Объединение сравнений в цепочку позволяет проверить, принадлежит ли
значение некоторому диапазону. Следующее сравнение проверяет, принадлежит ли x диапазону от 1 до 5 включительно:
In [1]: x = 3
In [2]: 1 = 60:
...: print('Passed') # неправильный отступ
File "", line 2
print('Passed') # неправильный отступ
^
IndentationError: expected an indented block

Ошибка IndentationError также происходит и в том случае, если набор
содержит более одной команды, но эти команды имеют отступы разной
величины:
In [4]: if grade >= 60:
...:
print('Passed') # отступ в 4 пробела
...:
print('Good job!) # неправильный отступ в 2 пробела
File , line 3
print('Good job!) # неправильный отступ в 2 пробела
^
IndentationError: unindent does not match any outer indentation level

Иногда сообщения об ошибках выглядят невразумительно. Того факта, что
Python обращает ваше внимание на ту или иную строку, обычно достаточно
для того, чтобы вы поняли суть проблемы. Старайтесь последовательно применять соглашения об отступах в своем коде — программы с неравномерными
отступами труднее читать.

Любое выражение может интерпретироваться как True
или как False
Решения могут приниматься на основании любых выражений. Ненулевые
значения интерпретируются как True. Нуль интерпретируется как False.
In [5]: if 1:
...:
print('Nonzero values are true, so this will print')
...:
Nonzero values are true, so this will print
In [6]: if 0:
...:
print('Zero is false, so this will not print')
In [7]:

Строки, содержащие символы, интерпретируются как True; пустые строки ('',
"" или """""") интерпретируются как False.

3.4. Команды if…else и if…elif…else   125

Путаница с == и =
Использование оператора проверки равенства == вместо команды присваивания = может привести к коварным ошибкам. Например, в этом сеансе
фрагмент [1] определял переменную grade одновременно с присваиванием:
grade = 85

Если вместо этого случайно написать:
grade == 85

переменная grade окажется неопределенной, а вы получите сообщение об
ошибке NameError. Если переменная grade была определена до предшествующей команды, то grade == 85 просто дает результат True или False без выполнения присваивания, что является логической ошибкой.

3.4. Команды if…else и if…elif…else
Команда if…else выполняет разные наборы в зависимости от того, истинно
или ложно условие.
In [1]: grade = 85
In [2]: if grade >= 60:
...:
print('Passed')
...: else:
...:
print('Failed')
...:
Passed

Приведенное выше условие истинно, поэтому набор if выводит сообщение
'Passed'. Обратите внимание: если нажать Enter после ввода print('Passed'),
то IPython снабжает следующую строку отступом из четырех пробелов. Вы
должны удалить эти четыре пробела, чтобы набор else: правильно выравнивался по букве i в if.
Следующий код присваивает значение 57 переменной grade, а затем снова
включает команду if…else для демонстрации того, что набор else выполняется
только при ложном условии:

126   Глава 3. Управляющие команды
In [3]: grade = 57
In [4]: if grade >= 60:
...:
print('Passed')
...: else:
...:
print('Failed')
...:
Failed

Для перемещения между фрагментами текущего интерактивного сеанса используются клавиши ↑ и ↓. При нажатии Enter заново выполняется отображаемый фрагмент. Присвойте grade значение 99, дважды нажмите клавишу ↑,
чтобы вызвать код из фрагмента [4], после чего нажмите Enter, чтобы снова
выполнить этот код как фрагмент [6]. Каждому фрагменту, к которому возвращаетесь, присваивается новый идентификатор:
In [5]: grade = 99
In [6]: if grade >= 60:
...:
print('Passed')
...: else:
...:
print('Failed')
...:
Passed

Условные выражения
Иногда в наборах команды if…else переменной присваиваются разные значения в зависимости от условия:
In [7]: grade = 87
In [8]: if grade >= 60:
...:
result = 'Passed'
...: else:
...:
result = 'Failed'
...:

После этого можно вывести или получить значение этой переменной:
In [9]: result
Out[9]: 'Passed'

Такие команды, как в фрагменте [8], можно записать в виде компактного
условного выражения:

3.4. Команды if…else и if…elif…else   127
In [10]: result = ('Passed' if grade >= 60 else 'Failed')
In [11]: result
Out[11]: 'Passed'

Круглые скобки необязательны, но они ясно показывают, что команда присваивает значение условного выражения переменной result. Сначала Python
вычисляет условие grade >= 60:
ØØЕсли условие истинно, то фрагмент [10] присваивает result значение вы-

ражения слева от if, а именно 'Passed'. Часть else не выполняется.
ØØЕсли условие ложно, то фрагмент [10] присваивает result значение выра-

жения справа от else, а именно 'Failed'.
Условное выражение также можно напрямую выполнить в интерактивном
режиме:
In [12]: 'Passed' if grade >= 60 else 'Failed'
Out[12]: 'Passed'

Несколько команд в наборе
В следующем коде набор else команды if…else содержит две команды:
In [13]: grade = 49
In [14]: if grade >= 60:
...:
print('Passed')
...: else:
...:
print('Failed')
...:
print('You must take this course again')
...:
Failed
You must take this course again

В этом случае значение grade меньше 60, поэтому будут выполнены обе команды в наборе else.
Если второй вызов print не снабжен отступом, он не входит в набор else. Следовательно, эта команда будет выполняться всегда, что приведет к странному
и некорректному выводу:
In [15]: grade = 100
In [16]: if grade >= 60:
...:
print('Passed')

128   Глава 3. Управляющие команды
...: else:
...:
print('Failed')
...: print('You must take this course again')
...:
Passed
You must take this course again

Команда if…elif…else
Команда if…elif…else позволяет проверить несколько возможных вариантов.
Следующий код выводит «A» для значений grade, больших либо равных 90,
«B» — для значений из диапазона 80–89, «C» — для значений 70–79, «D» — для
значений 60–69 и «F» — для других значений. Выполняется только действие
для первого условия True. Фрагмент [18] выводит C, потому что значение
grade равно 77:
In [17]: grade = 77
In [18]:
...:
...:
...:
...:
...:
...:
...:
...:
...:
...:
C

if grade >= 90:
print('A')
elif grade >= 80:
print('B')
elif grade >= 70:
print('C')
elif grade >= 60:
print('D')
else:
print('F')

Первое условие — grade >= 90 — ложно, поэтому команда print('A') пропускается. Второе условие — grade >= 80 — тоже ложно, поэтому команда
print('B') пропускается. Третье условие — grade >= 70 — истинно, поэтому
команда print('C') выполняется. Затем весь оставшийся код в команде if…
elif…else пропускается. Конструкция if…elif…else работает быстрее серии
команд if, потому что проверка условий останавливается, как только будет
найдено истинное условие.

Секция else необязательна
Наличие секции else в команде if…elif…else необязательно. Ее присутствие
позволяет вам обработать значения, не удовлетворяющие никаким из условий.

3.5. Команда while   129

Если команда if…elif без else проверяет значение, с которым ни одно из
условий не будет истинным, то программа не выполняет ни один из наборов —
будет выполнена следующая команда после if…elif. Если секция else присутствует, то она должна следовать за последней секцией elif; в противном
случае происходит ошибка SyntaxError.

Логические ошибки
Код с неправильным отступом в фрагменте [16] является примером нефатальной логической ошибки. Код выполняется, но выдает неправильные результаты. При наличии фатальной логической ошибки в сценарии происходит
исключение (например, ошибка ZeroDivisionError при попытке деления на 0),
Python выдает трассировку стека и завершает сценарий. Фатальная ошибка
в интерактивном режиме завершает только текущий фрагмент — далее IPython
ожидает следующего ввода.

3.5. Команда while
Команда while позволяет повторить одно или несколько действий, пока условие остается истинным. Воспользуемся командой while для нахождения первой
степени 3, превышающей 50:
In [1]: product = 3
In [2]: while product 2}

использует форматный спецификатор >2, чтобы показать, что значение года
должно быть выровнено по правому краю (>) поля ширины 2 — ширина поля
определяет количество позиций символов для вывода значения. Для года, состоящего из одной цифры (1–9), спецификатор формата >2 выводит пробел,
за которым следует значение, таким образом, это гарантирует правильное
выравнивание лет в первом столбце. Следующая диаграмма показывает, как
числа 1 и 10 форматируются с шириной поля 2:
ɲɢɪɢɧɚɩɨɥɹ
ɧɚɱɚɥɶɧɵɣɩɪɨɛɟɥ


 

Знак < выравнивает значение по левому краю.

3.12. Команды break и continue   143

Форматный спецификатор 10.2f в заполнителе
{amount:>10.2f}

форматирует amount как число с плавающей точкой (f ), выровненное по
правому краю (>) поля ширины 10, и двумя знаками в дробной части (.2).
Такое форматирование суммы обеспечивает вертикальное выравнивание сумм
по разделителю дробной части, как обычно делается с денежными суммами.
В десяти позициях вывода три крайних правых символа предназначены для
разделителя-точки и двух цифр дробной части. Остальные семь позиций заполняются начальными пробелами и цифрами целой части. В этом примере все
суммы состоят из четырех цифр, так что каждое число форматируется с тремя
начальными пробелами. На следующей диаграмме показано форматирование
для значения 1050.00:
ɲɢɪɢɧɚɩɨɥɹ
   

.  

ɧɚɱɚɥɶɧɵɟɩɪɨɛɟɥɵ
ɞɜɟɰɢɮɪɵɞɪɨɛɧɨɣɱɚɫɬɢ
ɪɚɡɞɟɥɢɬɟɥɶɞɪɨɛɧɨɣɱɚɫɬɢ

3.12. Команды break и continue
Команды break и continue изменяют логику выполнения цикла. Выполнение
команды break в while или for приводит к немедленному выходу из этой
команды. В следующем коде range генерирует целочисленную последовательность 0–99, но цикл завершается при достижении number значения 10:
In [1]: for number in range(100):
...:
if number == 10:
...:
break
...:
print(number, end=' ')
...:
0 1 2 3 4 5 6 7 8 9

В сценарии выполнение продолжится со следующей команды после цикла for.
Каждая из команд while и for может содержать необязательную секцию else,
которая выполняется только в том случае, если цикл завершается нормально,
то есть не в результате break.
При выполнении команды continue в цикле while или for оставшаяся часть
набора цикла пропускается. В цикле while затем проверяется условие для

144   Глава 3. Управляющие команды
определения того, должен ли цикл продолжаться. В цикле for обрабатывается
следующий элемент последовательности (если он есть):
In [2]: for number in range(10):
...:
if number == 5:
...:
continue
...:
print(number, end=' ')
...:
0 1 2 3 4 6 7 8 9

3.13. Логические операторы and, or или not
Условные операторы >, =, = 60. Для построения более сложных условий, объединяющих
несколько простых условий, используются операторы and, or или not.

Логический оператор and
Чтобы проверить истинность сразу двух условий перед выполнением набора
управляющей команды, используйте логический оператор and. В следующем
коде определяются две переменные, после чего проверяется условие, которое
истинно только в случае истинности обоих простых условий: если хотя бы одно
(или оба) простое условие ложно, то все выражение and тоже ложно:
In [1]: gender = 'Female'
In [2]: age = 70
In [3]: if gender == 'Female' and age >= 65:
...:
print('Senior female')
...:
Senior female

Команда if содержит два простых условия, gender == 'Female' и age >= 65.
Простое условие слева от оператора and вычисляется первым, потому что ==
имеет более высокий приоритет, чем and. При необходимости следующим
вычисляется простое условие справа от and, потому что >= обладает более
высоким приоритетом, чем and. (Вскоре мы объясним, почему правая сторона
оператора and вычисляется только в случае истинности левой стороны.) В совокупности условие команды if истинно в том и только том случае, если оба
простых условия также истинны. Объединенное условие можно сделать более
понятным при помощи дополнительных круглых скобок:
(gender == 'Female') and (age >= 65)

3.13. Логические операторы and, or или not    145

В табл. 3.3 приведена сводка всех возможных комбинаций значений True
и False для компонентов выражение1 и выражение2 — такие таблицы называются таблицами истинности:
Таблица 3.3. Сводка комбинаций значений True и False для компонентов
«выражение1» и «выражение2»
выражение1

выражение2

выражение1 and выражение2

False

False

False

False

True

False

True

False

False

True

True

True

Логический оператор or
Логический оператор or проверяет, что одно или оба условия истинны. Следующий пример проверяет условие, которое истинно в случае истинности хотя
бы одного из простых условий — все условие ложно только при ложности обоих
простых условий:
In [4]: semester_average = 83
In [5]: final_exam = 95
In [6]: if semester_average >= 90 or final_exam >= 90:
...:
print('Student gets an A')
...:
Student gets an A

Фрагмент [6] также содержит два простых условия, semester_average >= 90
и final_exam >= 90. В табл. 3.4 приведена сводка для логического оператора or.
Оператор and обладает более высоким приоритетом, чем or.
Таблица 3.4. Сводка для логического оператора or
выражение1

выражение2

выражение1 or выражение2

False

False

False

False

True

True

True

False

True

True

True

True

146   Глава 3. Управляющие команды

Повышение быстродействия за счет
ускоренного вычисления
Python прерывает вычисление выражения and, как только определит, что все
условие ложно, а вычисление выражения or — как только определит, что все
условие истинно. Это называется ускоренным вычислением. Таким образом,
обработка условия
gender == 'Female' and age >= 65

немедленно прерывается, если переменная gender не равна 'Female',потому
что все выражение заведомо ложно. Если же переменная gender равна 'Female',
то вычисление продолжается, потому что все выражение будет истинным только
в случае, если переменная age больше или равна 65.
Аналогичным образом обработка условия
semester_average >= 90 or final_exam >= 90

немедленно прерывается, если переменная semester_average больше или
равна 90, потому что все выражение заведомо истинно. Если же переменная semester_average меньше 90, то выполнение продолжается, потому
что выражение будет истинным, если переменная final_exam больше или
равна 90.
Выражения, использующие and, следует строить так, чтобы выражение, которое с большей вероятностью будет ложным, располагалось в левой части.
В выражениях or в левой части должно располагаться условие, которое
с большей вероятностью будет истинным. Эти методы позволяют сократить
время выполнения программы.

Логический оператор not
Логический оператор not «инвертирует» смысл условия — True превращается
в False, а False превращается в True. Это унарный оператор, то есть он имеет
только один операнд. Поставив оператор not перед условием, вы выберете
ветвь выполнения, в которой исходное условие (без оператора not) ложно, как
в следующем примере:
In [7]: grade = 87
In [8]: if not grade == -1:

3.13. Логические операторы and, or или not    147
...:
print('The next grade is', grade)
...:
The next grade is 87

Часто можно обойтись без not, выражая условие более «естественным» или
удобным способом. Например, предыдущая команда if может быть записана
в следующем виде:
In [9]: if grade != -1:
...:
print('The next grade is', grade)
...:
The next grade is 87

Ниже приведена таблица истинности оператора not (табл. 3.5).
Таблица 3.5. Таблица истинности оператора not
выражение1

not выражение

False

True

True

False

В табл. 3.6 приведены приоритеты и варианты группировки операторов, упоминавшихся до настоящего момента. Операторы перечисляются сверху вниз
в порядке убывания приоритета.
Таблица 3.6. Приоритеты и варианты группировки операторов
Операторы

Группировка

()

слева направо

**

справа налево

*

/

+



<



>= == !=

слева направо

not

слева направо

and

слева направо

or

слева направо

148   Глава 3. Управляющие команды

3.14. Введение в data science: параметры,
характеризующие положение центра
распределения, — математическое ожидание,
медиана и мода
Продолжим обсуждение применения статистики для анализа данных с несколькими дополнительными описательными статистическими параметрами,
включая:
ØØматематическое ожидание — среднее значение среди набора значений;
ØØмедиану — среднее значение при расположении значений в порядке сор­

тировки;
ØØмоду — наиболее часто встречающееся значение.

Существуют и другие параметры, характеризующие положение центра
распределения, каждый из них генерирует одно значение, представляющее
«центральное» значение в множестве значений, то есть значение, которое
в определенном смысле типично для множества.
Вычислим математическое ожидание, медиану и моду для списка целых чисел.
Следующий сеанс создает список с именем grades, использует встроенные
функции sum и len для вычисления математического ожидания «вручную»:
sum вычисляет сумму оценок (397), а len возвращает их количество (5):
In [1]: grades = [85, 93, 45, 89, 85]
In [2]: sum(grades) / len(grades)
Out[2]: 79.4

В главе 2 упоминались статистические показатели количества и суммы — они
реализованы в Python в виде встроенных функций len и sum. Как и функции
min и max (представленные в главе 2), sum и len являются примерами свертки
из области программирования в функциональном стиле — они сворачивают
коллекцию значений в одно значение, сумму этих значений и количество
значений соответственно. В примере вычисления средней оценки из раздела 3.8 можно было бы удалить строки 10–15 из сценария и заменить average
в строке 16 вычислением из фрагмента [2].

3.14. Введение в data science   149

Модуль statistics из стандартной библиотеки Python предоставляет функции для вычисления математического ожидания, медианы и моды — все эти
характеристики тоже являются свертками. Чтобы использовать эти средства,
сначала импортируйте модуль statistics:
In [3]: import statistics

После этого вы сможете обращаться к функциям модуля: укажите префикс
statistics и имя вызываемой функции. Следующий фрагмент вычисляет
математическое ожидание, медиану и моду для списка оценок, используя для
этого функции mean, median и mode модуля statistics:
In [4]: statistics.mean(grades)
Out[4]: 79.4
In [5]: statistics.median(grades)
Out[5]: 85
In [6]: statistics.mode(grades)
Out[6]: 85

Аргументом каждой функции должен быть итерируемый объект — в данном
случае список оценок. Чтобы убедиться в правильности медианы и моды,
можно воспользоваться встроенной функцией sorted для получения списка
оценок, упорядоченных по возрастанию:
In [7]: sorted(grades)
Out[7]: [45, 85, 85, 89, 93]

Список grades содержит нечетное количество значений (5), поэтому median
возвращает среднее значение (85). Если список значений в списке имеет четное количество элементов, то median возвращает среднее для двух средних
значений. Проверяя отсортированные значения, можно убедиться в том, что
мода равна 85, потому что это значение встречается чаще других (дважды).
Функция mode выдает ошибку StatisticsError для списков вида
[85, 93, 45, 89, 85, 93]

в которых встречаются два и более «самых частых» значения. Такой набор
значений называется бимодальным. В данном случае каждое из значений 85
и 93 встречается дважды.

150   Глава 3. Управляющие команды

3.15. Итоги
В этой главе рассматривались управляющие команды Python, включая if,
if…else, if…elif…else, while, for, break и continue. Как было показано,
команда for выполняет повторения, управляемые последовательностью, —
она обрабатывает все элементы итерируемого объекта (например, диапазона
целых чисел, строки или списка). Встроенная функция range использовалась
для генерирования последовательностей от 0 до значения своего аргумента,
исключая последний, и для определения количества итераций команды for.
Мы использовали повторение, управляемое контрольным значением, с командой while для создания цикла, который продолжает выполняться, пока не
будет обнаружено контрольное значение. Версия встроенной функции range
с двумя аргументами используется для генерирования последовательностей
целых чисел от значения первого аргумента до значения второго аргумента,
исключая последний. Также встречалась версия с тремя аргументами, в которой третий аргумент задает приращение между целыми числами диапазона.
Далее был описан тип Decimal для точных финансовых вычислений, который
был применен для вычисления сложного процента. Форматные строки и различные спецификаторы были использованы для создания отформатированного вывода. Мы представили команды break и continue для изменения последовательности выполнения в циклах. Также были рассмотрены логические
операторы and, or или not для создания условий, объединяющих несколько
простых условий.
Наконец, наше обсуждение описательной статистики продолжилось на характеристиках, описывающих положение центра распределения, — математическом ожидании, медиане и моде, — и вычислении их при помощи функций
модуля statistics стандартной библиотеки Python.
В следующей главе мы перейдем к созданию пользовательских функций
и использованию существующих функций из модулей Python math и random.
В ней будут продемонстрированы некоторые заранее определенные свертки
и другие средства функционального программирования.

4
Функции
В этой главе…
•• Создание пользовательских функций.
•• Импортирование и использование модулей стандартной библиотеки Python
(таких как random и math) для повторного использования кода и предотвращения необходимости «изобретения велосипеда».
•• Передача данных между функциями.
•• Генерирование диапазона случайных чисел.
•• Методы моделирования, основанные на генерировании случайных чисел.
•• Инициализация генератора случайных чисел для обеспечения воспроизводимости.
•• Упаковка значений в кортеж и распаковка значений из кортежа.
•• Возвращение нескольких значений из функции в кортеже.
•• Область видимости идентификатора и определение того, где он может использоваться.
•• Создание функций со значениями параметров по умолчанию.
•• Вызов функций с ключевыми аргументами.
•• Создание функций, которые могут получать любое количество аргументов.
•• Использование методов объекта.
•• Написание и использование рекурсивных функций.

152   Глава 4. Функции

4.1. Введение
В этой главе изучение основ Python продолжится в направлении пользовательских функций и сопутствующих тем. Мы воспользуемся модулем
random стандартной библиотеки Python и генератором случайных чисел для
моделирования бросков шестигранного кубика. Пользовательские функции
и генерирование случайных чисел будут использованы в сценарии, реализующем азартную игру «крэпс». В этом примере также будет представлен кортеж — новый тип последовательности Python; кортежи будут использованы
для возвращения нескольких значений из функции. Также в этой главе рассматривается инициализация генератора случайных чисел для обеспечения
воспроизводимости.
Мы импортируем модуль math стандартной библиотеки Python, а затем используем его для изучения функциональности автозаполнения IPython,
ускоряющей процессы программирования и обучения. Мы создадим функции
со значениями параметров по умолчанию, исследуем вызов функций с ключевыми аргументами и определение функций с произвольными списками
аргументов. Также мы продемонстрируем вызов методов объектов и поговорим
о том, как область видимости идентификатора определяет возможности его
использования в тех или иных компонентах программы.
Далее процесс импортирования модулей будет исследован более основательно.
Вы узнаете, как происходит передача аргументов функциям по ссылке. Затем
мы рассмотрим рекурсивную функцию и начнем знакомство с поддержкой
программирования в функциональном стиле Python.
В разделе «Введение в data science» продолжится обсуждение описательных статистических характеристик. В нем будут рассмотрены дисперсионные ­характеристики — дисперсия и стандартное отклонение — и вычисления
их при помощи функций из модуля statistics стандартной библиотеки
Python.

4.2. Определение функций
В предыдущих главах вызывались многие встроенные функции (int, float,
print, input, type, sum, len, min и max), а также некоторые функции из модуля
statistics (mean, median и mode). Каждая функция выполняла конкретную задачу.
В программах часто определяются и вызываются пользовательские функции.
В следующем сеансе определяется функция square, вычисляющая квадрат

4.2. Определение функций   153

своего аргумента. Затем функция вызывается дважды — для значения 7 типа
int (вызов дает значение 49 типа int) и для значения 2.5 типа float (вызов
дает значение с плавающей точкой 6.25):
In [1]: def square(number):
...:
"""Вычисление квадрата числа."""
...:
return number ** 2
...:
In [2]: square(7)
Out[2]: 49
In [3]: square(2.5)
Out[3]: 6.25

Команды из определения функции в первом фрагменте написаны один
раз, но они могут вызываться «для выполнения своей операции» в разных
точках программы, причем это можно делать многократно. Вызов square
с нечисловым аргументом (например, 'hello') вызовет ошибку TypeError,
потому что оператор возведения в степень (**) работает только с числовыми
значениями.

Определение пользовательской функции
Определение функции (например, square из фрагмента [1]) начинается с ключевого слова def, за которым следует имя функции square в круглых скобках
и двоеточие (:). Как и идентификаторы переменных, по соглашению имена
функций должны начинаться с буквы нижнего регистра, а в именах, состоящих из нескольких слов, составляющие должны разделяться символами
подчеркивания.
Требуемые круглые скобки содержат список параметров функции — разделенный запятыми список параметров, представляющий данные, необходимые
функции для выполнения соответствующей операции. Функция square имеет
только один параметр с именем number — значение, возводимое в квадрат. Если
круглые скобки пусты, то это означает, что функция не использует параметры,
заданные для выполнения операции.
Снабженные отступом строки после двоеточия (:) образуют блок функции. Он
состоит из необязательной doc-строки, за которой следуют команды, выполняющие операцию функции (о различиях между блоком функции и набором
управляющей команды см. далее).

154   Глава 4. Функции

Определение doc-строки пользовательской функции
В «Руководстве по стилю для кода Python» указано, что первой строкой блока
функции должна быть doc-строка, кратко поясняющая назначение функции:
"""Вычисление квадрата числа."""

Чтобы предоставить более подробную информацию, используйте многострочную doc-строку — «Руководство по стилю» рекомендует начать с краткого
пояснения, за которым следует пустая строка и дополнительные подробности.

Возвращение результата на сторону вызова функции
Завершив выполнение, функция возвращает управление в точку вызова, то
есть в строку кода, которая вызвала функцию. В блоке square команда return
return number ** 2

сначала возводит число в квадрат, а затем завершает функцию и возвращает
результат на сторону вызова. В нашем примере функция впервые вызывается
во фрагменте [2], поэтому IPython выводит результат в Out[2]. Второй вызов
находится во фрагменте [3], так что IPython выводит результат в Out[3].
Вызовы функций также могут быть встроены в выражения. Следующий код
сначала вызывает square, а затем выводит результат функцией print:
In [4]: print('The square of 7 is', square(7))
The square of 7 is 49

Функция может вернуть управление еще двумя способами:
ØØВыполнение команды return без выражения завершает функцию и неяв-

но возвращает значение None на сторону вызова. В документации Python
сказано, что None означает отсутствие значения. В условиях None интерпретируется как False.
ØØЕсли в функции нет команды return, то она неявно возвращает значение

None после выполнения последней команды в блоке функции.

Локальные переменные
Хотя мы не определяем переменные в блоке square, это можно сделать. Параметры функции и переменные, определенные в ее блоке, являются локальными

4.2. Определение функций   155

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

Обращение к doc-строке функции из механизма справки IPython
IPython поможет получить информацию о модулях и функциях, которые вы
собираетесь использовать в формируемом коде, а также о самом IPython. Например, для просмотра doc-строки функции (чтобы узнать, как пользоваться
ею) введите имя функции, дополненное вопросительным знаком (?):
In [5]: square?
Signature: square(number)
Docstring: Calculate the square of number.
File:
~/Documents/examples/ch04/
Type:
function

Для функции square выводится следующая информация:
ØØИмя и список параметров функции (ее сигнатура).
ØØDoc-строка функции.
ØØИмя файла, содержащего определение функции. Для функции в интерак-

тивном сеансе в этой строке выводится информация о фрагменте, определившем функцию, — 1 в "" означает фрагмент [1].
ØØТип элемента, для которого вы обратились к механизму справки

IPython, — в данном случае это функция.
Если исходный код функции доступен из IPython, например, если функция
определяется в текущем сеансе или импортируется в сеанс из файла .py, то
вы сможете воспользоваться командой ?? для вывода полного определения
исходного кода функции:
In [6]: square??
Signature: square(number)
Source:
def square(number):
"""Вычисление квадрата числа."""
return number ** 2
File:
~/Documents/examples/ch04/
Type:
function

156   Глава 4. Функции
Если исходный код недоступен из IPython, то ?? просто выводит doc-строку.
Если doc-строка помещается в окне, IPython выводит следующее приглашение
In[]. Если doc-строка имеет слишком большую длину, то IPython сообщает
о наличии продолжения, выводя двоеточие (:) в нижней части окна: нажмите
клавишу «пробел», чтобы вывести следующий экран. Вы можете перемещаться
по doc-строке при помощи клавиш ↑ и ↓, соответственно. IPython выводит
(END) в конце doc-строки. Нажатие q (сокращение от «quit») в любом приглашении : или (END) возвращает вас к следующему приглашению In []. Чтобы
получить представление о тех или иных возможностях IPython, введите ?
в любом приглашении In [], нажмите Enter и прочитайте обзор справочной
документации.

4.3. Функции с несколькими параметрами
Определим функцию maximum, которая определяет и возвращает наибольшее из
трех значений, — в следующем сеансе функция вызывается трижды с целыми
числами, числами с плавающей точкой и строками соответственно.
In [1]: def maximum(value1, value2, value3):
...:
"""Возвращает наибольшее из трех значений."""
...:
max_value = value1
...:
if value2 > max_value:
...:
max_value = value2
...:
if value3 > max_value:
...:
max_value = value3
...:
return max_value
...:
In [2]: maximum(12, 27, 36)
Out[2]: 36
In [3]: maximum(12.3, 45.6, 9.7)
Out[3]: 45.6
In [4]: maximum('yellow', 'red', 'orange')
Out[4]: 'yellow'

Мы не поставили пустые строки до и после команд if, потому что нажатие Enter
в пустой строке в интерактивном режиме завершает определение функции.
Также maximum можно вызывать со смешанными типами — например, int
и float:

4.3. Функции с несколькими параметрами   157
In [5]: maximum(13.5, -3, 7)
Out[5]: 13.5

Вызов maximum(13.5, 'hello', 7) приводит к ошибке TypeError, потому
что строки и числа не могут сравниваться друг с другом оператором «больше» (>).

Определение функции maximum
Функция maximum задает три параметра в списке, разделенном запятыми. Аргументы фрагмента [2] 12, 27 и 36 присваиваются параметрам value1, value2
и value3 соответственно.
Чтобы определить наибольшее значение, обработаем значения по одному:
ØØИзначально предполагается, что value1 содержит наибольшее значение,

которое присваивается локальной переменной max_value. Конечно, может
оказаться, что наибольшее значение на самом деле хранится в value2 или
value3, поэтому их также необходимо сравнить с max_value.
ØØЗатем первая команда if проверяет условие value2 > max_value; если это

условие истинно, то value2 присваивается max_value.
ØØВторая команда if проверяет условие value3 > max_value; если это усло-

вие истинно, то value3 присваивается max_value.
Теперь max_value содержит наибольшее значение, поэтому мы возвращаем
его. Когда управление возвращается на сторону вызова, параметры value1,
value2 и value3, а также переменная max_value в блоке функции — все они
были локальными переменными — уже не существуют.

Встроенные функции max и min
Для многих распространенных задач необходимая функциональность уже
существует в Python. Например, встроенные функции max и min «знают»,
как определить наибольший и наименьший (соответственно) из своих двух
и более аргументов:
In [6]: max('yellow', 'red', 'orange', 'blue', 'green')
Out[6]: 'yellow'
In [7]: min(15, 9, 27, 14)
Out[7]: 9

158   Глава 4. Функции
Каждая из этих функций также может получать в аргументе итерируемый
объект, например список или строку. Использование встроенных функций
или функций из модулей стандартной библиотеки Python вместо написания
собственных реализаций может сократить время разработки и повысить надежность программы, улучшить ее компактность и быстродействие. За списком встроенных функций и модулей Python обращайтесь по адресу https://
docs.python.org/3/library/index.html.

4.4. Генератор случайных чисел
Теперь ненадолго отвлечемся на популярную область программных приложений — моделирование и игры. Чтобы добавить в программу случайный
элемент, воспользуйтесь модулем random из стандартной библиотеки Python.

Бросок шестигранного кубика
Сгенерируем 10 случайных чисел в диапазоне 1–6 для моделирования броска
шестигранного кубика:
In [1]: import random
In [2]: for roll in range(10):
...:
print(random.randrange(1, 7), end=' ')
...:
4 2 5 5 4 6 4 6 1 5

Сначала импортируется модуль random — это позволит программе использовать функциональность этого модуля. Функция randrange генерирует целое
число в диапазоне от значения первого аргумента до значения второго аргумента, не включая последний. Воспользуйтесь клавишей ↑, чтобы вызвать команду
for, а затем нажмите Enter, чтобы снова выполнить ее. Обратите внимание: на
этот раз выводятся другие значения:
In [3]: for roll in range(10):
...:
print(random.randrange(1, 7), end=' ')
...:
4 5 4 5 1 4 1 4 6 5

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

4.4. Генератор случайных чисел   159

6 000 000 бросков шестигранного кубика
Если randrange действительно генерирует случайные целые числа, то каждое
число в диапазоне имеет одинаковую вероятность выпадения при каждом
вызове функции. Чтобы продемонстрировать, что грани 1–6 выпадают с одинаковой вероятностью, следующий сценарий моделирует 6 000 000 бросков
кубика. При выполнении сценария, представленного в примере, каждая грань
встретится приблизительно 1 000 000 раз:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# fig04_01.py
"""6 000 000 бросков шестигранного кубика."""
import random
# счетчик соответствующей грани
frequency1 = 0
frequency2 = 0
frequency3 = 0
frequency4 = 0
frequency5 = 0
frequency6 = 0
# 6,000,000 die rolls
for roll in range(6_000_000): # Обратите внимание на разделители
face = random.randrange(1, 7)
# Увеличение счетчика соответствующей грани
if face == 1:
frequency1 += 1
elif face == 2:
frequency2 += 1
elif face == 3:
frequency3 += 1
elif face == 4:
frequency4 += 1
elif face == 5:
frequency5 += 1
elif face == 6:
frequency6 += 1
print(f'Face{"Frequency":>13}')
print(f'{1:>4}{frequency1:>13}')
print(f'{2:>4}{frequency2:>13}')
print(f'{3:>4}{frequency3:>13}')
print(f'{4:>4}{frequency4:>13}')
print(f'{5:>4}{frequency5:>13}')
print(f'{6:>4}{frequency6:>13}')

160   Глава 4. Функции
Face
1
2
3
4
5
6

Frequency
998686
1001481
999900
1000453
999953
999527

В сценарии используются вложенные управляющие команды (команда if…
elif , вложенная в команду for ) для определения количества выпадений
каждой грани. Команда for выполняет цикл 6 000 000 раз. Мы используем
разделитель групп разрядов (_), чтобы значение 6000000 лучше читалось.
Выражение вида range(6,000,000) было бы признано ошибочным: запятые
разделяют аргументы в вызовах функций, поэтому Python интерпретирует
range(6,000,000) как вызов range с тремя аргументами 6, 0 и 0.
Для каждого броска кубика сценарий увеличивает соответствующую переменную-счетчик на единицу. Запустите программу и проследите за результатом.
Возможно, для завершения работы ей понадобится несколько секунд. Как вы
увидите, при каждом запуске программа выдает разные результаты. Обратите
внимание: в команду if…elif не включена секция else.

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

Согласно документации, Python выбирает значение инициализации на основании системных часов или источника случайности, зависящего от конкретной операционной
системы. В областях, в которых необходимы безопасные случайные числа (например,
в криптографии), документация рекомендует использовать модуль secrets вместо модуля
random.

4.5. Практический пример: игра «крэпс»   161

логические ошибки не будут устранены, прежде чем тестировать программу
с другими значениями. В этом случае можно воспользоваться функцией seed
модуля random, чтобы самостоятельно инициализировать генератор случайных
чисел, — это заставит randrange начать вычисления своей псевдослучайной
числовой последовательности с заданного вами значения. В следующем разделе фрагменты [5] и [8] выдают одинаковые результаты, потому что фрагменты
[4] и [7] используют одно значение инициализации (32):
In [4]: random.seed(32)
In [5]:
...:
...:
1 2 2 3
In [6]:
...:
...:
1 3 5 3
In [7]:

for roll in range(10):
print(random.randrange(1, 7), end=' ')
6 2 4 1 6 1
for roll in range(10):
print(random.randrange(1, 7), end=' ')
1 5 6 4 3 5
random.seed(32)

In [8]: for roll in range(10):
...:
print(random.randrange(1, 7), end=' ')
...:
1 2 2 3 6 2 4 1 6 1

Фрагмент [6] генерирует разные значения, потому что он просто продолжает
последовательность псевдослучайных чисел, начатую во фрагменте [5].

4.5. Практический пример: игра «крэпс»
В этом разделе моделируется популярная азартная игра «крэпс». Формулировка задачи:
Игрок бросает два шестигранных кубика, на грани которых нанесены 1, 2, 3,
4, 5 или 6 очков. Когда кубики останавливаются, вычисляется сумма очков
на двух верхних гранях. Если при первом броске выпадает 7 или 11, игрок
побеждает. Если при первом броске сумма равна 2, 3 или 12 («крэпс»), игрок
проигрывает (побеждает «казино»). Если при первом броске сумма равна 4,
5, 6, 8, 9 или 10, то она становится «целью» игрока. Чтобы победить, игрок
должен на последующих бросках выбросить то же целевое значение. Если
перед этим игрок выбросит 7, он проигрывает.

162   Глава 4. Функции
Следующий сценарий моделирует игру с несколькими тестовыми запусками:
с выигрышем при первом броске, с проигрышем при первом броске, с выигрышем на последующем броске и проигрышем на последующем броске.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# fig04_02.py
"""Моделирование игры крэпс."""
import random
def roll_dice():
"""Моделирует бросок двух кубиков и возвращает результат в виде кортежа."""
die1 = random.randrange(1, 7)
die2 = random.randrange(1, 7)
return (die1, die2) # Два результата упаковываются в кортеж
def display_dice(dice):
"""Выводит результат одного броска двух кубиков."""
die1, die2 = dice # Распаковывает кортеж в переменные die1 и die2
print(f'Player rolled {die1} + {die2} = {sum(dice)}')
die_values = roll_dice()
display_dice(die_values)

# first roll

# Состояние игры и цель определяются на основании первого броска
sum_of_dice = sum(die_values)
if sum_of_dice in (7, 11): # Выигрыш
game_status = 'WON'
elif sum_of_dice in (2, 3, 12): # Проигрыш
game_status = 'LOST'
else: # запомнить цель
game_status = 'CONTINUE'
my_point = sum_of_dice
print('Point is', my_point)
# Броски продолжаются до выигрыша или проигрыша
while game_status == 'CONTINUE':
die_values = roll_dice()
display_dice(die_values)
sum_of_dice = sum(die_values)
if sum_of_dice == my_point: # Выигрыш по цели
game_status = 'WON'
elif sum_of_dice == 7: # Проигрыш по выпадению 7
game_status = 'LOST'
# Вывод сообщения "wins" или "loses"
if game_status == 'WON':
print('Player wins')

4.5. Практический пример: игра «крэпс»   163
45 else:
46
print('Player loses')
Player rolled 2 + 5 = 7
Player wins
Player rolled 1 + 2 = 3
Player loses
Player rolled
Point is 9
Player rolled
Player rolled
Player rolled
Player wins

5 + 4 = 9
4 + 4 = 8
2 + 3 = 5
5 + 4 = 9

Player rolled 1 + 5 = 6
Point is 6
Player rolled 1 + 6 = 7
Player loses

Функция roll_dice — возвращение нескольких значений
в кортеже
Функция roll_dice (строки 5–9) моделирует бросок двух кубиков. Функция
определяется один раз, а затем вызывается в нескольких точках программы
(строки 16 и 33). Пустой список параметров означает, что функции roll_dice
не требуются аргументы для решения ее задачи.
Встроенные и пользовательские функции, вызываемые в программе, возвращают одно значение. Иногда бывает нужно получить из функции сразу несколько значений, как в функции roll_dice, которая возвращает два значения
(строка 9) в виде кортежа — неизменяемой последовательности значений.
Чтобы создать кортеж, разделите его значения запятыми, как в строке 9:
(die1, die2)

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

Функция display_dice
Чтобы использовать значения из кортежа, можно присвоить их списку переменных, разделенных запятыми, — при этом происходит распаковка кортежа.

164   Глава 4. Функции
Чтобы вывести каждый результат броска, функция display_dice (определенная в строках 11–14 и вызываемая в строках 17 и 34) распаковывает полученный аргумент-кортеж (строка 13). Количество переменных слева от = должно
совпадать с количеством элементов в кортеже; в противном случае происходит
ошибка ValueError. Строка 14 выводит отформатированную строку, которая
содержит как значения на кубиках, так и их сумму. Чтобы вычислить сумму,
мы передаем кортеж встроенной функции sum — кортеж, как и список, является
последовательностью.
Следует заметить, что блок каждой из функций roll_dice и display_dice
начинается с doc-строки, описывающей назначение функции. Кроме того,
обе функции содержат локальные переменные die1 и die2. Эти переменные
не «конфликтуют», поскольку принадлежат блокам разных функций. Каждая локальная переменная доступна только в том блоке, в котором она была
определена.

Первый бросок
В начале выполнения сценария в строках 16–17 моделируется бросок кубиков
и выводятся результаты. Строка 20 вычисляет сумму кубиков для использования в строках 22–29. Игрок может выиграть или проиграть как при первом, так
и при любом последующем броске. В переменной game_status отслеживается
статус игры (выигрыш/проигрыш).
Оператор in в строке 22
sum_of_dice in (7, 11)

проверяет, содержит ли кортеж (7, 11) значение sum_of_dice. Если условие
истинно, то это означает, что на кубиках выпал результат 7 или 11. В этом случае
игрок выиграл при первом броске, поэтому сценарий присваивает переменной
game_status значение 'WON'. Правым операндом оператора может быть любой
итерируемый объект. Также существует оператор not in, который проверяет,
что значение не содержится в итерируемом объекте. Предыдущее компактное
условие эквивалентно сложному условию
(sum_of_dice == 7) or (sum_of_dice == 11)

Аналогичным образом условие в строке 24
sum_of_dice in (2, 3, 12)

4.6. Стандартная библиотека Python   165

проверяет, что кортеж (2,3,12) содержит значение sum_of_dice. В этом случае
игрок проиграл при первом броске, поэтому сценарий присваивает переменной
game_status значение 'LOST'.
Для любой другой суммы на кубиках(4, 5, 6, 8, 9 или 10):
ØØстрока 27 присваивает game_status значение 'CONTINUE', чтобы продол-

жить броски;
ØØстрока 28 сохраняет сумму кубиков в my_point, чтобы отслеживать нуж-

ный для победы результат;
ØØстрока 29 выводит my_point.

Последующие броски
Если переменная game_status равна 'CONTINUE' (строка 32), то игрок не
вы­играл и не проиграл, поэтому выполняется набор команды while (строки 33–40). Каждая итерация цикла вызывает roll_dice, выводит результаты
на кубиках и вычисляет их сумму. Если значение sum_of_dice равно my_point
(строка 37) или 7 (строка 39), то сценарий присваивает переменной game_
status значение 'WON' или 'LOST' соответственно, и цикл завершается. В противном случае цикл while продолжается моделированием следующего броска.

Вывод окончательных результатов
При завершении цикла сценарий переходит к команде if…else (строки 43–46),
которая выводит сообщение 'Player wins', если переменная game_status равна
'WON', или 'Player loses' в противном случае.

4.6. Стандартная библиотека Python
Как правило, при написании программы Python разработчик объединяет
функции и классы (то есть пользовательские типы), созданные им, с готовыми
функциями и классами, определенными в модулях — например, в стандартной
библиотеке Python и других библиотеках. При этом одна из главных целей
программирования — обойтись без «изобретения велосипеда».
Модуль представляет собой файл со взаимосвязанными функциями, данными
и классами. Тип Decimal из модуля decimal стандартной библиотеки Python

166   Глава 4. Функции
в действительности представляет собой класс. Мы кратко представили классы
в главе 1, а их более подробное описание будет приведено в главе 10. Взаимо­
связанные модули группируются в пакетах. В этой книге мы будем работать
со многими готовыми модулями и пакетами, а читатели будет создавать собственные модули — собственно, каждый созданный файл с исходным кодом
Python (.py) и является модулем. Создание пакетов выходит за рамки этой
книги. Обычно они используются для структурирования функциональности
большой библиотеки на меньшие подмножества, более простые в сопровождении, которые можно импортировать по отдельности для удобства. Например,
библиотека визуализации matplotlib, использованная в разделе 5.17, обладает
весьма обширной функциональностью (объем ее документации превышает
2300 страниц), поэтому мы импортируем только те подмножества, которые
нужны в наших примерах (pyplot и animation).
Стандартная библиотека Python входит в базовую поставку Python. Ее пакеты
и модули содержат средства для выполнения широкого спектра повседневных
задач программирования1. Полный список модулей стандартной библиотеки
доступен по адресу:
https://docs.python.org/3/library/

Мы уже пользовались функциональностью модулей decimal , statistics
и ran­dom. В следующем разделе будет использоваться функциональность вычислений из модуля math. В примерах этой книги встречаются многие другие
модули стандартной библиотеки Python, включая модули из табл. 4.1.
Таблица 4.1. Наиболее часто применяемые модули стандартной библиотеки Python
collections — структуры данных помимо

math — распространенные математиче-

списков, кортежей, словарей и множеств.

ские библиотеки и операции.

Криптографические модули — шифрование данных для безопасной передачи.

os — взаимодействие с операционной

csv — обработка файлов с данными, разде-

profile, pstats, timeit — анализ быстро-

ленными запятыми (по аналогии с Excel).

действия.

datetime — работа с датой и временем
(а также модули time и calendar).

random — псевдослучайные числа.

decimal — вычисления с фиксированной

по тексту.

и плавающей точкой, включая денежные
вычисления.

1

системой.

re — регулярные выражения для поиска
sqlite3 — работа с реляционными база-

ми данных SQLite.

В учебном курсе Python этот подход называется «батарейки входят в комплект».

4.7. Функции модуля math   167

doctest — внедрение проверочных тестов

и ожидаемых результатов в doc-строки
для простого модульного тестирования.

gettext и locale — модули интернациона-

лизации и локализации.

json — обработка формата JSON

(JavaScript Object Notation), используемого при работе с веб-сервисами и документными базами данных NoSQL.
statistics — функции математической
статистики (такие как mean, median, mode
и variance).

string — работа со строками.
sys — обработка аргументов командной

строки; стандартные потоки ввода, вывода и ошибок.
tkinter — графические интерфейсы

пользователя (GUI) и рисование на
холсте.
turtle — графика Turtle.

webbrowser — удобное отображение веб-

страниц в приложениях Python

4.7. Функции модуля math
В модуле math определяются функции для выполнения различных распространенных математических вычислений. Вспомните, о чем говорилось
в предыдущей главе: команда import следующего вида позволяет использовать определения из внешнего модуля, для чего следует указать имя модуля
и точку (.):
In [1]: import math

Например, следующий фрагмент вычисляет квадратный корень из 900 вызовом функции sqrt модуля math, возвращающей свой результат в виде значения
float:
In [2]: math.sqrt(900)
Out[2]: 30.0

Аналогичным образом следующий фрагмент вычисляет абсолютное значение –10 вызовом функции fabs модуля math, возвращающей свой результат
в виде значения float:
In [3]: math.fabs(-10)
Out[3]: 10.0

В табл. 4.2 перечислены некоторые функции модуля math — полный список
можно просмотреть по адресу:
https://docs.python.org/3/library/math.html

168   Глава 4. Функции
Таблица 4.2. Некоторые функции модуля math
Функция

Описание

Пример

ceil(x)

Округляет x до наименьшего целого, не
меньшего x

ceil(9.2) равно 10.0
ceil(-9.8) равно -9.0

floor(x)

Округляет x до наименьшего целого, не
большего x

floor(9.2) равно 9.0
floor(-9.8) равно -10.0

sin(x)

Синус x (x задается в радианах)

sin(0.0) равно 0.0

cos(x)

Косинус x (x задается в радианах)

cos(0.0) равно 1.0

tan(x)

Тангенс x (x задается в радианах)

tan(0.0) равно 0.0

exp(x)

Экспонента ex

exp(1.0) равно 2.718282
exp(2.0) равно 7.389056

log(x)

Натуральный логарифм x (по основанию e)

log(2.718282) равно 1.0
log(7.389056) равно 2.0

log10(x)

Логарифм x (по основанию 10)

log10(10.0) равно 1.0
log10(100.0) равно 2.0

pow(x, y)

x в степени y (x y)

pow(2.0, 7.0) равно 128.0
pow(9.0, .5) равно 3.0

sqrt(x)

Квадратный корень из x

sqrt(900.0) равно 30.0
sqrt(9.0) равно 3.0

fabs(x)

Абсолютное значение x — всегда возвращает float. В Python также существует встроенная функция abs, которая возвращает int
или float в зависимости от ее аргумента

fabs(5.1) равно 5.1
fabs(-5.1) равно 5.1

fmod(x, y)

Остаток от деления x/y в виде float

fmod(9.8, 4.0) равно 1.8

4.8. Использование автозаполнения IPython
Документацию модуля можно просмотреть в интерактивном режиме IPython
при помощи автозаполнения — возможности, ускоряющей процессы программирования и изучения языка. Если ввести часть идентификатора и нажать
клавишу Tab, то IPython завершает идентификатор за вас или предоставляет
список идентификаторов, начинающихся с введенных символов. Состав
списка зависит от платформы ОС и того, что именно было импортировано
в текущий сеанс IPython:

4.8. Использование автозаполнения IPython   169
In [1]: import math
In [2]: ma
map
math
max()

%macro
%magic
%man

%%markdown
%matplotlib

Список идентификаторов прокручивается клавишами ↑ и ↓. При этом IPython
автоматически выделяет идентификатор и выводит его справа от префикса In[].

Просмотр идентификаторов в модуле
Чтобы просмотреть список идентификаторов, определенных в модуле, введите
имя модуля и точку (.), а затем нажмите Tab:
In [3]: math.
acos()
acosh()
asin()
asinh()

atan()
atan2()
atanh()
ceil()

copysign()
cos()
cosh()
degrees()

e
erf()
erfc()
exp()

expm1()
fabs()
factorial() >
floor()

Если подходящих идентификаторов больше, чем отображается в настоящий
момент, то IPython выводит символ > (на некоторых платформах) у правого
края (в данном случае справа от factorial()). Вы можете воспользоваться
клавишами ↑ и ↓ для прокрутки списка. В списке идентификаторов:
ØØИдентификаторы, за которыми стоят круглые скобки, являются именами

функций (или методов, см. далее).
ØØИдентификаторы из одного слова (скажем, Employee), начинающие-

ся с буквы верхнего регистра, и идентификаторы из нескольких слов,
в которых каждое слово начинается с буквы верхнего регистра (например, CommissionEmployee), представляют имена классов (в приведенном
выше списке их нет). Эта схема формирования имен, рекомендованная
в «Руководстве по стилю для кода Python», называется «верблюжьим регистром», потому что буквы верхнего регистра выделяются, словно горбы верблюда.
ØØИдентификаторы нижнего регистра без круглых скобок, такие как pi

(не входит в приведенный список) и e, являются именами переменных.
Идентификатор pi представляет число 3.141592653589793, а идентификатор e — число 2.718281828459045. В модуле math идентификаторы pi и e
представляют математические константы π и e соответственно.

170   Глава 4. Функции
В Python нет констант, хотя многие объекты в Python неизменяемы. Таким
образом, хотя pi и e в реальном мире являются константами, им не следует
присваивать новые значения — это приведет к изменению их значений. Чтобы
разработчику было проще отличить константы от других переменных, «Руководство по стилю» рекомендует записывать имена констант, определенных
в вашем коде, в верхнем регистре.

Использование выделенной функции
Чтобы в процессе перебора идентификаторов использовать функцию, выделенную в настоящий момент, просто начните вводить ее аргументы в круглых
скобках. После этого IPython скрывает список автозаполнения. Если потребуется больше информации о текущем выделенном элементе, то вы сможете
просмотреть его doc-строку — введите после имени вопросительный знак (?)
и нажмите Enter, чтобы просмотреть справочную документацию. Ниже приведена doc-строка функции fabs:
In [4]: math.fabs?
Docstring:
fabs(x)
Return the absolute value of the float x.
Type:
builtin_function_or_method

Текст builtin_function_or_method показывает, что fabs является частью
модуля стандартной библиотеки Python. Такие модули считаются встроенными в Python. В данном случае fabs является встроенной функцией из
модуля math.

4.9. Значения параметров по умолчанию
При определении функции можно указать, что параметр имеет значение по
умолчанию. Если при вызове функции отсутствует аргумент для параметра со
значением по умолчанию, то для этого параметра автоматически передается
значение по умолчанию. Определим функцию rectangle_area со значениями
параметров по умолчанию:
In [1]: def rectangle_area(length=2, width=3):
...:
"""Возвращает площадь прямоугольника."""
...:
return length * width
...:

4.10. Ключевые аргументы   171

Чтобы задать значение параметра по умолчанию, поставьте после имени параметра знак = и укажите значение — в данном случае значениями по умолчанию являются 2 и 3 для параметров length и width соответственно. Любые
параметры со значениями по умолчанию должны находиться в списке справа
от параметров, не имеющих значений по умолчанию.
Следующий вызов rectangle_area не имеет аргументов, поэтому IPython использует оба значения параметров по умолчанию так, как если бы функция
была вызвана в виде rectangle_area(2, 3):
In [2]: rectangle_area()
Out[2]: 6

Следующий вызов rectangle_area имеет только один аргумент. Аргументы сопоставляются с параметрами слева направо, поэтому для length
используется значение 10. Интерпретатор передает для width значение
параметра по умолчанию 3 , как если бы функция была вызвана в виде
rectangle_area(10, 3):
In [3]: rectangle_area(10)
Out[3]: 30

Новый вызов rectangle_area получает аргументы для length и width, и IPython
игнорирует значения параметров по умолчанию:
In [4]: rectangle_area(10, 5)
Out[4]: 50

4.10. Ключевые аргументы
При вызове функций можно использовать ключевые (именованные) аргументы для передачи аргументов в любом порядке. Чтобы продемонстрировать, как
работают ключевые аргументы, переопределим функцию rectangle_area — на
этот раз без значений параметров по умолчанию:
In [1]: def rectangle_area(length, width):
...:
"""Возвращает площадь прямоугольника."""
...:
return length * width
...:

Каждый ключевой аргумент при вызове задается в форме имя парамет­
ра = значение. Следующий вызов показывает, что порядок ключевых аргумен-

172   Глава 4. Функции
тов роли не играет — он не обязан соответствовать порядку этих параметров
в определении функции:
In [2]: rectangle_area(width=5, length=10)
Out[3]: 50

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

4.11. Произвольные списки аргументов
Функции с произвольными списками аргументов (например, встроенные
функции min и max) могут получать любое количество аргументов. Возьмем
следующий вызов min:
min(88, 75, 96, 55, 83)

В документации функции указано, что min имеет два обязательных параметра
(с именами arg1 и arg2) и необязательный третий параметр в форме *args, который означает, что функция может получать любое количество дополнительных аргументов. Оператор * перед именем параметра предписывает Python
упаковать остальные аргументы в кортеж, который передается параметру args.
В приведенном вызове параметр arg1 получает значение 88, параметр arg2 —
значение 75, а параметр args — кортеж (96, 55, 83).

Определение функции с произвольным списком аргументов
Определим функцию average, которая может получать произвольное количество аргументов:
In [1]: def average(*args):
...:
return sum(args) / len(args)
...:

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

4.12. Методы: функции, принадлежащие объектам   173

Теперь вызовем average несколько раз с произвольными списками аргументов
разной длины:
In [2]: average(5, 10)
Out[2]: 7.5
In [3]: average(5, 10, 15)
Out[3]: 10.0
In [4]: average(5, 10, 15, 20)
Out[4]: 12.5

Чтобы вычислить среднее значение, разделим сумму элементов кортежа
args (возвращаемую встроенной функцией sum) на количество элементов
в кортеже (возвращаемое встроенной функцией len). В нашем определении
average обратите внимание на то, что при нулевой длине args происходит
ошибка ZeroDivisionError. В следующей главе поясняется, как обратиться
к элементам кортежа без их распаковки.

Передача отдельных элементов итерируемого объекта
в аргументах функций
Элементы кортежа, списка или другого итерируемого объекта можно распаковать, чтобы передать их в отдельных аргументах функции. Оператор *,
применяемый к итерируемому объекту из аргумента при вызове функции,
распаковывает свои элементы. Следующий код создает список оценок из пяти
элементов, после чего использует выражение *grades для распаковки своих
элементов в аргументы average:
In [5]: grades = [88, 75, 96, 55, 83]
In [6]: average(*grades)
Out[6]: 79.4

Этот вызов эквивалентен вызову average(88, 75, 96, 55, 83).

4.12. Методы: функции,
принадлежащие объектам
Метод представляет собой функцию, вызываемую для конкретного объекта
в форме
имя_объекта.имя_метода(аргументы)

174   Глава 4. Функции
Например, в следующем сеансе создается строковая переменная s, которой
присваивается строковый объект 'Hello'. Затем сеанс вызывает для объекта
методы lower и upper, которые возвращают новые строки с версией исходной
строки, преобразованной, соответственно, к нижнему или верхнему регистру;
переменная s при этом остается без изменений:
In [1]: s = 'Hello'
In [2]: s.lower()
Out[2]: 'hello'

# вызов s в нижнем регистре

In [3]: s.upper()
Out[3]: 'HELLO'
In [4]: s
Out[4]: 'Hello'

Стандартная библиотека Python доступна по адресу:
https://docs.python.org/3/library/index.html

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

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

Локальная область видимости
Идентификатор локальной переменной обладает локальной областью видимости. Он находится в области видимости только от своего определения до
конца блока функции. Когда функция возвращает управление на сторону
вызова, идентификатор «выходит из области видимости». Таким образом,
локальная переменная может использоваться только внутри той функции,
в которой она определена.

4.13. Правила области видимости   175

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

Обращение к глобальной переменной из функции
К значению глобальной переменной можно обратиться из функции:
In [1]: x = 7
In [2]: def access_global():
...:
print('x printed from access_global:', x)
...:
In [3]: access_global()
x printed from access_global: 7

Однако по умолчанию глобальную переменную не удастся изменить внутри
функции — когда вы присваиваете значение переменной в блоке функции,
Python создает новую локальную переменную:
In [4]: def try_to_modify_global():
...:
x = 3.5
...:
print('x printed from try_to_modify_global:', x)
...:
In [5]: try_to_modify_global()
x printed from try_to_modify_global: 3.5
In [6]: x
Out[6]: 7

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

176   Глава 4. Функции
Чтобы изменить глобальную переменную в блоке функции, используем
коман­ду global для объявления того, что переменная определена в глобальной
области видимости:
In [7]: def modify_global():
...:
global x
...:
x = 'hello'
...:
print('x printed from modify_global:', x)
...:
In [8]: modify_global()
x printed from modify_global: hello
In [9]: x
Out[9]: 'hello'

Блоки и наборы
Ранее мы определяли блоки функций и наборы управляющих команд. При
создании переменной в блоке эта переменная становится локальной по отношению к блоку. Если переменная создается в наборе управляющего блока,
то область видимости переменной зависит от того, где именно определяется
управляющая команда:
ØØЕсли управляющая команда находится в глобальной области видимости,

то любые переменные, определяемые в управляющей команде, имеют глобальную область видимости.
ØØЕсли управляющая команда находится в блоке функции, то любые пере-

менные, определяемые в управляющей команде, имеют локальную область видимости.
Мы вернемся к теме областей видимости в главе 10, когда займемся написанием пользовательских классов.

Замещение функций
В предыдущих главах при суммировании значений сумма сохранялась в переменной с именем total. А почему не sum? Дело в том, что sum является встроенной функцией. Если вы определите переменную с именем sum, то переменная
заместит встроенную функцию, и та станет недоступной для вашего кода.
При выполнении следующей команды Python связывает идентификатор sum
с объектом int, содержащим 15. На этой стадии идентификатор sum уже не
ссылается на встроенную функцию. Таким образом, при попытке использования sum как функции произойдет ошибка TypeError:

4.14. Подробнее об импортировании   177
In [10]: sum = 10 + 5
In [11]: sum
Out[11]: 15
In [12]: sum([10, 5])
------------------------------------------------------------------------TypeError
Traceback (most recent call last)
in ()
----> 1 sum([10, 5])
TypeError: 'int' object is not callable

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

4.14. Подробнее об импортировании
Для импортирования модулей (таких как math и random) мы ранее использовали команды следующего вида:
import имя_модуля

а затем обращались к нужной функциональности с указанием имени модуля
через точку (.). Кроме того, из модуля также можно импортировать конкретный идентификатор (например, тип Decimal из модуля decimal) командой
следующего вида:
from имя_модуля import идентификатор

а затем использовать его без префикса из имени модуля с точкой (.).

Импортирование нескольких идентификаторов из модуля
Использование команды from…import позволяет импортировать разделенный
запятыми список идентификаторов из модуля, а затем использовать их без
указания имени модуля и точки (.):

178   Глава 4. Функции
In [1]: from math import ceil, floor
In [2]: ceil(10.3)
Out[2]: 11
In [3]: floor(10.7)
Out[3]: 10

Попытка использования неимпортированной функции приводит к ошибке
NameError; она указывает на то, что имя не определено.

Не используйте импортирование с *
Все идентификаторы, определенные в модуле, можно импортировать массовой
формой команды
from имя_модуля import *

Все идентификаторы модуля становятся доступными для использования
в вашем коде. Импортирование идентификаторов модуля с * может привести
к коварным ошибкам — такая практика считается опасной, и ее следует избегать. Возьмем следующие фрагменты:
In [4]: e = 'hello'
In [5]: from math import *
In [6]: e
Out[6]: 2.718281828459045

Изначально строка 'hello' присваивается переменной с именем e. Однако
после выполнения фрагмента [5] переменная e заменяется (вероятно, непреднамеренно) константой e из модуля math, представляющей математическую
константу с плавающей точкой e.

Определение синонимов для модулей
и идентификаторов модулей
Иногда бывает полезно импортировать модуль и назначить ему сокращение
для упрощения кода. Секция as команды import позволяет задать имя, которое
будет использоваться для обращения к идентификаторам модуля. Например,
в разделе 3.14 можно было импортировать модуль statistics и обратиться
к его функции mean следующим образом:

4.15. Подробнее о передаче аргументов функциям   179
In [7]: import statistics as stats
In [8]: grades = [85, 93, 45, 87, 93]
In [9]: stats.mean(grades)
Out[9]: 80.6

В последующих главах конструкция import…as часто используется для импортирования библиотек Python с удобными сокращениями — например, stats
для модуля statistics. Или, например, мы будем использовать модуль numpy,
который обычно импортируется командой
import numpy as np

В документации к библиотекам указаны популярные сокращенные имена.
Как правило, при импортировании модуля следует использовать команды
import или import…as, а затем обращаться к функциональности модуля по
имени модуля или по сокращению, следующему за ключевым словом as соответственно. Тем самым предотвращается случайное импортирование идентификатора, конфликтующего с идентификатором в вашем коде.

4.15. Подробнее о передаче аргументов
функциям
Давайте подробнее разберемся в том, как аргументы передаются функциям.
Во многих языках программирования существуют два механизма передачи
аргументов — передача по значению и передача по ссылке.
ØØПри передаче по значению вызываемая функция получает копию значения

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

чению аргумента на стороне вызова напрямую и изменить это значение,
если оно не является неизменяемым.
Аргументы в Python всегда передаются по ссылке. Некоторые разработчики
называют этот механизм «передачей по ссылке на объект», потому что «все
в Python является объектом»1. Когда при вызове функции передается аргумент,
1

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

180   Глава 4. Функции
Python копирует ссылку на объект аргумента (не сам объект!) в соответствующий параметр. Это важно для быстродействия программы. Функции часто
работают с большими объектами — постоянное копирование таких объектов
ведет к большим затратам компьютерной памяти и существенному замедлению работы программы.

Адреса памяти, ссылки и «указатели»
Все взаимодействие с объектом выполняется по ссылке, которая во внутренней реализации представляет адрес объекта (его местоположение в памяти
компьютера) — в других языках иногда используется термин «указатель».
После присваивания вида
x = 7

переменная x на самом деле не содержит значение 7. Вместо этого она содержит ссылку на объект, содержащий значение 7, хранящийся где-то в памяти.
Можно сказать, что x «указывает» на объект, содержащий 7, как на следующей
диаграмме:
Переменная
x

Объект
7

Встроенная функция id и идентичность объектов
Теперь посмотрим, как происходит передача аргументов функциям. Начнем
с создания целочисленной переменной x, упоминавшейся выше, — вскоре x
будет использоваться как аргумент функции:
In [1]: x = 7

Теперь x ссылается (или указывает) на объект, представляющий целое число
со значением 7. Два объекта ни при каких условиях не могут размещаться
в памяти по одному адресу, поэтому каждый объект в памяти обладает уникальным адресом.
Хотя фактический адрес объекта остается неизвестным, вы можете воспользоваться встроенной функцией id для получения уникального значения int,

4.15. Подробнее о передаче аргументов функциям   181

идентифицирующего этот объект, пока он остается в памяти (скорее всего,
при выполнении этого фрагмента на своем компьютере вы получите другое
значение):
In [2]: id(x)
Out[2]: 4350477840

Целочисленный результат вызова id называется идентичностью объекта1. Два
объекта в памяти не могут обладать одинаковой идентичностью. Мы будем
использовать идентичность объектов для демонстрации того, что объекты
передаются по ссылке.

Передача объекта функции
Определим функцию cube, которая выводит идентичность своего параметра,
а затем возвращает значение параметра, возведенное в куб:
In [3]: def cube(number):
...:
print('id(number):', id(number))
...:
return number ** 3
...:

Теперь вызовем cube с аргументом x, который указывает на объект, содержащий значение 7:
In [4]: cube(x)
id(number): 4350477840
Out[4]: 343

Идентичность, выводимая для параметра cube — 4350477840, — совпадает
с той, которая ранее выводилась для x. Поскольку каждый объект обладает
уникальной идентичностью, аргумент x и параметр number ссылаются на один
и тот же объект во время выполнения cube. Таким образом, когда функция
cube использует свой параметр number в вычислениях, она получает значение
number из исходного объекта на стороне вызова.
1

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

182   Глава 4. Функции

Проверка идентичности объекта оператором is
Чтобы доказать, что аргумент и параметр ссылаются на один и тот же объект,
можно воспользоваться оператором Python is. Этот оператор возвращает
True, если идентичности двух операндов совпадают:
In [5]: def cube(number):
...:
print('number is x:', number is x)
...:
return number ** 3
...:

# x - глобальная переменная

In [6]: cube(x)
number is x: True
Out[6]: 343

Неизменяемые объекты как аргументы
Когда функция получает в аргументе ссылку на неизменяемый объект (например, int, float, string или tuple), даже при том, что вы можете напрямую
обратиться к исходному объекту на стороне вызова, вам не удастся изменить
значение исходного неизменяемого объекта. Чтобы убедиться в этом, изменим
функцию cube, чтобы она выводила id(number) до и после присваивания нового объекта параметру number с использованием расширенного присваивания:
In [7]: def cube(number):
...:
print('id(number) before modifying number:', id(number))
...:
number **= 3
...:
print('id(number) after modifying number:', id(number))
...:
return number
...:
In [8]: cube(x)
id(number) before modifying number: 4350477840
id(number) after modifying number: 4396653744
Out[8]: 343

При вызове cube(x) первая команда print показывает, что значение id(number)
изначально совпадало с id(x) из фрагмента [2]. Числовые значения неизменяемы, поэтому команда
number **= 3

на самом деле создает новый объект со значением, возведенным в куб, а затем присваивает ссылку на этот объект параметру number. Вспомните, что

4.16. Рекурсия   183

при отсутствии ссылок на исходный объект он будет уничтожен уборщиком
мусора. Вторая команда print функции cube выводит идентичность нового
объекта. Идентичности объектов должны быть уникальными. Следовательно, переменная number содержит ссылку на другой объект. Чтобы показать,
что значение x не изменилось, снова выведем значение и идентичность
переменной:
In [9]: print(f'x = {x}; id(x) = {id(x)}')
x = 7; id(x) = 4350477840

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

4.16. Рекурсия
Напишем программу для выполнения известных математических вычислений.
Факториал положительного целого числа n записывается в виде n! (читается
«n факториал».) Он вычисляется как произведение
n × (n – 1) × (n – 2) × … × 1.
При этом и 1!, и 0! определяются равными 1. Но, например, 5! вычисляется
как произведение 5 · 4 · 3 · 2 · 1, то есть его значение равно 120.

Итеративное вычисление факториала
Значение 5! можно вычислить итеративным методом с использованием
цикла for:
In [1]: factorial = 1
In [2]: for number in range(5, 0, -1):
...:
factorial *= number
...:
In [3]: factorial
Out[3]: 120

184   Глава 4. Функции

Рекурсивное решение задачи
Методы рекурсивного решения задач имеют несколько общих элементов.
Когда вы вызываете рекурсивную функцию для решения задачи, она в действительности умеет решать только простейший, или базовый, случай(-и)
задачи. Если вызвать функцию для базового случая, то она немедленно вернет результат. Если же функция вызывается для более сложной задачи, то
эта задача обычно делится на две части: одну из них функция умеет решать,
другую — нет. Чтобы рекурсия была приемлемой, вторая часть должна быть
слегка упрощенной или сокращенной версией исходной задачи. Так как новая
задача напоминает исходную задачу, функция вызывает новую копию самой
себя для работы над меньшей задачей — этот вызов называется рекурсивным
(также используется термин «шаг рекурсии»). Концепция разбиения задачи
на две меньшие части является разновидностью известного принципа «разделяй и властвуй».
На время выполнения шага рекурсии исходный вызов функции остается активным (то есть его выполнение еще не завершилось). Это может привести
к появлению множества новых рекурсивных вызовов, так как функция делит
каждую новую подзадачу на две концептуальные части.
Чтобы рекурсия рано или поздно завершилась, каждый раз, когда функция
вызывает сама себя с упрощенной версией исходной задачи, последовательность уменьшающихся задач должна сходиться к базовому случаю. Когда
функция распознает базовый случай, она возвращает результат предыдущей копии функции. Последовательность возвратов продолжается до тех
пор, пока исходный вызов функции не вернет окончательный результат на
сторону вызова.

Рекурсивное вычисление факториала
До перехода к рекурсивному представлению задачи вычисления факториала
заметим, что n! можно записать и в виде:
n! = n · (n — 1)!
Например, выражение 5! эквивалентно 5 · 4!:
5! = 5 · 4 · 3 · 2 · 1
5! = 5 · (4 · 3 · 2 · 1)
5! = 5 · (4!)

4.16. Рекурсия   185

Наглядное представление рекурсии
Процесс вычисления 5! продемонстрирован на следующей схеме. Левый
столбец показывает, как проходит последовательность рекурсивных вызовов
до того момента, как для 1! (базовый случай) возвращается 1, что завершает
рекурсию. В правом столбце показаны (снизу вверх) значения, возвращаемые
на каждом шаге рекурсии на сторону вызова, пока не будет вычислено и возвращено итоговое значение.
Итоговое значение = 120
5!

5!

5! = 5 * 24 = 120 возвращается
5 * 4!

5 * 4!

4! = 4 * 6 = 24 возвращается
4 * 3!

4 * 3!

3! = 3 * 2 = 6 возвращается
3 * 2!

3 * 2!

2! = 2 * 1 = 2 возвращается
2 * 1!

2 * 1!

1 возвращается
1

(a) последовательность
рекурсивных вызовов

1

(б) значения, возвращаемые
при каждом рекурсивном вызове

Реализация рекурсивной функции вычисления факториала
Следующий сеанс использует рекурсию для вычисления и вывода факториалов целых чисел от 0 до 10:
In [4]: def factorial(number):
...:
"""Возвращает факториал числа."""
...:
if number 1 c[100]
IndexError: list index out of range

Использование элементов списков в выражениях
Элементы списков могут использоваться как переменные в выражениях:
In [17]: c[0] + c[1] + c[2]
Out[17]: -39

5.2. Списки   199

Присоединение элементов к списку оператором +=
Начнем с пустого списка list [], а затем в цикле for присоединим к нему
значения от 1 до 5 — список динамически расширяется, чтобы вместить новые
элементы:
In [18]: a_list = []
In [19]: for number in range(1, 6):
...:
a_list += [number]
...:
In [20]: a_list
Out[20]: [1, 2, 3, 4, 5]

Когда левый операнд += является списком, правый операнд должен быть
итерируемым объектом; в противном случае происходит ошибка TypeError.
В наборе фрагмента [19] квадратные скобки вокруг числа создают список из
одного элемента, который присоединяется к списку. Если правый операнд
содержит несколько элементов, то операнд += присоединяет их все. В следующем фрагменте символы слова 'Python' присоединяются к списку letters:
In [21]: letters = []
In [22]: letters += 'Python'
In [23]: letters
Out[23]: ['P', 'y', 't', 'h', 'o', 'n']

Если правым операндом += является кортеж, то его элементы также присоединяются к списку. Позднее в этой главе мы используем метод списков append
для добавления элементов в список.

Конкатенация списков оператором +
Оператор + может использоваться для конкатенации (слияния) двух списков,
двух кортежей или двух строк. Результатом является новая последовательность того же типа, содержащая элементы левого операнда, за которыми
следуют элементы правого операнда. Исходные последовательности остаются
неизменными:

200   Глава 5. Последовательности: списки и кортежи
In [24]: list1 = [10, 20, 30]
In [25]: list2 = [40, 50]
In [26]: concatenated_list = list1 + list2
In [27]: concatenated_list
Out[27]: [10, 20, 30, 40, 50]

Если операнды оператора + относятся к разным типам последовательностей,
например происходит ошибка TypeError, то и конкатенация списка и кортежа
является ошибкой.

Использование for и range для обращения к индексам
и значениям списков
К элементам списков также можно обращаться по их индексам с использованием оператора индексирования ([]):
In [28]: for i in range(len(concatenated_list)):
...:
print(f'{i}: {concatenated_list[i]}')
...:
0: 10
1: 20
2: 30
3: 40
4: 50

Вызов функции range(len(concatenated_list)) выдает последовательность
целых чисел, представляющих индексы concatenated_list (в данном случае
от 0 до 4). Перебирая элементы таким способом, необходимо следить за тем,
чтобы индексы не вышли за пределы диапазона. Вскоре мы покажем более
безопасный способ обращения к индексам и значениям элементов с использованием встроенной функции enumerate.

Операторы сравнения
Операторы сравнения также могут использоваться для поэлементного сравнения целых списков:
In [29]: a = [1, 2, 3]
In [30]: b = [1, 2, 3]
In [31]: c = [1, 2, 3, 4]

5.3. Кортежи   201
In [32]: a == b
Out[32]: True

# True: соответствующие элементы обоих списков равны

In [33]: a == c
Out[33]: False

# False: у a и c различаются элементы и длины

In [34]: a < c
Out[34]: True
In [35]: c >= b
Out[35]: True

# True: a содержит меньше элементов, чем c

# True: элементы 0-2 равны, но в c больше элементов

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

Создание кортежей
Чтобы создать пустой кортеж, используйте пустые круглые скобки:
In [1]: student_tuple = ()
In [2]: student_tuple
Out[2]: ()
In [3]: len(student_tuple)
Out[3]: 0

Напомним, для упаковки элементов в кортеж можно перечислить их, разделяя
запятыми:
In [4]: student_tuple = 'John', 'Green', 3.3
In [5]: student_tuple
Out[5]: ('John', 'Green', 3.3)
In [6]: len(student_tuple)
Out[6]: 3

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

202   Глава 5. Последовательности: списки и кортежи
In [7]: another_student_tuple = ('Mary', 'Red', 3.3)
In [8]: another_student_tuple
Out[8]: ('Mary', 'Red', 3.3)

Следующий фрагмент создает кортеж из одного элемента:
In [9]: a_singleton_tuple = ('red',)

# Обратите внимание на запятую

In [10]: a_singleton_tuple
Out[10]: ('red',)

Запятая (,) после строки 'red' идентифицирует a_singleton_tuple как кортеж — круглые скобки необязательны. Если бы запятой не было, то круглые
скобки стали бы избыточными, а имя a_singleton_tuple обозначало бы строку
'red' вместо кортежа.

Обращение к элементам кортежа
Элементы кортежа, хотя и логически связаны друг с другом, часто относятся
к разным типам. Обычно в программе вы не перебираете их, а обращаетесь
к каждому элементу по отдельности. Как и индексы списков, индексы кортежей начинаются с 0. Следующий код создает кортеж time_tuple, представляющий часы, минуты и секунды, выводит кортеж, а затем использует его
элементы для вычисления количества секунд от полуночи; обратите внимание
на то, что с разными значениями в кортеже выполняются и разные операции:
In [11]: time_tuple = (9, 16, 1)
In [12]: time_tuple
Out[12]: (9, 16, 1)
In [13]: time_tuple[0] * 3600 + time_tuple[1] * 60 + time_tuple[2]
Out[13]: 33361

Присваивание значения элементу кортежа приводит к ошибке TypeError.

Добавление элементов в строку или кортеж
Как и в случае со списками, расширенное присваивание += может использоваться со строками и кортежами, несмотря на их неизменяемость. В следующем коде после двух присваиваний tuple1 и tuple2 ссылаются на один и тот
же объект кортежа:

5.3. Кортежи   203
In [14]: tuple1 = (10, 20, 30)
In [15]: tuple2 = tuple1
In [16]: tuple2
Out[16]: (10, 20, 30)

При конкатенации кортежа (40, 50) с tuple1 создается новый кортеж, ссылка
на который присваивается переменной tuple1, тогда как tuple2 все еще ссыла­
ется на исходный кортеж:
In [17]: tuple1 += (40, 50)
In [18]: tuple1
Out[18]: (10, 20, 30, 40, 50)
In [19]: tuple2
Out[19]: (10, 20, 30)

Для строки или кортежа справа от += должна находиться строка или кортеж,
соответственно, смешение типов приводит к ошибке TypeError.

Присоединение кортежей к спискам
Конструкция += также может использоваться для присоединения кортежа
к списку:
In [20]: numbers = [1, 2, 3, 4, 5]
In [21]: numbers += (6, 7)
In [22]: numbers
Out[22]: [1, 2, 3, 4, 5, 6, 7]

Кортежи могут содержать изменяемые объекты
Создадим кортеж student_tuple с именем, фамилией и списком оценок:
In [23]: student_tuple = ('Amanda', 'Blue', [98, 75, 87])

И хотя сам кортеж неизменяем, его элемент-список может изменяться:
In [24]: student_tuple[2][1] = 85
In [25]: student_tuple
Out[25]: ('Amanda', 'Blue', [98, 85, 87])

204   Глава 5. Последовательности: списки и кортежи
В имени student_tuple[2][1] с двойным индексированием Python рассматривает student_tuple[2] как элемент кортежа, содержащий список [98, 75, 87],
а затем использует [1] для обращения к элементу списка 75. Присваивание
в фрагменте [24] заменяет это значение на 85.

5.4. Распаковка последовательностей
В предыдущей главе был представлен механизм распаковки кортежей. Элементы любой последовательности можно распаковать, присваивая эту последовательность списку переменных, разделенных запятыми. Ошибка ValueError
возникает, если число переменных слева от символа присваивания не совпадает с количеством элементов в последовательности справа:
In [1]: student_tuple = ('Amanda', [98, 85, 87])
In [2]: first_name, grades = student_tuple
In [3]: first_name
Out[3]: 'Amanda'
In [4]: grades
Out[4]: [98, 85, 87]

Следующий код распаковывает строку, список и последовательность, сгенерированную range:
In [5]: first, second = 'hi'
In [6]: print(f'{first}
h i

{second}')

In [7]: number1, number2, number3 = [2, 3, 5]
In [8]: print(f'{number1}
2 3 5

{number2}

{number3}')

In [9]: number1, number2, number3 = range(10, 40, 10)
In [10]: print(f'{number1}
10 20 30

{number2}

{number3}')

Перестановка значений
Вы можете поменять местами значения двух переменных, используя механизмы упаковки и распаковки последовательностей:

5.4. Распаковка последовательностей   205
In [11]: number1 = 99
In [12]: number2 = 22
In [13]: number1, number2 = (number2, number1)
In [14]: print(f'number1 = {number1}; number2 = {number2}')
number1 = 22; number2 = 99

Безопасное обращение к индексам и значениям при помощи
встроенной функции enumerate
Ранее мы вызывали функцию range для генерирования последовательности
значений индексов, а затем обращались к элементам списков в цикле for при
помощи значений индексов и оператора индексирования ([]). Этот способ
повышает риск ошибок, потому что range могут быть переданы неправильные
аргументы. Если любое значение, сгенерированное range, окажется индексом,
выходящим за пределы диапазона, то его использование в качестве индекса
вызовет ошибку IndexError.
Для обращения к индексу и значению элемента рекомендуется использовать
встроенную функцию enumerate. Эта функция получает итерируемый объект
и создает итератор, который для каждого элемента возвращает кортеж с индексом и значением этого элемента. В следующем коде встроенная функция
list используется для создания списка, содержащего результаты enumerate:
In [15]: colors = ['red', 'orange', 'yellow']
In [16]: list(enumerate(colors))
Out[16]: [(0, 'red'), (1, 'orange'), (2, 'yellow')]

Аналогичным образом встроенная функция tuple создает кортеж из последовательности:
In [17]: tuple(enumerate(colors))
Out[17]: ((0, 'red'), (1, 'orange'), (2, 'yellow'))

Следующий цикл for распаковывает каждый кортеж, возвращенный enumerate,
в переменные index и value, и выводит их:
In [18]: for index, value in enumerate(colors):
...:
print(f'{index}: {value}')
...:
0: red
1: orange
2: yellow

206   Глава 5. Последовательности: списки и кортежи

Построение примитивной гистограммы
Следующий сценарий создает примитивную гистограмму, у которой строки
строятся из звездочек (*) в количестве, пропорциональном значению соответствующего элемента списка. Мы используем функцию enumerate для
безопасного обращения к индексам и значениям списка. Чтобы запустить
этот пример, перейдите в папку примеров главы ch05 и введите следующую
команду:
ipython fig05_01.py

или, если вы уже запустили IPython, введите команду:
run fig05_01.py
1
2
3
4
5
6
7
8
9

# fig05_01.py
"""Построение гистограммы"""
numbers = [19, 3, 15, 7, 11]
print('\nCreating a bar chart from numbers:')
print(f'Index{"Value":>8}
Bar')
for index, value in enumerate(numbers):
print(f'{index:>5}{value:>8}
{"*" * value}')
Creating a bar chart from numbers:
Index
Value
Bar
0
19
*******************
1
3
***
2
15
***************
3
7
*******
4
11
***********

Команда for использует enumerate для получения индекса и значения каждого
элемента, а затем выводит отформатированную строку с индексом, значением
элемента и серией звездочек соответствующей длины. Выражение
"*" * value

создает строку, состоящую из value звездочек. При использовании с последовательностью оператор умножения (*) повторяет последовательность —
в данном случае строку "*" — value раз. Позднее в этой главе библиотеки
с открытым кодом Seaborn и Matplotlib используются для построения гистограммы типографского качества.

5.5. Сегментация последовательностей   207

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

Определение сегмента по начальному и конечному индексу
Создадим сегмент, состоящий из элементов списка с индексами с 2 по 5:
In [1]: numbers = [2, 3, 5, 7, 11, 13, 17, 19]
In [2]: numbers[2:6]
Out[2]: [5, 7, 11, 13]

Операция сегментации копирует элементы от начального индекса слева от
двоеточия (2) до конечного индекса справа от двоеточия (6), не включая последний. Исходный список не изменяется.

Определение сегмента только по конечному индексу
Если начальный индекс не указан, то предполагается значение 0. Таким образом, обозначение сегмента numbers[:6] эквивалентно записи numbers[0:6]:
In [3]: numbers[:6]
Out[3]: [2, 3, 5, 7, 11, 13]
In [4]: numbers[0:6]
Out[4]: [2, 3, 5, 7, 11, 13]

Определение сегмента только по начальному индексу
Если не указан конечный индекс, то Python предполагает, что он равен длине
последовательности (8 в данном случае), поэтому сегмент из фрагмента [5]
содержит элементы numbers с индексами 6 и 7:
In [5]: numbers[6:]
Out[5]: [17, 19]
In [6]: numbers[6:len(numbers)]
Out[6]: [17, 19]

208   Глава 5. Последовательности: списки и кортежи

Определение сегмента без индексов
Если не указаны ни начальный, ни конечный индексы, то копируется вся последовательность:
In [7]: numbers[:]
Out[7]: [2, 3, 5, 7, 11, 13, 17, 19]

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

Сегментация с шагом
В следующем коде используется шаг 2 для создания сегмента, содержащего
каждый второй элемент numbers:
In [8]: numbers[::2]
Out[8]: [2, 5, 11, 17]

Начальный и конечный индекс не указаны, поэтому предполагаются значения
0 и len(numbers) соответственно.

Сегментация с отрицательными индексами и шагом
Отрицательное значение шага используется для сегментации в обратном
­порядке. Следующий код компактно создает новый список в обратном порядке:
In [9]: numbers[::-1]
Out[9]: [19, 17, 13, 11, 7, 5, 3, 2]

Эта запись эквивалентна следующей:
In [10]: numbers[-1:-9:-1]
Out[10]: [19, 17, 13, 11, 7, 5, 3, 2]

5.5. Сегментация последовательностей   209

Изменение списков через сегменты
Вы можете изменить список, выполнив присваивание его сегменту, остальная
часть списка остается неизменной. В следующем коде заменяются первые три
элемента numbers, а остальные элементы остаются без изменений:
In [11]: numbers[0:3] = ['two', 'three', 'five']
In [12]: numbers
Out[12]: ['two', 'three', 'five', 7, 11, 13, 17, 19]

А в этом примере удаляются только первые три элемента numbers, для чего
пустой список присваивается сегменту из трех элементов:
In [13]: numbers[0:3] = []
In [14]: numbers
Out[14]: [7, 11, 13, 17, 19]

В этом примере новые значения присваиваются через один элемент списка:
In [15]: numbers = [2, 3, 5, 7, 11, 13, 17, 19]
In [16]: numbers[::2] = [100, 100, 100, 100]
In [17]: numbers
Out[17]: [100, 3, 100, 7, 100, 13, 100, 19]
In [18]: id(numbers)
Out[18]: 4434456648

А здесь удаляются все элементы numbers, в результате чего существующий
список остается пустым:
In [19]: numbers[:] = []
In [20]: numbers
Out[20]: []
In [21]: id(numbers)
Out[21]: 4434456648

Удаление содержимого numbers (фрагмент [19]) отличается от присваивания
numbers нового пустого списка [] (фрагмент [22]). Чтобы доказать это, выведем

210   Глава 5. Последовательности: списки и кортежи
идентичность numbers после каждой операции. Идентичности не совпадают,
а значит, они представляют разные объекты в памяти:
In [22]: numbers = []
In [23]: numbers
Out[23]: []
In [24]: id(numbers)
Out[24]: 4406030920

Когда вы присваиваете новый объект переменной (как во фрагменте [21]),
исходный объект будет уничтожен уборщиком мусора, если на него не ссыла­
ются другие объекты.

5.6. Команда del
Команда del также может использоваться для удаления элементов из списка
и переменных из интерактивного сеанса. Вы можете удалить элемент с любым
действительным индексом или элемент(-ы) любого действительного сегмента.

Удаление элемента списка с заданным индексом
Создадим список, а затем воспользуемся del для удаления его последнего
элемента:
In [1]: numbers = list(range(0, 10))
In [2]: numbers
Out[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [3]: del numbers[-1]
In [4]: numbers
Out[4]: [0, 1, 2, 3, 4, 5, 6, 7, 8]

Удаление сегмента из списка
Следующий пример удаляет из списка первые два элемента:
In [5]: del numbers[0:2]
In [6]: numbers
Out[6]: [2, 3, 4, 5, 6, 7, 8]

5.7. Передача списков функциям   211

А здесь шаг сегментации применяется для удаления элементов через один во
всем списке:
In [7]: del numbers[::2]
In [8]: numbers
Out[8]: [3, 5, 7]

Удаление сегмента, представляющего весь список
В этом примере из списка удаляются все элементы:
In [9]: del numbers[:]
In [10]: numbers
Out[10]: []

Удаление переменной из текущего сеанса
Команда del может удалить любую переменную. Если удалить numbers из
интерактивного сеанса, а затем попытаться вывести значение переменной, то
произойдет ошибка NameError:
In [11]: del numbers
In [12]: numbers
-----------------------------------------------------------------_------NameError
Traceback (most recent call last)
in ()
----> 1 numbers
NameError: name 'numbers' is not defined

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

Передача функции всего списка
Рассмотрим функцию modify_elements, которая получает ссылку на список
и умножает значение каждого элемента на 2:

212   Глава 5. Последовательности: списки и кортежи
In [1]: def modify_elements(items):
...:
""""Умножает значения всех элементов items на 2."""
...:
for i in range(len(items)):
...:
items[i] *= 2
...:
In [2]: numbers = [10, 3, 7, 1, 9]
In [3]: modify_elements(numbers)
In [4]: numbers
Out[4]: [20, 6, 14, 2, 18]

Параметр items функции modify_elements получает ссылку на исходный список, поэтому команда в наборе цикла изменяет каждый элемент в исходном
объекте списка.

Передача кортежа функции
Если функции передается кортеж, то попытка изменить неизменяемые элементы кортежа приводит к ошибке TypeError:
In [5]: numbers_tuple = (10, 20, 30)
In [6]: numbers_tuple
Out[6]: (10, 20, 30)
In [7]: modify_elements(numbers_tuple)
-----------------------------------------------------------------------TypeError
Traceback (most recent call last)
in ()
----> 1 modify_elements(numbers_tuple)
in modify_elements(items)
2
""""Умножает значения всех элементов items на 2."""
3
for i in range(len(items)):
----> 4
items[i] *= 2
5
6
TypeError: 'tuple' object does not support item assignment

Вспомните, что кортежи могут содержать изменяемые объекты, например списки. Такие объекты могут быть изменены при передаче кортежа
функции.

5.8. Сортировка списков   213

Примечание по поводу трассировок
В приведенной трассировке показаны два фрагмента, приведшие к ошибке
TypeError. Первый — вызов функции из фрагмента [7]. Второй — определение
функции из фрагмента [1]. Коду каждого фрагмента предшествуют номера
строк. Мы демонстрировали в основном однострочные фрагменты. Когда
в таком фрагменте происходит исключение, ему всегда предшествует префикс ----> 1, означающий, что исключение вызвала строка 1 (единственная
строка фрагмента). Для многострочных фрагментов, таких как определение
modify_elements, приводятся последовательные номера строк начиная с 1. Запись ----> 4 в приведенном примере показывает, что исключение произошло
в строке 4 функции modify_elements. Какой бы длинной ни была трассировка,
причиной исключения стала последняя строка кода с ---->.

5.8. Сортировка списков
Сортировка позволяет упорядочить данные по возрастанию или по убы­
ванию.

Сортировка списка по возрастанию
Метод списков sort изменяет список и размещает элементы по возрастанию:
In [1]: numbers = [10, 3, 7, 1, 9, 4, 2, 8, 5, 6]
In [2]: numbers.sort()
In [3]: numbers
Out[3]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Сортировка списка по убыванию
Чтобы отсортировать список по убыванию, вызовите метод списка sort с необязательным ключевым аргументом reverse, равным True (по умолчанию
используется значение False):
In [4]: numbers.sort(reverse=True)
In [5]: numbers
Out[5]: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

214   Глава 5. Последовательности: списки и кортежи

Встроенная функция sorted
Встроенная функция sorted возвращает новый список, содержащий отсор­
тированные элементы своей последовательности-аргумента — исходная последовательность при этом не изменяется. Следующий код демонстрирует
использование функции sorted со списками, строками и кортежами:
In [6]: numbers = [10, 3, 7, 1, 9, 4, 2, 8, 5, 6]
In [7]: ascending_numbers = sorted(numbers)
In [8]: ascending_numbers
Out[8]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
In [9]: numbers
Out[9]: [10, 3, 7, 1, 9, 4, 2, 8, 5, 6]
In [10]: letters = 'fadgchjebi'
In [11]: ascending_letters = sorted(letters)
In [12]: ascending_letters
Out[12]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
In [13]: letters
Out[13]: 'fadgchjebi'
In [14]: colors = ('red', 'orange', 'yellow', 'green', 'blue')
In [15]: ascending_colors = sorted(colors)
In [16]: ascending_colors
Out[16]: ['blue', 'green', 'orange', 'red', 'yellow']
In [17]: colors
Out[17]: ('red', 'orange', 'yellow', 'green', 'blue')

Необязательный ключевой аргумент reverse со значением True заставляет
функцию отсортировать элементы по убыванию.

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

5.9. Поиск в последовательностях   215

Метод index
У списков имеется метод index, в аргументе которого передается ключ поиска. Метод начинает поиск с индекса 0 и возвращает индекс первого элемента,
который равен ключу поиска:
In [1]: numbers = [3, 7, 1, 4, 2, 8, 5, 6]
In [2]: numbers.index(5)
Out[2]: 6

Если искомое значение отсутствует в списке, то происходит ошибка ValueError.

Определение начального индекса поиска
С необязательными аргументами метода index можно ограничить поиск
подмножеством элементов списка. Оператор *= может использоваться для
умножения последовательности, то есть присоединения последовательности
к самой себе несколько раз. После выполнения следующего фрагмента numbers
содержит две копии содержимого исходного списка:
In [3]: numbers *= 2
In [4]: numbers
Out[4]: [3, 7, 1, 4, 2, 8, 5, 6, 3, 7, 1, 4, 2, 8, 5, 6]

А этот код ищет в обновленном списке значение 5, начиная с индекса 7 и до
конца списка:
In [5]: numbers.index(5, 7)
Out[5]: 14

Определение начального и конечного индексов для поиска
Если определить начальный и конечный индексы, то index начинает поиск от
начального до конечного индекса, исключая последний. Вызов index в фрагменте [5]:
numbers.index(5, 7)

предполагает, что третьим необязательным аргументом является длина
numbers; этот вызов эквивалентен следующему:
numbers.index(5, 7, len(numbers))

216   Глава 5. Последовательности: списки и кортежи
Следующий код ищет значение 7 в диапазоне элементов с индексами от 0 до 3:
In [6]: numbers.index(7, 0, 4)
Out[6]: 1

Операторы in и not in
Оператор in проверяет, содержит ли итерируемый объект, заданный правым
операндом, значение левого операнда:
In [7]: 1000 in numbers
Out[7]: False
In [8]: 5 in numbers
Out[8]: True

Аналогичным образом оператор not in проверяет, что итерируемый объект,
заданный правым операндом, не содержит значение левого операнда:
In [9]: 1000 not in numbers
Out[9]: True
In [10]: 5 not in numbers
Out[10]: False

Использование оператора in для предотвращения
ошибок ValueError
Оператор in поможет убедиться в том, что вызов метода index не приведет
к ошибке ValueError из-за ключей поиска, не входящих в соответствующую
последовательность:
In [11]: key = 1000
In [12]: if key innumbers:
...:
print(f'found {key} at index {numbers.index(search_key)}')
...: else:
...:
print(f'{key} not found')
...:
1000 not found

Встроенные функции any и all
Иногда требуется проверить, равен ли True хотя бы один элемент итерируемого объекта или равны ли True все элементы. Встроенная функция any воз-

5.10. Другие методы списков   217

вращает True, если хотя бы один элемент итерируемого объекта из аргумента
равен True. Встроенная функция all возвращает True, если все элементы
итерируемого объекта из аргумента равны True. Напомним, что ненулевые
значения интерпретируются как True, а 0 интерпретируется как False. Непустые итерируемые объекты также интерпретируются как True, тогда как любой
пустой итерируемый объект интерпретируется как False. Функции any и all
также являются примерами внутренних итераций при программировании
в функциональном стиле.

5.10. Другие методы списков
У списков также имеются методы для добавления и удаления элементов.
Возьмем список color_names:
In [1]: color_names = ['orange', 'yellow', 'green']

Вставка элемента в заданную позицию списка
Метод insert вставляет новый элемент в позицию с заданным индексом.
Следующий код вставляет 'red' в позицию с индексом 0:
In [2]: color_names.insert(0, 'red')
In [3]: color_names
Out[3]: ['red', 'orange', 'yellow', 'green']

Добавление элемента в конец списка
Метод append используется для добавления нового элемента в конец списка:
In [4]: color_names.append('blue')
In [5]: color_names
Out[5]: ['red', 'orange', 'yellow', 'green', 'blue']

Добавление всех элементов последовательности в конец списка
Метод extend списков используется для добавления всех элементов другой
последовательности в конец списка:
In [6]: color_names.extend(['indigo', 'violet'])
In [7]: color_names
Out[7]: ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']

218   Глава 5. Последовательности: списки и кортежи
Результат эквивалентен использованию +=. Следующий код добавляет в список все символы строки, а затем все элементы кортежа:
In [8]: sample_list = []
In [9]: s = 'abc'
In [10]: sample_list.extend(s)
In [11]: sample_list
Out[11]: ['a', 'b', 'c']
In [12]: t = (1, 2, 3)
In [13]: sample_list.extend(t)
In [14]: sample_list
Out[14]: ['a', 'b', 'c', 1, 2, 3]

Вместо того чтобы создавать временную переменную (такую как t) для хранения кортежа перед присоединением его к списку, возможно, вы захотите
передать кортеж методу extend напрямую. В этом случае круглые скобки
кортежа становятся обязательными, потому что extend ожидает получить
один аргумент с итерируемым объектом:
In [15]: sample_list.extend((4, 5, 6))

# Обратите внимание на вторые круглые
# скобки

In [16]: sample_list
Out[16]: ['a', 'b', 'c', 1, 2, 3, 4, 5, 6]

При отсутствии необходимых круглых скобок происходит ошибка TypeError.

Удаление первого вхождения элемента в списке
Метод remove удаляет первый элемент с заданным значением — если аргумент
remove отсутствует в списке, то происходит ошибка ValueError:
In [17]: color_names.remove('green')
In [18]: color_names
Out[18]: ['red', 'orange', 'yellow', 'blue', 'indigo', 'violet']

5.10. Другие методы списков   219

Очистка списка
Чтобы удалить все элементы из списка, вызовите метод clear:
In [19]: color_names.clear()
In [20]: color_names
Out[20]: []

Эта команда эквивалентна приведенному ранее сегментному присваиванию
color_names[:] = []

Подсчет количества вхождений элемента
Метод count ищет в списке заданный аргумент и возвращает количество его
найденных вхождений:
In [21]: responses = [1, 2, 5, 4, 3, 5, 2, 1, 3, 3,
...:
1, 4, 3, 3, 3, 2, 3, 3, 2, 2]
...:
In [22]: for i in range(1, 6):
...:
print(f'{i} appears {responses.count(i)} times in responses')
...:
1 appears 3 times in responses
2 appears 5 times in responses
3 appears 8 times in responses
4 appears 2 times in responses
5 appears 2 times in responses

Перестановка элементов списка в обратном порядке
Метод списков reverse переставляет содержимое списка в обратном порядке
(вместо создания копии с переставленными элементами, как это делалось
ранее с сегментом):
In [23]: color_names = ['red', 'orange', 'yellow', 'green', 'blue']
In [24]: color_names.reverse()
In [25]: color_names
Out[25]: ['blue', 'green', 'yellow', 'orange', 'red']

220   Глава 5. Последовательности: списки и кортежи

Копирование списка
Метод списков copy возвращает новый список, содержащий поверхностную
копию исходного списка:
In [26]: copied_list = color_names.copy()
In [27]: copied_list
Out[27]: ['blue', 'green', 'yellow', 'orange', 'red']

Эта команда эквивалентна приведенной ранее сегментной операции:
copied_list = color_names[:]

5.11. Моделирование стека на базе списка
В главе 4 был представлен стек вызовов функций. В Python нет встроенного
типа стека, но стек можно рассматривать как ограниченную версию списка.
Для занесения элементов в стек можно использовать метод append, который
добавляет новый элемент в конец списка. Извлечение элементов может моделироваться методом списков pop без аргументов — этот метод удаляет и возвращает элемент, находящийся в конце списка.
Создадим пустой список с именем stack, занесем в него две строки (при помощи append), а затем извлечем (pop) строки, чтобы убедиться в том, что они
извлекаются в порядке LIFO (Last In First Out, то есть «последним пришел,
первым вышел»):
In [1]: stack = []
In [2]: stack.append('red')
In [3]: stack
Out[3]: ['red']
In [4]: stack.append('green')
In [5]: stack
Out[5]: ['red', 'green']
In [6]: stack.pop()
Out[6]: 'green'
In [7]: stack

5.12. Трансформации списков   221
Out[7]: ['red']
In [8]: stack.pop()
Out[8]: 'red'
In [9]: stack
Out[9]: []
In [10]: stack.pop()
------------------------------------------------------------------------IndexError
Traceback (most recent call last)
in ()
----> 1 stack.pop()
IndexError: pop from empty list

В каждом фрагменте c pop выводится значение, которое удаляет и возвращает
pop. При попытке извлечения из пустого стека происходит ошибка IndexError
по аналогии с обращением к несуществующему элементу списка с []. Чтобы
предотвратить ошибку IndexError, перед вызовом pop убедитесь в том, что
len(stack) больше 0. Если элементы будут заноситься в стек быстрее, чем
извлекаться, то это может привести к исчерпанию свободной памяти.
Список также может использоваться для моделирования другой популярной
разновидности коллекций — очереди, у которой элементы вставляются в конце,
а извлекаются сначала. Элементы извлекаются из очередей в порядке FIFO
(«первым пришел, первым вышел»).

5.12. Трансформации списков
В этом разделе рассматриваются такие средства программирования в функциональном стиле, как трансформации списков (list comprehensions), — компактная
и удобная запись для создания новых списков. Трансформации списков могут
заменить многие команды for, в которых выполняется перебор существующих
последовательностей и создание новых списков:
In [1]: list1 = []
In [2]: for item in range(1, 6):
...:
list1.append(item)
...:
In [3]: list1
Out[3]: [1, 2, 3, 4, 5]

222   Глава 5. Последовательности: списки и кортежи

Использование трансформации списка для создания
списка целых чисел
С трансформацией списка ту же операцию можно выполнить всего в одной
строке кода:
In [4]: list2 = [item for item in range(1, 6)]
In [5]: list2
Out[5]: [1, 2, 3, 4, 5]

Как и команда for из фрагмента [2], секция for трансформации списка
for item in range(1, 6)

перебирает последовательность, сгенерированную вызовом range(1, 6). Для
каждого элемента трансформация списка вычисляет выражение слева от for
и помещает значение выражения (в данном случае сам элемент item) в новый
список. Конкретную трансформацию из фрагмента [4] можно было бы более
компактно выразить при помощи функции list:
list2 = list(range(1, 6))

Отображение: выполнение операций в выражениях
трансформации списков
Выражение трансформации списка может выполнять разные операции (например, вычисления), отображающие элементы на новые значения (в том
числе, возможно, и других типов). Отображение (mapping) — стандартная
операция программирования в функциональном стиле, которая выдает результат с таким же количеством элементов, как в исходных отображаемых
данных. Следующая трансформация отображает каждое значение на его куб,
для чего используется выражение item ** 3:
In [6]: list3 = [item ** 3 for item in range(1, 6)]
In [7]: list3
Out[7]: [1, 8, 27, 64, 125]

Фильтрация: трансформации списков с if
Другая распространенная операция программирования в функциональном
стиле — фильтрация элементов и отбор только тех элементов, которые удовлетворяют заданному условию. Как правило, при этом строится список с мень-

5.13. Выражения-генераторы   223

шим количеством элементов, чем в фильтруемых данных. Чтобы выполнить
эту операцию с использованием трансформации списка, используйте секцию
if. Следующий пример включает в list4 только четные значения, сгенерированные в секции for:
In [8]: list4 = [item for item in range(1, 11) if item % 2 == 0]
In [9]: list4
Out[9]: [2, 4, 6, 8, 10]

Трансформация списка, которая обрабатывает элементы
другого списка
Секция for может обрабатывать любые итерируемые объекты. Создадим
список строк в нижнем регистре и используем трансформацию списка для
создания нового списка, содержащего их версии в верхнем регистре:
In [10]: colors = ['red', 'orange', 'yellow', 'green', 'blue']
In [11]: colors2 = [item.upper() for item in colors]
In [12]: colors2
Out[12]: ['RED', 'ORANGE', 'YELLOW', 'GREEN', 'BLUE']
In [13]: colors
Out[13]: ['red', 'orange', 'yellow', 'green', 'blue']

5.13. Выражения-генераторы
Выражение-генератор отчасти напоминает трансформацию списка, но оно
создает итерируемый объект-генератор, производящий значения по требованию. Этот механизм называется отложенным вычислением. В трансформациях
списков используется быстрое вычисление, позволяющее создавать списки
в момент выполнения. При большом количестве элементов создание списка
может потребовать значительных затрат памяти и времени. Таким образом,
выражения-генераторы могут сократить потребление памяти программой
и повысить быстродействие, если не все содержимое списка понадобится
одновременно.
Выражения-генераторы обладают теми же возможностями, что и трансформации списков, но они определяются в круглых скобках вместо квадратных.
Выражение-генератор в фрагменте [2] возводит в квадрат и возвращает только
нечетные числа из numbers:

224   Глава 5. Последовательности: списки и кортежи
In [1]: numbers = [10, 3, 7, 1, 9, 4, 2, 8, 5, 6]
In [2]: for value in (x ** 2 for x in numbers if x % 2 != 0):
...:
print(value, end=' ')
...:
9 49 1 81 25

Чтобы показать, что выражение-генератор не создает список, присвоим выражение-генератор из предыдущего фрагмента переменной и выведем значение
этой переменной:
In [3]: squares_of_odds = (x ** 2 for x in numbers if x % 2 != 0)
In [3]: squares_of_odds
Out[3]:

Текст "generator object " сообщает, что square_of_odds является
объектом-генератором, который был создан на базе выражения-генератора
(genexpr).

5.14. Фильтрация, отображение и свертка
В предыдущем разделе были представлены некоторые средства программирования в функциональном стиле — трансформации списков, фильтрация
и отображение. В этом разделе мы продемонстрируем применение встроенных функций filter и map для фильтрации и отображения соответственно.
Мы продолжим обсуждение операции свертки, которая преобразует коллекцию элементов в одно значение (примерами служат операции получения
количества элементов, суммирования, произведения, усреднения, минимума
и максимума).

Фильтрация значений последовательности
встроенной функцией filter
Воспользуемся встроенной функцией filter для получения нечетных значений из numbers:
In [1]: numbers = [10, 3, 7, 1, 9, 4, 2, 8, 5, 6]
In [2]: def is_odd(x):
...:
"""Возвращает True только для нечетных x."""
...:
return x % 2 != 0
...:

5.14. Фильтрация, отображение и свертка   225
In [3]: list(filter(is_odd, numbers))
Out[3]: [3, 7, 1, 9, 5]

Функции Python, как и данные, представляют собой объекты, которые можно присваивать переменным, передавать другим функциям и возвращать из
функций. Функции, получающие другие функции в аргументах, относятся
к инструментарию функционального программирования и называются функциями высшего порядка. Например, первым аргументом filter должна быть
функция, которая получает один аргумент и возвращает True, если значение
должно включаться в результат. Функция is_odd возвращает True, если ее аргумент является нечетным. Функция filter вызывает is_odd по одному разу
для каждого значения в итерируемом объекте из второго аргумента (numbers).
Функции высшего порядка также могут возвращать функцию как результат.
Функция filter возвращает итератор, так что для получения результатов
filter нужно будет выполнить их перебор. Это еще один пример отложенного
вычисления. Во фрагменте [3] функция list перебирает результаты и создает
список, в котором они содержатся. Те же результаты можно получить с использованием трансформации списка с секцией if:
In [4]: [item for item in numbers if is_odd(item)]
Out[4]: [3, 7, 1, 9, 5]

Использование лямбда-выражения вместо функции
Для простых функций (таких как is_odd), возвращающих только значение
одного выражения, можно использовать лямбда-выражение для определения
функции во «встроенном» виде в том месте, где она нужна, — обычно при
передаче другой функции:
In [5]: list(filter(lambda x: x % 2 != 0, numbers))
Out[5]: [3, 7, 1, 9, 5]

Возвращаемое значение filter (итератор) передается функции list для преобразования результатов в список и их вывода.
Лямбда-выражение является анонимной функцией, то есть функцией, не имеющей имени. В вызове filter
filter(lambda x: x % 2 != 0, numbers)

первым аргументом является лямбда-выражение
lambda x: x % 2 != 0

226   Глава 5. Последовательности: списки и кортежи
Лямбда-выражение начинается с ключевого слова lambda, за которым следует разделенный запятыми список параметров, двоеточие (:) и выражение.
В данном случае список параметров состоит из одного параметра с именем x.
Лямбда-выражение неявно возвращает значение своего выражения. Таким
образом, любая простая функция в форме
def имя_функции(список_параметров):
return выражение

может быть выражена в более компактной форме посредством лямбда-выражения
lambda список_параметров: выражение

Отображение значений последовательности на новые значения
Воспользуемся встроенной функцией map с лямбда-выражением для возведения в квадрат каждого значения из numbers:
In [6]: numbers
Out[6]: [10, 3, 7, 1, 9, 4, 2, 8, 5, 6]
In [7]: list(map(lambda x: x ** 2, numbers))
Out[7]: [100, 9, 49, 1, 81, 16, 4, 64, 25, 36]

Первым аргументом функции map является функция, которая получает одно
значение и возвращает новое значение — в данном случае лямбда-выражение,
которое возводит свой аргумент в квадрат. Вторым аргументом является
итерируемый объект с отображаемыми значениями. Функция map использует отложенное вычисление, поэтому возвращаемый map итератор передается
функции list. Это позволит перебрать и создать список отображенных значений. Эквивалентная трансформация списка выглядит так:
In [8]: [item ** 2 for item in numbers]
Out[8]: [100, 9, 49, 1, 81, 16, 4, 64, 25, 36]

Объединение filter и map
Предшествующие операции filter и map можно объединить следующим образом:
In [9]: list(map(lambda x: x ** 2,
...:
filter(lambda x: x % 2 != 0, numbers)))
...:
Out[9]: [9, 49, 1, 81, 25]

5.15. Другие функции обработки последовательностей   227

Во фрагменте [9] происходит достаточно много всего, поэтому к нему стоит
присмотреться повнимательнее. Сначала filter возвращает итерируемый
объект, представляющий только нечетные значения из numbers. Затем map возвращает итерируемый объект, представляющий квадраты отфильтрованных
значений. Наконец, list использует итерируемый объект, возвращенный map,
для создания списка. Возможно, вы предпочтете использовать следующую
трансформацию списка вместо предыдущего фрагмента:
In [10]: [x ** 2 for x in numbers if x % 2 != 0]
Out[10]: [9, 49, 1, 81, 25]

Для каждого значения x из numbers выражение x**2 выполняется только в том
случае, если условие x%2!=0 дает истинный результат.

Свертка: суммирование элементов последовательности
функцией sum
Как вам известно, свертки обрабатывают элементы последовательности с вычислением одного значения. В частности, свертки выполняются встроенными
функциями len, sum, min и max. Также можно создавать собственные свертки
при помощи функции reduce модуля functools. За примером кода обращайтесь по адресу https://docs.python.org/3/library/functools.html. Когда мы займемся
изучением больших данных и Hadoop в главе 16, то продемонстрируем и программирование MapReduce, основанное на операциях фильтрации, отображения и свертки из программирования в функциональном стиле.

5.15. Другие функции обработки
последовательностей
Python предоставляет ряд других встроенных функций для работы с последовательностями.

Нахождение минимального и максимального значения
функцией key
Ранее были продемонстрированы встроенные функции свертки min и max
c аргументами (такими как int или списки int). Иногда требуется найти
минимум или максимум в наборе более сложных объектов, например строк.
Возьмем следующее сравнение:

228   Глава 5. Последовательности: списки и кортежи
In [1]: 'Red' < 'orange'
Out[1]: True

Буква 'R' следует «после» буквы 'o' в алфавите, поэтому можно было бы
ожидать, что строка 'Red' окажется меньше 'orange', а условие вернет False.
Однако строки сравниваются по числовым кодам входящих в них символов,
а буквы в нижнем регистре имеют более высокие значения кодов, чем буквы
верхнего регистра. В этом нетрудно убедиться при помощи встроенной функции ord, возвращающей числовое значение символа:
In [2]: ord('R')
Out[2]: 82
In [3]: ord('o')
Out[3]: 111

Возьмем список colors, содержащий строки с символами верхнего и нижнего
регистра:
In [4]: colors = ['Red', 'orange', 'Yellow', 'green', 'Blue']

Допустим, вы хотите определить наименьшую и наибольшую строку в алфавитном порядке, а не в числовом (лексикографическом). При расположении
цветов в алфавитном порядке:
'Blue', 'green', 'orange', 'Red', 'Yellow'

строка 'Blue' является наименьшей (то есть ближайшей к началу алфавита),
а строка 'Yellow' — наибольшей (то есть ближайшей к концу алфавита).
Так как Python сравнивает строки по числовым критериям, сначала необходимо преобразовать каждую строку к нижнему (или верхнему) регистру. Тогда
числовые и значения символов будут соответствовать их алфавитному порядку. В следующих фрагментах для определения наименьшей и наибольшей
строки в алфавитном порядке используются функции min и max:
In [5]: min(colors, key=lambda s: s.lower())
Out[5]: 'Blue'
In [6]: max(colors, key=lambda s: s.lower())
Out[6]: 'Yellow'

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

5.15. Другие функции обработки последовательностей   229

зывающее метод строк lower для получения версии строки в нижнем регистре.
Функции min и max вызывают функцию из аргумента key для каждого элемента
и используют результаты для сравнения элементов.

Обратный перебор элементов последовательности
Встроенная функция reversed возвращает итератор, позволяющий перебрать
значения последовательности в обратном порядке. Следующая трансформация списка создает новый список, содержащий квадраты значений numbers
в обратном порядке:
In [7]: numbers = [10, 3, 7, 1, 9, 4, 2, 8, 5, 6]
In [7]: reversed_numbers = [item for item in reversed(numbers)]
In [8]: reversed_numbers
Out[8]: [36, 25, 64, 4, 16, 81, 1, 49, 9, 100]

Объединение итерируемых объектов в кортежи
с соответствующими элементами
Встроенная функция zip перебирает несколько итерируемых объектов данных
одновременно. Функция получает в аргументах произвольное количество
итерируемых объектов и возвращает итератор, который строит кортежи,
содержащий элементы с одинаковыми индексами из каждого итерируемого
объекта. Например, вызов zip из фрагмента [11] генерирует кортежи ('Bob',
3.5), ('Sue', 4.0) и ('Amanda', 3.75), содержащие элементы с индексами 0,
1 и 2 каждого списка соответственно:
In [9]: names = ['Bob', 'Sue', 'Amanda']
In [10]: grade_point_averages = [3.5, 4.0, 3.75]
In [11]: for name, gpa in zip(names, grade_point_averages):
...:
print(f'Name={name}; GPA={gpa}')
...:
Name=Bob; GPA=3.5
Name=Sue; GPA=4.0
Name=Amanda; GPA=3.75

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

230   Глава 5. Последовательности: списки и кортежи

5.16. Двумерные списки
Элементами списков также могут быть другие списки. Типичный пример использования таких вложенных (или многомерных) списков — представление
таблиц с информацией, упорядоченной по строкам и столбцам. Чтобы задать
конкретный элемент таблицы, необходимо указать два индекса — по общепринятой схеме первый определяет строку элемента, а второй определяет столбец.
Списки, требующие индексов для идентификации элемента, называются
двумерными списками и рассматриваются в текущем разделе. Многомерные
списки могут иметь более двух индексов.

Создание двумерного списка
Рассмотрим двумерный список из трех строк и четырех столбцов (то есть
список 3 × 4), который может представлять оценки трех студентов, каждый
из которых сдал четыре экзамена:
In [1]: a = [[77, 68, 86, 73], [96, 87, 89, 81], [70, 90, 86, 81]]

Если записать список в нижеследующем виде, то его структура с делением на
строки и столбцы станет более понятной:
a = [[77, 68, 86, 73],
[96, 87, 89, 81],
[70, 90, 86, 81]]

# Оценки первого студента
# Оценки второго студента
# Оценки третьего студента

Работа с двумерным списком
На следующей диаграмме изображен список a, в строках и столбцах которого
располагаются оценки на экзаменах:
ɋɬɨɥɛɟɰ

ɋɬɨɥɛɟɰ

ɋɬɨɥɛɟɰ

ɋɬɨɥɛɟɰ

ɋɬɪɨɤɚ









ɋɬɪɨɤɚ









ɋɬɪɨɤɚ









Идентификация элементов в двумерном списке
На следующей диаграмме представлены имена элементов списка a:

5.16. Двумерные списки   231

ɋɬɨɥɛɟɰ

ɋɬɨɥɛɟɰ

ɋɬɨɥɛɟɰ

ɋɬɨɥɛɟɰ

ɋɬɪɨɤɚ

D>@>@

D>@>@

D>@>@

D>@>@

ɋɬɪɨɤɚ

D>@>@

D>@>@

D>@>@

D>@>@

ɋɬɪɨɤɚ

D>@>@

D>@>@

D>@>@

D>@>@

ɂɧɞɟɤɫɫɬɨɥɛɰɚ
ɂɧɞɟɤɫɫɬɪɨɤɢ
ɂɦɹɫɩɢɫɤɚ

Каждый элемент идентифицируется именем вида a[i][j]; здесь a — имя списка, а i и j — индексы, однозначно определяющие строку и столбец каждого
элемента соответственно. У всех элементов строки 0 первый индекс равен 0.
У всех элементов столбца 3 второй индекс равен 3.
В двумерном списке a:
ØØзначениями 77, 68, 86 и 73 инициализируются элементы a[0][0], a[0][1],

a[0][2] и a[0][3] соответственно;
ØØзначениями 96, 87, 89 и 81 инициализируются элементы a[1][0], a[1][1],

a[1][2] и a[1][3] соответственно, а также
ØØзначениями 70, 90, 86 и 81 инициализируются элементы a[2][0], a[2][1],

a[2][2] и a[2][3] соответственно.

Список из m строк и n столбцов называется списком m × n и содержит m × n
элементов. Следующая вложенная команда for выводит элементы предыдущего двумерного списка по строкам:
In [2]: for row in a:
...:
for item in row:
...:
print(item, end=' ')
...:
print()
...:
77 68 86 73
96 87 89 81
70 90 86 81

Как выполняются вложенные циклы
Изменим вложенный цикл так, чтобы он выводил имя списка, индексы строки
и столбца, а также значение каждого элемента:

232   Глава 5. Последовательности: списки и кортежи
In [3]: for
...:
...:
...:
...:
a[0][0]=77
a[1][0]=96
a[2][0]=70

i, row in enumerate(a):
for j, item in enumerate(row):
print(f'a[{i}][{j}]={item} ', end=' ')
print()
a[0][1]=68
a[1][1]=87
a[2][1]=90

a[0][2]=86
a[1][2]=89
a[2][2]=86

a[0][3]=73
a[1][3]=81
a[2][3]=81

Внешняя команда for последовательно перебирает строки двумерного списка.
При каждой итерации внешнего цикла for внутренний цикл for перебирает
все столбцы текущей строки. Таким образом, для первой итерации внешнего
цикла строка 0 имеет вид
[77, 68, 86, 73]

а вложенный цикл перебирает четыре элемента этого списка a[0][0]=77 ,
a[0][1]=68, a[0][2]=86 и a[0][3]=73.
При второй итерации внешнего цикла строка 1 состоит из элементов
[96, 87, 89, 81]

а вложенный цикл перебирает четыре элемента a[1][0]=96 , a[1][1]=87 ,
a[1][2]=89 и a[1][3]=81.
При третьей итерации внешнего цикла строка 2 состоит из элементов
[70, 90, 86, 81]

а вложенный цикл перебирает четыре элемента a[2][0]=70 , a[2][1]=90 ,
a[2][2]=86 и a[2][3]=81.
В главе 7 рассматривается коллекция ndarray из библиотеки NumPy и коллекция DataFrame из библиотеки Pandas. Они позволяют работать с многомерными коллекциями более компактно и удобно по сравнению с операциями
с двумерным списком, продемонстрированными в этом разделе.

5.17. Введение в data science: моделирование
и статические визуализации
В разделах «Введение в data science» последних глав рассматривались основные характеристики описательной статистики. В этом разделе мы сосредото-

5.17. Введение в data science: моделирование и статические визуализации   233

чимся на визуализациях, помогающих «разобраться в сути» данных. Визуализации предоставляют мощный механизм понимания данных, выходящий
за границы простого просмотра данных.
Мы используем две библиотеки визуализации с открытым кодом — Seaborn
и Matplotlib — для построения статических гистограмм, отображающих
результаты моделирования бросков шестигранных кубиков. Библиотека визуализации Seaborn построена на основе библиотеки визуализации Matplotlib
и упрощает многие операции Matplotlib. Мы будем пользоваться аспектами обеих библиотек, потому что некоторые операции Seaborn возвращают
объекты из библиотеки Matplotlib. В разделе «Введение в data science»
следующей главы мы «оживим» гистограмму при помощи динамических
визуализаций.

5.17.1. Примеры диаграмм для 600, 60 000
и 6 000 000 бросков
На следующем снимке изображена вертикальная гистограмма, которая для
600 бросков отображает сводную информацию о частотах выпадения каждой
из шести граней и их процента от общего количества бросков.
600 бросков шестигранного кубика
18,667%
16,500%

17,500%
15,333%

Частота

17,167%

Значение каждой грани

14,833%

234   Глава 5. Последовательности: списки и кортежи
Каждая грань должна выпасть приблизительно 100 раз. Тем не менее при таком
малом количестве бросков ни одна из частот не равна 100 (хотя некоторые достаточно близки), а большинство процентов отклоняется от 16,6667% (около
1/6). Если же запустить моделирование для 60 000 бросков, то размеры столбцов гистограммы будут намного более равномерными. При 6 000 000 бросков
размеры столбцов будут практически одинаковыми. В этом проявляется закон
больших чисел. В следующей главе будет показано, как построить гистограмму
с динамическим изменением размеров столбцов.
Вы узнаете, как управлять внешним видом и содержанием диаграммы, в частности:
ØØзаголовком диаграммы в окне (Rolling a Six-Sided Die 600 Times);
ØØпояснительными метками: Die Value для оси x и Frequency для оси y;
ØØтекстом, отображаемым над каждым столбцом, с частотами и процентом

от общего количества бросков;
ØØцветом столбцов.

Мы воспользуемся некоторыми настройками по умолчанию Seaborn. Например, Seaborn определяет текстовые метки по оси x для результатов бросков
1–6, а текстовые метки по оси y — для фактических частот выпадения. Во
внутренней реализации Matplotlib определяет позиции и размеры столбцов
на основании размера окна и диапазона значений, представленных столбцами. Библиотека, кроме того, расставляет числовые метки оси Frequency
на основании фактических частот результатов, представленных столбцами.
Существует и много других параметров, которые можно настраивать по
своему вкусу.
На первом скриншоте показаны результаты для 60 000 бросков — только представьте, что вам пришлось бы делать это вручную. В этом случае мы ожидаем,
что каждая грань выпадет около 10 000 раз. На втором скриншоте показаны
результаты для 6 000 000 бросков — безусловно, вручную вы бы с этим просто
не справились! На этот раз каждая грань должна выпасть около 1 000 000 раз,
и столбцы частот имеют почти одинаковую длину (они близки, но точного
совпадения все же нет). При большем количестве бросков проценты намного
ближе к ожидаемым 16,667%.

5.17. Введение в data science: моделирование и статические визуализации   235

60 000 бросков шестигранного кубика
16,883%

16,493%

16,742%

16,548%

16,697%

Частота

16,637%

Значение каждой грани

6 000 000 бросков шестигранного кубика
16,674%

16,663% 16,662%

Частота

16,668%

Значение каждой грани

16,652%

16,682%

236   Глава 5. Последовательности: списки и кортежи

5.17.2. Визуализация частот и процентов
Займемся теперь интерактивной разработкой гистограмм.

Запуск IPython для интерактивной разработки Matplotlib
В IPython предусмотрена встроенная поддержка интерактивной разработки
диаграмм Matplotlib, которая также понадобится для разработки диаграмм
Seaborn. Просто запустите IPython следующей командой:
ipython --matplotlib

Импортирование библиотек
Начнем с импортирования библиотек, которые будут использоваться для
работы с диаграммами:
In [1]: import matplotlib.pyplot as plt
In [2]: import numpy as np
In [3]: import random
In [4]: import seaborn as sns

1. Модуль matplotlib.pyplot содержит графическую поддержку библиотеки Matplotlib, которой мы будем пользоваться. Обычно этот модуль
импортируется под именем plt.
2. Библиотека NumPy (Numerical Python) включает функцию unique, которая понадобится для обобщения результатов бросков. Модуль numpy
обычно импортируется под именем np.
3. Модуль random содержит функции генерирования случайных чисел
Python.
4. Модуль seaborn содержит графические средства библиотеки Seaborn. Этот
модуль обычно импортируется под именем sns (если вам интересно, поищите информацию о том, почему было выбрано именно это сокращение).

Моделирование бросков и вычисление частот
Воспользуемся трансформацией списка для создания списка из 600 случайных
результатов, а затем при помощи функции unique библиотеки NumPy опре-

5.17. Введение в data science: моделирование и статические визуализации   237

делим уникальные значения (скорее всего, это будут все шесть возможных
результатов) и их частоты:
In [5]: rolls = [random.randrange(1, 7) for i in range(600)]
In [6]: values, frequencies = np.unique(rolls, return_counts=True)

Библиотека NumPy предоставляет высокопроизводительную коллекцию
ndarray, которая обычно работает намного быстрее списков1. Хотя ndarray не
используется здесь напрямую, функция unique из NumPy ожидает получить
аргумент ndarray и возвращает ndarray. Если передать ей список (например,
rolls), NumPy преобразует его в ndarray для повышения быстродействия.
Коллекция ndarray, возвращаемая unique, просто присваивается переменной,
которая должна использоваться функцией построения диаграмм Seaborn.
Ключевой аргумент return_counts=True приказывает unique подсчитать количество вхождений каждого уникального значения. В данном случае unique
возвращает кортеж из двух одномерных коллекций ndarray, содержащих от­
сортированные уникальные значения и их частоты соответственно. Коллекции
ndarray из кортежа распаковываются в переменные values и frequencies.
Если аргумент return_counts равен False, то возвращается только список
уникальных значений.

Построение исходной гистограммы
Создадим заголовок гистограммы, назначим ей стиль, а затем нанесем на
гистограмму результаты бросков и частоты:
In [7]: title = f'Rolling a Six-Sided Die {len(rolls):,} Times'
In [8]: sns.set_style('whitegrid')
In [9]: axes = sns.barplot(x=values, y=frequencies, palette='bright')

Форматная строка из фрагмента [7] включает количество бросков кубика
в заголовок гистограммы. Спецификатор «,» (запятая) в
{len(rolls):,}

выводит число с разделителями групп разрядов — таким образом, значение
60000 будет выведено в виде 60,000.
1

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

238   Глава 5. Последовательности: списки и кортежи
По умолчанию Seaborn выводит диаграммы на простом белом фоне, но
библио­тека предоставляет вам на выбор несколько стилей оформления
('darkgrid', 'whitegrid', 'dark', 'white' и 'ticks'). Фрагмент [8] задает
стиль 'whitegrid', который выводит светло-серые горизонтальные линии на
вертикальной гистограмме. Линии помогают понять соответствие между высотой каждого столбца и числовыми метками частот в левой части диаграммы.
Фрагмент [9] строит диаграмму частот при помощи функции Seaborn barplot.
Когда вы выполняете этот фрагмент, на экране появляется следующее окно
(потому что вы запустили IPython с параметром --matplotlib):

Seaborn взаимодействует с Matplotlib для вывода столбцов гистограммы; для
этого библиотека создает объект Matplotlib Axes, управляющий контентом,
находящимся в окне. Во внутренней реализации Seaborn использует объект
Matplotlib Figure для управления окном, в котором отображается Axes. Первые
два аргумента функции barplot содержат коллекции ndarray, содержащие значения осей x и y соответственно. Необязательный ключевой аргумент palette
используется для выбора заранее определенной цветовой палитры Seaborn
'bright'. За информацией о настройке палитры обращайтесь по адресу:
https://seaborn.pydata.org/tutorial/color_palettes.html

Функция barplot возвращает настроенный ей объект Axes. Он присваивается
переменной axes, чтобы его можно было использовать для настройки других

5.17. Введение в data science: моделирование и статические визуализации   239

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

Назначение заголовка окна и пометка осей x и y
Следующие два фрагмента добавляют описательный текст на гистограмму:
In [10]: axes.set_title(title)
Out[10]: Text(0.5,1,'Rolling a Six-Sided Die 600 Times')
In [11]: axes.set(xlabel='Die Value', ylabel='Frequency')
Out[11]: [Text(92.6667,0.5,'Frequency'), Text(0.5,58.7667,'Die Value')]

Фрагмент [10] использует метод set_title объекта axes для вывода строки title,
выровненной по центру гистограммы. Метод возвращает объект Text с заголовком и его позицией в окне, который IPython просто отображает для подтверждения. Вы можете игнорировать строки Out[] в приведенных выше фрагментах.
Фрагмент [11] добавляет метки на оси. Метод set получает ключевые аргументы для устанавливаемых свойств объекта Axes. Метод выводит текст xlabel
вдоль оси x, текст ylabel — вдоль оси y и возвращает список объектов Text,
содержащий метки и их позиции. Гистограмма теперь выглядит так:

600 бросков шестигранного кубика

240   Глава 5. Последовательности: списки и кортежи

Завершение гистограммы
Следующие два фрагмента завершают гистограмму — они выделяют место
для текста над каждым столбцом, а затем выводят его:
In [12]: axes.set_ylim(top=max(frequencies) * 1.10)
Out[12]: (0.0, 122.10000000000001)
In [13]: for bar, frequency in zip(axes.patches, frequencies):
...:
text_x = bar.get_x() + bar.get_width() / 2.0
...:
text_y = bar.get_height()
...:
text = f'{frequency:,}\n{frequency / len(rolls):.3%}'
...:
axes.text(text_x, text_y, text,
...:
fontsize=11, ha='center', va='bottom')
...:

Чтобы выделить место для текста над столбцами, фрагмент [12] масштабирует
ось y на 10%. Это значение было выбрано на основе экспериментов. Метод
set_ylim объекта Axes поддерживает много необязательных ключевых аргументов. В нашем примере используется только аргумент top для изменения максимального значения, представляемого осью y. Наибольшая частота умножается
на 1.10, чтобы гарантировать, что ось y на 10% выше самого высокого столбца.
Наконец, фрагмент [13] выводит значение частоты каждого столбца и процент
от общего количества бросков. Коллекция patches объекта axes содержит двумерные цветные фигуры, представляющие столбцы гистограммы. Команда for
использует функцию zip для перебора элементов patches и соответствующих
им значений frequency. Каждая итерация распаковывает в bar и frequency
один из кортежей, возвращенных zip. Набор команды for работает следующим образом:
ØØПервая команда вычисляет координату x центра области для вывода тек-

ста. Она вычисляется как сумма координаты x левого края столбца (bar.
get_x()) и половины ширины столбца (bar.get_width() / 2.0).
ØØВторая команда получает координату y области для вывода текста — зна-

чение bar.get_y() представляет верх столбца.
ØØТретья команда создает двустрочную строку, содержащую частоту этого

столбца и соответствующий процент от общего количества бросков.
ØØПоследняя команда вызывает метод text объекта Axes для вывода текста

над столбцом. Первые два аргумента метода задают позицию текста x-y,
а третий аргумент — выводимый текст. Ключевой аргумент ha задает горизонтальное выравнивание — мы выполнили горизонтальное выравнива-

5.17. Введение в data science: моделирование и статические визуализации   241

ние текста по центру относительно координаты x. Ключевой аргумент va
задает вертикальное выравнивание — нижний край текста выравнивается
по координате y. Итоговый вид гистограммы показан на следующем рисунке.

Частота

60 000 бросков шестигранного кубика

Значение каждой грани

Повторный бросок и обновление гистограммы — магические
команды IPython
Итак, гистограмма готова, и скорее всего, вы захотите поэкспериментировать
с другим количеством бросков. Для начала очистите существующий график
вызовом функции cla библиотеки Matplotlib:
In [14]: plt.cla()

IPython предоставляет специальные команды, называемые магическими командами (magics), для удобного выполнения различных операций. Воспользуйтесь
магической командой %recall для получения фрагмента [5], который создал
список rolls, и включите код в следующее приглашение In []:
In [15]: %recall 5
In [16]: rolls = [random.randrange(1, 7) for i in range(600)]

242   Глава 5. Последовательности: списки и кортежи
Отредактируйте фрагмент и измените количество бросков на 60000, а затем
нажмите Enter для создания нового списка:
In [16]: rolls = [random.randrange(1, 7) for i in range(60000)]

Теперь загрузите фрагменты с [6] по [13]. При этом все фрагменты выводятся
в заданном диапазоне в следующем приглашении In []. Нажмите Enter, чтобы
снова выполнить эти фрагменты:
In [17]: %recall 6-13
In [18]:
...:
...:
...:
...:
...:
...:
...:
...:
...:
...:
...:
...:
...:

values, frequencies = np.unique(rolls, return_counts=True)
title = f'Rolling a Six-Sided Die {len(rolls):,} Times'
sns.set_style('whitegrid')
axes = sns.barplot(x=values, y=frequencies, palette='bright')
axes.set_title(title)
axes.set(xlabel='Die Value', ylabel='Frequency')
axes.set_ylim(top=max(frequencies) * 1.10)
for bar, frequency in zip(axes.patches, frequencies):
text_x = bar.get_x() + bar.get_width() / 2.0
text_y = bar.get_height()
text = f'{frequency:,}\n{frequency / len(rolls):.3%}'
axes.text(text_x, text_y, text,
fontsize=11, ha='center', va='bottom')

Обновленная гистограмма показана ниже:

Частота

60 000 бросков шестигранного кубика

Значение каждой грани

5.17. Введение в data science: моделирование и статические визуализации   243

Сохранение фрагментов в файле при помощи магической
команды %save
После того как диаграмма будет построена в интерактивном режиме, возможно, вы захотите сохранить код в файле, чтобы превратить его в сценарий
и запустить его в будущем. Воспользуемся магической командой %save для
сохранения фрагментов с 1-го по 13-й в файле с именем RollDie.py. IPython
обозначает файл, в который были записаны данные, а затем выводит сохраненные строки:
In [19]: %save RollDie.py 1-13
The following commands were written to file `RollDie.py`:
import matplotlib.pyplot as plt
import numpy as np
import random
import seaborn as sns
rolls = [random.randrange(1, 7) for i in range(600)]
values, frequencies = np.unique(rolls, return_counts=True)
title = f'Rolling a Six-Sided Die {len(rolls):,} Times'
sns.set_style("whitegrid")
axes = sns.barplot(values, frequencies, palette='bright')
axes.set_title(title)
axes.set(xlabel='Die Value', ylabel='Frequency')
axes.set_ylim(top=max(frequencies) * 1.10)
for bar, frequency in zip(axes.patches, frequencies):
text_x = bar.get_x() + bar.get_width() / 2.0
text_y = bar.get_height()
text = f'{frequency:,}\n{frequency / len(rolls):.3%}'
axes.text(text_x, text_y, text,
fontsize=11, ha='center', va='bottom')

Аргументы командной строки; вывод диаграммы из сценария
В примерах этой главы содержится отредактированная версия файла RollDie.
py, сохраненного в предыдущем разделе. Мы добавили комментарии и два
небольших изменения, чтобы сценарий можно было запустить с аргументом,
определяющим количество бросков, например:
ipython RollDie.py 600

Модуль sys стандартной библиотеки Python дает возможность сценариям
получать аргументы командной строки, переданные программе. К их числу относится имя сценария и любые значения, указанные справа от него при запуске
сценария. Аргументы содержатся в списке argv модуля sys. В приведенной
выше команде argv[0] содержит строку 'RollDie.py', а argv[1] — строку '600'.

244   Глава 5. Последовательности: списки и кортежи
Чтобы управлять количеством бросков при помощи аргумента командной
строки, мы изменили команду создания списка rolls; теперь она выглядит так:
rolls = [random.randrange(1, 7) for i in range(int(sys.argv[1]))]

Обратите внимание на преобразование строки argv[1] в int.
Библиотеки Matplotlib и Seaborn не выводят автоматически диаграмму, созданную в сценарии. По этой причине в конец сценария добавляется
вызов функции show библиотеки Matplotlib, которая выводит на экран окно
с диаграммой:
plt.show()

5.18. Итоги
В этой главе приведена более подробная информация о списках и кортежах.
Были рассмотрены операции создания списков, обращения к их элементам
и определения длины. Вы узнали, что списки являются изменяемыми; это
означает, что вы можете изменять их содержимое, в частности увеличивать
и уменьшать размер списка во время выполнения программы. При обращении
к несуществующему элементу происходит ошибка IndexError. Для перебора
элементов списка используются команды for.
Далее были рассмотрены кортежи — они, как и списки, относятся к последовательностям, но являются неизменяемыми. Мы распаковывали элементы
кортежей на отдельные переменные и использовали enumerate для создания
итерируемого объекта, содержащего кортежи (каждый кортеж состоит из
индекса списка и значения соответствующего элемента).
Вы узнали, что все последовательности поддерживают операцию сегментации — создания новых последовательностей из подмножеств исходных
элементов. Команда del использовалась для удаления элементов из списков
и для удаления переменных из интерактивных сеансов. Мы передавали функциям списки, элементы списков и сегменты списков. Вы научились проводить
поиск и сортировать списки, а также выполнять поиск в кортежах. Методы
списков использовались для вставки, присоединения и удаления элементов,
а также для перестановки элементов списков в обратном порядке и копирования списков.
Затем было показано, как моделировать стеки на базе списков. Вы узнали,
как использовать компактную запись трансформаций списков для создания

5.18. Итоги   245

новых списков. При помощи дополнительных встроенных методов выполнялись такие операции, как суммирование элементов списков, перебор списка
в обратном порядке, нахождение наименьшего и наибольшего значения, фильтрация значений и отображение значений на новые значения. Мы показали,
как использовать встроенные списки для представления двумерных таблиц,
в которых данные упорядочены по строкам и столбцам, и как обрабатывать
двумерные списки во вложенных циклах for.
Эта глава завершается разделом «Введение в data science», в котором были
представлены примеры с моделированием бросков кубиков и статическими
визуализациями. В приведенном примере кода библиотеки визуализации
Seaborn и Matplotlib использовались для построения статической гистограммы на основе результатов моделирования. В следующем разделе «Введение
в data science» моделирование бросков кубиков будет использоваться для построения динамической визуализации, чтобы гистограмма «ожила» на экране.
В следующей главе продолжится изучение встроенных коллекций Python.
Словари предназначены для хранения неупорядоченных коллекций пар
«ключ-значение», связывающих неизменяемые ключи со значениями (по
аналогии с тем, как «обычные» словари связывают слова с определениями).
Множества используются для хранения неупорядоченных коллекций уникальных элементов.
В главе 7 коллекция ndarray библиотеки NumPy рассматривается более по­
дробно. Вы узнаете, что списки хорошо подходят для малых объемов данных,
но неэффективны для больших объемов, встречающихся в области аналитики
больших данных. В таких случаях следует применять высокооптимизированную коллекцию ndarray библиотеки NumPy. Коллекция ndarray (то есть
«n-мерный массив») может заметно превосходить списки по скорости. Чтобы
узнать, насколько быстрее они работают,воспользуемся профилирующими
тестами Python. NumPy также включает много средств для удобных и эффективных операций с многомерными массивами. В области аналитики больших
данных потребности в вычислительных мощностях могут быть колоссальными, поэтому все усилия, направленные на повышение быстродействия,
приводят к значительным последствиям. В главе 16 будет использоваться
одна из самых высокопроизводительных баз данных для работы с большими
данными — MongoDB1.

1

Название этой базы данных произошло от сокращенного слова humongous, что значит
«огромный».

6
Словари и множества
В этой главе…
•• Использование словарей для представления неупорядоченных коллекций
пар «ключ-значение».
•• Использование множеств для представления неупорядоченных коллекций
уникальных значений.
•• Создание, инициализация и обращение к элементам словарей и множеств.
•• Перебор ключей, значений и пар «ключ-значение» в словарях.
•• Добавление, удаление и обновление пар «ключ-значение» в словарях.
•• Операторы сравнения словарей и множеств.
•• Объединение множеств операторами и методами множеств.
•• Проверка наличия ключа в словаре или значения во множестве операторами in и not in.
•• Использование операций изменяемых множеств для изменения содержимого множеств.
•• Использование трансформаций для быстрого и удобного создания словарей и множеств.
•• Построение динамических визуализаций.
•• Более глубокое понимание изменяемости и неизменяемости.

6.2. Словари   247

6.1. Введение
В предыдущей главе были рассмотрены три встроенные коллекции, относящиеся к категории последовательностей, — строки, списки и кортежи. Эта глава
посвящена встроенным коллекциям, которые не являются последовательностями, — словарям и множествам. Словарь (dictionary) представляет собой
неупорядоченную коллекцию для хранения пар «ключ-значение», связывающих
неизменяемые ключи со значениями (по аналогии с тем, как в традиционных
словарях слова связываются с определениями). Множество (set) представляет
собой неупорядоченную коллекцию уникальных неизменяемых элементов.

6.2. Словари
Словарь связывает ключи со значениями. Каждому ключу соответствует
конкретное значение. В табл. 6.1 представлены примеры словарей с ключами,
типами ключей, значениями и типами значений:
Таблица 6.1. Примеры словарей с ключами, типами ключей, значениями и типами
значений
Ключи

Типы ключей

Значения

Типы
значений

Названия стран

str

Коды стран в интернете

str

Целые числа

int

Римские числа

str

Штаты

str

Сельскохозяйственные
продукты

список str

Пациенты в больнице

srt

Физиологические параметры

кортеж int
и float

Игроки в бейсбольной
команде

str

Средняя результативность

float

Единицы измерения

str

Сокращения

str

Коды складского учета

str

Запасы на складе

int

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

248   Глава 6. Словари и множества
соответствовать одинаковые значения — например, двум кодам складского
учета могут соответствовать одинаковые размеры складских запасов.

6.2.1. Создание словаря
Чтобы создать словарь, заключите в фигурные скобки {} список пар «ключзначение», разделенных запятыми, в форме ключ: значение. Пустой словарь
создается в форме {}.
Создадим словарь с ключами, содержащими названия стран ('Finland', 'South
Africa' и 'Nepal'), и значениями, представляющими коды стран в интернете:
'fi', 'za' и 'np':
In [1]: country_codes = {'Finland': 'fi', 'South Africa': 'za',
...:
'Nepal': 'np'}
...:
In [2]: country_codes
Out[2]: {'Finland': 'fi', 'South Africa': 'za', 'Nepal': 'np'}

При выводе словаря его список пар «ключ-значение», разделенных запятыми,
всегда заключается в фигурные скобки. Поскольку словари относятся к неупорядоченным коллекциям, порядок вывода может отличаться от порядка
добавления пар «ключ-значение» в словарь. Во фрагменте [2] пары «ключзначение» выводятся в порядке вставки, но ваш код не должен зависеть от
какого-то определенного порядка пар «ключ-значение».

Проверка пустого словаря
Встроенная функция len возвращает количество пар «ключ-значение» в словаре:
In [3]: len(country_codes)
Out[3]: 3

Словарь может использоваться в качестве условия для проверки того, содержит ли он хотя бы одну пару, — непустой словарь интерпретируется как True:
In [4]: if country_codes:
...:
print('country_codes is not empty')
...: else:
...:
print('country_codes is empty')
...:
country_codes is not empty

6.2. Словари   249

Пустой словарь интерпретируется как False. Чтобы продемонстрировать этот
факт, в следующем коде мы вызываем метод clear для удаления всех пар «ключзначение», а затем во фрагменте [6] возвращаем и снова выполняем фрагмент [4]:
In [5]: country_codes.clear()
In [6]: if country_codes:
...:
print('country_codes is not empty')
...: else:
...:
print('country_codes is empty')
...:
country_codes is empty

6.2.2. Перебор по словарю
Следующий словарь связывает строки названий месяцев со значениями int,
представляющими количество дней в соответствующем месяце. Обратите
внимание: разным ключам может соответствовать одно значение:
In [1]: days_per_month = {'January': 31, 'February': 28, 'March': 31}
In [2]: days_per_month
Out[2]: {'January': 31, 'February': 28, 'March': 31}

И снова в строковом представлении словаря пары «ключ-значение» выводятся
в порядке вставки, но этот порядок не гарантирован, потому что словари не
упорядочиваются. Позднее в этой главе мы покажем, как организовать обработку ключей в порядке сортировки.
Следующая команда for перебирает пары «ключ-значение» словаря days_per_
month. Метод словарей items возвращает каждую пару «ключ-значение» в виде
кортежа, который распаковывается по переменным month и days:
In [3]: for month, days in days_per_month.items():
...:
print(f'{month} has {days} days')
...:
January has 31 days
February has 28 days
March has 31 days

6.2.3. Основные операции со словарями
В этом разделе мы начнем с создания и отображения словаря roman_numerals.
Для ключа 'X' намеренно задается неправильное значение 100, которое мы
вскоре исправим:

250   Глава 6. Словари и множества
In [1]: roman_numerals = {'I': 1, 'II': 2, 'III': 3, 'V': 5, 'X': 100}
In [2]: roman_numerals
Out[2]: {'I': 1, 'II': 2, 'III': 3, 'V': 5, 'X': 100}

Обращение к значению, связанному с ключом
Получим значение, связанное с ключом 'V':
In [3]: roman_numerals['V']
Out[3]: 5

Обновление значения в существующей паре «ключ-значение»
Вы можете обновить значение, связанное с ключом, при помощи команды
присваивания. В следующем примере это делается для замены неправильного
значения, связанного с ключом 'X':
In [4]: roman_numerals['X'] = 10
In [5]: roman_numerals
Out[5]: {'I': 1, 'II': 2, 'III': 3, 'V': 5, 'X': 10}

Добавление новых пар «ключ-значение»
При попытке присваивания значения несуществующему ключу в словарь
вставляется пара «ключ-значение»:
In [6]: roman_numerals['L'] = 50
In [7]: roman_numerals
Out[7]: {'I': 1, 'II': 2, 'III': 3, 'V': 5, 'X': 10, 'L': 50}

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

Удаление пары «ключ-значение»
Пары «ключ-значение» удаляются из словаря командой del:
In [8]: del roman_numerals['III']
In [9]: roman_numerals
Out[9]: {'I': 1, 'II': 2, 'V': 5, 'X': 10, 'L': 50}

6.2. Словари   251

Пары «ключ-значение» также могут удаляться методом словарей pop, который
возвращает значение удаленного ключа:
In [10]: roman_numerals.pop('X')
Out[10]: 10
In [11]: roman_numerals
Out[11]: {'I': 1, 'II': 2, 'V': 5, 'L': 50}

Попытки обращения к несуществующему ключу
Попытка обращения к несуществующему ключу приводит к ошибке KeyError:
In [12]: roman_numerals['III']
------------------------------------------------------------------------KeyError
Traceback (most recent call last)
in ()
----> 1 roman_numerals['III']
KeyError: 'III'

Для предотвращения этой ошибки можно воспользоваться методом словарей
get, который обычно возвращает значение, соответствующее переданному
аргументу. Если ключ не найден, то get возвращает None. При возвращении
None в фрагменте [13] вывод отсутствует. Если передать при вызове второй
аргумент, то при отсутствии ключа выдается значение:
In [13]: roman_numerals.get('III')
In [14]: roman_numerals.get('III', 'III not in dictionary')
Out[14]: 'III not in dictionary'
In [15]: roman_numerals.get('V')
Out[15]: 5

Проверка наличия заданного ключа в словаре
Чтобы определить, содержит ли словарь заданный ключ, можно воспользоваться операторами in и not in:
In [16]: 'V' in roman_numerals
Out[16]: True
In [17]: 'III' in roman_numerals
Out[17]: False
In [18]: 'III' not in roman_numerals
Out[18]: True

252   Глава 6. Словари и множества

6.2.4. Методы keys и values
Ранее метод items словарей использовался для перебора кортежей пар «ключзначение», содержащихся в словаре. Похожие методы keys и values могут
использоваться для перебора только ключей или значений соответственно:
In [1]: months = {'January': 1, 'February': 2, 'March': 3}
In [2]: for month_name in months.keys():
...:
print(month_name, end=' ')
...:
January February March
In [3]: for month_number in months.values():
...:
print(month_number, end=' ')
...:
1 2 3

Представления словарей
Каждый из методов items, keys и values, поддерживаемых словарями, возвращает представление данных словаря. При переборе представление «видит»
текущее содержимое словаря — собственной копии данных у него нет.
Чтобы показать, что представления не поддерживают собственную копию
данных словаря, сначала сохраним представление, возвращаемое keys, в переменной months_view, а затем переберем ее содержимое:
In [4]: months_view = months.keys()
In [5]: for key in months_view:
...:
print(key, end=' ')
...:
January February March

Затем добавим в month новую пару «ключ-значение» и выведем обновленный
словарь:
In [6]: months['December'] = 12
In [7]: months
Out[7]: {'January': 1, 'February': 2, 'March': 3, 'December': 12}

Теперь снова переберем содержимое months_view. Добавленный ключ действительно выводится в числе прочих:

6.2. Словари   253
In [8]: for key in months_view:
...:
print(key, end=' ')
...:
January February March December

Не изменяйте словарь во время перебора представления. Как указано в разделе 4.10.1 документации стандартной библиотеки Python1, в этом случае либо
произойдет ошибка RuntimeError, либо цикл не обработает все значения из
представления.

Преобразование ключей, значений и пар «ключ-значение»
в списки
Возможно, в каких-то ситуациях вам понадобится получить список, содержащий ключи, значения или пары «ключ-значение» из словаря. Чтобы получить
такой список, передайте представление, возвращенное keys, values или items,
встроенной функции list. Модификация таких списков не приведет к изменению соответствующего словаря:
In [9]: list(months.keys())
Out[9]: ['January', 'February', 'March', 'December']
In [10]: list(months.values())
Out[10]: [1, 2, 3, 12]
In [11]: list(months.items())
Out[11]: [('January', 1), ('February', 2), ('March', 3), ('December', 12)]

Обработка ключей в порядке сортировки
Чтобы обработать ключи в порядке сортировки, используйте встроенную
функцию sorted:
In [12]: for month_name in sorted(months.keys()):
...:
print(month_name, end=' ')
...:
February December January March

1

https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects.

254   Глава 6. Словари и множества

6.2.5. Сравнения словарей
Операторы сравнения == и != могут использоваться для проверки того, имеют
ли два словаря идентичное (или разное) содержимое. Проверка равенства
(==) дает результат True, если оба словаря содержат одинаковые пары «ключзначение» независимо от того, в каком порядке эти пары добавлялись в каждый словарь:
In [1]: country_capitals1 = {'Belgium': 'Brussels',
...:
'Haiti': 'Port-au-Prince'}
...:
In [2]: country_capitals2 = {'Nepal': 'Kathmandu',
...:
'Uruguay': 'Montevideo'}
...:
In [3]: country_capitals3 = {'Haiti': 'Port-au-Prince',
...:
'Belgium': 'Brussels'}
...:
In [4]: country_capitals1 == country_capitals2
Out[4]: False
In [5]: country_capitals1 == country_capitals3
Out[5]: True
In [6]: country_capitals1 != country_capitals2
Out[6]: True

6.2.6. Пример: словарь с оценками студентов
Следующий сценарий представляет преподавательский журнал в виде словаря,
связывающего имя каждого студента (строка) со списком целых чисел, представляющим оценки этого студента на трех экзаменах. При каждой итерации
цикла вывода данных (строки 13–17) пара «ключ-значение» распаковывается
в переменные name и grades, содержащие имя одного студента и соответствующий список из трех оценок. В строке 14 встроенная функция sum суммирует
оценки, а строка 15 вычисляет среднюю оценку этого студента делением суммы total на количество оценок (len(grades)) и выводит ее. В строках 16–17
подсчитывается сумма оценок всех четырех студентов и количество оценок
для всех студентов соответственно. В строке 19 выводится средняя оценка для
класса, вычисленная усреднением оценок по всем экзаменам.

6.2. Словари   255
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# fig06_01.py
"""Хранение данных преподавательского журнала в словаре."""
grade_book = {
'Susan': [92, 85, 100],
'Eduardo': [83, 95, 79],
'Azizi': [91, 89, 82],
'Pantipa': [97, 91, 92]
}
all_grades_total = 0
all_grades_count = 0
for name, grades in grade_book.items():
total = sum(grades)
print(f'Average for {name} is {total/len(grades):.2f}')
all_grades_total += total
all_grades_count += len(grades)
print(f"Class's average is: {all_grades_total / all_grades_count:.2f}")

Average
Average
Average
Average
Class's

for Susan is 92.33
for Eduardo is 85.67
for Azizi is 87.33
for Pantipa is 93.33
average is: 89.67

6.2.7. Пример: подсчет слов1
Следующий сценарий строит словарь для подсчета количества вхождений
каждого слова в строке. В строках 4–5 создается строка text, которую мы собираемся разбить на слова, — этот процесс называется разбиением строки на
лексемы. Python автоматически выполняет конкатенацию строк, разделенных
пропусками, в круглых скобках. Строка 7 создает пустой словарь. Ключами
словаря будут уникальные слова, а значениями — целочисленные счетчики
вхождений каждого слова в тексте.

1

Такие методы, как вычисление частот вхождения слов, часто используются для анализа
публикаций. Например, некоторые специалисты полагают, что работы Уильяма Шекспира на самом деле были написаны сэром Фрэнсисом Бэконом, Кристофером Марлоу
или кем-нибудь еще. Сравнение частот слов в этих работах с частотами слов в работах
Шекспира может выявить сходство в стиле письма. Другие методы анализа документов
будут рассмотрены в главе 11.

256   Глава 6. Словари и множества
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# fig06_02.py
"""Разбиение строк на лексемы и подсчет уникальных слов."""
text = ('this is sample text with several words '
'this is more sample text with some different words')
word_counts = {}
# Подсчет вхождений уникальных слов
for word in text.split():
if word in word_counts:
word_counts[word] += 1 # Обновление существующей пары "ключ-значение"
else:
word_counts[word] = 1 # Вставка новой пары "ключ-значение"
print(f'{"WORD":>>>>'

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

Удаление начальных и конечных пропусков
Метод strip используется для удаления начальных и конечных пропусков
из строк:
In [1]: sentence = '\t

\n

This is a test string. \t\t \n'

In [2]: sentence.strip()
Out[2]: 'This is a test string.'

Удаление начальных пропусков
Метод lstrip удаляет только начальные пропуски:
In [3]: sentence.lstrip()
Out[3]: 'This is a test string. \t\t \n'

Удаление конечных пропусков
Метод rstrip удаляет только конечные пропуски:
In [4]: sentence.rstrip()
Out[4]: '\t \n This is a test string.'

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

8.6. Операторы сравнения для строк   331

8.5. Изменение регистра символов
В предыдущих главах методы строк lower и upper использовались для преобразования строк, после чего они содержали только символы нижнего (верхнего)
регистра. Можно изменить регистр символов и методами capitalize и title.

Изменение регистра первого символа строки
Метод capitalize копирует исходную строку и возвращает новую строку,
в которой преобразован к верхнему регистру только первый символ:
In [1]: 'happy birthday'.capitalize()
Out[1]: 'Happy birthday'

Изменение регистра первого символа каждого слова в строке
Метод title копирует исходную строку и возвращает новую строку, в которой
преобразован к верхнему регистру первый символ каждого слова:
In [2]: 'strings: a deeper look'.title()
Out[2]: 'Strings: A Deeper Look'

8.6. Операторы сравнения для строк
Строки можно сравнивать при помощи операторов сравнения. Напомним,
при сравнении строк учитываются используемые ими целочисленные значения. Таким образом, при сравнении буквы верхнего регистра «меньше»
букв нижнего регистра, потому что буквам верхнего регистра соответствуют
меньшие числовые значения. Например, букве 'A' соответствует числовой
код 65, а букве 'a' — код 97. Вы уже знаете, что код символа можно проверить
функцией ord:
In [1]: print(f'A: {ord("A")}; a: {ord("a")}')
A: 65; a: 97

Сравним строки 'Orange' и 'orange' операторами сравнения:
In [2]: 'Orange' == 'orange'
Out[2]: False
In [3]: 'Orange' != 'orange'
Out[3]: True

332   Глава 8. Подробнее о строках
In [4]: 'Orange' < 'orange'
Out[4]: True
In [5]: 'Orange' 'orange'
Out[6]: False
In [7]: 'Orange' >= 'orange'
Out[7]: False

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

Подсчет вхождений
Метод count возвращает количество вхождений аргумента в строке, для которой вызывается метод:
In [1]: sentence = 'to be or not to be that is the question'
In [2]: sentence.count('to')
Out[2]: 2

Если указать во втором аргументе начальный_индекс, то поиск ограничивается сегментом строка[начальный_индекс:], то есть от начального_индекса
до конца строки:
In [3]: sentence.count('to', 12)
Out[3]: 1

Если указаны второй и третий аргументы, то поиск ограничивается сегментом
строка[начальный_индекс:конечный_индекс], то есть от начального_индекса
до конечного_индекса, исключая последний:
In [4]: sentence.count('that', 12, 25)
Out[4]: 1

8.7. Поиск подстрок   333

Как и в случае с count, все методы строк, представленные в этом разделе, могут
получать аргументы начальный_индекс и конечный_индекс для ограничения
поиска сегментом исходной строки.

Поиск подстроки в строке
Метод index ищет подстроку в строке и возвращает первый индекс, по которому она была найдена; если поиск не принес результатов, то происходит
ошибка ValueError:
In [5]: sentence.index('be')
Out[5]: 3

Метод rindex выполняет ту же операцию, что и index, но проводит поиск от
конца строки и возвращает последний индекс, по которому была найдена подстрока; если поиск не принес результатов, происходит ошибка ValueError:
In [6]: sentence.rindex('be')
Out[6]: 16

Методы строк find и rfind выполняют те же задачи, что и index с rindex, но
если подстрока не найдена, методы возвращают –1 вместо ошибки ValueError.

Проверка вхождения подстроки в строку
Чтобы выяснить, содержит строка заданную подстроку или нет, воспользуйтесь оператором in или not in:
In [7]: 'that' in sentence
Out[7]: True
In [8]: 'THAT' in sentence
Out[8]: False
In [9]: 'THAT' not in sentence
Out[9]: True

Поиск подстроки в начале или в конце строки
Методы startswith и endswith возвращают True, если строка начинается или
заканчивается заданной подстрокой:
In [10]: sentence.startswith('to')
Out[10]: True

334   Глава 8. Подробнее о строках
In [11]: sentence.startswith('be')
Out[11]: False
In [12]: sentence.endswith('question')
Out[12]: True
In [13]: sentence.endswith('quest')
Out[13]: False

8.8. Замена подстрок
Поиск подстроки с ее заменой входит в число стандартных операций с текстом.
Метод replace получает две подстроки. Он ищет в строке подстроку, заданную
первым аргументом, и заменяет каждое ее вхождение подстрокой, заданной
вторым аргументом. Метод возвращает новую строку с результатами. Заменим
символы табуляции в строке запятыми:
In [1]: values = '1\t2\t3\t4\t5'
In [2]: values.replace('\t', ',')
Out[2]: '1,2,3,4,5'

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

8.9. Разбиение и объединение строк
Когда вы читаете предложение, ваш мозг разбивает его на отдельные слова,
или лексемы; каждая лексема передает значение. Интерпретаторы, такие как
IPython, тоже разбивают предложения на лексемы, то есть на отдельные компоненты: ключевые слова, идентификаторы, операторы и др. Лексемы обычно
разделяются символами-пропусками (пробелы, табуляции, символы новой
строки), хотя вместо них могут использоваться другие символы, называемые
ограничителями (delimiters).

Разбиение строк
Метод split без аргументов разбивает строку на подстроки по символампропускам, возвращая список лексем. Чтобы выполнить разбиение строки
по нестандартному ограничителю (например, по парам «запятая и пробел»),

8.9. Разбиение и объединение строк   335

укажите строку-ограничитель (например, ', '), которая должна использоваться при разбиении строки:
In [1]: letters = 'A, B, C, D'
In [2]: letters.split(', ')
Out[2]: ['A', 'B', 'C', 'D']

Если во втором аргументе передается целое число, то оно задает максимальное количество разбиений. Последней лексемой становится оставшаяся часть
строки после максимального количества разбиений:
In [3]: letters.split(', ', 2)
Out[3]: ['A', 'B', 'C, D']

Также существует метод rsplit, который делает то же самое, что split, но
максимальное количество разбиений отсчитывается от конца строки к началу.

Объединение строк
Метод join выполняет конкатенацию строк в своем аргументе, в котором должен
передаваться итерируемый объект, содержащий только строковые значения;
в противном случае происходит ошибка TypeError. Разделителем между объединяемыми строками становится строка, для которой вызывается join. В следующем коде создаются строки со списками значений, разделенных запятыми:
In [4]: letters_list = ['A', 'B', 'C', 'D']
In [5]: ','.join(letters_list)
Out[5]: 'A,B,C,D'

Следующий фрагмент объединяет результаты трансформации списка, который
создает список строк:
In [6]: ','.join([str(i) for i in range(10)])
Out[6]: '0,1,2,3,4,5,6,7,8,9'

В главе 9 вы научитесь работать с файлами, содержащими значения, разделенные запятыми. Такие файлы, называемые CSV-файлами (сокращение
от «Comma Separated Values»), являются стандартным форматом хранения
данных электронных таблиц (вроде Microsoft Excel или Google Sheets). В главах с практическими примерами data science многие ключевые библиотеки
(например, NumPy, Pandas и Seaborn) предоставляют встроенные средства
для работы с CSV-данными.

336   Глава 8. Подробнее о строках

Методы partition и rpartition
Метод partition разбивает строку на кортеж из трех строк по разделителю,
передаваемому в аргументе. Кортеж содержит следующие строки:
ØØчасть исходной строки перед разделителем;
ØØсам разделитель;
ØØчасть строки после разделителя.

Эта возможность может пригодиться при разбиении более сложных строк.
Возьмем строку с именем и оценками студента:
'Amanda: 89, 97, 92'

Разобьем исходную строку на имя студента, разделитель ': ' и строку, представляющую список оценок:
In [7]: 'Amanda: 89, 97, 92'.partition(': ')
Out[7]: ('Amanda', ': ', '89, 97, 92')

Чтобы поиск разделителя проводился от конца строки, используйте метод
rpartition. Для примера возьмем следующую строку с URL-адресом:
'http://www.deitel.com/books/PyCDS/table_of_contents.html'

Воспользуемся методом rpartition, чтобы отделить часть 'table_of_contents.
html' от остатка URL-адреса:
In [8]: url = 'http://www.deitel.com/books/PyCDS/table_of_contents.html'
In [9]: rest_of_url, separator, document = url.rpartition('/')
In [10]: document
Out[10]: 'table_of_contents.html'
In [11]: rest_of_url
Out[11]: 'http://www.deitel.com/books/PyCDS'

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

8.10. Символы и методы проверки символов   337

текст с внутренними символами \n, представляющими разрывы строк, как
показано во фрагменте [13]:
In [12]: lines = """This is line 1
...: This is line2
...: This is line3"""
In [13]: lines
Out[13]: 'This is line 1\nThis is line2\nThis is line3'
In [14]: lines.splitlines()
Out[14]: ['This is line 1', 'This is line2', 'This is line3']

Если при вызове splitlines передается аргумент True, то в конце каждого
элемента списка остается символ новой строки:
In [15]: lines.splitlines(True)
Out[15]: ['This is line 1\n', 'This is line2\n', 'This is line3']

8.10. Символы и методы проверки символов
Во многих языках программирования символы и строки представлены разными типами. В Python символ представляет собой строку из одного символа.
Python предоставляет методы строк для проверки того, обладает ли строка
некоторыми характеристиками. Так, метод isdigit возвращает True, если
строка, для которой вызван метод, состоит только из цифровых символов
(0–9). С помощью этого метода можно проверить, что пользовательский ввод
состоит из одних цифр:
In [1]: '-27'.isdigit()
Out[1]: False
In [2]: '27'.isdigit()
Out[2]: True

Метод isalnum возвращает True, если строка, для которой вызывается метод,
является алфавитно-цифровой, то есть состоит только из цифр и букв:
In [3]: 'A9876'.isalnum()
Out[3]: True
In [4]: '123 Main Street'.isalnum()
Out[4]: False

338   Глава 8. Подробнее о строках
В табл. 8.2 перечислены многие методы проверки символов. Каждый метод
возвращает False, если описанное условие не выполняется.
Таблица 8.2. Методы проверки символов
Метод строк

Описание

isalnum()

Возвращает True, если строка состоит только из алфавитно-цифровых символов (то есть только из букв и цифр)

isalpha()

Возвращает True, если строка состоит только из алфавитных символов (то есть только из букв)

isdecimal()

Возвращает True, если строка состоит только из десятичных цифр
(то есть цифр десятичной системы счисления) и не содержит
знаков + или -

isdigit()

Возвращает True, если строка состоит только из цифр (например,
'0', '1', '2')

isidentifier()

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

islower()

Возвращает True, если все алфавитные символы в строке относятся к нижнему регистру (например, 'a', 'b', 'c')

isnumeric()

Возвращает True, если символы в строке представляют числовое
значение без знака + или - и без точки-разделителя дробной части

isspace()

Возвращает True, если строка содержит только символы-пропуски

istitle()

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

isupper()

Возвращает True, если все алфавитные символы в строке относятся к верхнему регистру (например, 'A', 'B', 'C')

8.11. Необработанные строки
Напомним, символы \ (обратный слеш) в строках служат началом служебных
последовательностей — например, \n для символа новой строки или \t для
табуляции. Для включения символа \ в строку требуется использовать два
символа \\. Это усложняет чтение некоторых строк. Например, в Microsoft
Windows символ \ используется для разделения имен каталогов при определении местоположения файла. Чтобы указать путь к файлу в Windows,
используйте запись вида:

8.12. Знакомство с регулярными выражениями   339
In [1]: file_path = 'C:\\MyFolder\\MySubFolder\\MyFile.txt'
In [2]: file_path
Out[2]: 'C:\\MyFolder\\MySubFolder\\MyFile.txt'

В таких случаях удобнее использовать необработанные строки (raw strings),
начинающиеся с префикса r. В таких строках каждый символ \ рассматривается как обычный символ, а не как начало служебной последовательности:
In [3]: file_path = r'C:\MyFolder\MySubFolder\MyFile.txt'
In [4]: file_path
Out[4]: 'C:\\MyFolder\\MySubFolder\\MyFile.txt'

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

8.12. Знакомство с регулярными выражениями
Иногда возникает необходимость в поиске в тексте определенных структур —
телефонных номеров, адресов электронной почты, почтовых кодов, адресов
веб-страниц, номеров социального страхования и т. д. Строка с регулярным
выражением описывает шаблон для поиска совпадений в других строках.
Регулярные выражения помогают извлекать данные из неструктурированного
текста, например сообщений в социальных сетях. Они также помогают удостовериться в правильности формата данных перед их обработкой1.

Проверка данных
Прежде чем работать с текстовыми данными, вы будете часто применять
регулярные выражения для их проверки. Так, при помощи последних можно
проверить, что:
1

Тема регулярных выражений может показаться более сложной, чем многие другие средства
Python, которые мы использовали ранее. После того как вы освоите эту тему, вы сможете
писать более компактный код, чем с традиционными средствами обработки строк, а это
ускорит процесс разработки. Также ваш код будет обрабатывать «граничные» случаи,
которые вы могли упустить, — и возможно, это позволит избежать коварных ошибок.

340   Глава 8. Подробнее о строках
ØØпочтовый код США состоит из пяти цифр (например, 02215) или пяти

цифр, за которыми следует дефис и еще четыре цифры (например, 022154775);
ØØфамилия состоит только из букв, пробелов, апострофов и дефисов;
ØØадрес электронной почты состоит только из допустимых символов в до-

пустимом порядке;
ØØномер социального страхования в США состоит из трех цифр, дефиса,

двух цифр, дефиса и четырех цифр и соответствует другим правилам относительно цифр, которые могут входить в каждую группу цифр.
Вам не придется создавать ваши регулярные выражения для таких стандартных конструкций. На веб-сайтах:
ØØhttps://regex101.com
ØØhttp://www.regexlib.com
ØØhttps://www.regular-expressions.info

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

Другие применения регулярных выражений
Кроме проверки данных, регулярные выражения часто используются для
следующих целей:
ØØизвлечение данных из текста, например поиск всех URL-адресов на веб-

странице (возможно, для решения этой задачи вы предпочтете такие инструменты, как BeautifulSoup, XPath и lxml);
ØØочистка данных, например удаление необязательных данных, удаление

дубликатов, обработка неполных данных, исправление опечаток, проверка целостности форматов данных, обработка выбросов и т. д.;
ØØпреобразование данных в другие форматы, например переформатирова-

ние данных, собранных в формате значений, разделенных табуляциями
(пробелами), в формат CSV для приложения, которому требуются данные в формате CSV.

8.12. Знакомство с регулярными выражениями   341

8.12.1. Модуль re и функция fullmatch
Чтобы использовать регулярные выражения, импортируйте модуль re стандартной библиотеки Python:
In [1]: import re

Одна из простейших функций регулярных выражений fullmatch проверяет,
совпадает ли шаблон, заданный первым аргументом, со всей строкой, заданной
вторым аргументом.

Проверка совпадения для литеральных символов
Начнем с проверки совпадений для литеральных символов, то есть символов,
которые совпадают сами с собой:
In [2]: pattern = '02215'
In [3]: 'Match' if re.fullmatch(pattern, '02215') else 'No match'
Out[3]: 'Match'
In [4]: 'Match' if re.fullmatch(pattern, '51220') else 'No match'
Out[4]: 'No match'

Первым аргументом функции является регулярное выражение — шаблон, для
которого проверяется совпадение в строке. Любая строка может быть регулярным выражением. Значение переменной pattern '02215' состоит из цифровых
литералов, которые совпадают только сами с собой в заданном порядке. Во втором аргументе передается строка, с которой должен полностью совпасть шаблон.
Если шаблон из первого аргумента совпадает со строкой из второго аргумента,
fullmatch возвращает объект с текстом совпадения, который интерпретируется
как True. Позднее мы более подробно расскажем об этом объекте. Во фрагменте [4] второй аргумент содержит те же цифры, но эти цифры следуют в другом
порядке. Таким образом, совпадения нет, а fullmatch возвращает None, что
интерпретируется как False.

Метасимволы, символьные классы и квантификаторы
Регулярные выражения обычно содержат различные специальные символы,
которые называются метасимволами:
[]

{}

()

\

*

+

^

$

?

.

|

342   Глава 8. Подробнее о строках
С метасимвола \ начинается каждый из предварительно определенных символьных классов, каждый из которых совпадает с символом из конкретного
набора. Проверим, что почтовый код состоит из пяти цифр:
In [5]: 'Valid' if re.fullmatch(r'\d{5}', '02215') else 'Invalid'
Out[5]: 'Valid'
In [6]: 'Valid' if re.fullmatch(r'\d{5}', '9876') else 'Invalid'
Out[6]: 'Invalid'

В регулярном выражении \d{5} \d является символьным классом, представляющим цифру (0–9). Символьный класс — служебная последовательность
в регулярном выражении, совпадающая с одним символом. Чтобы совпадение
могло состоять из нескольких символов, за символьным классом следует указать квантификатор. Квантификатор {5} повторяет \d пять раз, как если бы
мы использовали запись \d\d\d\d\d для совпадения с пятью последовательными цифрами. Во фрагменте [6] fullmatch возвращает None, потому что '9876'
совпадает только с четырьмя последовательными цифровыми символами.

Другие предопределенные символьные классы
В табл. 8.3 перечислены некоторые предопределенные символьные классы
и группы символов, с которыми они совпадают. Чтобы любой метасимвол
совпадал со своим литеральным значением, поставьте перед ним символ \
(обратный слеш). Например, \\ совпадает с обратным слешем (\), а \$ совпадает со знаком $.
Таблица 8.3. Некоторые предопределенные символьные классы и группы символов,
с которыми они совпадают
Символьный класс

Совпадение

\d

Любая цифра (0–9)

\D

Любой символ, кроме цифр

\s

Любой символ-пропуск (пробелы, табуляции, новые строки)

\S

Любой символ, кроме пропусков

\w

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

\W

Любой символ, кроме символов слов

8.12. Знакомство с регулярными выражениями   343

Пользовательские символьные классы
Квадратные скобки [] определяют пользовательский символьный класс, совпадающий с одним символом. Так, [aeiou] совпадает с гласной буквой нижнего регистра, [A-Z] — с буквой верхнего регистра, [a-z] — с буквой нижнего
регистра и [a-zA-Z] — с любой буквой нижнего (верхнего) регистра.
Выполним простую проверку имени — последовательности букв без пробелов или знаков препинания. Проверим, что последовательность начинается
с буквы верхнего регистра (A–Z), а за ней следует произвольное количество
букв нижнего регистра (a–z):
In [7]: 'Valid' if re.fullmatch('[A-Z][a-z]*', 'Wally') else 'Invalid'
Out[7]: 'Valid'
In [8]: 'Valid' if re.fullmatch('[A-Z][a-z]*', 'eva') else 'Invalid'
Out[8]: 'Invalid'

Имя может содержать неизвестное заранее количество букв. Квантификатор * совпадает с нулем и более вхождениями подвыражения, находящегося
слева (в данном случае [a-z]). Таким образом, [A-Z][a-z]* совпадает с буквой
верхнего регистра, за которой следует нуль и более букв нижнего регистра (например, 'Amanda', 'Bo' и даже 'E').
Если пользовательский символьный класс начинается с символа ^ (крышка),
то класс совпадает с любым символом, который не подходит под определение
из класса. Таким образом, [^a-z] совпадает с любым символом, который не
является буквой нижнего регистра:
In [9]: 'Match' if re.fullmatch('[^a-z]', 'A') else 'No match'
Out[9]: 'Match'
In [10]: 'Match' if re.fullmatch('[^a-z]', 'a') else 'No match'
Out[10]: 'No match'

Метасимволы в пользовательском символьном классе интерпретируются как
литеральные символы, то есть как сами символы, не имеющие специального
смысла. Таким образом, символьный класс [*+$] совпадает с одним из символов *, + или $:
In [11]: 'Match' if re.fullmatch('[*+$]', '*') else 'No match'
Out[11]: 'Match'
In [12]: 'Match' if re.fullmatch('[*+$]', '!') else 'No match'
Out[12]: 'No match'

344   Глава 8. Подробнее о строках

Квантификаторы * и +
Для того чтобы имя содержало хотя бы одну букву нижнего регистра, квантификатор * во фрагменте [7] можно заменить знаком +, который совпадает
по крайней мере с одним вхождением подвыражения:
In [13]: 'Valid' if re.fullmatch('[A-Z][a-z]+', 'Wally') else 'Invalid'
Out[13]: 'Valid'
In [14]: 'Valid' if re.fullmatch('[A-Z][a-z]+', 'E') else 'Invalid'
Out[14]: 'Invalid'

Квантификаторы * и + являются максимальными («жадными») — они совпадают с максимально возможным количеством символов. Таким образом, регулярные выражения [A-Z][a-z]+ совпадают с именами 'Al', 'Eva', 'Samantha',
'Benjamin' и любыми другими словами, начинающимися с буквы верхнего
регистра, за которой следует хотя бы одна буква нижнего регистра.

Другие квантификаторы
Квантификатор ? совпадает с нулем или одним вхождением подвыражения:
In [15]: 'Match' if re.fullmatch('labell?ed', 'labelled') else 'No match'
Out[15]: 'Match'
In [16]: 'Match' if re.fullmatch('labell?ed', 'labeled') else 'No match'
Out[16]: 'Match'
In [17]: 'Match' if re.fullmatch('labell?ed', 'labellled') else 'No
match'
Out[17]: 'No match'

Регулярное выражение labell?ed совпадает со словами labelled и labeled,
но не с ошибочно написанным словом labellled. В каждом из приведенных
выше фрагментов первые пять литеральных символов регулярного выражения
(label) совпадают с первыми пятью символами второго аргумента. Часть l?
означает, что оставшимся литеральным символам ed может предшествовать
нуль или один символ l.
Квантификатор {n,} совпадает не менее чем с n вхождениями подвыражения.
Следующее регулярное выражение совпадает со строками, содержащими не
менее трех цифр:
In [18]: 'Match' if re.fullmatch(r'\d{3,}', '123') else 'No match'
Out[18]: 'Match'

8.12. Знакомство с регулярными выражениями   345
In [19]: 'Match' if re.fullmatch(r'\d{3,}', '1234567890') else 'No match'
Out[19]: 'Match'
In [20]: 'Match' if re.fullmatch(r'\d{3,}', '12') else 'No match'
Out[20]: 'No match'

Чтобы совпадение включало от n до m (включительно) вхождений, используйте квантификатор {n,m}. Следующее регулярное выражение совпадает со
строками, содержащими от 3 до 6 цифр:
In [21]: 'Match' if re.fullmatch(r'\d{3,6}', '123') else 'No match'
Out[21]: 'Match'
In [22]: 'Match' if re.fullmatch(r'\d{3,6}', '123456') else 'No match'
Out[22]: 'Match'
In [23]: 'Match' if re.fullmatch(r'\d{3,6}', '1234567') else 'No match'
Out[23]: 'No match'
In [24]: 'Match' if re.fullmatch(r'\d{3,6}', '12') else 'No match'
Out[24]: 'No match'

8.12.2. Замена подстрок и разбиение строк
Модуль re предоставляет функцию sub для замены совпадений шаблона
в строке, а также функцию split для разбиения строки на фрагменты на основании шаблонов.

Функция sub — замена совпадений шаблона
По умолчанию функция sub модуля re заменяет все вхождения шаблона заданным текстом. Преобразуем строку, разделенную табуляциями, в формат
с разделением запятыми:
In [1]: import re
In [2]: re.sub(r'\t', ', ', '1\t2\t3\t4')
Out[2]: '1, 2, 3, 4'

Функция sub получает три обязательных аргумента:
ØØшаблон для поиска (символ табуляции '\t');
ØØтекст замены (', ');
ØØстрока, в которой ведется поиск ('1\t2\t3\t4'),

346   Глава 8. Подробнее о строках
и возвращает новую строку. Ключевой аргумент count может использоваться
для определения максимального количества замен:
In [3]: re.sub(r'\t', ', ', '1\t2\t3\t4', count=2)
Out[3]: '1, 2, 3\t4'

Функция split
Функция split разбивает строку на лексемы, используя регулярное выражение для определения ограничителя, и возвращает список строк. Разобьем
строку по запятым, за которыми следует 0 или более пропусков — для обозначения пропусков используется символьный класс \s, а * обозначает 0 и более
вхождений предшествующего подвыражения:
In [4]: re.split(r',\s*', '1, 2, 3,4,
5,6,7,8')
Out[4]: ['1', '2', '3', '4', '5', '6', '7', '8']

Ключевой аргумент maxsplit задает максимальное количество разбиений:
In [5]: re.split(r',\s*', '1,
Out[5]: ['1', '2', '3', '4,

2, 3,4,
5,6,7,8']

5,6,7,8', maxsplit=3)

В данном случае после трех разбиений четвертая строка содержит остаток
исходной строки.

8.12.3. Другие функции поиска, обращение
к совпадениям
Ранее мы использовали функцию fullmatch для определения того, совпала ли
вся строка с регулярным выражением. Но существует и ряд других функций
поиска совпадений. В этом разделе мы рассмотрим функции search, match,
findall и finditer и покажем, как обратиться к подстрокам совпадений.

Функция search — поиск первого совпадения в строке
Функция search ищет в строке первое вхождение подстроки, совпадающей
с регулярным выражением, и возвращает объект совпадения (типа SRE_Match),
содержащий подстроку с совпадением. Метод group объекта совпадения возвращает эту подстроку:
In [1]: import re
In [2]: result = re.search('Python', 'Python is fun')

8.12. Знакомство с регулярными выражениями   347
In [3]: result.group() if result else 'not found'
Out[3]: 'Python'

Если совпадение шаблона в строке не найдено, то функция search возвращает
None:
In [4]: result2 = re.search('fun!', 'Python is fun')
In [5]: result2.group() if result2 else 'not found'
Out[5]: 'not found'

Функция match ищет совпадение только от начала строки.

Игнорирование регистра символов при помощи необязательного
ключевого аргумента flags
Многие функции модуля re получают необязательный ключевой аргумент
flags, который изменяет способ поиска совпадений для регулярного выражения. Например, по умолчанию при поиске учитывается регистр символов,
но при помощи константы IGNORECASE модуля re можно провести поиск без
учета регистра:
In [6]: result3 = re.search('Sam', 'SAM WHITE', flags=re.IGNORECASE)
In [7]: result3.group() if result3 else 'not found'
Out[7]: 'SAM'

Подстрока 'SAM' совпадает с шаблоном 'Sam', потому что обе строки состоят
из одинаковых букв, хотя 'SAM' содержит буквы только верхнего регистра.

Метасимволы, ограничивающие совпадение началом
или концом строки
Метасимвол ^ в начале регулярного выражения (и не в квадратных скобках) —
якорь, указывающий, что выражение совпадает только от начала строки:
In [8]: result = re.search('^Python', 'Python is fun')
In [9]: result.group() if result else 'not found'
Out[9]: 'Python'
In [10]: result = re.search('^fun', 'Python is fun')
In [11]: result.group() if result else 'not found'
Out[11]: 'not found'

348   Глава 8. Подробнее о строках
Аналогичным образом символ $ в конце регулярного выражения является
якорем, указывающим, что выражение совпадает только в конце строки:
In [12]: result = re.search('Python$', 'Python is fun')
In [13]: result.group() if result else 'not found'
Out[13]: 'not found'
In [14]: result = re.search('fun$', 'Python is fun')
In [15]: result.group() if result else 'not found'
Out[15]: 'fun'

Функции findall и finditer — поиск всех совпадений в строке
Функция findall находит все совпадающие подстроки и возвращает список
совпадений. Для примера извлечем все телефонные номера в строке, полагая,
что телефонные номера в США записываются в форме ###-###-####:
In [16]: contact = 'Wally White, Home: 555-555-1234, Work: 555-555-4321'
In [17]: re.findall(r'\d{3}-\d{3}-\d{4}', contact)
Out[17]: ['555-555-1234', '555-555-4321']

Функция finditer работает аналогично findall, но возвращает итерируемый
объект, содержащий объекты совпадений, с отложенным вычислением. При
большом количестве совпадений использование finditer позволит сэкономить память, потому что она возвращает по одному совпадению, тогда как
findall возвращает все совпадения сразу:
In [18]: for phone in re.finditer(r'\d{3}-\d{3}-\d{4}', contact):
...:
print(phone.group())
...:
555-555-1234
555-555-4321

Сохранение подстрок в совпадениях
Метасимволы ( и ) (круглые скобки) используются для сохранения подстрок
в совпадениях. Для примера сохраним отдельно имя и адрес электронной почты в тексте строки:
In [19]: text = 'Charlie Cyan, e-mail: demo1@deitel.com'
In [20]: pattern = r'([A-Z][a-z]+ [A-Z][a-z]+), e-mail: (\w+@\w+\.\w{3})'
In [21]: result = re.search(pattern, text)

8.12. Знакомство с регулярными выражениями   349

Регулярное выражение задает две сохраняемыеподстроки, заключенные
в метасимволы ( и ). Эти метасимволы не влияют на то, в каком месте текста
строки будет найдено совпадение шаблона, — функция match возвращает
объект совпадения только в том случае, если совпадение всего шаблона будет
найдено в тексте строки.
Рассмотрим регулярное выражение по частям:
ØØ'([A-Z][a-z]+ [A-Z][a-z]+)' совпадает с двумя словами, разделенными

пробелом. Каждое слово должно начинаться с буквы верхнего регистра.
ØØ', e-mail: ' содержит литеральные символы, которые совпадают сами

с собой.
ØØ(\w+@\w+\.\w{3}) совпадает с простым адресом электронной почты, со-

стоящим из одного или нескольких алфавитно-цифровых символов (\w+),
символа @, одного или нескольких алфавитно-цифровых символов (\w+),
точки (\.) и трех алфавитно-цифровых символов (\w{3}). Перед точкой
ставится символ \, потому что точка (.) в регулярных выражениях является метасимволом, совпадающим с одним символом.
Метод groups объекта совпадения возвращает кортеж совпавших подстрок:
In [22]: result.groups()
Out[22]: ('Charlie Cyan', 'demo1@deitel.com')

Метод group объекта совпадения возвращает все совпадения в виде одной
строки:
In [23]: result.group()
Out[23]: 'Charlie Cyan, e-mail: demo1@deitel.com'

Вы можете обратиться к каждой сохраненной строке, передав целое число
методу group. Нумерация сохраненных подстрок начинается с 1 (в отличие
от индексов списков, которые начинаются с 0):
In [24]: result.group(1)
Out[24]: 'Charlie Cyan'
In [25]: result.group(2)
Out[25]: 'demo1@deitel.com'

350   Глава 8. Подробнее о строках

8.13. Введение в data science: pandas,
регулярные выражения и первичная
обработка данных
Данные не всегда поступают в форме, готовой для анализа. Например, они
могут иметь неправильный формат, быть ошибочными, а то и вовсе отсутствовать. Опыт показывает, что специалисты по data science тратят до 75% своего
времени на подготовку данных перед началом анализа. Подготовка данных
называется первичной обработкой, два важнейших шага которой заключаются
в очистке данных и последующем преобразовании данных в форматы, оптимальные для ваших систем баз данных и аналитических программ. Несколько
типичных примеров очистки данных:
ØØудаление наблюдений с отсутствующими значениями;
ØØзамена отсутствующих значений подходящими значениями;
ØØудаление наблюдений с некорректными значениями;
ØØзамена некорректных значений подходящими значениями;
ØØисключение выбросов (хотя в некоторых случаях их лучше оставить);
ØØустранение дубликатов (хотя некоторые дубликаты содержат действи-

тельную информацию);
ØØобработка с данными, целостность которых была нарушена

и так далее.
Вероятно, вы уже думаете, что очистка данных — сложный и хлопотный
процесс, в котором легко можно принять ошибочные решения, отрицательно
сказывающиеся на ваших результатах. Да, все правильно. Когда мы дойдем
до практических примеров data science в следующих главах, вы увидите,
что data science в большей степени является дисциплиной эмпирической,
как медицина, и в меньшей степени — дисциплиной теоретической, как
теоретическая физика. В эмпирических дисциплинах выводы делаются на
основании наблюдений и практического опыта. Например, многие лекарства,
эффективно решающие современные медицинские проблемы, были получены на основе наблюдения за воздействием ранних версий этих лекарств
на лабораторных животных, а в конечном итоге и на людей и постепенного
уточнения ингредиентов и дозировок. Действия специалистов data science
могут изменяться в зависимости от проекта, могут зависеть от качества

8.13. Введение в data science   351

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

подробно рассказано в практических примерах data science);
ØØобъединение взаимосвязанных признаков;
ØØформирование выборок данных для получения репрезентативного под-

множества (в практических примерах data science вы увидите, что случайная выборка особенно эффективна в этом отношении; мы также объясним
почему);
ØØстандартизация форматов данных;
ØØгруппировка данных

и так далее.
Всегда целесообразно сохранить исходные данные.Некоторые примеры очистки и преобразования данных будут рассмотрены в контексте коллекций Series
и DataFrame библиотеки pandas.

Очистка данных
Некорректные и отсутствующие значения в данных могут оказать значительное влияние на анализ данных. Некоторые специалисты data science
протестуют против любых попыток вставки «разумных значений». Вместо
этого они рекомендуют четко пометить отсутствующие данные и предоставить
решение проблемы пакету аналитики данных. Другие советуют действовать
более осторожно1.
1

Эта сноска написана на основе комментария, полученного нами 20 июля 2018 года от
одного из рецензентов этой книги, доктора Элисон Санчес (Alison Sanchez) факультета экономики и предпринимательства Университета Сан-Диего. Она заметила: «При
подстановке “разумных значений” на место отсутствующих или некорректных данных
необходимо действовать очень осторожно. “Подставлять” значения, повышающие статистическую значимость или обеспечивающие более “разумные” или “лучшие” результаты,
недопустимо. “Подстановка” данных не должна превратиться в “подтасовку” данных.
Первое, чему должен научиться читатель, — не исключать и не изменять значения,
противоречащие гипотезам. “Подстановка” разумных значений не означает, что читатель
может изменять данные, чтобы получить нужный результат».

352   Глава 8. Подробнее о строках
Представьте, что в больнице пациентам измеряют температуру (и возможно,
другие показатели состояния организма) четыре раза в день. Данные состоят
из имени и четырех значений с плавающей точкой:
['Brown, Sue', 98.6, 98.4, 98.7, 0.0]

Для этого пациента были зарегистрированы только три значения температуры (по шкале Фаренгейта) — 99.7, 98.4 и 98.7 (возможно, из-за сбоя датчика.) Среднее первых трех значений равно 98,57, что близко к нормальной
температуре. Но если при вычислении средней температуры будет учтено
отсутствующее значение, замененное на 0.0, то среднее будет равно всего
73.93 — весьма сомнительный результат. Конечно, врач не должен срочно
отправлять такого пациента в реанимацию — очень важно привести данные
«к разумному виду».
Один из распространенных способов очистки данных заключается в подстановке разумного значения на место отсутствующей температуры, например
среднего значения других показаний температуры пациента. Если бы это
было сделано в рассмотренном примере, то средняя температура пациента
оставалась бы равной 98.57 — намного более вероятная средняя температура
на основании других показаний.

Проверка данных
Начнем с создания коллекции Series почтовых кодов, состоящих из пяти
цифр, на базе словаря пар «название-города/почтовый-код-из-5-цифр». Мы
намеренно указали ошибочный индекс для Майами:
In [1]: import pandas as pd
In [2]: zips = pd.Series({'Boston': '02215', 'Miami': '3310'})
In [3]: zips
Out[3]:
Boston
02215
Miami
3310
dtype: object

Хотя zips может показаться двумерным массивом, в действительности это
одномерный массив. «Второй столбец» представляет значения почтовых кодов
из коллекции Series (из значений словаря), а «первый столбец» — их индексы
(из ключей словаря).

8.13. Введение в data science   353

Для проверки данных можно воспользоваться регулярными выражениями
с pandas. Атрибут str коллекции Series предоставляет средства обработки
строк и различные методы регулярных выражений. Чтобы проверить правильность каждого отдельного почтового кода, воспользуемся методом match
атрибута str:
In [4]: zips.str.match(r'\d{5}')
Out[4]:
Boston
True
Miami
False
dtype: bool

Метод match применяет регулярное выражение \d{5} к каждому элементу
Series, чтобы убедиться в том, что элемент состоит ровно из пяти цифр. Явно
перебирать все почтовые коды в цикле не нужно — match сделает это за вас. Это
еще один пример программирования в функциональном стиле с внутренними
итерациями (в отличие от внешних). Метод возвращает новую коллекцию
Series, содержащую значение True для каждого действительного элемента.
В данном случае почтовый код Майами проверку не прошел, поэтому его
элемент равен False.
С недопустимыми данными есть несколько вариантов действий. Первый —
обнаружить их источник и взаимодействовать с источником, чтобы исправить
ошибку. Это возможно не всегда. Например, данные могут поступить с высокоскоростных датчиков в «интернете вещей». В этом случае исправить ошибку
на уровне источника не удастся, поэтому можно применить методы очистки
данных. Например, в случае недействительного кода Майами 3310 можно
поискать почтовые коды Майами, начинающиеся с 3310. Поиск возвращает
два таких кода — 33101 и 33109; можно выбрать один из них.
Иногда вместо того, чтобы проверять на совпадение шаблона всю строку, требуется узнать, содержит ли значение подстроку, совпадающую с шаблоном.
В этом случае следует использовать метод contains вместо match. Создадим
коллекцию Series строк, каждая из которых содержит название города в США,
штата и почтовый код, а затем определим, содержит ли каждая строку подстроку, совпадающую с шаблоном ' [A-Z]{2} ' (пробел, за которым следуют
две буквы верхнего регистра, и еще один пробел):
In [5]: cities = pd.Series(['Boston, MA 02215', 'Miami, FL 33101'])
In [6]: cities
Out[6]:

354   Глава 8. Подробнее о строках
0
Boston, MA 02215
1
Miami, FL 33101
dtype: object
In [7]: cities.str.contains(r' [A-Z]{2} ')
Out[7]:
0
True
1
True
dtype: bool
In [8]: cities.str.match(r' [A-Z]{2} ')
Out[8]:
0
False
1
False
dtype: bool

Значения индексов не указаны, поэтому коллекция Series по умолчанию
использует индексы, начинающиеся с 0 (фрагмент [6]). Фрагмент [7] использует функцию contains, чтобы показать, что оба элемента Series содержат подстроки, совпадающие с ' [A-Z]{2} '. Фрагмент [8] использует
match для проверки того, что значение элемента не совпадает с шаблоном
полностью, потому что в полном значении каждого элемента присутствуют
другие символы.

Переформатирование данных
От очистки данных перейдем к первичной обработке данных в другой формат.
Возьмем простой пример: допустим, приложение работает с телефонными
номерами США в формате ###-###-####, с разделением групп цифр дефисами. При этом телефонные номера были предоставлены в виде строк из десяти
цифр без дефисов. Создадим коллекцию DataFrame:
In [9]: contacts = [['Mike Green', 'demo1@deitel.com', '5555555555'],
...:
['Sue Brown', 'demo2@deitel.com', '5555551234']]
...:
In [10]: contactsdf = pd.DataFrame(contacts,
...:
columns=['Name', 'Email', 'Phone'])
...:
In [11]: contactsdf
Out[11]:
Name
Email
0 Mike Green demo1@deitel.com
1
Sue Brown demo2@deitel.com

Phone
5555555555
5555551234

8.13. Введение в data science   355

В этой коллекции DataFrame мы задали индексы столбцов ключевым аргументом columns, но не указали индексы строк, так что строки индексируются с 0.
Кроме того, в выводе показаны значения столбцов, по умолчанию выравниваемые по правому краю. Ситуация отличается от форматирования Python,
в котором числа по умолчанию выравниваются по правому краю поля, но нечисловые значения по умолчанию выравниваются по левому краю.
Теперь произведем первичную обработку данных с применением программирования в функциональном стиле. Телефонные номера можно перевести
в правильный формат вызовом метода map коллекции Series для столбца
'Phone' коллекции DataFrame. Аргументом метода map является функция,
которая получает значение и возвращает отображенное (преобразованное)
значение. Функция get_formatted_phone отображает десять последовательных
цифр в формат ###-###-####:
In [12]: import re
In [13]: def get_formatted_phone(value):
...:
result = re.fullmatch(r'(\d{3})(\d{3})(\d{4})', value)
...:
return '-'.join(result.groups()) if result else value
...:
...:

Регулярное выражение в первой команде блока совпадает только с первыми
десятью последовательно идущими цифрами. Оно сохраняет подстроки, которые содержат первые три цифры, следующие три цифры и последние четыре
цифры. Команда return работает следующим образом:
ØØЕсли результат равен None, то значение просто возвращается в неизмен-

ном виде.
ØØВ противном случае вызывается метод result.groups() для получения

кортежа, содержащего сохраненные подстроки. Кортеж передается методу join строк для выполнения конкатенации элементов, с разделением
элементов символом '-' для формирования преобразованного телефонного номера.
Метод map коллекции Series новую коллекцию Series, которая содержит
результаты вызова ее функции-аргумента для каждого значения в столбце.
Фрагмент [15] выводит результаты, включающие имя и тип столбца:
In [14]: formatted_phone = contactsdf['Phone'].map(get_formatted_phone)
In [15]: formatted_phone

356   Глава 8. Подробнее о строках
0
555-555-5555
1
555-555-1234
Name: Phone, dtype: object

Убедившись в том, что данные имеют правильный формат, можно обновить их
в исходной коллекции DataFrame, присвоив новую коллекцию Series столбцу
'Phone':
In [16]: contactsdf['Phone'] = formatted_phone
In [17]: contactsdf
Out[17]:
Name
Email
0 Mike Green demo1@deitel.com
1
Sue Brown demo2@deitel.com

Phone
555-555-5555
555-555-1234

Обсуждение pandas продолжится в разделе «Введение в data science» следующей главы. Кроме того, pandas будет использоваться и в других последующих
главах.

8.14. Итоги
В этой главе были представлены различные средства форматирования и обработки строк. Мы занимались форматированием данных с использованием
форматных строк и метода format. Вы увидели, как применять расширенное
присваивание для конкатенации и повторения строк, научились удалять пропуски в начале и в конце строки, а также изменять регистр символов. Были
рассмотрены дополнительные методы для разбиения строк и для объединения
итерируемых объектов, содержащих строки. Также были представлены различные методы проверки символов.
Вы узнали, что в необработанных строках символы \ интерпретируются как
литеральные символы, а не как начало служебных последовательностей. Необработанные строки особенно удобны для определения регулярных выражений,
которые часто содержат много символов \.
Затем мы представили замечательные возможности поиска по шаблону,
предоставляемые функциями регулярных выражений из модуля re. Функция
fullmatch проверяет, что шаблон совпадает со всей строкой, и может пригодиться при проверке данных. Мы показали, как использовать функцию replace
для поиска и замены подстрок. Функция split использовалась для разбиения

8.14. Итоги   357

строк на лексемы по ограничителям, совпадающим с шаблоном регулярного
выражения. Затем были представлены различные способы поиска совпадений
шаблонов в строках и обращения к полученным совпадениям.
В разделе «Введение в data science» был описан процесс первичной обработки данных и продемонстрирован пример преобразования данных. Затем мы
вернулись к коллекциям Series и DataFrame на примере использования регулярных выражений для проверки и первичной обработки данных.
Обсуждение средств обработки строк продолжится в следующей главе, когда
мы займемся чтением текста из файлов и записью текста в файлы. Мы используем модуль csv для обработки файлов с данными, разделенными запятыми
(CSV). Также будет представлен механизм обработки исключений, который
позволяет обрабатывать ошибки при их возникновении вместо вывода трассировки.

9
Файлы и исключения
В этой главе…
•• Файлы и долгосрочное хранение данных.
•• Чтение, запись и обновление файлов.
•• Чтение и запись CSV-файлов — наборов данных стандартного формата для
машинного обучения.
•• Сериализация объектов в формат JSON, часто применяемая для передачи
данных по интернету, и десериализация JSON в объекты.
•• Использование команды with для обеспечения гарантированного освобождения ресурсов и предотвращения «утечки ресурсов».
•• Использование команды try для ограничения кода, в котором могут возникнуть исключения, и обработка этих исключений в соответствующих
секциях except.
•• Использование секции else команды try для выполнения кода, если в наборе try не возникают исключения.
•• Использование секции finally команды try для выполнения кода независимо от того, возникло исключение в try или нет.
•• Выдача исключений для обозначения возникающих проблем.
•• Трассировка функций и методов, приведших к исключению.
•• Использование pandas для загрузки в DataFrame и обработки набора данных катастрофы «Титаника» в формате CSV.

9.1. Введение   359

9.1. Введение
Переменные, списки, кортежи, словари, множества, массивы, коллекции
Series и DataFrame из библиотеки pandas предоставляют возможность только
временного хранения данных. Такие данные теряются, когда локальная переменная выходит «из области видимости» или при завершении программы.
Файлы предоставляют средства для долгосрочного хранения больших объемов
данных даже после завершения программы, создавшей эти данные. Компьютеры хранят файлы на устройствах вторичного хранения данных, включая
твердотельные диски, жесткие диски и т. д. В этой главе мы расскажем, как
программы Python создают, обновляют и обрабатывают файлы данных.
Нами рассматриваются текстовые файлы в нескольких популярных форматах — простой текст (по общепринятым соглашениям расширение .txt обозначает текстовый файл), JSON (JavaScript Object Notation) и CSV (значения, разделенные запятыми). Формат JSON будет использоваться для сериализации
и десериализации объектов с целью упрощения сохранения этих объектов во
вторичной памяти и передачи их по интернету. Непременно прочитайте раздел
«Введение в data science», в котором модуль csv из стандартной библиотеки
Python и pandas будут использоваться для загрузки и обработки CSV-данных.
В частности, будет рассмотрена CSV-версия набора данных катастрофы «Титаника». Мы используем многие популярные наборы данных в практических
примерах, посвященных обработке естественных языков, глубокому анализу
данных Twitter, IBM Watson, машинному обучению, глубокому обучению
и большим данным.
Так как в книге мы уделяем особое внимание безопасности Python, будут
рассмотрены проблемы уязвимости при сериализации и десериализации
данных при использовании модуля pickle стандартной библиотеки Python.
Мы рекомендуем использовать сериализацию в формате JSON вместо pickle.
Кроме того, в главе будут описаны средства обработки исключений. Исключение свидетельствует о проблеме, возникшей во время выполнения. Вам уже
встречались исключения типов ZeroDivisionError, NameError, ValueError,
StatisticsError, TypeError, IndexError, KeyError и RuntimeError. Мы покажем, как обрабатывать возникающие исключения при помощи команд try
и связанных с ними секций except. Будут рассмотрены и секции else и finally
команды try. Средства, представленные в этой главе, помогут вам создавать
надежные, защищенные от ошибок программы, которые могут справиться с возникшими проблемами и продолжить выполнение или корректно завершиться.

360   Глава 9. Файлы и исключения
Программы обычно запрашивают и освобождают ресурсы (в частности,
файлы) в ходе выполнения программы. Часто эти ресурсы ограниченны или
могут использоваться только одной программой в любой момент времени. Мы
покажем, как гарантировать, что после использования ресурса программой
он будет освобожден для других программ даже в случае возникновения исключения (для решения этой задачи используется команда with).

9.2. Файлы
Python рассматривает текстовый файл как последовательность символов,
а двоичный файл (графическое изображение, видео и т. д.) как последовательность байтов. Как и в списках и массивах, первый символ текстового
файла и первый байт двоичного файла располагаются в позиции 0, так что
в файле, содержащем n символов или байтов, наивысший номер позиции
равен n – 1. На следующей диаграмме изображено концептуальное представление файла:
0

1

2

3

4

5

6

7

8

9

...

n–1

...

Маркер конца файла

Для каждого открытого вами файла Python создает объект файла, который
будет использоваться для работы с файлом.

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

Стандартные объекты файлов
В начале своего выполнения программа Python создает три стандартных
файл-объекта:
ØØsys.stdin — стандартный объект файла для ввода;

9.3. Обработка текстовых файлов   361
ØØsys.stdout — стандартный объект файла для вывода;
ØØsys.stderr — стандартный объект файла для ошибок.

Хотя эти объекты считаются объектами файлов, по умолчанию они не читают
и не записывают данные в файлы. Функция input неявно использует sys.
stdin для получения пользовательского ввода с клавиатуры. Функция print
неявно направляет вывод в sys.stdout, что соответствует командной строке.
Python неявно направляет ошибки программы и трассировку в sys.stderr;
эта информация также отображается в командной строке. Если потребуется
явно обращаться к этим объектам в своем коде, то вам придется импортировать
модуль sys, но такие ситуации встречаются нечасто.

9.3. Обработка текстовых файлов
В этом разделе мы запишем простой текстовый файл, который может использоваться системой управления расчетами с клиентами для отслеживания
задолженностей клиентов. Затем мы прочитаем этот текстовый файл и убедимся в том, что он содержит данные. Для каждого клиента в файле будет
храниться номер счета, фамилия и сумма задолженности. В совокупности
эти поля данных представляют запись клиента. Python не определяет особую
структуру файла, так что в Python не существует встроенной концепции записей. Программист должен самостоятельно структурировать файлы, чтобы они
соответствовали требованиям его приложения. Мы будем создавать и вести
этот файл по номеру счета. В определенном смысле номер счета может рассматриваться как ключ записи. В этой главе предполагается, что вы запустили
IPython из папки примеров ch09.

9.3.1. Запись в текстовый файл: команда with
Создадим файл accounts.txt и запишем в него пять записей клиентов. В общем
случае записи в текстовых файлах хранятся по одной на строку текста, поэтому
каждая запись завершается символом новой строки:
In [1]: with open('accounts.txt', mode='w') as accounts:
...:
accounts.write('100 Jones 24.98\n')
...:
accounts.write('200 Doe 345.67\n')
...:
accounts.write('300 White 0.00\n')
...:
accounts.write('400 Stone -42.16\n')
...:
accounts.write('500 Rich 224.62\n')
...:

362   Глава 9. Файлы и исключения
Данные также можно записать в файл вызовом print (который автоматически
выводит завершающий символ \n):
print('100 Jones 24.98', file=accounts)

Команда with
Многие приложения захватывают ресурсы: файлы, сетевые подключения,
подключения к базам данных и т. д. Как только необходимость в ресурсе отпала, его необходимо освободить. Это правило гарантирует, что ресурсами
смогут воспользоваться другие приложения. Команда Python with:
ØØзахватывает ресурс (в данном случае объект файла для accounts.txt) и при-

сваивает соответствующий объект переменной (accounts в данном примере);
ØØпозволяет приложению использовать ресурс через эту переменную;
ØØвызывает метод close для объекта ресурса, чтобы освободить ресурс при

достижении конца набора команды with в программе.

Встроенная функция open
Встроенная функция open открывает файл accounts.txt и связывает его с объектом файла. Аргумент mode определяет режим открытия файла, то есть он
указывает, будет ли файл открыт для чтения, для записи или для чтения/
записи. Режим 'w' открывает файл для записи; если файл не существует, то
он будет создан. Если не указать путь к файлу, то Python создаст его в текущей папке (ch09). Будьте внимательны: открытие файла для записи приводит
к удалению всех существующих данных в этом файле.

Запись в файл
Команда with присваивает объект, возвращенный open, переменной accounts
из секции as. В наборе команды with переменная accounts используется для
взаимодействия с файлом. В данном случае метод write вызывается пять раз
для сохранения пяти записей в файле, каждая из которых представляет собой отдельную строку текста, завершаемую символом новой строки. В конце
набора команды with неявно вызывается метод close объекта файла, чтобы
файл был закрыт.

9.3. Обработка текстовых файлов   363

Содержимое файла accounts.txt
После выполнения предыдущего фрагмента в каталоге ch09 появляется файл
accounts.txt со следующим содержимым (вы можете просмотреть его, открыв
файл в текстовом редакторе):
100
200
300
400
500

Jones 24.98
Doe 345.67
White 0.00
Stone -42.16
Rich 224.62

В следующем разделе мы прочитаем файл и выведем его содержимое.

9.3.2. Чтение данных из текстового файла
Мы только что создали текстовый файл accounts.txt и записали в него данные.
Теперь прочитаем эти данные из файла последовательно, от начала до конца.
Следующий сеанс читает записи из файла accounts.txt и выводит содержимое
каждой записи по столбцам: столбцы Account и Name выравниваются по левому
краю, а столбец Balance выравнивается по правому краю, чтобы точки — разделители дробной части были выровнены по вертикали:
In [1]: with open('accounts.txt', mode='r') as accounts:
...:
print(f'{"Account":= to 0.00.')
self.name = name
self.balance = balance

10.2. Класс Account    403

При вызове метода для конкретного объекта Python неявно передает ссылку на этот объект в первом аргументе метода. По этой причине все методы
класса должны определяться хотя бы с одним параметром. По общепринятым соглашениям программисты Python присваивают первому параметру
метода имя self. Методы класса должны использовать эту ссылку (self) для
обращения к атрибутам и другим методам объекта. Метод __init__ класса
Account также определяет параметры name и balance для имени владельца
счета и баланса.
Команда if проверяет параметр balance. Если значение balance меньше 0.00,
то __init__ выдает ошибку ValueError, которая завершает метод __init__.
В противном случае метод создает и инициализирует атрибуты name и balance
нового объекта Account.
В момент создания объект класса Account еще не имеет никаких атрибутов.
Они добавляются динамически присваиванием вида:
self.имя_атрибута = значение

Классы Python могут определять много специальных методов (таких, как
__init__), обозначаемых начальными и конечными двойными подчеркиваниями (__) в имени метода. Класс Python object, который будет рассматриваться позднее в этой главе, определяет специальные методы, доступные для
всех объектов Python.

Метод deposit
Метод deposit класса Account прибавляет положительную величину к атрибуту balance счета. Если аргумент amount меньше 0.00, то метод выдает ошибку
ValueError, указывающую на то, что разрешены только положительные вносимые суммы. Если amount проходит проверку, то строка 25 добавляет это
значение к атрибуту balance объекта.
18
19
20
21
22
23
24
25

def deposit(self, amount):
"""Внесение средств на счет."""
# Если amount меньше 0.00, выдать исключение
if amount < Decimal('0.00'):
raise ValueError('amount must be positive.')
self.balance += amount

404   Глава 10. Объектно-ориентированное программирование

10.2.3. Композиция: ссылка на объекты
как компоненты классов
Объект Account содержит имя владельца (name) и баланс (balance.) Вспомните, что «в Python нет ничего, кроме объектов». Это означает, что атрибуты
объектов являются ссылками на объекты других классов. Например, атрибут
name объекта Account представляет собой ссылку на объект строки, а атрибут
balance содержит ссылку на объект Decimal. Внедрение ссылки на объекты
других типов является разновидностью повторного использования программных компонентов, которая обычно называется композицией (реже —
отношением «содержит»). Позднее в этой главе будет рассмотрен механизм
наследования, который создает отношение «является».

10.3. Управление доступом к атрибутам
Методы класса Account проверяют свои аргументы, с тем чтобы значение balance
всегда оставалось действительным, то есть большим или равным 0.00. В предыдущем примере атрибуты name и balance использовались только для получения
их значений. Оказывается, атрибуты могут использоваться и для изменения этих
значений. Рассмотрим объект Account в следующем сеансе IPython:
In [1]: from account import Account
In [2]: from decimal import Decimal
In [3]: account1 = Account('John Green', Decimal('50.00'))
In [4]: account1.balance
Out[4]: Decimal('50.00')

Изначально account1 содержит действительный баланс. Теперь попробуем
присвоить атрибуту balance недопустимое отрицательное значение, а затем
вывести balance:
In [5]: account1.balance = Decimal('-1000.00')
In [6]: account1.balance
Out[6]: Decimal('-1000.00')

Вывод фрагмента [6] показывает, что баланс account1 стал отрицательным.
Таким образом, в отличие от методов, атрибуты данных не могут проверять
присвоенные им значения.

10.4. Использование свойств для доступа к данным   405

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

Соглашение об именах с начальными подчеркиваниями (_)
В Python не существует приватных данных. Вместо этого при проектировании
классов используется соглашение (соглашения) об именах, способствующее правильному использованию. Программисты Python знают, что по распространенным соглашениям любое имя атрибута, начинающееся с символа подчеркивания
(_), предназначено только для внутреннего использования классом. Клиентский
код должен использовать методы класса и (как будет показано в следующем разделе) свойства класса для взаимодействия с атрибутами данных каждого объекта,
предназначенными исключительно для внутреннего использования. Атрибуты,
идентификаторы которых не начинаются с символа подчеркивания (_), считаются общедоступными для использования в клиентском коде. В следующем разделе
мы определим класс Time и воспользуемся этими соглашениями. Тем не менее
и при использовании соглашений атрибуты все равно остаются доступными.

10.4. Использование свойств для доступа
к данным
Разработаем класс Time, предназначенный для хранения времени в 24-часовом формате: часы в диапазоне 0–23, а минуты и секунды в диапазоне 0–59.
Для этого класса мы определим свойства, которые с точки зрения клиентских
программистов похожи на атрибуты данных, но управляют способом чтения
и запи­си данных объекта. Предполагается, что другие программисты соблюдают
соглашения Python для правильного использования объектов вашего класса.

10.4.1. Класс Time в действии
Прежде чем рассматривать определение класса Time, продемонстрируем его
возможности. Для начала убедитесь в том, что текущим каталогом является
ch10, и импортируйте класс Time из файла timewithproperties.py:
In [1]: from timewithproperties import Time

406   Глава 10. Объектно-ориентированное программирование

Создание объекта Time
Теперь создадим объект Time. Метод __init__ класса Time получает параметры
hour, minute и second, каждому из которых по умолчанию соответствует значение аргумента 0. В данном случае мы задаем значения для hour и minute —
second по умолчанию использует значение 0:
In [2]: wake_up = Time(hour=6, minute=30)

Вывод объекта Time
Класс Time определяет два метода для формирования строковых представлений объекта Time. Когда вы выводите переменную в IPython, как во фрагменте [3], IPython вызывает специальный метод __repr__ объекта для получения
строкового представления объекта. Реализация __repr__ создает строку
в таком формате:
In [3]: wake_up
Out[3]: Time(hour=6, minute=30, second=0)

Мы также предоставили специальный метод __str__, который вызывается
при преобразовании объекта в строку, например при выводе объекта вызовом print1. Наша реализация __str__ создает строку в 12-часовом формате
времени:
In [4]: print(wake_up)
6:30:00 AM

Получение атрибута через свойство
Класс Time предоставляет свойства hour, minute и second, которые не уступают
по удобству атрибутам данных для получения и изменения данных объекта.
Тем не менее, как вы вскоре увидите, свойства реализуются как методы и поэтому могут содержать дополнительную логику — например, для определения
формата, в котором должно возвращаться значение атрибута данных, или для
проверки нового значения перед его использованием для изменения атрибута
данных. В следующем примере мы получаем значение hour объекта wake_up:
In [5]: wake_up.hour
Out[5]: 6
1

Если класс не предоставляет метод __str__ , то при преобразовании объекта класса
в строку будет вызван метод __repr__ класса.

10.4. Использование свойств для доступа к данным   407

И хотя этот фрагмент вроде бы получает значение атрибута данных hour,
в действительности это вызов метода hour, который возвращает значение
атрибута данных (который мы назвали _hour, как будет показано в следующем разделе).

Присваивание времени
Вы можете задать новое значение времени при помощи метода set_time объекта Time. Как и метод __init__, метод set_time имеет параметры hour, minute
и second, каждый из которых имеет значение по умолчанию 0:
In [6]: wake_up.set_time(hour=7, minute=45)
In [7]: wake_up
Out[7]: Time(hour=7, minute=45, second=0)

Присваивание значения атрибута через свойство
Класс Time позволяет задать значения hour, minute и second по отдельности
при помощи свойств. Присвоим hour значение 6:
In [8]: wake_up.hour = 6
In [9]: wake_up
Out[9]: Time(hour=6, minute=45, second=0)

Хотя на первый взгляд фрагмент [8] просто присваивает значение атрибуту данных, в действительности это вызов метода hour , который получает
значение 6 в аргументе. Метод проверяет значение, а затем присваивает его
соответствующему атрибуту данных (который мы назвали _hour, как будет
показано в следующем разделе).

Попытка присваивания недействительного значения
Чтобы доказать, что свойства класса Time проверяют значения, которые вы
пытаетесь им присвоить, попробуем задать некорректное значение для свойства hour. Это приводит к ошибке ValueError:
In [10]: wake_up.hour = 100
------------------------------------------------------------------------ValueError
Traceback (most recent call last)
in ()
----> 1 wake_up.hour = 100

408   Глава 10. Объектно-ориентированное программирование
~/Documents/examples/ch10/timewithproperties.py in hour(self, hour)
20
"""Настройка hour."""
21
if not (0 22
raise ValueError(f'Hour ({hour}) must be 0-23')
23
24
self._hour = hour
ValueError: Hour (100) must be 0-23

10.4.2. Определение класса Time
Итак, вы увидели класс Time в действии. Обратимся к его определению.

Класс Time: метод __init__ со значениями параметров
по умолчанию
Метод __init__ класса Time получает параметры hour, minute и second со значением 0 по умолчанию. Как и в случае с методом __init__ класса Account ,
параметр self содержит ссылку на инициализируемый объект Time. Команды,
содержащие self.hour, self.minute и self.second, вроде бы создают атрибуты hour, minute и second для нового объекта Time (self). Тем не менее эти
команды в действительности вызывают методы, реализующие свойства hour,
minute и second класса (строки 13–50). Затем эти методы создают атрибуты
с именами _hour, _minute и _second, предназначенные только для использования внутри класса:
1 # timewithproperties.py
2 """Класс Time со свойствами, доступными для чтения/записи."""
3
4 class Time:
5
"""Класс Time со свойствами, доступными для чтения/записи."""
6
7
def __init__(self, hour=0, minute=0, second=0):
8
"""Инициализация каждого атрибута."""
9
self.hour = hour # 0-23
10
self.minute = minute # 0-59
11
self.second = second # 0-59
12

Класс Time: свойство hour
Строки 13–24 определяют общедоступное свойство hour, доступное для чтения записи, которое работает с атрибутом данных с именем _hour. Соглашение
о начальном символе подчеркивания (_) указывает, что клиентский код не

10.4. Использование свойств для доступа к данным   409

должен обращаться к _hour напрямую. Как показывают фрагменты [5] и [8]
предыдущего раздела, с точки зрения программиста, работающего с объектами Time, свойства очень похожи на атрибуты данных. Тем не менее следует
помнить, что свойства реализованы в виде методов. Каждое свойство определяет get-метод, который получает значение атрибута данных, а также может
определить set-метод, который задает значение атрибута данных:
13
14
15
16
17
18
19
20
21
22
23
24
25

@property
def hour(self):
"""Возвращает значение часов."""
return self._hour
@hour.setter
def hour(self, hour):
"""Присваивает значение часов."""
if not (0 1 my_object.__private_data
AttributeError: 'PrivateClass' object has no attribute '__private_data'

Это происходит из-за того, что Python изменил имя атрибута. К сожалению,
атрибут __private_data при этом остается косвенно доступным.

10.6. Практический пример: моделирование
тасования и сдачи карт
В следующем примере представлены два класса, которые могут использоваться для тасования и сдачи карт. Класс Card представляет игральную карту
с номиналом ('Ace', '2', '3', …, 'Jack', 'Queen', 'King') и мастью ('Hearts',
'Diamonds', 'Clubs', 'Spades'). Класс DeckOfCards представляет колоду из
52 карт в виде списка объектов Card. Сначала мы протестируем эти классы
в сеансе IPython, чтобы продемонстрировать функции тасования и сдачи,
а также вывода карт в текстовом виде. Затем будут рассмотрены определения
классов. Наконец, мы используем другой сеанс IPython для вывода 52 карт
в графическом виде при помощи библиотеки Matplotlib. Заодно вы узнаете, где
можно найти красивые изображения карт, находящиеся в открытом доступе.

10.6.1. Классы Card и DeckOfCards в действии
Прежде чем рассматривать код классов Card и DeckOfCards, воспользуемся
сеансом IPython для демонстрации их возможностей.

Создание объектов, тасование и сдача карт
Импортируйте класс DeckOfCards из deck.py и создайте объект этого класса:
In [1]: from deck import DeckOfCards
In [2]: deck_of_cards = DeckOfCards()

10.6. Практический пример: моделирование тасования и сдачи карт   417

Метод __init__ класса DeckOfCards создает 52 объекта Card, упорядоченных по
мастям и по номиналу в каждой масти. Чтобы убедиться в этом, можно вывести
объект deck_of_cards, который вызывает метод __str__ класса DeckOfCards для
получения строкового представления колоды карт. Прочитайте каждую строку
слева направо и убедитесь, что все карты идут по порядку в каждой масти:
In [3]: print(deck_of_cards)
Ace of Hearts
2 of Hearts
5 of Hearts
6 of Hearts
9 of Hearts
10 of Hearts
King of Hearts
Ace of Diamonds
4 of Diamonds
5 of Diamonds
8 of Diamonds
9 of Diamonds
Queen of Diamonds King of Diamonds
3 of Clubs
4 of Clubs
7 of Clubs
8 of Clubs
Jack of Clubs
Queen of Clubs
2 of Spades
3 of Spades
6 of Spades
7 of Spades
10 of Spades
Jack of Spades

3 of Hearts
7 of Hearts
Jack of Hearts
2 of Diamonds
6 of Diamonds
10 of Diamonds
Ace of Clubs
5 of Clubs
9 of Clubs
King of Clubs
4 of Spades
8 of Spades
Queen of Spades

4 of Hearts
8 of Hearts
Queen of Hearts
3 of Diamonds
7 of Diamonds
Jack of Diamonds
2 of Clubs
6 of Clubs
10 of Clubs
Ace of Spades
5 of Spades
9 of Spades
King of Spades

Перетасуем колоду и снова выведем объект deck_of_cards. Мы не стали задавать значение для инициализации генератора, поэтому при каждом тасовании
вы будете получать разные результаты:
In [4]: deck_of_cards.shuffle()
In [5]: print(deck_of_cards)
King of Hearts
Queen of Clubs
5 of Hearts
7 of Hearts
5 of Clubs
8 of Diamonds
8 of Spades
5 of Spades
8 of Clubs
7 of Spades
4 of Diamonds
8 of Hearts
9 of Hearts
4 of Spades
3 of Spades
9 of Diamonds
Ace of Hearts
3 of Diamonds
King of Diamonds
Jack of Spades
5 of Diamonds
4 of Clubs
10 of Diamonds
2 of Clubs
9 of Spades
Jack of Hearts

Queen of Diamonds
4 of Hearts
3 of Hearts
Queen of Spades
Jack of Diamonds
6 of Spades
6 of Clubs
3 of Clubs
2 of Diamonds
Jack of Clubs
Queen of Hearts
Ace of Diamonds
6 of Diamonds

10 of Clubs
2 of Hearts
10 of Hearts
Ace of Clubs
10 of Spades
King of Spades
King of Clubs
Ace of Spades
6 of Hearts
2 of Spades
9 of Clubs
7 of Diamonds
7 of Clubs

Сдача карт
Вызов метода deal_card сдает карты по одной. IPython вызывает метод
__repr__ возвращенного объекта для получения строкового вывода, показанного в приглашении Out[]:

418   Глава 10. Объектно-ориентированное программирование
In [6]: deck_of_cards.deal_card()
Out[6]: Card(face='King', suit='Hearts')

Другие возможности класса Card
Чтобы продемонстрировать работу метода __str__ класса Card, сдадим еще
одну карту и передадим ее встроенной функции str:
In [7]: card = deck_of_cards.deal_card()
In [8]: str(card)
Out[8]: 'Queen of Clubs'

У каждого объекта Card существует соответствующий файл с графическим
изображением, которое можно получить из свойства image_name, доступного
только для чтения. Вскоре мы воспользуемся этой возможностью, когда будем
выводить объекты Card в графическом виде:
In [9]: card.image_name
Out[9]: 'Queen_of_Clubs.png'

10.6.2. Класс Card — знакомство с атрибутами класса
Каждый объект Card содержит три строковых свойства, представляющих номинал карты (face), масть (suit) и имя файла, содержащего соответствующее
изображение (image_name). Как было показано в сеансе IPython из предыдущего раздела, класс Card также предоставляет методы для инициализации Card
и получения различных строковых представлений.

Атрибуты класса FACES и SUITS
Каждый объект класса содержит собственные копии атрибутов данных класса.
Например, каждый объект Account содержит собственные атрибуты имени
владельца (name) и баланса (balance). Иногда атрибут должен совместно использоваться всеми объектами класса. Атрибут класса (также называемый
переменной класса) представляет информацию уровня класса, которая принадлежит классу, а не конкретному объекту этого класса. Класс Card определяет
два атрибута класса (строки 5–7):
ØØFACES — список имен номиналов карт.
ØØSUITS — список имен мастей карт.

10.6. Практический пример: моделирование тасования и сдачи карт   419
1 # card.py
2 """Класс Card представляет игральную карту и имя файла с ее изображением."""
3
4 class Card:
5
FACES = ['Ace', '2', '3', '4', '5', '6',
6
'7', '8', '9', '10', 'Jack', 'Queen', 'King']
7
SUITS = ['Hearts', 'Diamonds', 'Clubs', 'Spades']
8

Чтобы определить атрибут класса, присвойте ему значение в определении
класса, но не в методах или свойствах класса (в этом случае они станут локальными переменными). Константы FACES и SUITS не должны изменяться.
Вспомните, что «Руководство по стилю для кода Python» рекомендует присваивать константам имена, состоящие из букв верхнего регистра1.
Мы будем использовать элементы этих списков для инициализации всех
создаваемых объектов Card. Тем не менее хранить копию каждого списка
в каждом объекте Card не нужно. К атрибутам классов можно обращаться из
любого объекта класса, но обычно программы обращаются к ним с указанием имени класса: Card.FACES, Card.SUITS и т. п. Атрибуты классов начинают
существовать сразу же с импортирования определений их классов.

Метод __init__
При создании объекта Card метод __init__ определяет атрибуты данных _face
и _suit:
9
10
11
12
13

def __init__(self, face, suit):
"""Инициализирует карту номиналом и мастью."""
self._face = face
self._suit = suit

Свойства face, suit и image_name
После того как объект Card будет создан, значения face, suit и image_name
не изменяются, поэтому мы реализуем их в виде свойств, доступных только
для чтения (строки 14–17, 19–22 и 24–27). Свойства face и suit возвращают соответствующие атрибуты данных _face и _suit. Свойство не обязано
иметь соответствующий атрибут данных. Для демонстрации этой возмож1

Напомним, что в Python нет полноценных констант, так что значения FACES и SUITS
могут изменяться.

420   Глава 10. Объектно-ориентированное программирование
ности значение свойства image_name объекта Card создается динамически:
для этого свойство получает строковое представление объекта Card вызовом
str(self), заменяет все пробелы символами подчеркивания и присоединяет расширение '.png'. Таким образом, 'Ace of Spades' преобразуется
в'Ace_of_Spades.png'. Это имя файла будет использоваться для загрузки
изображения в формате PNG, представляющего карту. PNG (Portable
Network Graphics) — популярный графический формат для изображений,
размещаемых в интернете.
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

@property
def face(self):
"""Возвращает значение self._face объекта Card."""
return self._face
@property
def suit(self):
"""Возвращает значение self._suit объекта Card."""
return self._suit
@property
def image_name(self):
"""Возвращает имя файла изображения объекта Card."""
return str(self).replace(' ', '_') + '.png'

Методы, возвращающие строковое представление Card
Класс Card предоставляет три специальных метода, возвращающих строковые
представления. Как и в классе Time, метод __repr__ возвращает строковое
представление, которое выглядит как выражение-конструктор для создания
и инициализации объекта Card:
29
30
31
32

def __repr__(self):
"""Возвращает строковое представление для repr()."""
return f"Card(face='{self.face}', suit='{self.suit}')"

Метод __str__ возвращает строку в формате 'face of suit' — например, 'Ace
of Hearts':
33
34
35
36

def __str__(self):
"""Возвращает строковое представление для str()."""
return f'{self.face} of {self.suit}'

10.6. Практический пример: моделирование тасования и сдачи карт   421

Когда сеанс IPython из предыдущего раздела выводил всю колоду, вы видели,
что объекты Card выводились в четыре столбца, выровненных по левому краю.
Как будет показано в методе __str__ класса DeckOfCards, для форматирования
объектов Card по полям, состоящим из 19 символов, используются форматные
строки. Специальный метод __format__ класса Card вызывается при форматировании объекта Card как строки — например, в форматной строке:
37
38
39

def __format__(self, format):
"""Возвращает отформатированное строковое представление для str()."""
return f'{str(self):{format}}'

Второй аргумент этого метода содержит форматную строку, используемую для
форматирования объекта. Чтобы использовать значение параметра format как
спецификатор формата, заключите имя параметра в фигурные скобки справа
от двоеточия. В данном случае форматируется строковое представление объекта Card, возвращаемое str(self). Мы снова обсудим __format__ при описании
метода __str__ в классе DeckOfCards.

10.6.3. Класс DeckOfCards
Класс DeckOfCards содержит атрибут класса NUMBER_OF_CARDS, представляющий
количество объектов Card в колоде, и создает два атрибута данных:
ØØатрибут _current_card отслеживает карту, которая будет сдана следую-

щей (0–51), и;
ØØатрибут _deck (строка 12) содержит список 52 объектов Card.

Метод __init__
Метод __init__ класса DeckOfCards инициализирует колоду (_deck) объектов
Card. Команда for заполняет список _deck присоединением новых объектов
Card, каждый из которых инициализируется двумя строками — из списка Card.
FACES и из списка Card.SUITS. Выражение count % 13 всегда дает значение от 0
до 12 (13 индексов Card.FACES), а выражение count // 13 всегда дает значение
от 0 до 3 (четыре индекса Card.SUITS). При инициализации списка _deck он
содержит объекты Card с номиналами от 'Ace' до 'King' по порядку для всех
четырех мастей (Hearts, Diamonds, Clubs, Spades).
1 # deck.py
2 """Класс DeckofCards представляет колоду карт."""
3 import random

422   Глава 10. Объектно-ориентированное программирование
4 from card import Card
5
6 class DeckOfCards:
7
NUMBER_OF_CARDS = 52 # число карт - константа
8
9
def __init__(self):
10
"""Инициализирует колоду."""
11
self._current_card = 0
12
self._deck = []
13
14
for count in range(DeckOfCards.NUMBER_OF_CARDS):
15
self._deck.append(Card(Card.FACES[count % 13],
16
Card.SUITS[count // 13]))
17

Метод shuffle
Метод shuffle обнуляет _current_card, после чего перетасовывает карты
в колоде _deck при помощи функции shuffle модуля random:
18
19
20
21
22

def shuffle(self):
"""Тасует колоду."""
self._current_card = 0
random.shuffle(self._deck)

Метод deal_card
Метод deal_card сдает одну карту (объект Card ) из колоды (_deck ). Напомним, _current_card определяет индекс (0–51) следующего объекта Card
для сдачи (то есть карты на верху колоды). Строка 26 пытается получить
элемент _deck с индексом _current_card. Если попытка оказалась успешной,
то метод увеличивает _current_card на 1, после чего возвращает объект Card;
в противном случае метод возвращает None, чтобы показать, что в колоде не
осталось ни одной карты.
23
24
25
26
27
28
29
30
31

def deal_card(self):
"""Возвращает одну карту."""
try:
card = self._deck[self._current_card]
self._current_card += 1
return card
except:
return None

10.6. Практический пример: моделирование тасования и сдачи карт   423

Метод __str__
Класс DeckOfCards также определяет специальный метод __str__ для получения строкового представления колоды из четырех столбцов, в котором
каждый объект Card выравнивается по левому краю поля из 19 символов.
Когда строка 37 форматирует объект Card, его специальный метод __format__
вызывается с передачей форматного спецификатора '= to 0.00.')
self.name = name
self.balance = balance
def deposit(self, amount):
"""Внесение средств на счет."""
# Если amount меньше 0.00, выдать исключение
if amount < Decimal('0.00'):
raise ValueError('amount must be positive.')
self.balance += amount
if __name__ == '__main__':
import doctest
doctest.testmod(verbose=True)

Модуль __main__
При загрузке любого модуля Python присваивает строку, содержащую имя
модуля, глобальному атрибуту модуля с именем __name__. При выполнении
исходного файла Python (например, accountdoctest.py) в виде сценария Python

460   Глава 10. Объектно-ориентированное программирование
использует строку '__main__' как имя модуля. Вы можете использовать
__name__ в команде if (строки 40–42), чтобы указать код, который должен
выполняться только в случае выполнения исходного файла в форме сценария.
В данном примере строка 41 импортирует модуль doctest, а строка 42 вызывает
функцию testmod модуля для выполнения модульных тестов из doc-строк.

Выполнение тестов
Запустите файл accountdoctest.py в виде сценария, чтобы выполнить тесты.
По умолчанию при вызове testmod без аргументов результаты успешно прошедших тестов не выводятся. В этом случае отсутствие вывода означает, что
все тесты были выполнены успешно. В данном примере строка 42 вызывает
testmod с ключевым аргументом verbose=True. Он приказывает testmod выдавать подробный вывод с результатами каждого теста:
Trying:
account1 = Account('John Green', Decimal('50.00'))
Expecting nothing
ok
Trying:
account1.name
Expecting:
'John Green'
ok
Trying:
account1.balance
Expecting:
Decimal('50.00')
ok
Trying:
account2 = Account('John Green', Decimal('-50.00'))
Expecting:
Traceback (most recent call last):
...
ValueError: Initial balance must be >= to 0.00.
ok
3 items had no tests:
__main__
__main__.Account
__main__.Account.deposit
1 items passed all tests:
4 tests in __main__.Account.__init__
4 tests in 4 items.
4 passed and 0 failed.
Test passed.

10.14. Модульное тестирование с doc-строками и doctest   461

В режиме подробного вывода функция testmod для каждого теста сообщает,
что она пытается сделать ("Trying") и что она ожидает получить в результате
("Expecting"), а в случае успешного прохождения теста выводит "ok". После
завершения тестов в режиме подробного вывода testmod выводит сводку
результатов.
Чтобы продемонстрировать неудачу при прохождении теста, закомментируйте
строки 25–26 в файле accountdoctest.py, поставив в начало строки знак #, после
чего выполните accountdoctest.py в форме сценария. Для экономии места мы
приводим только части вывода doctest, относящиеся к сбойному тесту:
...
**********************************************************************
File "accountdoctest.py", line 18, in __main__.Account.__init__
Failed example:
account2 = Account('John Green', Decimal('-50.00'))
Expected:
Traceback (most recent call last):
...
ValueError: Initial balance must be >= to 0.00.
Got nothing
**********************************************************************
1 items had failures:
1 of
4 in __main__.Account.__init__
4 tests in 4 items.
3 passed and 1 failed.
***Test Failed*** 1 failures.

В данном случае мы видим, что тест в строке 18 не прошел. Функция testmod
ожидала получить трассировку, которая указывает, что из-за недопустимого
исходного баланса была инициирована ошибка ValueError. Это исключение
не произошло, что и привело к провалу теста. Для программиста, отвечающего
за определение этого класса, ошибка при прохождении этого теста указывает
на то, что в коде проверки из метода __init__ что-то пошло не так.

Магическая команда IPython %doctest_mode
Удобный способ создания doc-тестов для существующего кода основан на использовании интерактивного сеанса IPython при тестировании кода с последующим копированием/вставкой сеанса в doc-строку. Приглашения IPython
In [] и Out[] несовместимы с doctest, поэтому для вывода приглашений
в формате doctest IPython предоставляет магическую команду %doctest_mode,
которая переключается между двумя стилями оформления приглашений. При

462   Глава 10. Объектно-ориентированное программирование
первом выполнении %doctest_mode IPython переключается в режим с приглашениями >>> для ввода и без приглашений при выводе. При втором выполнении %doctest_mode IPython возвращается к приглашениям In [] и Out[].

10.15. Пространства имен и области видимости
Каждый идентификатор имеет область видимости, определяющую, в каких
местах программы он может использоваться; напомним, области видимости
могут быть локальными и глобальными. Продолжим изучение темы областей
видимости и рассмотрим пространства имен.
Области видимости определяются пространствами имен, существующими
независимо друг от друга, связывающими идентификаторы с объектами
и строящими внутреннюю реализацию на базе словарей. Таким образом, один
идентификатор может входить в несколько пространств имен. Основные пространства имен: локальное, глобальное и встроенное.

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

Глобальное пространство имен
Каждый модуль обладает глобальным пространством имен, связывающим
глобальные идентификаторы модуля (глобальные переменные, имена функций и имена классов) с объектами. Python создает глобальное пространство
имен при загрузке модуля. Глобальное пространство имен модуля существует,
а его идентификаторы находятся в области видимости для кода внутри этого
модуля до завершения программы (интерактивного сеанса). Сеанс IPython
обладает собственным глобальным пространством имен для всех идентификаторов, созданных в этом сеансе.

10.15. Пространства имен и области видимости   463

Глобальное пространство имен каждого модуля также включает идентификатор с именем __name__, содержащий имя модуля (например, 'math' для модуля
math или 'random' для модуля random). Как показано в примере doctest из
предыдущего раздела, __name__ содержит '__main__' для файла .py, запускаемого в виде сценария.

Встроенное пространство имен
Встроенное пространство имен содержит сопутствующие идентификаторы
для встроенных функций Python (например, input и range) и типов (например,
int, float и str) с объектами, определяющими эти функции и типы. Python
создает встроенное пространство имен в начале выполнения интерпретатора.
Идентификаторы встроенного пространства имен остаются в области видимости для всего кода вплоть до завершения программы (или интерактивного
сеанса1).

Поиск идентификаторов в пространствах имен
При использовании идентификатора Python ищет этот идентификатор в пространствах имен, доступных в настоящий момент; поиск начинается с локального пространства имен, переходит к глобальному, а затем к встроенному. Для
лучшего понимания порядка поиска в пространствах имен возьмем следующий
сеанс IPython:
In [1]: z = 'global z'
In [2]: def print_variables():
...:
y = 'local y in print_variables'
...:
print(y)
...:
print(z)
...:
In [3]: print_variables()
local y in print_variables
global z

Идентификаторы, определяемые в сеансе IPython, размещаются в глобальном
пространстве имен сеанса. Когда фрагмент [3] вызывает print_variables,
1

Предполагается, что вы не замещаете встроенные функции или типы, переопределяя
их идентификаторы в локальном или глобальном пространстве имен. Замещение рассматривалось в главе 4.

464   Глава 10. Объектно-ориентированное программирование
Python проводит поиск по локальному, глобальному и встроенному пространствам имен:
ØØФрагмент [3] не является функцией или методом, поэтому глобаль-

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

странство имен функции. Когда функция print_variables определяет
локальную переменную y, Python добавляет y в локальное пространство
имен функции. Переменная y находится в области видимости, пока функция не завершится.
ØØЗатем print_variables вызывает встроенную функцию print, передавая

y в аргументе. Чтобы выполнить этот вызов, Python должен найти идентификаторы y и print. Идентификатор y определяется в локальном про-

странстве имен, поэтому он находится в области видимости, и Python
сможет использовать соответствующий объект (строка 'local y in
print_variables') как аргумент print. Чтобы вызвать функцию, Python
должен найти объект, соответствующий print. Сначала поиск ведется по
локальному пространству имен, в котором print не определяется. Затем
поиск продолжается по глобальному пространству имен сеанса, в котором
тоже не определяется print. Наконец, поиск продолжается во встроенном
пространстве имен, в котором идентификатор print определяется. Следовательно, print находится в области видимости, и Python использует
соответствующий объект для вызова print.
ØØЗатем print_variables снова вызывает встроенную функцию print — уже

с аргументом z, который не определяется в локальном пространстве имен.
Python переходит к глобальному пространству имен. Аргумент z определяется в глобальном пространстве имен, так что z находится в области видимости, и Python использует соответствующий объект (строка 'global
z') в качестве аргумента print. И снова Python находит идентификатор
print во встроенном пространстве имен, используя соответствующий
объект для вызова print.
ØØК этому моменту достигнут конец набора функции print_variables,

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

10.15. Пространства имен и области видимости   465

Чтобы доказать, что идентификатор y не определен, попробуем вывести y:
In [4]: y
------------------------------------------------------------------------NameError
Traceback (most recent call last)
in ()
----> 1 y
NameError: name 'y' is not defined

В этом случае локального пространства имен нет, поэтому Python ищет y
в глобальном пространстве имен сеанса. Идентификатор y здесь не определен,
и Python продолжает поиск y во встроенном пространстве имен, так и не находя y. Пространств имен не осталось, поэтому Python выдает исключение
NameError, которое показывает, что идентификатор y не определен.
Но идентификаторы print_variables и z все еще существуют в глобальном
пространстве имен сеанса, и их по-прежнему можно использовать. Например,
вычислим идентификатор z, чтобы узнать его значение:
In [5]: z
Out[5]: 'global z'

Вложенные функции
Одним из пространств имен, которые не были рассмотрены в предшествующем обсуждении, является вмещающее пространство имен. Python позволяет
определять вложенные функции внутри других функций или методов. Например, если функция или метод выполняют одну операцию несколько раз,
можно определить вложенную функцию, чтобы избежать повторения кода
во вмещающей функции. Когда вы обращаетесь к идентификатору внутри
вложенной функции, Python сначала просматривает локальное пространство
имен вложенной функции, затем пространство имен вмещающей функции,
после этого глобальное пространство имен и, наконец, встроенное пространство имен. Иногда эта последовательность называется правилом LEGB (Local,
Enclosing, Global, Built-in).

Пространство имен класса
Класс содержит пространство имен, в котором хранятся все атрибуты его
класса. При обращении к атрибуту класса Python сначала ищет этот атрибут
в пространстве имен класса, затем в пространстве имен базового класса и т. д.,

466   Глава 10. Объектно-ориентированное программирование
пока атрибут не будет найден или не будет достигнут класс object. Если атрибут не найден, то происходит ошибка NameError.

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

10.16. Введение в data science: временные ряды
и простая линейная регрессия
Ранее нами рассматривались последовательности: списки, кортежи и массивы.
В этом разделе рассматриваются временные ряды — последовательности значений (называемых наблюдениями), связанных с определенными моментами
времени. Примерами такого рода являются котировки на момент закрытия
операций на бирже, почасовые измерения температуры, изменения локации
летящего самолета, годовая урожайность и квартальная прибыль компании.
Пожалуй, самым выразительным примером временного ряда является поток
твитов с временными метками, поступающих от пользователей Twitter со всего
мира (подробный анализ данных Twitter представлен в главе 12).
Для формирования прогнозов на основе данных временных рядов (точнее,
для прогнозирования средних январских температур в будущем и средних
январских температур до 1895 года) воспользуемся методом простой линейной
регрессии и данными средней январской температуры в Нью-Йорке с 1895
по 2018 год. Кстати, в главе 14 мы вернемся к этому примеру с библиотекой
scikit-learn, а в главе 15 для анализа временных рядов применены рекуррентные нейронные сети (RNN). В главе 16 рассматриваются временные ряды,
часто встречающиеся в финансовых приложениях и в области «интернета
вещей» (IoT).
В этом разделе для вывода диаграмм будут использоваться библиотеки Seaborn
и pandas, работающие на базе Matplotlib. Запустите IPython с поддержкой
Matplotlib:
ipython --matplotlib

10.16. Введение в data science    467

Временные ряды
Данные, которые будут использованы в примере, представляют собой временной ряд, в котором наблюдения упорядочены по годам. Одномерные временные
ряды содержат одно наблюдение на один момент времени, например среднюю
январскую температуру в Нью-Йорке за конкретный год. Многомерные временные ряды содержат по два и более наблюдения на один момент времени
(например, температуру, влажность и атмосферное давление в метеорологическом приложении). В этой главе будут анализироваться одномерные
временные ряды.
С временными рядами часто выполняются две операции:
ØØАнализ временных рядов выявляет закономерности в существующих дан-

ных временных рядов, помогая аналитикам понять суть данных. Стандартная аналитическая задача — выявление сезонности в данных. Например, в Нью-Йорке ежемесячная температура существенно изменяется
в зависимости от времени года (зима, весна, лето или осень).
ØØПрогнозирование временных рядов использует прошлые данные для про-

гнозирования будущего.
В этом разделе рассматривается прогнозирование временных рядов.

Простая линейная регрессия
При помощи метода, называемого простой линейной регрессией, построим прогнозы, выявляющие линейные отношения между месяцами (январь каждого
года) и средней температурой в Нью-Йорке. Для заданной коллекции значений, представляющих независимую переменную (комбинация «месяц/год»)
и зависимую переменную (средняя температура за этот месяц/год), простая
линейная регрессия описывает отношение между этими переменными прямой
линией — регрессионной прямой.

Линейные отношения
Чтобы понять общую концепцию линейных отношений, рассмотрим температуры по шкале Фаренгейта и Цельсия. Для заданной температуры по
Фаренгейту соответствующая температура по Цельсию вычисляется по следующей формуле:
c = 5 / 9 * (f - 32)

468   Глава 10. Объектно-ориентированное программирование
В этой формуле f (температура по Фаренгейту) — независимая переменная,
а c (температура по Цельсию) — зависимая переменная; каждое значение c
зависит от значения f, использованного при вычислении.
График температур по Фаренгейту и соответствующих им температур по Цельсию представляет собой прямую линию. Чтобы убедиться в этом, сначала создадим лямбда-выражение для предыдущей формулы и воспользуемся им для
вычисления эквивалентов по шкале Цельсия для температур по Фаренгейту
0–100 с приращением 10 градусов. Каждая пара температур по Фаренгейту/
Цельсию будет храниться в виде кортежа в temps:
In [1]: c = lambda f: 5 / 9 * (f - 32)
In [2]: temps = [(f, c(f)) for f in range(0, 101, 10)]

Поместим данные в DataFrame и используем метод plot для вывода линейной
зависимости между температурами по Фаренгейту и по Цельсию. Ключевой
аргумент style метода plot управляет внешним видом данных. Точка в строке '.-' указывает, что каждая точка данных должна обозначаться точкой на
графике, а дефис — что точки должны соединяться линиями. Оси y вручную
назначается метка 'Celsius', потому что метод plot по умолчанию выводит
'Celsius' только в левом верхнем углу условных обозначений на графике:

In [3]: import pandas as pd
In [4]: temps_df = pd.DataFrame(temps, columns=['Fahrenheit', 'Celsius'])

10.16. Введение в data science    469
In [5]: axes = temps_df.plot(x='Fahrenheit', y='Celsius', style='.-')
In [6]: y_label = axes.set_ylabel('Celsius')

Компоненты уравнения простой линейной регрессии
Точки на любой прямой линии (в двумерном пространстве) могут быть описаны уравнением:
y = mx + b,
где m — коэффициент наклона линии;
b — точка пересечения линии с осью y (при x = 0);
x — независимая переменная (дата в нашем примере);
y — зависимая переменная (температура в нашем примере).
В простой линейной регрессии y — прогнозируемое значение для заданного x.

Функция linregress из модуля stats библиотеки SciPy
Простая линейная регрессия определяет коэффициент наклона (m) и точку
пересечения (b) прямой линии, лучше всего подходящую к вашим данным. На
следующей диаграмме показаны некоторые точки данных временного ряда,
обрабатываемые в этом разделе, и соответствующая линия регрессии. Мы
добавили вертикальные линии для обозначения расстояний каждой строки
от регрессионной прямой:

470   Глава 10. Объектно-ориентированное программирование
Алгоритм простой линейной регрессии производит итерационную настройку
угла наклона и точки пересечения, вычисляя для каждой корректировки квадрат расстояния каждой точки от линии. «Наилучшая подгонка» достигается,
когда значения угла наклона и точки пересечения минимизируют сумму квадратов расстояний. Этот принцип называется обычным методом наименьших
квадратов1.
Библиотека SciPy (Scientific Python) широко применяется в инженерных, научных и математических вычислениях на языке Python. Функция linregress
этой библиотеки (из модуля scipy.stats) выполняет простую линейную
регрессию за вас. После вызова linregress остается подставить полученные
значения угла наклона и точки пересечения в формулу y = mx + b для получения прогноза.

Pandas
В трех предыдущих разделах «Введение в data science» для работы с данными использовалась библиотека pandas. Мы продолжим использовать pandas
в оставшейся части книги. В этом примере мы загрузим данные средних январских температур в Нью-Йорке в 1895–2018 годах из CSV-файла в DataFrame.
Затем данные будут отформатированы для использования в примере.

Визуализация в Seaborn
Библиотека Seaborn будет использована для графического представления
данных DataFrame в виде регрессионной прямой, представляющей график
изменения средней температуры за период 1895–2018 годов.

Получение метеорологических данных от NOAA
Загрузим данные для исследования. Национальное управление по исследованию океанов и атмосферы (NOAA, National Oceanic and Atmospheric
Administration2, или Национальное управление по исследованию океанов
и атмосферы) предоставляет доступ к обширным статистическим данным,
включая временные ряды для средних температур в конкретных городах
с различными интервалами времени.

1
2

https://en.wikipedia.org/wiki/Ordinary_least_squares.
http://www.noaa.gov.

10.16. Введение в data science    471

Мы получили средние январские температуры в Нью-Йорке с 1895 по 2018 год
из временных рядов NOAA «Climate at a Glance»:
https://www.ncdc.noaa.gov/cag/

На этой веб-странице можно выбрать температуру, уровень осадков и другие данные для целых регионов США, штатов, городов и т. д. Выбрав зону
и период времени, щелкните на кнопке Plot , чтобы вывести диаграмму
и просмотреть таблицу с выбранными данными. В верхней части таблицы
размещаются ссылки для загрузки данных в нескольких форматах, включая формат CSV (см. главу 9). На момент написания книги максимальный
диапазон доступных данных NOAA — с 1895 до 2018 года. Для удобства мы
разместили данные в каталоге примеров ch10 в файле ave_hi_nyc_jan_1895-2018.
csv. Если вы загрузите данные самостоятельно, то удалите строки, лежащие
выше строки "Date,Value,Anomaly". Данные содержат три столбца для каждого наблюдения:
ØØDate — значение в форме 'YYYYMM' (например, '201801'). Часть MM всегда

содержит 01, потому что мы загружали данные только за январь каждого
года.
ØØValue — температура по Фаренгейту в формате с плавающей точкой.
ØØAnomaly — разность между значением для заданной даты и средними зна-

чениями для всех дат. В нашем примере значение Anomaly не используется, поэтому мы его проигнорируем.

Загрузка средних температур в DataFrame
Загрузим и выведем данные для Нью-Йорка из файла ave_hi_nyc_jan_1895-2018.
csv:
In [7]: nyc = pd.read_csv('ave_hi_nyc_jan_1895-2018.csv')

Чтобы получить общее представление о данных, можно просмотреть начальные и конечные записи DataFrame:
In [8]: nyc.head()
Out[8]:
Date Value Anomaly
0 189501
34.2
-3.2
1 189601
34.7
-2.7
2 189701
35.5
-1.9
3 189801
39.6
2.2
4 189901
36.4
-1.0

472   Глава 10. Объектно-ориентированное программирование
In [9]: nyc.tail()
Out[9]:
Date Value
119 201401
35.5
120 201501
36.1
121 201601
40.8
122 201701
42.8
123 201801
38.7

Anomaly
-1.9
-1.3
3.4
5.4
1.3

Очистка данных
Скоро мы воспользуемся Seaborn для графического представления пар DateValue и регрессионной прямой. При отображении данных из DataFrame Seaborn
помечает оси графика именами столбцов DataFrame. Для удобочитаемости
переименуем столбец 'Value' в 'Temperature':
In [10]: nyc.columns = ['Date', 'Temperature', 'Anomaly']
In [11]: nyc.head(3)
Out[11]:
Date Temperature
0 189501
34.2
1 189601
34.7
2 189701
35.5

Anomaly
-3.2
-2.7
-1.9

Seaborn помечает деления на оси x значениями Date. Поскольку в примере
обрабатываются только январские данные, метки оси x будут лучше читаться
без обозначения 01 (для января); удалим его из Date. Сначала проверим тип
столбца:
In [12]: nyc.Date.dtype
Out[12]: dtype('int64')

Значения являются целыми числами, поэтому мы можем разделить их на
100 для отсечения двух последних цифр. Вспомните, что каждый столбец
в DataFrame представляет собой коллекцию Series. Вызов метода floordiv коллекции Series выполняет целочисленное деление с каждым элементом Series:
In [13]: nyc.Date = nyc.Date.floordiv(100)
In [14]: nyc.head(3)
Out[14]:
Date Temperature
0 1895
34.2
1 1896
34.7
2 1897
35.5

Anomaly
-3.2
-2.7
-1.9

10.16. Введение в data science    473

Вычисление базовых описательных статистик
для наборов данных
Чтобы быстро получить некоторые статистики для температур из набора
данных, вызовем describe для столбца Temperature . В коллекции присутствуют 124 наблюдения, среднее значение наблюдений равно 37.60 ,
а наименьшее и наибольшее наблюдение равны 26.10 и 47.60 градусам
соответственно:
In [15]: pd.set_option('precision', 2)
In [16]: nyc.Temperature.describe()
Out[16]:
count
124.00
mean
37.60
std
4.54
min
26.10
25%
34.58
50%
37.60
75%
40.60
max
47.60
Name: Temperature, dtype: float64

Прогнозирование будущих январских температур
Библиотека SciPy (Scientific Python) широко применяется в инженерных,
научных и математических вычислениях на языке Python. Ее модуль stats
предоставляет функцию linregress, которая вычисляет наклон и точку пересечения регрессионной прямой для заданного набора точек данных:
In [17]: from scipy import stats
In [18]: linear_regression = stats.linregress(x=nyc.Date,
...:
y=nyc.Temperature)
...:

Функция linregress получает два одномерных массива1 одинаковой длины,
представляющих координаты x и y точек данных. Ключевые аргументы x и y
представляют независимые и зависимые переменные соответственно. Объект, возвращаемый linregress, содержит угол наклона и точку пересечения
регрессионной прямой:
1

Эти аргументы также могут быть объектами, сходными с одномерными массивами, например списками или коллекциями Series.

474   Глава 10. Объектно-ориентированное программирование
In [19]: linear_regression.slope
Out[19]: 0.00014771361132966167
In [20]: linear_regression.intercept
Out[20]: 8.694845520062952

Эти значения можно объединить с уравнением простой линейной регрессии для прямой линии, y = mx + b при прогнозировании средней январской
температуры в Нью-Йорке для заданного года. Спрогнозируем среднюю
температуру по Фаренгейту за январь 2019 года. В следующих вычислениях
linear_regression.slope соответствует m, 2019 соответствует x (значение,
для которого прогнозируется температура), а linear_regression.intercept
соответствует b:
In [21]: linear_regression.slope * 2019 + linear_regression.intercept
Out[21]: 38.51837136113298

Также по формуле можно приближенно оценить, какой могла быть средняя
температура до 1895 года. Например, оценка средней температуры за январь
1890 года может быть получена следующим образом:
In [22]: linear_regression.slope * 1890 + linear_regression.intercept
Out[22]: 36.612865774980335

Напомним, в примере были доступны данные за 1895–2018 годы, и чем дальше
вы выходите за границы диапазона, тем менее надежными становятся прогнозы.

Построение графика со средними температурами
и регрессионной прямой
Затем воспользуемся функцией regplot библиотеки Seaborn для вывода всех
точек данных; даты представляются на оси x, а температуры на оси y. Функция
regplot строит диаграмму разброса данных, на которой точки представляют
температуры за заданный год, а прямая линия — регрессионную прямую.
Сначала закройте предыдущее окно Matplotlib, если это не было сделано ранее,
в противном случае regplot будет использовать существующее окно, в котором
уже находится график. Ключевые аргументы x и y функции regplot — одномерные массивы1 совпадающей длины, представляющие пары координат x-y
1

Эти аргументы также могут быть объектами, сходными с одномерными массивами, например списками или коллекциями Series библиотеки pandas.

10.16. Введение в data science    475

для нанесения на график. Напомним, pandas автоматически создает атрибуты
для каждого имени столбца, если оно является действительным идентификатором Python1:
In [23]: import seaborn as sns
In [24]: sns.set_style('whitegrid')
In [25]: axes = sns.regplot(x=nyc.Date, y=nyc.Temperature)

Наклон регрессионной прямой (подъем от левой части к правой) указывает
на то, что в последние 124 года средняя температура повышалась. На графике
ось y представляет температурный диапазон между минимумом 26.1 и максимумом 47.6 по Фаренгейту, в результате чего наблюдается значительный
разброс данных вокруг регрессионной прямой, из-за которого труднее выявить линейную связь. Эта проблема типична для визуализаций в области
аналитики данных. Если оси на графике отражают разные типы данных (даты
и температуры в данном случае), то как разумно определить их относительные масштабы? На предыдущем графике все определяется исключительно
высотой графика — Seaborn и Matplotlib автоматически масштабируют оси
1

Затененная область рядом с регрессионной прямой демонстрирует 95-процентный
доверительный интервал для регрессионной прямой (https://en.wikipedia.org/wiki/Simple_
linеar_regression#Confidence_intervals). Для вывода графика без доверительного интервала
добавьте ключевой аргумент ci=None в список аргументов функции regplot.

476   Глава 10. Объектно-ориентированное программирование
на основании диапазонов значений данных. Ось значений y можно масштабировать, чтобы подчеркнуть линейность отношения. В следующем примере
ось y была масштабирована от 21,5-градусного диапазона до 60-градусного
диапазона (от 10 до 70 градусов):
In [26]: axes.set_ylim(10, 70)
Out[26]: (10, 70)

Загрузка наборов данных временных рядов
Ниже перечислены некоторые популярные сайты, на которых вы сможете
загрузить временные ряды для своих исследований.
Исходные наборы данных с временными рядами
ØØhttps://data.gov/ — портал открытых данных правительства США. Поиск

текста «time series» дает свыше 7200 наборов данных с временными рядами.
ØØhttps://www.ncdc.noaa.gov/cag/ — климатический портал NOAA предоставля-

ет данные временных рядов — как глобальные, так и для США.
ØØhttps://www.esrl.noaa.gov/psd/data/timeseries/ — портал Земной научно-иссле-

довательской лаборатории (ESRL, Earth System Research Laboratory) администрации NOAA предоставляет ежемесячные и сезонные временные
ряды с климатическими данными.

10.17. Итоги   477
ØØhttps://www.quandl.com/search — Quandl предоставляет сотни бесплатных

временных рядов с финансовыми данными, а также наборы данных
с платным доступом.
ØØhttps://datamarket.com/data/list/?q=provider:tsdl — библиотека данных TSDL

(Time Series Data Library) содержит ссылки на сотни наборов данных временных рядов по многим промышленным областям.
ØØhttp://archive.ics.uci.edu/ml/datasets.html — репозиторий машинного обучения

Калифорнийского университета в Ирвайне (UCI, University of California
Irvine) содержит десятки наборов данных временных рядов из разно­
образных областей.
ØØhttp://inforumweb.umd.edu/econdata/econdata.html — сервис EcoData Универ-

ситета Мэриленда предоставляет ссылки на тысячи экономических временных рядов, собранных различными правительственными агентствами
США.

10.17. Итоги
В этой главе подробно рассматривалась тема построения полезных классов. Вы
узнали, как определить класс, создать объекты класса, обратиться к атрибутам
объекта и вызвать его методы. Мы определили специальный метод __init__
для создания и инициализации атрибутов данных нового объекта, рассмотрели
тему управления доступом к атрибутам и использования свойств, показав, что
клиент может напрямую обращаться ко всем атрибутам объектов. Идентификаторы, начинающиеся с одного символа подчеркивания (_), обозначают
атрибуты, не предназначенные для обращения со стороны клиентского кода.
Мы показали, как реализовать «приватные» атрибуты при помощи соглашения
о двойном начальном символе подчеркивания (__), предписывающем Python
преобразовать имя атрибута.
Затем была реализована модель тасования и сдачи карт, в которой был задействован класс Card и класс DeckOfCards, поддерживающий список Card.
Колода карт выводилась как в строковом виде, так и в виде изображений карт
с использованием Matplotlib. Мы реализовали специальные методы __repr__,
__str__ и __format__ для создания строковых представлений объектов.
Далее мы перешли к средствам Python для создания базовых классов и подклассов, показав, как создать подкласс, наследующий многие возможности
суперкласса, а затем добавить новые средства — возможно, с переопределением

478   Глава 10. Объектно-ориентированное программирование
методов базового класса. В частности, был создан список, содержащий объекты
базового класса и подкласса, для демонстрации возможностей полиморфного
программирования Python.
Перегрузка операторов определяет, как встроенные операторы Python должны
работать с объектами пользовательских классов. Вы узнали, что перегруженные методы операторов реализуются перегрузкой различных специальных
методов, наследуемых всеми классами от класса object. Мы обсудили иерар­
хию классов исключений Python и проблемы создания пользовательских
классов исключений.
Мы показали, как создать именованный кортеж, позволяющий обращаться
к элементам кортежа по именам атрибутов вместо индексов. Затем были
представлены новые классы данных Python 3.7, которые могут автоматически
генерировать различный шаблонный код, который обычно предоставляется
в определениях классов, в частности специальные методы __init__, __repr__
и __eq__.
Вы узнали, как писать модульные тесты для вашего кода в doc-строках, а затем
выполнять их при помощи функции testmod модуля doctest. Наконец, мы обсудили различные пространства имен, используемые Python для определения
области видимости идентификаторов.
В следующей части книги будут представлены практические примеры, использующие смесь искусственного интеллекта и технологий больших данных.
Мы исследуем обработку естественного языка, анализ данных Twitter, IBM
Watson и когнитивных вычислений, машинное обучение с учителем и без,
а также глубокое обучение с применением сверточных и рекуррентных нейронных сетей. Будет обсуждаться программное обеспечение и аппаратная
инфраструктура больших данных, включая базы данных NoSQL, Hadoop
и Spark, уделяющие первостепенное внимание производительности. Словом,
вы узнаете много интересного!

11
Обработка естественного
языка (NLP)
В этой главе…
•• Задачи обработки естественного языка (NLP), имеющие фундаментальное
значение для многих последующих глав с практическими применениями
data science.
•• Выполнение демонстрационных приложений NLP.
•• Использование NLP-библиотек TextBlob, NLTK, Textatistic и spaCy, а также их
предварительно обученных моделей для выполнения различных задач NLP.
•• Разбиение текста на слова и предложения.
•• Пометка частей речи.
•• Использование анализа эмоциональной окраски высказываний для определения положительной, отрицательной или нейтральной окраски текста.
•• Распознавание языка текста и перевод на другие языки с использованием
поддержки TextBlob Google Translate.
•• Определение корней слов посредством выделения основы и лемматизации.
•• Средства проверки орфографии и исправления ошибок в TextBlob.
•• Получение определений слов, синонимов и антонимов.
•• Удаление игнорируемых слов из текста.
•• Построение словарных облаков.
•• Определение удобочитаемости текста при помощи Textatistic.
•• Использование библиотеки spaCy для распознавания именованных сущностей и выявления сходства.

480   Глава 11. Обработка естественного языка (NLP)

11.1. Введение
Вы просыпаетесь от звонка будильника и нажимаете кнопку «Выкл.». Вы
берете в руки смартфон, читаете текстовые сообщения и просматриваете
свежие новости. Вы слушаете, как ведущие телепрограммы берут интервью
у знаменитостей. Вы общаетесь с друзьями, коллегами и членами семьи, слушаете их ответы. У вас есть друг с нарушением слуха, с которым вы общаетесь
на языке знаков и который смотрит видеопрограммы с субтитрами. У вас
есть слепой коллега, который читает текст, написанный алфавитом Брайля,
слушает книги, которые читает вслух специальная программа, и экранного
диктора, который описывает содержимое экрана компьютера. Вы читаете
сообщения электронной почты, отделяя «мусор» от важной информации,
и отправляете ответы. Вы ведете машину, обращая внимание на дорожные
знаки: «Стоп», «Ограничение скорости 60», «Дорожные работы» и т. д. Вы
отдаете своей машине голосовые команды: «Позвонить домой», «Включить
классическую музыку» или задаете вопросы типа «Где находится ближайшая
заправка?». Вы учите ребенка говорить и читать. Отправляете открытку другу.
Читаете художественные и научные тексты: книги, газеты и журналы. Вы
делаете заметки во время лекции или встречи. Вы учите иностранный язык,
готовясь к заграничной поездке. Вы получаете сообщение от клиента на испанском языке и обрабатываете его бесплатной программой-переводчиком.
Вы отвечаете на английском языке, зная, что клиент может легко перевести
его на испанский. Вы не уверены в том, на каком языке написано полученное
сообщение, но программа моментально определяет это за вас и переводит
текст на английский.
Все перечисленное — примеры общения на естественном языке в форме текста,
голоса, видео, знака жестов, алфавита Брайля и в других формах на разных
языках: английском, испанском, французском, русском, китайском, японском
и сотнях других. В этой главе вы освоите ряд средств обработки естественного
языка (NLP) на нескольких практических примерах и сеансах NLP. Многие из
этих средств NLP будут использоваться в практических примерах data science
в последующих главах.
Обработка естественного языка может применяться к текстовым коллекциям,
состоящим из твитов, сообщений в Facebook, диалогов, рецензий на фильмы,
пьес Шекспира, исторических документов, новостей, протоколов собраний
и т. д. Текстовая коллекция также называется корпусом.
Естественные языки не обладают математической точностью. Смысловые нюансы могут усложнить понимание естественных языков. Смысл текста может

11.2. TextBlob   481

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

11.2. TextBlob1
TextBlob — объектно-ориентированная библиотека NLP-обработки текста, построенная на базе NLP-библиотек NLTK и pattern и упрощающая некоторые
аспекты их функциональности. Вот примеры операций NLP, которые можно
выполнять при помощи TextBlob:
ØØразбиение на лексемы — разбиение текста на содержательные блоки (на-

пример, слова и числа);
ØØпометка частей речи — идентификация части речи каждого слова (суще-

ствительное, глагол, прилагательное и т. д.);
ØØизвлечение именных конструкций — обнаружение групп слов, представля-

ющих имена существительные;
ØØанализ эмоциональной окраски — определение положительной, отрица-

тельной или нейтральной окраски текста;
ØØперевод на другие языки и распознавание языка на базе Google Translate;
ØØформообразование — образование множественного и единственного чис-

ла. У формообразования существуют и другие аспекты, которые не поддерживаются TextBlob;
ØØпроверка орфографии и исправление ошибок;
ØØвыделение основы — исключение приставок, суффиксов и т. д.; напри-

мер, при выделении основы из слова «varieties» будет получен результат
«varieti»;
ØØлемматизация — аналог выделения основы, но с формированием реаль-

ных слов на основании контекста исходных слов; например, результатом
лемматизации «varieties» является слово «variety»;
ØØопределение частот слов — определение того, сколько раз каждое слово

встречается в корпусе;
1

https://textblob.readthedocs.io/en/latest/.

482   Глава 11. Обработка естественного языка (NLP)
ØØинтеграция с WordNet для поиска определений слов, синонимов и анто-

нимов;
ØØустранение игнорируемых слов — исключение таких слов, как a, an, the, I,

we, you и т. д., с целью анализа важных слов в корпусе;
ØØn-граммы — построение множеств последовательно идущих слов вкорпу-

се для выявления слов, часто располагающихся по соседству друг с другом.
Многие из этих возможностей используются как составная часть более сложных задач NLP. В этом разделе эти NLP-задачи будут выполняться при помощи
TextBlob и NLTK.

Установка модуля TextBlob
Чтобы установить TextBlob, откройте приглашение Anaconda (Windows),
терминал (macOS/Linux) или командную оболочку (Linux), после чего выполните команду:
conda install -c conda-forge textblob

Возможно, пользователям Windows придется запустить приглашение Anaconda
с правами администратора для получения необходимых привилегий для установки программного обеспечения. Щелкните правой кнопкой мыши на команде Anaconda Prompt в меню Пуск и выберите команду MoreRun as administrator.
После завершения установки выполните загрузку корпусов NLTK, используемых TextBlob:
ipython -m textblob.download_corpora

К их числу относятся:
ØØBrown Corpus (создан в Университете Брауна1) — для пометки частей

речи.
ØØPunkt — для разбиения английских предложений на лексемы.
ØØWordNet — для определений слов, синонимов и антонимов.
ØØAveraged Perceptron Tagger — для пометки частей речи.
1

https://en.wikipedia.org/wiki/Brown_Corpus.

11.2. TextBlob   483
ØØconll2000 — для разбиения текста на компоненты (существительные,

глаголы и т. д.). Имя conll2000 происходит от конференции, на которой
были созданы эти данные (Conference on Computational Natural Language
Learning).
ØØMovie Reviews — для анализа эмоциональной окраски.

Проект «Гутенберг»
Превосходным источником текстов для анализа станут бесплатные электронные книги на сайте проекта «Гутенберг»:
https://www.gutenberg.org

Сайт содержит свыше 57 000 электронных книг в различных форматах, включая простые текстовые файлы. В США на эти книги не распространяется
авторское право. За информацией об условиях использования сайта проекта
«Гутенберг» и авторских правах в других странах обращайтесь по адресу:
https://www.gutenberg.org/wiki/Gutenberg:Terms_of_Use

В некоторых примерах этой книги используется простой текстовый файл
с текстом пьесы Шекспира «Ромео и Джульетта», который можно загрузить
по адресу:
https://www.gutenberg.org/ebooks/1513

Проект «Гутенберг» не предоставляет программного доступа к своим электронным книгам; для этого необходимо скопировать книги1. Чтобы загрузить
«Ромео и Джульетту» в виде простого текстового файла, щелкните правой
кнопкой мыши на ссылке Plain Text UTF-8 на веб-странице книги, после чего выберите команду Save Link As… (Chrome/FireFox), Download Linked File As… (Safari)
или Save target as (Microsoft Edge), чтобы сохранить книгу в своей системе.
Сохраните файл с именем RomeoAndJuliet.txt в каталоге ch11, чтобы примеры
этой главы правильно работали. Для целей анализа мы удалили текст проекта «Гутенберг» перед строкой "THE TRAGEDY OF ROMEO AND JULIET", а также
информацию проекта «Гутенберг» в конце файла, начиная с текста:
End of the Project Gutenberg EBook of Romeo and Juliet,
by William Shakespeare

1

https://www.gutenberg.org/wiki/Gutenberg:Information_About_Robot_Access_to_our_Pages.

484   Глава 11. Обработка естественного языка (NLP)

11.2.1. Создание TextBlob
TextBlob1 — фундаментальный класс для NLP-операций в модуле textblob.
Создадим объект TextBlob с двумя предложениями:
In [1]: from textblob import TextBlob
In [2]: text = 'Today is a beautiful day. Tomorrow looks like bad weather.'
In [3]: blob = TextBlob(text)
In [4]: blob
Out[4]: TextBlob("Today is a beautiful day. Tomorrow looks like bad
weather.")

Объекты TextBlob (и, как вы вскоре убедитесь, объекты Sentence и Word) поддерживают строковые методы и могут сравниваться со строками. Они также
предоставляют методы для различных NLP-операций. Классы Sentence, Word
и TextBlob наследуют от BaseBlob и потому содержат много общих методов
и свойств.

11.2.2. Разбиение текста на предложения и слова
Обработка естественного языка часто требует разбиения текста на лексемы
перед выполнением других NLP-операций. TextBlob предоставляет удобные
свойства для обращения к предложениям и словам в TextBlob. Используем
свойство sentence для получения списка объектов Sentence:
In [5]: blob.sentences
Out[5]:
[Sentence("Today is a beautiful day."),
Sentence("Tomorrow looks like bad weather.")]

Свойство words возвращает объект WordList со списком объектов Word, представляющим каждое слово в TextBlob после удаления знаков препинания:
In [6]: blob.words
Out[6]: WordList(['Today', 'is', 'a', 'beautiful', 'day', 'Tomorrow',
'looks', 'like', 'bad', 'weather'])
1

http://textblob.readthedocs.io/en/latest/api_reference.html#textblob.blob.TextBlob.

11.2. TextBlob   485

11.2.3. Пометка частей речи
Пометка частей речи — процесс проверки слов с учетом контекста для определения части речи каждого слова. В английском языке существуют восемь
основных частей речи — существительные, местоимения, глаголы, прилагательные, наречия, предлоги, союзы и междометия (слова, выражающие эмоции,
за которыми обычно следует знак препинания, например «Yes!» или «Ha!»).
В каждой категории существует множество подкатегорий.
Некоторые слова имеют несколько значений — так, у слов «set» или «run»
возможные значения исчисляются сотнями! Взглянув на определения слова
«run» на сайте dictionary.com, вы увидите, что оно может быть глаголом, существительным, прилагательным или частью глагольной группы. Одно из
важных применений пометки частей речи — определение смысла слова из
множества возможных вариантов. Эта операция играет важную роль в «понимании» естественных языков компьютерами.
Свойство tags возвращает список кортежей, каждый из которых содержит
слово и строку, представляющую его пометку части речи:
In [7]: blob
Out[7]: TextBlob("Today is a beautiful day. Tomorrow looks like bad
weather.")
In [8]: blob.tags
Out[8]:
[('Today', 'NN'),
('is', 'VBZ'),
('a', 'DT'),
('beautiful', 'JJ'),
('day', 'NN'),
('Tomorrow', 'NNP'),
('looks', 'VBZ'),
('like', 'IN'),
('bad', 'JJ'),
('weather', 'NN')]

По умолчанию TextBlob использует PatternTagger для определения частей
речи, используя функциональность определения частей речи библиотеки
pattern:
https://www.clips.uantwerpen.be/pattern

486   Глава 11. Обработка естественного языка (NLP)
Шестьдесят три поддерживаемые пометки частей речи этой библиотеки можно
найти по адресу:
https://www.clips.uantwerpen.be/pages/MBSP-tags

В выводе приведенного фрагмента:
ØØToday, day и weather имеют пометку NN — признак существительного

в единственном или множественном числе.
ØØis и looks имеют пометку VBZ — признак глагола третьего лица единствен-

ного числа.
ØØa имеет пометку DT — признак определяющего слова1.
ØØbeautiful и bad имеют пометку JJ — признак прилагательного.
ØØTomorrow имеет пометку NNP — признак имени собственного единственно-

го числа.
ØØlike имеет пометку IN — признак подчинительного союза или предлога.

11.2.4. Извлечение именных конструкций
Допустим, вы собираетесь купить водные лыжи и ищете информацию о них
в интернете — скажем, по строке «best water ski». В данном случае группа
«water ski» является именной конструкцией. Если поисковая система не сможет правильно выделить именную конструкцию, то вероятно, что полученные
результаты не будут оптимальными. Попробуйте поискать информацию по
строкам «best water», «best ski» и «best water ski» и посмотрите, что вы найдете.
Свойство noun_phrases класса TextBlob возвращает объект WordList со списком объектов Word — по одному для каждой именной конструкции в тексте:
In [9]: blob
Out[9]: TextBlob("Today is a beautiful day. Tomorrow looks like bad
weather.")
In [10]: blob.noun_phrases
Out[10]: WordList(['beautiful day', 'tomorrow', 'bad weather'])

Обратите внимание: объект Word, представляющий именную конструкцию,
может содержать несколько слов. Класс WordList является расширением
1

https://en.wikipedia.org/wiki/Determiner.

11.2. TextBlob   487

встроенного типа списка Python. Объекты WordList предоставляют дополнительные методы для выделения основы, лемматизации, образования формы
единственного и множественного числа.

11.2.5. Анализ эмоциональной окраски
с использованием анализатора TextBlob по умолчанию
Одна из самых частых и полезных NLP-операций — анализ эмоциональной
окраски, определяющий положительную, отрицательную или нейтральную
тональность текста. Например, компании могут использовать их для определения того, как потребители отзываются в интернете об их продуктах — положительно или отрицательно. Будем считать слово «good» положительным,
а слово «bad» — отрицательным. Но сам факт присутствия слова «good» или
«bad» в предложении еще не означает, что предложение в целом эмоционально
окрашено положительно или отрицательно. Например, предложение
The food is not good.

безусловно, обладает отрицательной эмоциональной окраской. Аналогичным
образом предложение
The movie was not bad.

явно обладает положительной эмоциональной окраской, хотя, возможно, и не
настолько положительной, как предложение вида
The movie was excellent!

Анализ эмоциональной окраски — сложная задача из области машинного обу­
чения. Тем не менее такие библиотеки, как TextBlob, содержат предварительно
обученные модели для ее выполнения.

Оценка эмоциональной окраски средствами TextBlob
Свойство sentiment класса TextBlob возвращает объект Sentiment, который
сообщает, имеет текст положительную или отрицательную эмоциональную
окраску и является ли он объективным или субъективным:
In [11]: blob
Out[11]: TextBlob("Today is a beautiful day. Tomorrow looks like bad
weather.")

488   Глава 11. Обработка естественного языка (NLP)
In [12]: blob.sentiment
Out[12]: Sentiment(polarity=0.07500000000000007,
subjectivity=0.8333333333333333)

В показанном выводе полярность (показатель polarity) означает эмоциональную окраску со значениями от -1.0 (отрицательная) до 1.0 (положительная);
значение 0.0 соответствует нейтральной эмоциональной окраске. На основании данных TextBlob общая эмоциональная окраска близка к нейтральной,
а текст в целом субъективен.

Получение значений polarity и subjectivity из объекта Sentiment
Вероятно, показанные значения обладают большей точностью, чем необходимо в большинстве случаев. Излишняя точность может усложнить чтение
числового вывода. Магическая команда IPython %precision позволяет задать
точность по умолчанию для автономных объектов float и объектов float во
встроенных типах, таких как списки, словари и кортежи. Воспользуемся этой
магической командой для округления значений polarity и subjectivity до
трех цифр в дробной части:
In [13]: %precision 3
Out[13]: '%.3f'
In [14]: blob.sentiment.polarity
Out[14]: 0.075
In [15]: blob.sentiment.subjectivity
Out[15]: 0.833

Получение эмоциональной окраски предложения
Также можно получить данные эмоциональной окраски на уровне отдельных
предложений. Воспользуемся свойством sentence для получения списка объектов Sequence1, а затем переберем их и выведем свойство sentiment каждого
объекта Sentence:
In [16]: for sentence in blob.sentences:
...:
print(sentence.sentiment)
...:
Sentiment(polarity=0.85, subjectivity=1.0)
Sentiment(polarity=-0.6999999999999998, subjectivity=0.6666666666666666)

1

http://textblob.readthedocs.io/en/latest/api_reference.html#textblob.blob.Sentence.

11.2. TextBlob   489

Это может объяснить, почему эмоциональная окраска TextBlob близка к 0.0
(нейтральная) — одно предложение имеет положительную окраску (0.85),
а другое отрицательную (-0.6999999999999998).

11.2.6. Анализ эмоциональной окраски
с использованием NaiveBayesAnalyzer
По умолчанию объект TextBlob и объекты Sentence и Word, получаемые от него,
определяют эмоциональную окраску при помощи объекта PatternAnalyzer, который использует те же методы анализа эмоциональной окраски, что и библиотека Pattern. Библиотека TextBlob также содержит объект NaiveBayesAnalyzer1
(модуль text-blob.sentiments), прошедший обучение на базе данных рецензий о фильмах. Наивный байесовский классификатор2 — часто применяемый
алгоритм классификации текста с машинным обучением. Следующий пример
использует ключевой аргумент analyzer для назначения анализатора эмоциональной окраски TextBlob. Напомним, что в текущем сеансе IPython text
содержит 'Today is a beautiful day. Tomorrow looks like bad weather.':
In [17]: from textblob.sentiments import NaiveBayesAnalyzer
In [18]: blob = TextBlob(text, analyzer=NaiveBayesAnalyzer())
In [19]: blob
Out[19]: TextBlob("Today is a beautiful day. Tomorrow looks like bad
weather.")

Воспользуемся свойством sentiment объекта TextBlob, чтобы вывести данные
эмоциональной окраски текста с использованием NaiveBayesAnalyzer:
In [20]: blob.sentiment
Out[20]: Sentiment(classification='neg', p_pos=0.47662917962091056,
p_neg=0.5233708203790892)

В данном случае общая эмоциональная окраска классифицируется как отрицательная (classification='neg'.) Свойство p_pos объекта Sentiment показывает, что текст TextBlob положителен на 47,66%, а свойство p_neg показывает, что
текст TextBlob отрицателен на 52,34%. Так как общая эмоциональная окраска
всего лишь немногим более отрицательна, эмоциональная окраска TextBlob
может рассматриваться как в целом нейтральная.
1
2

https://textblob.readthedocs.io/en/latest/api_reference.html#module-textblob.en.sentiments.
https://ru.wikipedia.org/wiki/Наивный_байесовский_классификатор.

490   Глава 11. Обработка естественного языка (NLP)
А теперь получим данные об эмоциональной окраске каждого объекта Sentence:
In [21]: for sentence in blob.sentences:
...:
print(sentence.sentiment)
...:
Sentiment(classification='pos', p_pos=0.8117563121751951,
p_neg=0.18824368782480477)
Sentiment(classification='neg', p_pos=0.174363226578349,
p_neg=0.8256367734216521)

Вместо значений polarity и subjectivity объекты Sentiment, получаемые от
NaiveBayesAnalyzer, содержат классификацию ('pos' (положительная) или
'neg' (отрицательная)) и значения p_pos (положительный процент) и p_neg
(отрицательный процент) в диапазоне от 0.0 до 1.0. И снова мы видим, что
первое предложение имеет положительную, а второе — отрицательную эмоциональную окраску.

11.2.7. Распознавание языка и перевод
Перевод на другой язык — довольно сложная задача из области обработки
естественного языка и искусственного интеллекта. Благодаря новейшим
достижениям в области машинного обучения, искусственного интеллекта
и обработки естественных языков такие сервисы, как Google Translate (100+
языков) и Microsoft Bing Translator (60+ языков), могут мгновенно переводить
тексты на другие языки.
Автоматический перевод также очень удобен для людей, посещающих зарубежные страны. Они могут использовать приложение-переводчик для
перевода меню, дорожных знаков и т. д. Также ведутся исследования в области перевода речи, чтобы вы могли общаться в реальном времени с людьми,
которые не знают ваш основной язык1,2. Некоторые современные смартфоны
в сочетании с наушниками-вкладышами обеспечивают практически мгновенный перевод со многих языков3,4,5. В главе 13 будет разработан сценарий,
который практически в реальном времени осуществляет перевод на другие
языки из числа поддерживаемых Watson.
1
2
3

4
5

https://www.skype.com/en/features/skype-translator/.
https://www.microsoft.com/en-us/translator/business/live/.
https://www.telegraph.co.uk/technology/2017/10/04/googles-new-headphones-can-translate-foreignlanguages-real/.
https://store.google.com/us/product/google_pixel_buds?hl=en-US.
http://www.chicagotribune.com/bluesky/originals/ct-bsi-google-pixel-buds-review-20171115-story.
html.

11.2. TextBlob   491

Библиотека TextBlob использует сервис Google Translate для распознавания
языка текста и перевода объектов TextBlob, Sentence и Word на другие языки1.
Воспользуемся методом detect_language для распознавания языка текста
('en' — английский):
In [22]: blob
Out[22]: TextBlob("Today is a beautiful day. Tomorrow looks like bad
weather.")
In [23]: blob.detect_language()
Out[23]: 'en'

А теперь воспользуемся методом translate для перевода текста на испанский
язык ('es') с последующим распознаванием языка результата. Ключевой
аргумент to задает целевой язык.
In [24]: spanish = blob.translate(to='es')
In [25]: spanish
Out[25]: TextBlob("Hoy es un hermoso dia. Mañana parece mal tiempo.")
In [26]: spanish.detect_language()
Out[26]: 'es'

На следующем шаге переведем текст TextBlob на упрощенный китайский ('zh'
или 'zh-CN') с последующим распознаванием языка результата:
In [27]: chinese = blob.translate(to='zh')
In [28]: chinese
Out[28]: TextBlob("

")

In [29]: chinese.detect_language()
Out[29]: 'zh-CN'

В выводе detect_language упрощенный китайский всегда обозначается 'zhCN', несмотря на то что функция translate может получать упрощенный
китайский в виде 'zh' или 'zh-CN'.
В каждом из предыдущих случаев Google Translate автоматически распознает
исходный язык. Вы можете явно задать исходный язык, передав ключевой
аргумент from_lang методу translate:
chinese = blob.translate(from_lang='en', to='zh')
1

Для этого необходимо подключение к интернету.

492   Глава 11. Обработка естественного языка (NLP)
Google Translate использует коды языков ISO-639-11:
https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes

Для поддерживаемых языков эти коды можно использовать в значениях аргументов from_lang и to. Список поддерживаемых языков Google Translate
доступен по адресу:
https://cloud.google.com/translate/docs/languages

При вызове translate без аргументов осуществляется перевод с распознанного
исходного языка на английский:
In [30]: spanish.translate()
Out[30]: TextBlob("Today is a beautiful day. Tomorrow seems like bad
weather.")
In [31]: chinese.translate()
Out[31]: TextBlob("Today is a beautiful day. Tomorrow looks like bad
weather.")

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

11.2.8. Формообразование: образование
единственного и множественного числа
У одного слова может быть несколько разных форм — например, единственное
или множественное число («person» и «people») или разное время глагола
(«run» и «ran»). Возможно, при вычислении частот слов в тексте вы захотите
сначала преобразовать все формы слов к одной форме для получения более
точных данных частот. Word и WordList поддерживают преобразование слов
к форме единственного или множественного числа. Попробуем выполнить
это преобразование с парой объектов Word:
In [1]: from textblob import Word
In [2]: index = Word('index')
In [3]: index.pluralize()
Out[3]: 'indices'
In [4]: cacti = Word('cacti')
In [5]: cacti.singularize()
Out[5]: 'cactus'
1

ISO — Международная организация по стандартизации (https://www.iso.org/).

11.2. TextBlob   493

Образование формы множественного и единственного числа — сложные задачи, которые, как было показано ранее, не сводятся к простому добавлению
или удалению «s» или «es» в конце слова.
То же самое можно сделать с WordList:
In [6]: from textblob import TextBlob
In [7]: animals = TextBlob('dog cat fish bird').words
In [8]: animals.pluralize()
Out[8]: WordList(['dogs', 'cats', 'fish', 'birds'])

11.2.9. Проверка орфографии и исправление ошибок
Для задач обработки естественного языка очень важно, чтобы текст был
свободен от орфографических ошибок. Программные пакеты для ввода и редактирования текста, такие как Microsoft Word, Google Docs и им подобные,
автоматически проверяют орфографию во время ввода текста и обычно подчеркивают неправильные слова красной волнистой линией. Другие инструменты позволяют выполнить проверку орфографии вручную.
Вы можете проверить орфографию текста в Word методом spellcheck этого
объекта. Этот метод возвращает список кортежей, содержащих возможные
варианты написания слова и уровень достоверности. Предположим, вы хотели написать слово «they», но случайно ввели его в виде «theyr». Результаты
проверки предлагают два возможных исправления, при этом вариант 'they'
имеет наивысший уровень достоверности:
In [1]: from textblob import Word
In [2]: word = Word('theyr')
In [3]: %precision 2
Out[3]: '%.2f'
In [4]: word.spellcheck()
Out[4]: [('they', 0.57), ('their', 0.43)]

Следует учитывать, что слово с наивысшим уровнем достоверности может
и не быть правильным словом для заданного контекста.
Объекты TextBlob, Sentence и Word содержат метод correct, который можно
вызвать для исправления ошибки. Вызов correct для Word возвращает пра-

494   Глава 11. Обработка естественного языка (NLP)
вильно написанное слово с наибольшим уровнем достоверности (по данным
проверки орфографии):
In [5]: word.correct()
Out[5]: 'they'

# Выбирает слово с наибольшим уровнем достоверности

Вызов correct для объекта TextBlob или Sentence проверяет орфографию каждого слова. Для каждого неправильного слова correct заменяет его правильно
написанным вариантом с наибольшим уровнем достоверности:
In [6]: from textblob import Word
In [7]: sentence = TextBlob('Ths sentense has missplled wrds.')
In [8]: sentence.correct()
Out[8]: TextBlob("The sentence has misspelled words.")

11.2.10. Нормализация: выделение основы
и лемматизация
В результате выделения основы из слова удаляется префикс или суффикс
и остается только основа, которая может быть реальным словом (но может и не
быть). Лемматизация выполняется аналогичным образом, но ее результатом
является осмысленная часть речи, то есть реальное слово.
Выделение основы и лемматизация относятся к операциям нормализации, готовящим слова для анализа. Например, перед вычислением статистики вхождения слов в корпусе текста все слова могут быть преобразованы к нижнему
регистру, чтобы слова, начинающиеся с букв нижнего и верхнего регистра,
обрабатывались одинаково. Иногда для представления разных форм слова
приходится использовать только корень. Например, в некотором приложении
все следующие слова могут рассматриваться как слово «program»: program,
programs, programmer, programming и programmed (и, возможно, английские
варианты написания — скажем, programmes).
У объектов Word и WordList для выделения основы и лемматизации используются методы stem и lemmatize. Попробуем использовать их с Word:
In [1]: from textblob import Word
In [2]: word = Word('varieties')
In [3]: word.stem()
Out[3]: 'varieti'

11.2. TextBlob   495
In [4]: word.lemmatize()
Out[4]: 'variety'

11.2.11. Частоты слов
Различные методы выявления сходства между документами основаны на
частотах вхождения слов. Как вы вскоре узнаете, TextBlob подсчитывает частоты автоматически. Начнем с загрузки электронного текста пьесы Шекспира
«Ромео и Джульетта» в TextBlob. Для этого будет использоваться класс Path
из модуля pathlib стандартной библиотеки Python:
In [1]: from pathlib import Path
In [2]: from textblob import TextBlob
In [3]: blob = TextBlob(Path('RomeoAndJuliet.txt').read_text())

Используйте загруженный ранее файл RomeoAndJuliet.txt1. Предполагается, что
вы запустили сеанс IPython из этой папки. Когда вы читаете файл методом
read_text объекта Path, файл будет немедленно закрыт после завершения
чтения файла.
Частоты вхождения слов в тексте TextBlob хранятся в словаре word_counts.
Подсчитаем вхождения некоторых слов в пьесе:
In [4]: blob.word_counts['juliet']
Out[4]: 190
In [5]: blob.word_counts['romeo']
Out[5]: 315
In [6]: blob.word_counts['thou']
Out[6]: 278

Если вы уже разобрали TextBlob в список WordList, то для подсчета вхождений
конкретных слов в список можно воспользоваться методом count:
In [7]: blob.words.count('joy')
Out[7]: 14
In [8]: blob.noun_phrases.count('lady capulet')
Out[8]: 46
1

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

496   Глава 11. Обработка естественного языка (NLP)

11.2.12. Получение определений, синонимов
и антонимов из WordNet
WordNet1– база данных слов, созданная в Принстонском университете. Библиотека TextBlob использует интерфейс WordNet библиотеки NLTK, позволяющий
искать определения слов, синонимы и антонимы. За дополнительной информацией обращайтесь к документации интерфейса NLTK WordNet по адресу:
https://www.nltk.org/api/nltk.corpus.reader.html#module-nltk.corpus.reader.wordnet

Получение определений
Начнем с создания объекта Word:
In [1]: from textblob import Word
In [2]: happy = Word('happy')

Свойство definitions класса Word возвращает список всех определений слова
в базе данных WordNet:
In [3]: happy.definitions
Out[3]:
['enjoying or showing or marked by joy or pleasure',
'marked by good fortune',
'eagerly disposed to act or to be of service',
'well expressed and to the point']

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

Получение синонимов
Наборы синонимов Word доступны в свойстве synsets. Результат представляет
собой список объектов Synset:
In [4]: happy.synsets
Out[4]:
[Synset('happy.a.01'),
1

https://wordnet.princeton.edu/.

11.2. TextBlob   497
Synset('felicitous.s.02'),
Synset('glad.s.02'),
Synset('happy.s.04')]

Каждый объект Synset представляет группу синонимов. В записи happy.a.01:
ØØhappy — лемматизированная форма исходного объекта Word (в данном

случае они совпадают).
ØØa — часть речи: a — прилагательное, n — существительное, v — глагол, r —

наречие, s — прилагательное-сателлит. Многие наборы синонимов прилагательных в WordNet имеют сателлитные наборы синонимов, представляющие похожие прилагательные.
ØØ01 — индекс, начинающийся с 0. Многие слова обладают несколькими

смыслами; значение является индексом соответствующего смысла в базе
данных WordNet.
Также имеется метод get_synsets, который позволяет передать часть речи
в аргументе, чтобы получить набор Synset только для указанной части речи.
Вы можете перебрать список synsets, чтобы найти синонимы исходного слова.
Каждый объект Synset содержит метод lemmas, который возвращает список
объектов Lemma, представляющих синонимы. Метод name объекта Lemma возвращает слово-синоним в виде строки. В следующем коде для каждого объекта
Synset в списке synsets вложенный цикл for перебирает объекты Lemma из
этого объекта Synset (при наличии). Затем синоним добавляется в множество
с именем synonyms. Мы использовали множество, потому что оно автоматически устраняет все добавленные в него дубликаты:
In [5]: synonyms = set()
In [6]: for synset in happy.synsets:
...:
for lemma in synset.lemmas():
...:
synonyms.add(lemma.name())
...:
In [7]: synonyms
Out[7]: {'felicitous', 'glad', 'happy', 'well-chosen'}

Получение антонимов
Если слово, представленное объектом Lemma, имеет антонимы в базе данных
WordNet, то вызов метода antonyms объекта Lemma возвращает список объектов
Lemma, представляющих антонимы (или пустой список, если в базе данных

498   Глава 11. Обработка естественного языка (NLP)
нет ни одного антонима). Во фрагменте [4] показано, что для 'happy' было
найдено четыре объекта Synset. Сначала найдем объекты Lemma для Synset
с индексом 0 в списке synsets:
In [8]: lemmas = happy.synsets[0].lemmas()
In [9]: lemmas
Out[9]: [Lemma('happy.a.01.happy')]

В данном случае lemmas возвращает список из одного элемента Lemma. Мы
можем проверить, содержит ли база данных какие-либо антонимы для этого
объекта Lemma:
In [10]: lemmas[0].antonyms()
Out[10]: [Lemma('unhappy.a.01.unhappy')]

Результат является списком объектов Lemma, представляющих антоним (-ы).
В данном случае мы видим, что база данных содержит для слова 'happy' один
антоним 'unhappy'.

11.2.13. Удаление игнорируемых слов
Игнорируемые слова (стоп-слова) — часто встречающиеся в тексте слова, которые часто удаляются из текста перед анализом, поскольку обычно не несут полезной информации. Ниже приведен список игнорируемых слов английского
языка из NLTK, возвращаемый функцией words1 модуля stopwords (которой
мы вскоре воспользуемся на практике):
Список игнорируемых слов английского языка из NLTK
['a', 'about', 'above', 'after', 'again', 'against', 'ain', 'all', 'am', 'an',
'and', 'any', 'are', 'aren', "aren't", 'as', 'at', 'be', 'because', 'been',
'before', 'being', 'below', 'between', 'both', 'but', 'by', 'can', 'couldn',
"couldn't", 'd', 'did', 'didn', "didn't", 'do', 'does', 'doesn', "doesn't",
'doing', 'don', "don't", 'down', 'during', 'each', 'few', 'for', 'from',
'further', 'had', 'hadn', "hadn't", 'has', 'hasn', "hasn't", 'have', 'haven',
"haven't", 'having', 'he', 'her', 'here', 'hers', 'herself', 'him', 'himself',
'his', 'how', 'i', 'if', 'in', 'into', 'is', 'isn', "isn't", 'it', "it's", 'its',
'itself', 'just', 'll', 'm', 'ma', 'me', 'mightn', "mightn't", 'more', 'most',
'mustn', "mustn't", 'my', 'myself', 'needn', "needn't", 'no', 'nor', 'not',
1

https://www.nltk.org/book/ch02.html.

11.2. TextBlob   499
'now', 'o', 'of', 'off', 'on', 'once', 'only', 'or', 'other', 'our', 'ours',
'ourselves', 'out', 'over', 'own', 're', 's', 'same', 'shan', "shan't", 'she',
"she's", 'should', "should've", 'shouldn', "shouldn't", 'so', 'some', 'such',
't', 'than', 'that', "that'll", 'the', 'their', 'theirs', 'them', 'themselves',
'then', 'there', 'these', 'they', 'this', 'those', 'through', 'to', 'too',
'under', 'until', 'up', 've', 'very', 'was', 'wasn', "wasn't", 'we', 'were',
'weren', "weren't", 'what', 'when', 'where', 'which', 'while', 'who', 'whom',
'why', 'will', 'with', 'won', "won't", 'wouldn', "wouldn't", 'y', 'you', "you'd",
"you'll", "you're", "you've", 'your', 'yours', 'yourself', 'yourselves']

Библиотека NLTK также содержит списки игнорируемых слов для ряда других
естественных языков. Прежде чем использовать списки игнорируемых слов
NLTK, их необходимо загрузить при помощи функции download модуля nltk:
In [1]: import nltk
In [2]: nltk.download('stopwords')
[nltk_data] Downloading package stopwords to
[nltk_data]
C:\Users\PaulDeitel\AppData\Roaming\nltk_data...
[nltk_data]
Unzipping corpora\stopwords.zip.
Out[2]: True

В этом примере будет загружен список игнорируемых слов английского языка
'english'. Импортируйте stopwords из модуля nltk.corpus, а затем используйте метод words класса stopwords для загрузки списка игнорируемых слов
'english':
In [3]: from nltk.corpus import stopwords
In [4]: stops = stopwords.words('english')

Затем создадим объект TextBlob, из которого будут удаляться игнорируемые
слова:
In [5]: from textblob import TextBlob
In [6]: blob = TextBlob('Today is a beautiful day.')

Наконец, чтобы удалить игнорируемые слова, используйте слова TextBlob
в трансформации списка, которая добавляет каждое слово в полученный
список только в том случае, если слово не входит в stops:
In [7]: [word for word in blob.words if word not in stops]
Out[7]: ['Today', 'beautiful', 'day']

500   Глава 11. Обработка естественного языка (NLP)

11.2.14. n-граммы
N-грамма1 представляет собой последовательность из n текстовых элементов,
например букв в словах или слов в предложении. В обработке естественных
языков n-граммы могут использоваться для выявления букв или слов, часто
располагающихся рядом друг с другом. Для текстового ввода это поможет
предсказать следующую букву или слово, вводимое пользователем, например
при завершении элементов в IPython по нажатии клавиши Tab или при вводе
текстового сообщения в вашем любимом месседжере для смартфона. При
преобразовании речи в текст n-граммы могут использоваться для повышения
качества текста. N-граммы представляют собой форму лексической солидарности, то есть расположения букв или слов рядом друг с другом.
Метод ngrams объекта TextBlob выдает список WordList n-грамм, которые
по умолчанию имеют длину 3 (они называются триграммами). Передавая
ключевой аргумент n, вы сможете получать n-граммы любой длины по своему
усмотрению. Вывод показывает, что первая триграмма содержит первые три
слова предложения ('Today', 'is' и 'a'.) Затем ngrams создает триграмму,
начинающуюся со второго слова ('is', 'a' и 'beautiful'), и т. д. — до тех
пор, пока не будет создана триграмма с тремя последними словами TextBlob:
In [1]: from textblob import TextBlob
In [2]: text = 'Today is a beautiful day. Tomorrow looks like bad weather.'
In [3]: blob = TextBlob(text)
In [4]: blob.ngrams()
Out[4]:
[WordList(['Today', 'is', 'a']),
WordList(['is', 'a', 'beautiful']),
WordList(['a', 'beautiful', 'day']),
WordList(['beautiful', 'day', 'Tomorrow']),
WordList(['day', 'Tomorrow', 'looks']),
WordList(['Tomorrow', 'looks', 'like']),
WordList(['looks', 'like', 'bad']),
WordList(['like', 'bad', 'weather'])]

Следующий пример строит n-граммы из пяти слов:
In [5]: blob.ngrams(n=5)
Out[5]:
[WordList(['Today', 'is', 'a', 'beautiful', 'day']),
1

https://en.wikipedia.org/wiki/N-gram.

11.3. Визуализация частот вхождения слов с использованием гистограмм   501
WordList(['is', 'a', 'beautiful', 'day', 'Tomorrow']),
WordList(['a', 'beautiful', 'day', 'Tomorrow', 'looks']),
WordList(['beautiful', 'day', 'Tomorrow', 'looks', 'like']),
WordList(['day', 'Tomorrow', 'looks', 'like', 'bad']),
WordList(['Tomorrow', 'looks', 'like', 'bad', 'weather'])]

11.3. Визуализация частот вхождения слов
с использованием гистограмм и словарных
облаков
Ранее мы получили частоты вхождения некоторых слов в пьесе «Ромео
и Джульетта». В некоторых случаях визуализации частот слов повышают
качество анализа текстового корпуса. Часто существует несколько возможных
вариантов визуализации данных, и один из них оказывается лучше других.
Например, вас могут интересовать частоты вхождения слов друг относительно
друга или же данные относительного использования слов в корпусе. В этом
разделе будут рассмотрены два способа наглядного представления частот слов:
ØØГистограмма для наглядного количественного представления частот самых

частых слов в пьесе «Ромео и Джульетта» в виде столбцов разной длины.
ØØСловарное облако для наглядного качественного представления частот:

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

11.3.1. Визуализация частот вхождения слов
средствами Pandas
Построим наглядное представление частот в тексте «Ромео и Джульетты» для
20 самых частых слов, которые не являются игнорируемыми словами. Для
этого воспользуемся функциональностью TextBlob, NLTK и Pandas. Средства
визуализации Pandas базируются на Matplotlib, поэтому в этом сеансе IPython
следует запустить следующей командой:
ipython --matplotlib

Загрузка данных
Начнем с загрузки текста «Ромео и Джульетты». Прежде чем выполнять следующий код, запустите IPython из каталога ch11, чтобы вы могли обратиться
к файлу электронной книги RomeoAndJuliet.txt, загруженному ранее в этой главе:

502   Глава 11. Обработка естественного языка (NLP)
In [1]: from pathlib import Path
In [2]: from textblob import TextBlob
In [3]: blob = TextBlob(Path('RomeoAndJuliet.txt').read_text())

Затем загрузите список игнорируемых слов NLTK:
In [4]: from nltk.corpus import stopwords
In [5]: stop_words = stopwords.words('english')

Получение частот слов
Чтобы построить визуализацию частот 20 слов, необходимо знать все слова
и их частоты. Вызовем метод items словаря blob.word_counts, чтобы получить
список кортежей «слово-частота»:
In [6]: items = blob.word_counts.items()

Исключение игнорируемых слов
Воспользуемся трансформацией списка для удаления кортежей, содержащих
стоп-слова:
In [7]: items = [item for item in items if item[0] not in stop_words]

Выражение item[0] получает слово из каждого кортежа, чтобы проверить,
входит ли оно в список stop_words.

Сортировка слов по частотам
Чтобы определить 20 наиболее частых слов, отсортируем кортежи в items по
убыванию частоты. Мы можем воспользоваться встроенной функцией sorted
с аргументом key для сортировки кортежей по элементу частоты в каждом
кортеже. Чтобы задать элемент кортежа для выполнения сортировки, используйте функцию itemgetter из модуля operator стандартной библиотеки Python:
In [8]: from operator import itemgetter
In [9]: sorted_items = sorted(items, key=itemgetter(1), reverse=True)

В процессе упорядочения элементов items функция sorted обращается
к элементу с индексом 1 в каждом кортеже с использованием выражения
itemgetter(1). Ключевой аргумент reverse=True означает, что кортежи должны сортироваться по убыванию.

11.3. Визуализация частот вхождения слов с использованием гистограмм   503

Получение 20 самых частых слов
Затем сегмент используется для получения 20 самых частых слов из sorted_
items. Когда объект TextBlob проводит разбиение корпуса на лексемы, он
разбивает все сокращенные формы по апострофам и подсчитывает общее количество апострофов как одно из «слов». Текст «Ромео и Джульетты» содержит
множество сокращенных форм. Если вывести sorted_items[0], вы увидите,
что это самое часто встречающееся «слово» с 867 вхождениями1. Выводиться
должны только слова, поэтому мы игнорируем элемент 0 и получаем сегмент
с элементами с 1 по 20 списка sorted_items:
In [10]: top20 = sorted_items[1:21]

Преобразование top20 в DataFrame
Затем преобразуем список кортежей top20 в коллекцию DataFrame библиотеки
Pandas для удобства его представления в визуальном виде:
In [11]: import pandas as pd
In [12]: df = pd.DataFrame(top20, columns=['word', 'count'])
In [13]: df
Out[13]:
word
0
romeo
1
thou
2
juliet
3
thy
4
capulet
5
nurse
6
love
7
thee
8
lady
9
shall
10
friar
11
come
12 mercutio
13 lawrence
14
good
15 benvolio
16
tybalt
17
enter
18
go
19
night
1

count
315
278
190
170
163
149
148
138
117
110
105
94
88
82
80
79
79
75
75
73

В некоторых локальных контекстах этого не происходит, и элементом 0 становится слово
'romeo'.

504   Глава 11. Обработка естественного языка (NLP)

Визуализация DataFrame
Чтобы построить визуализацию данных, используем метод bar свойства plot
коллекции DataFrame. Аргументы указывают, какие данные столбца должны
выводиться по осям x и y, и что на диаграмме не должны выводиться условные
обозначения:
In [14]: axes = df.plot.bar(x='word', y='count', legend=False)

Метод bar создает и выводит гистограмму Matplotlib.
Взглянув на исходную гистограмму, которая появляется на экране, можно
заметить, что некоторые из ее столбцов усечены. Чтобы решить эту проблему, используйте функцию gcf (Get Current Figure) для получения рисунка
Matlpotlib, выведенного pandas, а затем вызовите метод tight_layout. Вызов
«сжимает» гистограмму, чтобы все ее компоненты поместились в окне:
In [15]: import matplotlib.pyplot as plt
In [16]: plt.gcf().tight_layout()

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

11.3. Визуализация частот вхождения слов с использованием гистограмм   505

11.3.2. Визуализация частот слов
в словарных облаках
Теперь построим словарное облако для визуализации наиболее частых
200 слов в тексте «Ромео и Джульетты». Мы можем воспользоваться для
этого классом WordCloud модуля wordcloud 1 с открытым кодом для генерирования словарных облаков всего в нескольких строках кода. По умолчанию
wordcloud создает прямоугольные словарные облака, но, как вы вскоре увидите, библиотека также может создавать словарные облака произвольной
формы.

Установка модуля wordcloud
Чтобы установить wordcloud, откройте приглашение Anaconda (Windows),
терминал (macOS/Linux) или командную оболочку (Linux), после чего выполните следующую команду:
conda install -c conda-forge wordcloud

Возможно, пользователям Windows придется запустить приглашение Anaconda
с правами администратора для получения необходимых привилегий при установке программного обеспечения. Щелкните правой кнопкой мыши на команде Anaconda Prompt в меню Пуск и выберите команду MoreRun as administrator.

Загрузка текста
Сначала нужно загрузить текст пьесы «Ромео и Джульетты». Прежде чем выполнять следующий код, запустите IPython из каталога ch11, чтобы вы могли
обратиться к файлу электронной книги RomeoAndJuliet.txt, загруженному ранее
в этой главе:
In [1]: from pathlib import Path
In [2]: text = Path('RomeoAndJuliet.txt').read_text()

Загрузка маски, определяющей форму словарного облака
Чтобы создать словарное облако нужной формы, нужно инициализировать
объект WordCloud изображением, которое называется маской. WordCloud за1

https://github.com/amueller/word_cloud.

506   Глава 11. Обработка естественного языка (NLP)
полняет текстом области изображения маски, цвет которых отличен от белого.
В нашем примере будет использована форма сердца, определяемая файлом
mask_heart.png из каталога примеров ch11. Более сложным маскам для создания
словарного облака требуется больше времени.
Загрузим изображение маски функцией imread из модуля imageio, включенного в поставку Anaconda:
In [3]: import imageio
In [4]: mask_image = imageio.imread('mask_heart.png')

Функция возвращает изображение в виде коллекции array библиотеки NumPy,
как того требует WordCloud.

Настройка объекта WordCloud
Затем создадим и настроим объект WordCloud:
In [5]: from wordcloud import WordCloud
In [6]: wordcloud = WordCloud(colormap='prism', mask=mask_image,
...:
background_color='white')
...:

По умолчанию ширина и высота объекта WordCloud составляет 400 × 200 пикселов, если только другие значения не заданы ключевыми аргументами width
и height или маской. При использовании маски размер WordCloud определяется размером изображения. WordCloud использует Matplotlib во внутренней
реализации. WordCloud назначает словам случайные цвета с цветовой карты.
Вы можете передать ключевой аргумент colormap и воспользоваться одной
из именованных цветовых карт Matplotlib. Список имен цветовых карт и их
цветов доступен по адресу:
https://matplotlib.org/examples/color/colormaps_reference.html

Ключевой аргумент mask задает загруженное ранее изображение mask_image.
По умолчанию слова выводятся на черном фоне, но мы изменили цвет фона
при помощи ключевого аргумента background_color и выбрали белый цвет
('white'). За полным списком ключевых аргументов WordCloud обращайтесь
по адресу:
http://amueller.github.io/word_cloud/generated/wordcloud.WordCloud.html

11.3. Визуализация частот вхождения слов с использованием гистограмм   507

Генерирование словарного облака
Метод generate объекта WordCloud получает используемый текст в аргументе
и строит словарное облако, которое возвращается в виде объекта WordCloud:
In [7]: wordcloud = wordcloud.generate(text)

Прежде чем строить словарное облако, generate сначала удаляет игнорируемые слова из аргумента text по встроенному списку игнорируемых слов
модуля wordcloud. Затем generate вычисляет частоты для оставшихся слов.
По умолчанию метод использует не более 200 слов для словарного облака, но
это количество можно изменить при помощи ключевого аргумента max_words.

Сохранение словарного облака в файле
Наконец, воспользуемся методом to_file объекта WordCloud для сохранения
изображения словарного облака в заданном файле:
In [8]: wordcloud = wordcloud.to_file('RomeoAndJulietHeart.png')

Откройте каталог примеров ch11 в своей системе и сделайте двойной щелчок
на файле RomeoAndJuliet.png, чтобы просмотреть изображение, — вероятно,
в вашей версии слова будут располагаться в других позициях и выводиться
другим цветом:

508   Глава 11. Обработка естественного языка (NLP)

Генерирование словарного облака по словарю
Если у вас уже имеется словарь с парами «ключ-значение», представляющими
счетчики слов, то их можно передать методу fit_words объекта WordCloud.
Этот метод предполагает, что игнорируемые слова уже были удалены ранее.

Вывод изображения средствами Matplotlib
Для вывода изображения на экран используйте магическую команду IPython
%matplotlib

чтобы включить интерактивную поддержку Matplotlib в IPython, после чего
выполните следующие команды:
import matplotlib.pyplot as plt
plt.imshow(wordcloud)

11.4. Оценка удобочитаемости
с использованием Textatistic
Интересным применением обработки естественного языка является оценка
удобочитаемости текста, которая зависит от используемого словаря, структуры предложений, длины предложений, темы и многих других факторов. При
написании этой книги мы воспользовались платным инструментом Grammarly,
чтобы улучшить текст и добиться того, чтобы он нормально воспринимался
широкой аудиторией.
В этом разделе для оценки удобочитаемости будет использоваться библиотека
Textatistic1,2. Существует много разных формул, используемых при обработке
естественного языка для вычисления индекса удобочитаемости.

Установка Textatistic
Чтобы установить Textatistic, откройте приглашение Anaconda (Windows),
терминал (macOS/Linux) или командную оболочку (Linux), после чего выполните команду:
pip install textatistic
1

https://github.com/erinhengel/Textatistic.

2

Другие библиотеки оценки удобочитаемости для Python — readability-score, textstat,
readability и pylinguistics.

11.4. Оценка удобочитаемости с использованием Textatistic   509

Возможно, пользователям Windows придется запустить приглашение
Anaconda с правами администратора для получения необходимых привилегий при установке программного обеспечения. Щелкните правой кнопкой
мыши на команде Anaconda Prompt в меню Пуск и выберите команду MoreRun
as administrator.

Вычисление статистики и индексов удобочитаемости
Загрузим текст «Ромео и Джульетты» в переменную text:
In [1]: from pathlib import Path
In [2]: text = Path('RomeoAndJuliet.txt').read_text()

Для вычисления статистики и индексов удобочитаемости потребуется объект
Textatistic, инициализированный текстом, который вы хотите обработать:
In [3]: from textatistic import Textatistic
In [4]: readability = Textatistic(text)

Метод dict объекта Textatistic возвращает словарь с различными статистическими характеристиками и индексами удобочитаемости1:
In [5]: %precision 3
Out[5]: '%.3f'
In [6]: readability.dict()
Out[6]:
{'char_count': 115141,
'word_count': 26120,
'sent_count': 3218,
'sybl_count': 30166,
'notdalechall_count': 5823,
'polysyblword_count': 549,
'flesch_score': 100.892,
'fleschkincaid_score': 1.203,
'gunningfog_score': 4.087,
'smog_score': 5.489,
'dalechall_score': 7.559}
1

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

510   Глава 11. Обработка естественного языка (NLP)
Каждое значение в словаре также доступно через свойство Textatistic с именем, совпадающим с ключом в показанном выводе. Вот некоторые статистические показатели:
ØØchar_count — количество символов в тексте.
ØØword_count — количество слов в тексте.
ØØsent_count — количество предложений в тексте.
ØØsybl_count — количество слогов в тексте.
ØØnotdalechall_count — количество слов, не входящих в список Дейла —

Челла (список слов, понятных 80 % пятиклассников1). Чем выше это число по сравнению с общим количеством слов, тем менее понятным считается текст.
ØØpolysyblword_count — количество слов из трех и более слогов.
ØØflesch_score — индекс удобочитаемости Флеша, который может быть

связан с уровнем образования. Считается, что тексты со значением индекса более 90 понятны пятиклассникам. При значениях ниже 30 для
усвоения текста обычно требуется образование на уровне колледжа.
Промежуточные диапазоны соответствуют другимобразовательным
уровням.
ØØfleschkincaid_score — индекс Флеша  —  Кинкейда, соответствующий

уровню образования.
ØØgunningfog_score — индекс туманности Ганнинга, соответствующий уров-

ню образования.
ØØsmog_score — индекс SMOG (Simple Measure of Gobbledygook), соответ-

ствующий годам образования, необходимым для понимания текста. Эта
метрика считается особенно эффективной для материалов по здравоохранению2.
ØØdalechall_score — индекс Дейла  —  Челла, который может быть свя-

зан с уровнями образования от 4 и ниже до выпускника колледжа (уровень 16) и выше. Этот индекс считается наиболее надежным для широкого диапазона типов текста3,4.
1
2
3
4

http://www.readabilityformulas.com/articles/dale-chall-readability-word-list.php.
https://en.wikipedia.org/wiki/SMOG.
https://en.wikipedia.org/wiki/Readability#The_Dale%E2%80%93Chall_formula.
http://www.readabilityformulas.com/articles/how-do-i-decide-which-readability-formula-to-use.php.

11.5. Распознавание именованных сущностей с использованием spaCy   511

За дополнительной информацией о каждом индексе удобочитаемости, представленном здесь, а также нескольких других обращайтесь по адресу:
https://en.wikipedia.org/wiki/Readability

В документации Textatistic приведены и использованные формулы индексов
удобочитаемости:
http://www.erinhengel.com/software/textatistic/

11.5. Распознавание именованных сущностей
с использованием spaCy
NLP может определить, о чем говорится в тексте. Ключевой аспект этого
процесса — распознавание именованных сущностей в целях поиска и классификации таких элементов, как даты, время, количества, места, имена людей,
названия предметов, организаций и т. д. В этом разделе для анализа текста
используются средства распознавания именованных сущностей из NLPбиблиотеки spaCy1,2.

Установка spaCy
Для установки spaCy откройте приглашение Anaconda (Windows), терминал
(macOS/Linux) или командную оболочку (Linux), после чего выполните
­команду:
conda install -c conda-forge spacy

Возможно, пользователям Windows придется запустить приглашение Anaconda
с правами администратора для получения необходимых привилегий для установки программного обеспечения. Щелкните правой кнопкой мыши на команде Anaconda Prompt в меню Пуск и выберите команду MoreRun as administrator.
Чтобы библиотека spaCy могла загрузить дополнительные необходимые
компоненты для обработки английского (en) текста, по завершении установки
необходимо выполнить команду,:
python -m spacy download en
1

https://spacy.io/.

2

Также стоит обратить внимание на Textacy (https://github.com/chartbeat-labs/textacy) — биб­
лиотеку NLP, построенную на базе spaCy и поддерживающую другие операции NLP.

512   Глава 11. Обработка естественного языка (NLP)

Загрузка языковой модели
Первый шаг использования spaCy — загрузка языковой модели, представляющей естественный язык анализируемого текста. Для этого следует вызвать
функцию load модуля spacy. Загрузим ранее примененную модель английского
языка:
In [1]: import spacy
In [2]: nlp = spacy.load('en')

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

Создание объекта spaCy Doc
Затем объект nlp используется для создания объекта spaCy Doc 1, представляющего обрабатываемый документ. В данном случае используется предложение
из вводного описания Всемирной паутины, встречающегося во многих наших
книгах:
In [3]: document = nlp('In 1994, Tim Berners-Lee founded the ' +
...:
'World Wide Web Consortium (W3C), devoted to ' +
...:
'developing web technologies')
...:

Получение именованных сущностей
Свойство ents объекта Doc возвращает кортеж объектов Span, представляющих
именованные сущности, встречающиеся в Doc. Каждый объект Span содержит
многочисленные свойства2. Переберем объекты Span и выведем свойства text
и label_:
In [4]: for entity in document.ents:
...:
print(f'{entity.text}: {entity.label_}')
...:
1994: DATE
Tim Berners-Lee: PERSON
the World Wide Web Consortium: ORG

Свойство text каждого объекта Span возвращает сущность в виде строки,
а свойство label_ возвращает строку, обозначающую классификацию сущ1
2

https://spacy.io/api/doc.
https://spacy.io/api/span.

11.6. Выявление сходства средствами spaCy   513

ности. В данном случае spaCy обнаруживает три сущности, представляющие
дату (DATE — 1994), человека (PERSON — Tim Berners-Lee) и организацию
(ORG — World Wide Web Consortium). За дополнительной информацией о spaCy
и кратким руководством по Quickstart обращайтесь по адресу:
https://spacy.io/usage/models#section-quickstart

11.6. Выявление сходства средствами spaCy
Выявление сходства — процесс анализа документов для определения того, насколько они похожи друг на друга. Один из возможных методов выявления
сходства — подсчет частот слов. Так, некоторые специалисты считают, что
работы Уильяма Шекспира в действительности могли быть написаны сэром
Фрэнсисом Бэконом, Кристофером Марло или другими людьми1. Сравнение
частот слов в их работах с частотами слов в работах Шекспира может открыть
сходство в авторском стиле.
Для анализа сходства документов могут применяться различные методы
машинного обучения, которые будут рассмотрены в дальнейших главах. Тем
не менее, как это часто бывает в Python, некоторые библиотеки, такие как
spaCy и Gensim, могут сделать это за вас. Мы воспользуемся средствами выявления сходства spaCy для сравнения объектов Doc, представляющих пьесу
Шекспира «Ромео и Джульетта», с работой Кристофера Марло «Эдуард II».
Текст «Эдуарда II» можно загрузить с сайта проекта «Гутенберг» так же, как
мы это сделали ранее для «Ромео и Джульетты»2.

Загрузка языковой модели и создание объекта spaCy Doc
Как и в предыдущем разделе, начнем с загрузки языковой модели для английского языка:
In [1]: import spacy
In [2]: nlp = spacy.load('en')

1

https://en.wikipedia.org/wiki/Shakespeare_authorship_question.

2

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

514   Глава 11. Обработка естественного языка (NLP)

Создание объекта spaCy Doc
Затем создаются два объекта Doc — для «Ромео и Джульетты» и для «Эдуарда II»:
In [3]: from pathlib import Path
In [4]: document1 = nlp(Path('RomeoAndJuliet.txt').read_text())
In [5]: document2 = nlp(Path('EdwardTheSecond.txt').read_text())

Сравнение сходства книг
Наконец, метод similarity класса Doc используется для получения значения
в диапазоне от 0.0 (сходство отсутствует) до 1.0 (полная идентичность), которое показывает, насколько похожи документы:
In [6]: document1.similarity(document2)
Out[6]: 0.9349950179100041

spaCy считает, что два документа обладают высокой степенью сходства. Для
чистоты эксперимента мы создали объект Doc для текста новостной заметки
и сравнили его с «Ромео и Джульеттой». Как и предполагалось, spaCy возвращает низкое значение, свидетельствующее о незначительном сходстве между
ними. Попробуйте скопировать текст заметки в текстовый файл и провести
сравнение самостоятельно.

11.7. Другие библиотеки и инструменты NLP
В этой главе были представлены различные библиотеки NLP, но вам всегда
стоит самостоятельно исследовать возможные варианты, чтобы выбрать
оптимальный инструмент для ваших задач. Ниже перечислены некоторые
(в основном бесплатные и распространяемые с открытым кодом) библиотеки
NLP и API:
ØØGensim — выявление сходства и тематическое моделирование.
ØØGoogle Cloud Natural Language API — облачный API для задач NLP (рас-

познавание именованных сущностей, анализ эмоциональной окраски,
анализ частей речи и визуализация, определение категорий контента
и т. д.).
ØØMicrosoft Linguistic Analysis API.

11.8. Машинное обучение и NLP-приложения с глубоким обучением   515
ØØАнализ эмоциональной окраски Bing — поисковая система Bing компа-

нии Microsoft использует эмоциональную окраску при выборе результатов поиска (на момент написания книги эта возможность была доступна
только в США).
ØØPyTorch NLP — библиотека глубокого обучения для NLP.
ØØStanford CoreNLP — NLP-библиотека для языка Java, которая также

предоставляет обертку для Python. Включает разрешение кореференции,
то есть поиск всех ссылок на один ресурс.
ØØApache OpenNLP — другая NLP-библиотека на базе Java для выполнения

распространенных операций, включая разрешение кореференции. Доступны обертки для Python.
ØØPyNLPl — NLP-библиотека для языка Python; включает как базовую, так

и более сложную функциональность NLP.
ØØSnowNLP — библиотека Python, упрощающая обработку текста на китай-

ском языке.
ØØKoNLPy — NLP для корейского языка.
ØØstop-words — библиотека Python с игнорируемыми словами для мно-

гих языков. В этой главе использовались списки игнорируемых слов
NLTK.
ØØTextRazor — коммерческий облачный NLP API с бесплатным уровнем.

11.8. Машинное обучение и NLP-приложения
с глубоким обучением
Существует множество областей обработки естественного языка, требующих
применения машинного обучения и методов глубокого обучения. Некоторые
из них рассмотрены в главах, посвященных машинному обучению и глубокому
обучению:
ØØОтветы на вопросы на естественном языке — например, издатель Pearson

Education использует IBM Watson в качестве виртуального преподавателя. Студенты задают Watson вопросы на естественном языке и получают
ответы.
ØØСоставление сводки документа — анализ документа и построение краткой

сводки (также называемой конспектом), которая, например, может вклю-

516   Глава 11. Обработка естественного языка (NLP)
чаться в результаты поиска, чтобы упростить пользователю выбор материал для чтения.
ØØСинтез речи (текст в речь) и распознавание речи (речь в текст) — эти сред-

ства будут использоваться в главе 13 наряду с переводом текста в текст на
другом языке, для разработки голосового переводчика, функционирующего практически в реальном времени.
ØØСовместная фильтрация — используется для реализации рекомендатель-

ных систем («если вам понравился этот фильм, то вам также может понравиться…»).
ØØКлассификация текста — например, классификация новостных заметок

по категориям: мировые новости, национальные новости, местные новости, спорт, бизнес, развлечения и т. д.
ØØТематическое моделирование — определение тем, обсуждаемых в доку-

ментах.
ØØРаспознавание сарказма — часто используется в сочетании с анализом

эмоциональной окраски.
ØØУпрощение текста — преобразование текста, которое делает его более

компактным и простым для чтения.
ØØПреобразование речи на язык жестов и наоборот — для общения со сла-

бослышащими людьми.
ØØТехнология чтения по губам — преобразование движения губ в текст или

речь для общения с людьми, которые не могут говорить.
ØØФормирование субтитров — включение субтитров в видео.

11.9. Наборы данных естественных языков
Разработчикам доступно огромное количество источников данных для обработки естественных языков:
ØØ«Википедия» — https://meta.wikimedia.org/wiki/Datasets.
ØØIMDB (Internet Movie Database) — различные наборы данных с информа-

цией о фильмах и ТВ-шоу.
ØØТекстовые наборы данных UCIs — множество наборов данных, включая

Spambase.

11.10. Итоги   517
ØØПроект «Гутенберг» — 50 000+ бесплатных электронных книг, не защи-

щенных авторским правом в США.
ØØНабор данных Jeopardy! — 200 000+ вопросов телевикторины Jeopardy!

Одной из важных вех в развитии искусственного интеллекта стала победа
IBM Watson над двумя сильнейшими игроками Jeopardy! в 2011 году.
ØØНаборы данных для обработки естественных языков:
machinelearningmastery.com/datasets-natural-language-processing/.

https://

ØØДанные NLTK: https://www.nltk.org/data.html.
ØØНабор данных предложений с эмоциональной разметкой (из различных

источников, включая IMDB.com, amazon.com, yelp.com).
ØØРеестр открытых данных AWS — каталог наборов данных, размещенных
в Amazon Web Services (https://registry.opendata.aws).
ØØНабор данных с отзывами клиентов Amazon — 130+ миллионов отзывов
о продуктах (https://registry.opendata.aws/amazon-reviews/).
ØØКорпус Pitt.edu (http://mpqa.cs.pitt.edu/corpora/).

11.10. Итоги
В этой главе были рассмотрены разнообразные операции обработки естественного языка (NLP) с использованием нескольких библиотек NLP, продемонстрировано использование NLP с текстовыми коллекциями, которые
называются корпусами. Мы обсудили, почему смысловые нюансы затрудняют
понимание естественных языков.
Основное внимание было уделено NLP-библиотеке TextBlob, которая построена на базе библиотек NLTK и pattern, но более проста в использовании. Мы
создали объекты TextBlob и разбили их на объекты Sentence и Word, определили
части речи каждого слова в TextBlob и выделили в тексте именные конструкции.
Далее показано, как оценить положительную или отрицательную эмоциональную окраску TextBlob и Sentence при помощи стандартного анализатора
TextBlob и при помощи анализатора NaiveBayesAnalyzer. Средства интеграции
библиотеки TextBlob c Google Translate были использованы для выявления
языка текста и перевода текста на другой язык.
Также было продемонстрировано выполнение других операций NLP, включая
образование множественного и единственного числа, проверку орфографии

518   Глава 11. Обработка естественного языка (NLP)
и исправление ошибок, нормализацию с выделением основы и лемматизацией,
а также определение частот вхождения слов. Определения слов, синонимы
и антонимы были загружены из WordNet. Также список игнорируемых слов
NLTK был использован для устранения этих слов из текста, и мы создали
n-граммы, содержащие группы последовательных слов.
Далее вы узнали, как создать количественную визуализацию частот вхождения слов в виде гистограммы с использованием встроенных средств создания
диаграмм pandas. Затем библиотека wordcloud была использована для качественного представления частот вхождения слов в виде словарных облаков.
Для оценки удобочитаемости текста использовалась библиотека Textatistic.
Наконец, библиотека spaCy использовалась для поиска именованных сущностей и выявления сходства между документами. В следующей главе мы
продолжим использование обработки естественных языков при глубоком
анализе твитов средствами Twitter API.

12
Глубокий анализ
данных Twitter
В этой главе…
•• Влияние Twitter на бизнес, бренды, репутацию, анализ эмоциональной
окраски, прогнозы и т. д.
•• Использование Tweepy, одного из самых популярных Python-клиентов
Twitter API, для глубокого анализа данных Twitter.
•• Использование Twitter Search API для загрузки прошлых твитов, удовлетворяющих заданному критерию.
•• Использование Twitter Streaming API для работы с потоком твитов по мере
их появления.
•• Полезная информация в объектах твитов, возвращаемых Twitter.
•• Использование методов обработки естественных языков из предыдущей
главы с целью очистки и предварительной обработки твитов для их подготовки к анализу.
•• Выполнение анализа эмоциональной окраски твитов.
•• Выявление тенденций с использованием Twitter Trends API.
•• Отображение твитов с использованием folium и OpenStreetMap.
•• Различные способы хранения твитов.

520   Глава 12. Глубокий анализ данных Twitter

12.1. Введение
Мы часто стремимся предугадать будущее. Пойдет ли дождь в день предстоящего пикника? Будут ли подниматься или снижаться биржевые индексы
или котировки отдельных ценных бумаг, когда и насколько? Как люди будут
голосовать на следующих выборах? С какой вероятностью экспедиция откроет
новое месторождение и сколько нефти оно сможет произвести? Будет ли бейсбольная команда чаще выигрывать при переходе на новую тактику? Сколько
клиентов воспользуется услугами авиакомпании за ближайшие месяцы? По
какой траектории пойдет ураган и какую силу он наберет: категория 1, 2, 3, 4
или 5? Согласитесь, такая информация жизненно важна для подготовленности
к чрезвычайным ситуациям. Или, скажем, с какой вероятностью та или иная
финансовая операция может оказаться мошеннической? Будет ли ипотечный
кредит погашен вовремя? Насколько вероятно быстрое распространение болезни, и если вероятно, то в какой географической области следует ожидать
вспышки? И так далее и тому подобное.
Прогнозирование — сложный и нередко дорогостоящий процесс, но потенциальный выигрыш может быть очень большим. Благодаря технологиям
этой и следующих глав вы увидите, как искусственный интеллект — часто
в сочетании с большими данными — стремительно расширяет возможности
прогнозирования.
В этой главе мы сосредоточимся на глубоком анализе данных Twitter и определении эмоциональной окраски твитов. Глубоким анализом данных называется процесс поиска в больших коллекциях данных (часто больших данных)
с целью выявления закономерностей, которые могут представлять интерес для
отдельных лиц и организаций. Информация об эмоциональной окраске, извлеченная из твитов, поможет спрогнозировать результаты выборов, вероятные
доходы от нового фильма или успех рекламной кампании, выявить слабости
в продуктах, предлагаемых конкурентами, и многое другое.
Установление связи с Twitter осуществляется через веб-сервисы. Мы воспользуемся Twitter Search API для подключения к гигантской базе данных
прошлых твитов. Twitter Streaming API будет использоваться для выборки
информации из потока новых твитов по мере их появления. Twitter Trends
API поможет узнать наиболее актуальные темы. Вы увидите, что большая
часть информации, представленной в главе 11, пригодится для построения
приложений для Twitter.

12.1. Введение   521

Как уже показано в книге, благодаря мощным библиотекам достаточно серьезные задачи часто решаются всего в нескольких строках кода, и это — одна из
самых привлекательных сторон языка Python и его сообщества разработки
с открытым кодом.
Twitter постепенно вытесняет крупные информационные агентства и занимает
место основного источника достоверных новостей. Многие публикации в Twitter
общедоступны и происходят в реальном времени одновременно с глобальными
событиями. Люди откровенно высказываются по любым темам и пишут о своей
личной и деловой жизни, оставляя комментарии по социальным, развлекательным, политическим темам и любым иным мыслимым вопросам. Они снимают
на свои мобильные телефоны происходящие вокруг события. Часто встречающиеся термины «Twitter-сфера» и «мир Twitter» обозначают сотни миллионов
пользователей, имеющих отношение к отправке, получению и анализу твитов.

Что такое Twitter?
Компания Twitter основана в 2006 году как сервис микроблогов; сейчас она
стала одним из самых популярных сайтов в интернете. Концепция проста:
люди пишут короткие сообщения — твиты (изначально их длина была
ограничена 140 символами, но теперь для многих языков она увеличилась до
280 символов). Каждый желающий может читать сообщения любого другого
пользователя. В этом отношении Twitter отличается от замкнутых сообществ
других социальных сетей вроде Facebook, LinkedIn и т. д., в которых «отношения подписки» должны быть взаимными.

Статистика Twitter
У Twitter сотни миллионов пользователей. Ежедневно отправляются сотни
миллионов твитов, по несколько тысяч в секунду1. Поиск в интернете по критериям «интернет-статистика» и «статистика Twitter» поможет объективно оценить эти числа. Некоторые авторы имеют более 100 миллионов подписчиков.
Обычно они публикуют по несколько сообщений в день, чтобы подписчики
не теряли интереса. Как правило, наибольшее количество подписчиков имеют
артисты и политики. Разработчики могут получать информацию из «живого»
потока твитов прямо во время его появления. Из-за невероятной скорости
1

http://www.internetlivestats.com/twitter-statistics/.

522   Глава 12. Глубокий анализ данных Twitter
появления твитов этот способ получения информации иногда сравнивают
с «питьем из пожарного шланга».

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

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

12.2. Обзор Twitter APIs
Twitter-API реализуются в форме облачных веб-сервисов, поэтому для выполнения кода этой главы потребуется подключение к интернету. Веб-сервисы
представляют собой методы, которые вызываются в облаке, как это будет
делаться с Twitter-API в этой главе, с IBM Watson API — в следующей главе,
а также с другими API, которыми вы будете пользоваться при переносе вычислений на облачные платформы. Каждый метод API имеет конечную точку
веб-сервиса, представленную URL-адресом для вызова метода по интернету.
Различные Twitter-API включают разные категории функциональности; одни
из них бесплатны, другие требуют оплаты. У многих предусмотрены ограни-

12.2. Обзор Twitter APIs   523

чения частоты использования, то есть максимальное количество возможных
обращений за 15-минутный интервал. В этой главе библиотека Tweepy будет
использоваться для вызова методов из следующих Twitter API:
ØØAuthentication API — передача своих регистрационных данных Twitter (см.

далее) для использования других API.
ØØAccounts and Users API — обращение к информации учетных записей.
ØØTweets-API — поиск по старым твитам, обращение к потокам текущих тви-

тов и т. д.
ØØTrends-API — поиск актуальных тем и получение списков актуальных тем

по местоположению.
Полный список категорий, подкатегорий и отдельных методов Twitter-API —
здесь:
https://developer.twitter.com/en/docs/api-reference-index.html

Ограничения частоты использования: предупреждение
Twitter ожидает, что разработчики будут ответственно пользоваться его сервисами. У каждого метода Twitter API существует ограничение частоты использования — максимальное количество запросов (то есть вызовов), которые
могут быть выданы за 15-минутное окно. Если вы продолжите вызвать метод
API после превышения ограничения частоты использования этого метода,
Twitter может заблокировать вам доступ к API.
До использования метода API прочитайте документацию и поймите его
ограничения частоты использования1. Мы настроим Tweepy так, чтобы при
достижении ограничения библиотека переходила в ожидание, чтобы предотвратить возможное превышение ограничения частоты. В некоторых списках
указываются ограничения частоты использования как для пользователей, так
и для приложений. Во всех примерах этой главы используются ограничения
частоты для приложений. Они предназначены для приложений, позволяющих отдельному пользователю подключиться к Twitter (например, сторонним приложениям, взаимодействующим с Twitter от вашего имени, скажем,
приложениям на смартфонах). За подробностями об ограничениях частоты
использования обращайтесь по адресу:
https://developer.twitter.com/en/docs/basics/rate-limiting
1

Учтите, что в будущем Twitter может изменить эти ограничения.

524   Глава 12. Глубокий анализ данных Twitter
За информацией об ограничениях частоты использования конкретных методов
API обращайтесь по адресу:
https://developer.twitter.com/en/docs/basics/rate-limits

и к документации методов API.

Другие ограничения
Twitter — настоящая золотая жила для глубокого анализа данных, и с помощью
бесплатных сервисов можно сделать очень много. Вы не поверите, насколько
полезные приложения можно построить на этой основе и насколько они способны улучшить ваши личные и карьерные достижения. Тем не менее если
вы будете нарушать правила Twitter, то ваша учетная запись разработчика
может быть заблокирована. Внимательно ознакомьтесь со следующими
текстами и теми документами, на которые они ссылаются:
ØØУсловия обслуживания: https://twitter.com/tos
ØØСоглашение с разработчиком: https://developer.twitter.com/en/developer-terms/
agreement-and-policy.html
ØØПолитика для разработчиков: https://developer.twitter.com/en/developer-terms/
policy.html
ØØДругие ограничения: https://developer.twitter.com/en/developer-terms/more-onrestricted-use-cases

Позднее в этой главе будет показано, что бесплатный Twitter-API позволяет
проводить поиск только по твитам за последние семь дней с получением
ограниченного количества твитов. Некоторые книги и статьи сообщают, что
эти ограничения можно обойти прямым извлечением данных с twitter.com. Тем
не менее в условиях обслуживания явно указано, что «извлечение данных
с сервисов без предварительного согласования с Twitter категорически запрещается».

12.3. Создание учетной записи Twitter
Twitter требует подачи заявок на создание учетной записи разработчика для
использования своих API. Откройте страницу
https://developer.twitter.com/en/apply-for-access

12.4. Получение регистрационных данных Twitter — создание приложения   525

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

12.4. Получение регистрационных данных
Twitter — создание приложения
Когда у вас появится учетная запись разработчика Twitter, вы должны получить регистрационные данные для взаимодействия с Twitter API. Для этого
необходимо создать приложение; разные приложения используют разные регистрационные данные. Чтобы создать приложение, выполните вход по адресу:
https://developer.twitter.com

и следующие действия:
1. Щелкните на раскрывающемся меню своей учетной записи в правой
верхней части страницы и выберите пункт Apps.
2. Щелкните на ссылке Create an app.
3. В поле App name укажите имя своего приложения. Если вы отправляете
твиты через API, то имя вашего приложения будет использоваться в качестве отправителя твитов. Оно также будет выводиться для пользователей, если вы создаете приложение, требующее входа пользователя через
Twitter. В этой главе мы не будем делать ни того ни другого, поэтому для
ее целей будет достаточно имени вида «ВашеИмя Test App».
4. В поле Application description введите описание приложения. При создании
приложений на базе Twitter, которые будут использоваться другими
людьми, оно будет содержать информацию о том, что делает ваше приложение. В этой главе будет использоваться строка "Learning to use
the Twitter API.".

526   Глава 12. Глубокий анализ данных Twitter
5. В поле Website URL введите URL-адрес своего сайта. При создании приложений на базе Twitter это должен быть веб-сайт, на котором размещается
ваше приложение. Вы можете использовать URL-адрес Twitter: https://
twitter.com/ВашеИмяПользователя, где ВашеИмяПользователя — имя вашей
учетной записи Twitter. Например, адрес https://twitter.com/nasa соответствует имени NASA @nasa.
6. В поле Tell us how this app will be used вводится описание, содержащее не
менее 100 символов. Оно поможет работникам Twitter понять, что делает ваше приложение. Мы ввели строку с информацией о том, что приложение предназначено исключительно для учебных целей ("I am new
to Twitter app development and am simply learning how to use the
Twitter APIs for educational purposes").

7. Оставьте остальные поля пустыми и щелкните на кнопке Create, а затем
внимательно просмотрите (длинные) условия использования сервиса для
разработчиков, после чего снова щелкните на кнопке Create.

Получение регистрационных данных
После выполнения шага 7 в предыдущем списке Twitter выводит веб-страницу
для управления приложением. В начале страницы располагаются вкладки App
details (Подробное описание приложения), Keys and tokens (Ключи и маркеры)
и Permissions (Разрешения). Щелкните на кнопке вкладки Keys and tokens, чтобы
просмотреть регистрационные данные вашего приложения. Изначально на
странице выводятся ключи Consumer API — ключ API и секретный ключ API.
Щелкните на кнопке Create, чтобы получить маркер доступа и секретный
маркер доступа. Все четыре ключа будут использоваться для выполнения
аутентификации Twitter с целью использования методов API.

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

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

12.5. Какую информацию содержит объект Tweet?   527

маркера доступа и секретного маркера доступа хранятся в приведенном ниже
файле keys.py. Вы найдете этот файл в каталоге примеров ch12:
consumer_key='ВашКлючПользователя'
consumer_secret='ВашСекретныйКлючПользователя'
access_token='ВашМаркерДоступа'
access_token_secret='ВашСекретныйМаркерДоступа'

Отредактируйте файл и замените ВашКлючПользователя, ВашСекретный
КлючПользователя, ВашМаркерДоступа и ВашСекретныйМаркерДоступа
соответствующими значениями. Сохраните полученный файл.

OAuth 2.0
Ключ пользователя, секретный ключ пользователя, маркер доступа и секретный маркер доступа являются частью процесса аутентификации OAuth 2.01,2,
используемого Twitter для обращения к API. Библиотека Tweepy позволяет вам
передать ключ пользователя, секретный ключ пользователя, маркер доступа
и секретный маркер доступа, принимая на себя все детали аутентификации
OAuth 2.0.

12.5. Какую информацию содержит
объект Tweet?
Методы Twitter API возвращают объекты JSON. JSON (JavaScript Object
Notation) — текстовый формат передачи данных, используемый для представления объектов в виде коллекций пар «имя-значение». Он часто применяется
при обращениях к веб-сервисам. Формат JSON может читаться как человеком, так и компьютером и позволяет легко отправлять и получать данные по
интернету.
Объекты JSON сходны со словарями Python. Каждый объект JSON содержит
список имен свойств и их значений, заключенный в фигурные скобки:
{имяСвойства1: значение1, имяСвойства2: значение2}

1
2

(например, bcrypt — https://github.com/pyca/bcrypt/), а затем читать и расшифровывать их
только при передаче Twitter.
https://developer.twitter.com/en/docs/basics/authentication/overview.
https://oauth.net/.

528   Глава 12. Глубокий анализ данных Twitter
Как и в Python, списки JSON — это значения, разделенные запятыми в квадратных скобках:
[значение1, значение2, значение3]

Для вашего удобства Tweepy берет на себя все операции с JSON, преобразуя
JSON в объекты Python с использованием классов, определенных в библиотеке Tweepy.

Ключевые свойства объекта Tweet
Твит (также называемый обновлением статуса) может содержать не более
280 символов, но объекты, возвращаемые Twitter API, содержат множество
атрибутов метаданных, описывающих разные аспекты твита:
ØØкогда он был создан;
ØØкто его создал;
ØØсписки хештегов, URL, @-упоминаний и аудиовизуальные материалы

(например, изображения и видео, заданные URL-адресами), входящие
в твит;
ØØи многое другое.

В табл. 12.1 перечислены некоторые ключевые атрибуты объекта твита.
Таблица 12.1. Некоторые ключевые атрибуты объекта твита
Атрибут

Описание

created_at

Дата и время создания в формате UTC (Всемирное координированное время)

entities

Twitter извлекает из твитов хештеги, URL, @-упоминания, ссылки
на аудиовизуальные материалы (графика, видео), условные обозначения и опросы и помещает их в словарь entities в форме списков,
чтобы вы могли обращаться к ним по соответствующим ключам

extended_tweet

Дополнительная информация для твитов, длина которых превышает 140 символов (например, full_text и entities)

favorite_count

Счетчик помещений твита в «Избранное» другими пользователями

coordinates

Координаты (широта и долгота) отправки твита. Часто содержит
null (None в Python), потому что многие пользователи отключают
отправку данных своего местоположения

12.5. Какую информацию содержит объект Tweet?   529

Атрибут
place

Описание

Пользователи могут связать с твитом определенное место. В таком
случае этот атрибут будет содержать объект place:
https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/
geo-objects#place-dictionary; в противном случае он равен null (None

в Python)
id

Целочисленный идентификатор твита. Twitter рекомедует использовать id_str для обеспечения портируемости

id_str

Строковое представление целочисленного идентификатора твита

lang

Язык твита (например, 'en' для английского или 'fr' для французского)

retweet_count

Количество ретвитов данного твита другими пользователями

text

Текст твита. Если твит использует новое 280-символьное ограничение длины и содержит более 140 символов, это свойство будет
усечено, а свойство truncated будет равно true. Также это может
произойти в том случае, если в результате ретвита 140-символьный
твит превысил длину в 140 символов

user

Объект User, представляющий пользователя, отправившего твит. За
списком JSON-свойств объекта User обращайтесь по адресу: https://
developer.twitter.com/en/docs/tweets/data-dictionary/overview/user-object

Пример объекта твита в формате JSON
Рассмотрим пример JSON для следующего твита от учетной записи @nasa:
@NoFear1075 Great question, Anthony! Throughout its seven-year mission, our Parker #SolarProbe spacecraft... https://t.co/xKd6ym8waT'

Мы добавили нумерацию строк и переформатировали часть разметки JSON
для переноса строк. Отметим, что не все поля JSON-разметки поддерживаются
всеми методами Twitter-API; различия объясняются в электронной документации каждого метода.
1 {'created_at': 'Wed Sep 05 18:19:34 +0000 2018',
2 'id': 1037404890354606082,
3 'id_str': '1037404890354606082',
4 'text': '@NoFear1075 Great question, Anthony! Throughout its seven-year
mission, our Parker #SolarProbe spacecraft… https://t.co/xKd6ym8waT',
5 'truncated': True,
6 'entities': {'hashtags': [{'text': 'SolarProbe', 'indices': [84, 95]}],
7
'symbols': [],

530   Глава 12. Глубокий анализ данных Twitter
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

'user_mentions': [{'screen_name': 'NoFear1075',
'name': 'Anthony Perrone',
'id': 284339791,
'id_str': '284339791',
'indices': [0, 11]}],
'urls': [{'url': 'https://t.co/xKd6ym8waT',
'expanded_url': 'https://twitter.com/i/web/status/
1037404890354606082',
'display_url': 'twitter.com/i/web/status/1…',
'indices': [117, 140]}]},
'source': 'Twitter Web
Client',
'in_reply_to_status_id': 1037390542424956928,
'in_reply_to_status_id_str': '1037390542424956928',
'in_reply_to_user_id': 284339791,
'in_reply_to_user_id_str': '284339791',
'in_reply_to_screen_name': 'NoFear1075',
'user': {'id': 11348282,
'id_str': '11348282',
'name': 'NASA',
'screen_name': 'NASA',
'location': '',
'description': 'Explore the universe and discover our home planet with
@NASA. We usually post in EST (UTC-5)',
'url': 'https://t.co/TcEE6NS8nD',
'entities': {'url': {'urls': [{'url': 'https://t.co/TcEE6NS8nD',
'expanded_url': 'http://www.nasa.gov',
'display_url': 'nasa.gov',
'indices': [0, 23]}]},
'description': {'urls': []}},
'protected': False,
'followers_count': 29486081,
'friends_count': 287,
'listed_count': 91928,
'created_at': 'Wed Dec 19 20:20:32 +0000 2007',
'favourites_count': 3963,
'time_zone': None,
'geo_enabled': False,
'verified': True,
'statuses_count': 53147,
'lang': 'en',
'contributors_enabled': False,
'is_translator': False,
'is_translation_enabled': False,
'profile_background_color': '000000',
'profile_background_image_url': 'http://abs.twimg.com/images/themes/
theme1/bg.png',
'profile_background_image_url_https': 'https://abs.twimg.com/images/
themes/theme1/bg.png',

12.5. Какую информацию содержит объект Tweet?   531
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

'profile_image_url': 'http://pbs.twimg.com/profile_images/188302352/
nasalogo_twitter_normal.jpg',
'profile_image_url_https': 'https://pbs.twimg.com/profile_images/
188302352/nasalogo_twitter_normal.jpg',
'profile_banner_url': 'https://pbs.twimg.com/profile_banners/11348282/
1535145490',
'profile_link_color': '205BA7',
'profile_sidebar_border_color': '000000',
'profile_sidebar_fill_color': 'F3F2F2',
'profile_text_color': '000000',
'profile_use_background_image': True,
'has_extended_profile': True,
'default_profile': False,
'default_profile_image': False,
'following': True,
'follow_request_sent': False,
'notifications': False,
'translator_type': 'regular'},
'geo': None,
'coordinates': None,
'place': None,
'contributors': None,
'is_quote_status': False,
'retweet_count': 7,
'favorite_count': 19,
'favorited': False,
'retweeted': False,
'possibly_sensitive': False,
'lang': 'en'}

Ресурсы с информацией по JSON-объектам Twitter
Полный и более удобочитаемый список атрибутов объекта твита доступен
по адресу:
https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/tweet-object.html

Дополнительная информация об изменениях, произошедших при переходе
Twitter на новое ограничение длины твита (с 140 на 280 символов), здесь:
https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/intro-to-tweetjson.html#extendedtweet

Общий обзор всех объектов JSON, возвращаемых разными Twitter-API,
а также ссылки на информацию о конкретных объектах, доступны по адресу:
https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/intro-to-tweet-json

532   Глава 12. Глубокий анализ данных Twitter

12.6. Tweepy
Мы будем использовать библиотеку Tweepy1 (http://www.tweepy.org/) — одну из
самых популярных библиотек Python для взаимодействия с разными TwitterAPI. Tweepy упрощает доступ к функциональности Twitter, скрывая подробности обработки объектов JSON, возвращаемых Twitter-API. Документацию
Tweepy2 можно просмотреть по адресу:
http://docs.tweepy.org/en/latest/

За дополнительной информацией и исходным кодом Tweepy обращайтесь по
адресу:
https://github.com/tweepy/tweepy

Установка Tweepy
Для установки Tweepy откройте приглашение Anaconda (Windows), терминал (macOS/Linux) или командную оболочку (Linux), после чего выполните
­команду:
pip install tweepy==3.7

Возможно, пользователям Windows придется запустить приглашение Anaconda
с правами администратора для получения необходимых привилегий для установки программного обеспечения. Щелкните правой кнопкой мыши на команде Anaconda Prompt в меню Пуск и выберите команду MoreRun as administrator.

Установка geopy
В процессе работы с Tweepy вы также будете использовать функции из файла
tweetutilities.py (файл включен в примеры кода этой главы). Одна из вспомо1

2

Среди других библиотек Python, рекомендуемых Twitter, — Birdy, python-twitter, Python
Twitter Tools, TweetPony, TwitterAPI, twitter-gobject, TwitterSearch и twython. За подробностями обращайтесь по адресу https://developer.twitter.com/en/docs/developer-utilities/
twitter-libraries.html.
Работа над документацией Tweepy еще не завершена. На момент написания книги
у Tweepy еще не было документации по классам, соответствующим объектам JSON, возвращаемым Twitter-API. Классы Tweepy используют те же имена атрибутов и структуру,
что и объекты JSON. Правильные имена атрибутов можно определить по документации
JSON компании Twitter. Мы будем пояснять все атрибуты, используемые нами в коде,
предоставив ссылки на описания JSON-объектов Twitter.

12.7. Аутентификация Twitter с использованием Tweepy    533

гательных функций файла зависит от библиотеки geopy (https://github.com/
geopy/geopy, см. раздел 12.15). Чтобы установить geopy, выполните следующую
­команду:
conda install -c conda-forge geopy

12.7. Аутентификация Twitter
с использованием Tweepy
В нескольких ближайших разделах мы будем обращаться к различным облачным Twitter-API через Tweepy. Здесь мы начнем использовать Tweepy для
выполнения аутентификации Twitter и создания объекта Tweepy API, который
становится шлюзом для использования Twitter API по интернету. Далее мы
будем работать с разными Twitter API, вызывая методы объекта API.
До вызова методов Twitter-API необходимо использовать ключ API, секретный ключ API, маркер доступа и секретный маркер доступа для выполнения
аутентификации Twitter1. Запустите IPython из папки примеров ch12, затем
импортируйте модуль tweepy и файл keys.py, который был изменен ранее в этой
главе. Вы можете импортировать любой файл .py в виде модуля, для чего имя
файла указывается без расширения .py в команде import:
In [1]: import tweepy
In [2]: import keys

Если файл keys.py был импортирован как модуль, то можно обращаться
к каждой из четырех переменных, определенных в этом файле, в форме keys.
имя_переменной.

Создание и настройка OAuthHandler для выполнения
аутентификации Twitter
Аутентификация Twitter с использованием Tweepy состоит из двух этапов.
Создайте объект класса OAuthHandler модуля tweepy, передав свой ключ API
1

Возможно, вы займетесь созданием приложений, которые позволяют пользователям
входить в учетные записи Twitter, управлять ими, отправлять твиты, читать твиты других
пользователей, искать твиты и т. д. Дополнительная информация об аутентификации
пользователей доступна в учебном руководстве по адресу http://docs.tweepy.org/en/latest/
auth_tutorial.html.

534   Глава 12. Глубокий анализ данных Twitter
и секретный ключ API его конструктору. Конструктором называется функция, имя которой совпадает с именем класса (в данном случае OAuthHandler)
и которая получает аргументы для настройки нового объекта:
In [3]: auth = tweepy.OAuthHandler(keys.consumer_key,
...:
keys.consumer_secret)
...:

Чтобы передать маркер доступа и секретный маркер доступа, вызовите метод
set_access_token объекта OAuthHandler:
In [4]: auth.set_access_token(keys.access_token,
...:
keys.access_token_secret)
...:

Создание объекта API
Теперь создайте объект API , который используется для взаимодействия
с Twitter:
In [5]: api = tweepy.API(auth, wait_on_rate_limit=True,
...:
wait_on_rate_limit_notify=True)
...:

При вызове конструктора API передаются три объекта:
ØØauth — объект OAuthHandler, содержащий регистрационные данные.
ØØКлючевой аргумент wait_on_rate_limit=True сообщает Tweepy, чтобы

при каждом превышении ограничения частоты использования метода
API делалась пауза продолжительностью 15 минут. Тем самым предотвращаются возможные нарушения ограничений частоты использования
Twitter.
ØØКлючевой аргумент wait_on_rate_limit_notify=True сообщает Tweepy,

что при необходимости ожидания из-за превышения частоты использования библиотека должна оповестить вас об этом выводом сообщения в командной строке.
Теперь все готово для взаимодействия с Twitter через Tweepy. Обратите внимание: примеры кода в нескольких ближайших разделах представлены в виде
повторяющегося сеанса IPython, чтобы вам не приходилось повторять ранее
пройденный процесс авторизации.

12.8. Получение информации об учетной записи Twitter   535

12.8. Получение информации об учетной
записи Twitter
После аутентификации Twitter метод get_user объекта Tweepy API может
использоваться для получения объекта tweepy.models.User с информацией
об учетной записи пользователя Twitter. Получим объект User для учетной
записи NASA @nasa:
In [6]: nasa = api.get_user('nasa')

Метод get_user вызывает метод users/show из Twitter API1. Для каждого
метода Twitter, вызываемого через Tweepy, устанавливается ограничение
частоты использования. Метод users/show, предназначенный для получения
информации о конкретных учетных записях пользователей, может вызываться
до 900 раз каждые 15 минут. При упоминании других методов Twitter API мы
будем предоставлять сноску со ссылкой на документацию каждого метода,
в которой можно узнать его ограничения частоты использования.
Каждый класс tweepy.models соответствует объекту JSON, возвращаемому
Twitter. Например, класс User соответствует объекту Twitter user:
https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/user-object

У каждого класса tweepy.models существует метод, который читаетразметку
JSON и преобразует ее в объект соответствующего класса Tweepy.

Получение основной информации об учетной записи
Обратимся к некоторым свойствам объекта User для вывода информации об
учетной записи @nasa:
ØØСвойство id содержит числовой идентификатор учетной записи, который

создается Twitter при регистрации пользователя.
ØØСвойство name содержит имя, связанное с учетной записью пользователя.
ØØСвойство screen_name содержит экранное имя пользователя в Twitter

(@nasa). Как name, так и screen_name могут быть вымышленными именами
для защиты конфиденциальности пользователя.
ØØСвойство description содержит описание из профиля пользователя.
1

https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/getusers-show.

536   Глава 12. Глубокий анализ данных Twitter
In [7]: nasa.id
Out[7]: 11348282
In [8]: nasa.name
Out[8]: 'NASA'
In [9]: nasa.screen_name
Out[9]: 'NASA'
In [10]: nasa.description
Out[10]: 'Explore the universe and discover our home planet with @NASA.
We usually post in EST (UTC-5)'

Получение последних обновлений статуса
Свойство status объекта User возвращает объект tweepy.models.Status, соответствующий объекту твита Twitter:
https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/tweet-object

Свойство text объекта Status содержит текст последнего твита учетной
­записи:
In [11]: nasa.status.text
Out[11]: 'The interaction of a high-velocity young star with the cloud of
gas and dust may have created this unusually sharp-... https://t.co/
J6uUf7MYMI'

Свойство text изначально предназначалось для твитов длиной до 140 символов. Многоточие ... в приведенном фрагменте означает, что текст твита
был усечен. Увеличив длину сообщения до 280 символов, компания Twitter
добавила свойство extended_tweet (описанное ниже) для обращения к тексту
и другой информации о твитах между 141-м и 280-м символами. В этом случае
Twitter присваивает text усеченную версию текста extended_tweet. Ретвит
также приводит к усечению текста, поскольку добавляет символы, которые
могут превысить ограничения по количеству символов.

Получение количества подписчиков
Количество подписчиков учетной записи можно получить из свойства
followers_count:
In [12]: nasa.followers_count
Out[12]: 29453541

12.9. Введение в курсоры Tweepy   537

Хотя это количество велико, некоторые учетные записи, напомним, имеют
более 100 миллионов подписчиков1.

Получение количества друзей
Аналогичным образом можно узнать и количество друзей учетной записи
(то есть количество учетных записей, на которые она подписана) в свойстве
friends_count:
In [13]: nasa.friends_count
Out[13]: 287

Получение информации вашей учетной записи
Свойства из этого раздела также могут использоваться с вашей учетной
­записью. Для этого следует вызвать метод me объекта Tweepy API:
me = api.me()

Оно возвращает объект User для той учетной записи, которая была использована для аутентификации Twitter из предыдущего раздела.

12.9. Введение в курсоры Tweepy: получение
подписчиков и друзей учетной записи
При вызове методов Twitter API вы часто получаете в результатах коллекции
объектов — например, коллекции твитов на вашей временной шкале Twitter,
твитов на временной шкале другой учетной записи или списков твитов, соответствующих заданным критериям поиска. Временная шкала состоит из
твитов, отправленных этим пользователем и друзьями этого пользователя, то
есть другими учетными записями, на которые подписан данный пользователь.
В документации каждого метода API упоминается максимальное количество
элементов, которые метод может вернуть за один вызов, — оно называется
страницей результатов. Когда по вашему запросу выбирается больше результатов, чем может вернуть метод, в JSON-ответы Twitter включается
информация о наличии других страниц. Класс Tweepy Cursor берет на себя
все подробности. Cursor вызывает указанный метод и проверяет, сообщает
1

https://friendorfollow.com/twitter/most-followers/.

538   Глава 12. Глубокий анализ данных Twitter
ли Twitter о наличии других страниц результатов. Если они есть, то Cursor
снова автоматически вызывает метод для получения этих результатов. Это
продолжается до тех пор, пока не останется дополнительных результатов
для обработки (с учетом ограничений частоты использования). Если вы настроили объект API для перехода в ожидание при достижении ограничений
частоты использования (как это сделано в нашем примере), то объект Cursor
будет соблюдать ограничения частоты использования и делать необходимые
паузы между вызовами. В следующих подразделах изложены основы работы
с Cursor. За дополнительной информацией обращайтесь к учебному руководству Cursor по адресу:
http://docs.tweepy.org/en/latest/cursor_tutorial.html

12.9.1. Определение подписчиков учетной записи
Используем объект Tweepy Cursor для вызова метода followers объекта API,
вызывающий метод Twitter-API followers/list1 для получения подписчиков
учетной записи. По умолчанию Twitter возвращает их группами по двадцать,
но вы можете запрашивать до 200 записей за прием. Для демонстрационных
целей мы загрузим информацию о 10 подписчиках NASA.
Метод followers возвращает объекты tweepy.models.User с информацией
о каждом подписчике. Начнем с создания списка для хранения объектов User:
In [14]: followers = []

Создание объекта Cursor
Затем создадим объект Cursor, который вызывает метод followers для учетной
записи NASA, задаваемой ключевым аргументом screen_name:
In [15]: cursor = tweepy.Cursor(api.followers, screen_name='nasa')

Конструктор Cursor получает в аргументе имя вызываемого метода — значение
api.followers означает, что Cursor будет вызывать метод followers объекта
api. Если конструктор Cursor получает какие-либо дополнительные ключевые
аргументы (скажем, screen_name), то они будут переданы методу, заданному
первым аргументом конструктора. Таким образом, в результате Cursor получит данные подписчиков для учетной записи Twitter @nasa.
1

https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/getfollowers-list.

12.9. Введение в курсоры Tweepy   539

Получение результатов
Теперь объект Cursor можно использовать для получения подписчиков. Следующая команда for перебирает результаты выражения cursor.items(10).
Метод items объекта Cursor инициирует вызов api.followers и возвращает
результаты метода followers. В данном случае методу items передается значение 10, чтобы получить только 10 результатов:
In [16]: for account in cursor.items(10):
...:
followers.append(account.screen_name)
...:
In [17]: print('Followers:',
...:
' '.join(sorted(followers, key=lambda s: s.lower())))
...:
Followers: abhinavborra BHood1976 Eshwar12341 Harish90469614 heshamkisha
Highyaan2407 JiraaJaarra KimYooJ91459029 Lindsey06771483 Wendy_UAE_NL

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

Автоматическая разбивка на страницы
Если запрашиваемое количество результатов больше того, что может быть
возвращено одним вызовом followers, то метод items автоматически «перелистывает» результаты многократными вызовами api.followers. Вспомните, что
followers по умолчанию возвращает до 20 подписчиков, поэтому предыдущему
коду достаточно вызвать followers всего один раз. Для получения до 200 подписчиков за прием создадим объект Cursor с ключевым аргументом count:
cursor = tweepy.Cursor(api.followers, screen_name='nasa', count=200)

Если аргумент метода items не задан, то Cursor пытается получить всех
подпис­чиков учетной записи. Однако при большом количестве подписчиков
это может потребовать значительного времени из-за ограничений частоты
использования Twitter. Метод Twitter API followers/list может вернуть
не более 200 подписчиков, а Twitter допускает не более 15 вызовов каждые
15 минут. Следовательно, при использовании бесплатных Twitter API за
15 минут можно получить данные не более 3000 подписчиков. Вспомните,

540   Глава 12. Глубокий анализ данных Twitter
что мы настроили объект API для автоматического перехода в ожидание при
достижении ограничения частоты, так что если вы попытаетесь получить всех
подписчиков, а у учетной записи их более 3000, Tweepy после каждых 3000 подписчиков автоматически делает 15-минутную паузу и выводит сообщение.
На момент написания книги у NASA было более 29,5 миллиона подписчиков.
Следовательно, при загрузке данных 12 000 подписчиков в час для получения
всех данных потребуется более 100 дней.
Обратите внимание: в данном случае мы могли напрямую вызвать метод
followers вместо использования Cursor, так как мы получаем лишь небольшое
количество подписчиков. В данном случае Cursor используется только для
демонстрации того, как обычно вызывается followers. В дальнейших примерах для получения небольшого количества результатов мы будем вызывать
методы API напрямую без использования Cursor.

Получение идентификаторов вместо подписчиков
Хотя за один раз можно получить объекты User не более чем для 200 подписчиков, вы можете получить существенно больше числовых идентификаторов Twitter вызовом метода followers_ids объекта API. Он вызывает метод
Twitter API followers/ids, возвращающий до 5000 идентификаторов за раз
(напомним, что в будущем эти ограничения могут измениться)1. Этот метод
можно вызывать до 15 раз за 15 минут, так что за один интервал ограничения
можно получить до 75 000 учетных записей. Он может быть особенно полезен
в сочетании с методом lookup_users объекта API. Этот метод вызывает метод
Twitter API users/lookup2, который может возвращать до 100 объектов User
за раз и может вызываться до 300 раз за каждые 15 минут. Таким образом,
комбинация этих методов позволяет получить до 30 000 объектов User за один
интервал ограничения частоты использования.

12.9.2. Определение друзей учетной записи
Метод friends объекта API вызывает метод Twitter API friends/list 3 для
получения списка объектов User, представляющих друзей учетной записи.
1

2

3

https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/getfollowers-ids.
https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/getusers-lookup.
https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/getfriends-list.

12.9. Введение в курсоры Tweepy   541

Twitter по умолчанию возвращает данные группами по двадцать, но вы можете
запросить до 200 записей за раз, как было описано выше для метода followers.
Twitter позволяет вызывать метод friends/list до 15 раз каждые 15 минут.
Получим 10 учетных записей друзей для учетной записи NASA:
In [18]: friends = []
In [19]: cursor = tweepy.Cursor(api.friends, screen_name='nasa')
In [20]: for friend in cursor.items(10):
...:
friends.append(friend.screen_name)
...:
In [21]: print('Friends:',
...:
' '.join(sorted(friends, key=lambda s: s.lower())))
...:
Friends: AFSpace Astro2fish Astro_Kimiya AstroAnnimal AstroDuke
NASA3DPrinter NASASMAP Outpost_42 POTUS44 VicGlover

12.9.3. Получение недавних твитов пользователя
Метод user_timeline объекта API возвращает твиты с временной шкалы конкретной учетной записи. Временная шкала включает твиты учетной записи и твиты
ее друзей. Метод вызывает метод Twitter API statuses/user_timeline1, который
возвращает последние 20 твитов, но может возвращать до 200 записей за раз.
Метод может вернуть до 3200 последних твитов учетной записи. Приложения,
в которых используется этот метод, могут вызывать его до 1500 раз за 15 минут.
Метод user_timeline возвращает объекты Status, каждый из которых представляет один твит. Свойство user каждого объекта Status содержит ссылку на
объект tweepy.models.User с информацией о пользователе, который отправил
этот твит, например экранное имя screen_name пользователя. Свойство text
объекта Status содержит текст твита. Выведем значения screen_name и text
для трех твитов от @nasa:
In [22]: nasa_tweets = api.user_timeline(screen_name='nasa', count=3)
In [23]: for tweet in nasa_tweets:
...:
print(f'{tweet.user.screen_name}: {tweet.text}\n')
...:
NASA: Your Gut in Space: Microorganisms in the intestinal tract play an
especially important role in human health. But wh… https://t.co/
uLOsUhwn5p
1

https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-status-es-user_timeline.

542   Глава 12. Глубокий анализ данных Twitter
NASA: We need your help! Want to see panels at @SXSW related to space
exploration? There are a number of exciting panels… https://t.co/
ycqMMdGKUB
NASA: "You are as good as anyone in this town, but you are no better than
any of them," says retired @NASA_Langley mathem… https://t.co/nhMD4n84Nf

Эти твиты были усечены (на что указывает многоточие …); скорее всего, они
используют более новое ограничение на длину твита в 280 символов. Вскоре
мы покажем, как использовать свойство extended_tweet для обращения к полному тексту таких твитов.
В предшествующих фрагментах мы решили вызывать метод user_timeline
напрямую и использовать ключевой аргумент count для указания количества
загружаемых твитов. Если вы хотите получить максимальное количество
твитов на прием (200), то используйте Cursor для вызова user_timeline, как
было продемонстрировано выше. Вспомните, что Cursor при необходимости
автоматически «пролистывает» результаты повторными вызовами метода.

Получение недавних твитов со своей временной шкалы
Вызов метода home_timeline объекта API:
api.home_timeline()

позволяет получить твиты с вашей домашней временной шкалы1, то есть ваших твитов и твитов людей, на которых вы подписаны, вызывая метод Twitter
statuses/home_timeline2. По умолчанию home_timeline возвращает 20 последних твитов, но может вернуть до 200 твитов за прием. Как и прежде, если
вам потребуется получить более 200 твитов с домашней временной шкалы, то
лучше использовать объект Tweepy Cursor для вызова home_timeline.

12.10. Поиск недавних твитов
Метод search объекта Tweepy API возвращает твиты, соответствующие
строке запроса. Согласно документации метода, Twitter ведет поисковый
индекс только для твитов за последние 7 дней, поэтому возвращение всех
подходящих твитов не гарантировано. Метод search вызывает метод Twitter
1

Для учетной записи, использованной для аутентификации Twitter.

2

https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline.

12.10. Поиск недавних твитов   543
search/tweets1, который по умолчанию возвращает 15 твитов, но может воз-

вращать до ста.

Вспомогательная функция print_tweets из tweetutilities.py
Для этого раздела мы создали вспомогательную функцию print_tweets, которая
получает результаты вызова метода API search и выводит для каждого твита
экранное имя пользователя и текст твита. Если твит написан не на английском
языке, а значение tweet.lang отлично от 'und' (не определено), то твит также
будет переведен на английский язык средствами TextBlob, как в главе 11. Чтобы
использовать эту функцию, импортируйте ее из файла tweetutilities.py:
In [24]: from tweetutilities import print_tweets

Ниже выводится только определение функции print_tweets из этого файла:
def print_tweets(tweets):
"""Для каждого объекта Tweepy Status выводит значение
screen_name пользователя и текст твита. Если язык отличен
от английского, то текст переводится средствами TextBlob."""
for tweet in tweets:
print(f'{tweet.screen_name}:', end=' ')
if 'en' in tweet.lang:
print(f'{tweet.text}\n')
elif 'und' not in tweet.lang: # Сначала переводится на английский
print(f'\n ORIGINAL: {tweet.text}')
print(f'TRANSLATED: {TextBlob(tweet.text).translate()}\n')

Поиск по конкретным словам
Проведем поиск трех последних твитов, относящихся к NASA Mars Opportunity
Rover. Ключевой аргумент q метода search задает строку запроса для поиска,
а ключевой аргумент count задает количество возвращаемых твитов:
In [25]: tweets = api.search(q='Mars Opportunity Rover', count=3)
In [26]: print_tweets(tweets)
Jacker760: NASA set a deadline on the Mars Rover opportunity! As the dust
on Mars settles the Rover will start to regain power… https://t.co/
KQ7xaFgrzr
Shivak32637174: RT @Gadgets360: NASA 'Cautiously Optimistic' of Hearing
1

https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets.

544   Глава 12. Глубокий анализ данных Twitter
Back From Opportunity Rover as Mars Dust Storm Settles
https://t.co/O1iTTwRvFq
ladyanakina: NASA's Opportunity Rover Still Silent on #Mars. https://
t.co/njcyP6zCm3

Если вам потребуется получить больше результатов, чем может вернуть один
прием search, то, как и ранее, лучше использовать объект Cursor.

Поиск с использованием поисковых операторов Twitter
В строку запроса можно включать различные поисковые операторы Twitter для
уточнения результатов поиска. В табл. 12.2 перечислены некоторые поисковые
операторы Twitter. Объединяя несколько операторов, вы сможете строить
более сложные запросы. Чтобы увидеть все операторы, откройте страницу
https://twitter.com/search-home

и щелкните по ссылке operators.
Таблица 12.2. Некоторые поисковые операторы Twitter
Пример

Находит твиты, содержащие

python twitter

Неявный «логический оператор И» — находит твиты, содержащие python и twitter

python OR twitter

Логический оператор ИЛИ — находит твиты, содержащие
python или twitter или и то и другое

python ?

? (вопросительный знак) — находит твиты, содержащие вопросы о python

planets -mars

– (минус) — находит твиты, содержащие planets, но не содержащие mars

python :)

:) (веселый смайлик) — находит твиты, содержащие python
и имеющие положительную эмоциональную окраску

python :(

:( (грустный смайлик) — находит твиты, содержащие python
и имеющие отрицательную эмоциональную окраску

since:2018-09-01

Находит твиты, написанные в указанную дату или позднее
(дата должна задаваться в форме ГГГГ-ММ-ДД)

near:"New York City"

Находит твиты, отправленные недалеко от заданного места

from:nasa

Находит твиты от учетной записи @nasa

to:nasa

Находит твиты, обращенные к учетной записи @nasa

12.11. Выявление тенденций: Twitter Trends API   545

Воспользуемся операторами from и since для получения трех твитов от NASA
начиная с 1 сентября 2018 года (используйте дату на неделю ранее дня выполнения этого кода):
In [27]: tweets = api.search(q='from:nasa since:2018-09-01', count=3)
In [28]: print_tweets(tweets)
NASA: @WYSIW Our missions detect active burning fires, track the transport
of fire smoke, provide info for fire managemen… https://t.co/jx2iUoMlIy
NASA: Scarring of the landscape is evident in the wake of the Mendocino
Complex fire, the largest #wildfire in California… https://t.co/Nboo5GD90m
NASA: RT @NASAglenn: To celebrate the #NASA60th anniversary, we're
exploring our history. In this image, Research Pilot Bill Swann prepares
for a…

Поиск по хештегу
Твиты часто содержат хештеги, начинающиеся со знака #; они обозначают
некий важный аспект (например, актуальную тему). Получим два твита
с хештегом #collegefootball:
In [29]: tweets = api.search(q='#collegefootball', count=2)
In [30]: print_tweets(tweets)
dmcreek: So much for #FAU giving #OU a game. #Oklahoma #FloridaAtlantic
#CollegeFootball #LWOS
theangrychef: It's game day folks! And our BBQ game is strong. #bbq
#atlanta #collegefootball #gameday @ Smoke Ring https://t.co/J4lkKhCQE7

12.11. Выявление тенденций:
Twitter Trends API
Если некоторая тема «взрывает интернет», то по ней могут одновременно
писать многие тысячи и даже миллионы людей. Twitter называет такие
темы актуальными, поддерживая списки актуальных тем по всему миру.
При помощи Twitter Trends API можно получить списки географических
мест с актуальными темами и списки верхних 50 актуальных тем для каждого места.

546   Глава 12. Глубокий анализ данных Twitter

12.11.1. Места с актуальными темами
Метод trends_available объекта API вызывает метод Twitter API trends/
available1 для получения списка всех мест, для которых у Twitter существуют актуальные темы. Метод trends_available возвращает список словарей,
представляющих эти места. При выполнении следующего кода было найдено
467 мест с актуальными темами:
In [31]: trends_available = api.trends_available()
In [32]: len(trends_available)
Out[32]: 467

Словарь в каждом элементе списка, возвращаемый trends_available, содержит разнообразную информацию, включая название места и идентификатор
woeid (см. ниже):
In [33]: trends_available[0]
Out[33]:
{'name': 'Worldwide',
'placeType': {'code': 19, 'name': 'Supername'},
'url': 'http://where.yahooapis.com/v1/place/1',
'parentid': 0,
'country': '',
'woeid': 1,
'countryCode': None}
In [34]: trends_available[1]
Out[34]:
{'name': 'Winnipeg',
'placeType': {'code': 7, 'name': 'Town'},
'url': 'http://where.yahooapis.com/v1/place/2972',
'parentid': 23424775,
'country': 'Canada',
'woeid': 2972,
'countryCode': 'CA'}

Метод Twitter Trends API trends/place (о котором будет рассказано ниже)
использует идентификаторы WOEID (Yahoo! Where on Earth ID) для поиска
актуальных тем. WOEID 1 соответствует миру в целом. Другие места имеют
уникальные WOEID со значением больше 1. В двух следующих подразделах
используются значения WOEID для получения общемировых актуальных тем
1

https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trendsavailable.

12.11. Выявление тенденций: Twitter Trends API   547

и актуальных тем для конкретного города. В табл. 12.3 приведены значения
WOEID для некоторых достопримечательностей, городов, государств и континентов. Хотя все эти значения WOEID действительны, это не означает, что
в Twitter обязательно имеются актуальные темы для всех этих мест.
Таблица 12.3. Значения WOEID для некоторых достопримечательностей, городов,
государств и континентов
Место

WOEID

Статуя Свободы

23617050

Лос-Анджелес (штат Калифорния)

2442047

Вашингтон (федеральный округ Колумбия)

2514815

Париж (Франция)

615702

Водопад Игуасу

468785

Соединенные Штаты

23424977

Северная Америка

24865672

Европа

24865675

Вы также можете искать места, близкие к точке, заданной широтой и долготой. Для этого вызывается метод trends_closest объекта API, который, в свою
очередь, вызывает метод Twitter API trends/closest1.

12.11.2. Получение списка актуальных тем
Метод trends_place объекта API вызывает метод Twitter Trends API trends/
place2 для получения первых 50 актуальных тем для места с заданным WOEID.
Значения WOEID можно получить из атрибута woeid каждого словаря, возвращаемого методами trends_available или trends_closest из предыдущего
раздела, или же найти WOEID конкретного места, проведя поиск по названию
города, государства, страны, адресу, почтовому индексу или достопримечательностям на сайте:
http://www.woeidlookup.com

1

2

https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trendsclosest.
https://developer.twitter.com/en/docs/trends/trends-for-location/api-reference/get-trends-place.

548   Глава 12. Глубокий анализ данных Twitter
Также можно провести программный поиск WOEID при помощи веб-сервисов
Yahoo! из таких библиотек Python, как woeid1:
https://github.com/Ray-SunR/woeid

Общемировые актуальные темы
Получим список общемировых актуальных тем на сегодняшний день (ваши
результаты будут другими):
In [35]: world_trends = api.trends_place(id=1)

Метод trends_place возвращает одноэлементный список, который содержит
словарь. Ключ словаря 'trends' ссылается на список словарей, каждый из
которых представляет одну актуальную тему:
In [36]: trends_list = world_trends[0]['trends']

Каждый словарь актуальной темы содержит ключи name, url, promoted_content
(признак рекламного твита), query и tweet_volume (см. далее). Следующая актуальная тема пишется на испанском языке — #BienvenidoSeptiembre означает
«Добро пожаловать, сентябрь»:
In [37]: trends_list[0]
Out[37]:
{'name': '#BienvenidoSeptiembre',
'url': 'http://twitter.com/search?q=%23BienvenidoSeptiembre',
'promoted_content': None,
'query': '%23BienvenidoSeptiembre',
'tweet_volume': 15186}

Для актуальных тем с более чем 10 000 твитов tweet_volume содержит число
твитов; в противном случае оно равно None. Воспользуемся трансформацией
списка для его фильтрации, чтобы список содержал только актуальные темы
с более чем 10 000 твитов:
In [38]: trends_list = [t for t in trends_list if t['tweet_volume']]

Затем отсортируем актуальные темы по убыванию tweet_volume:
In [39]: from operator import itemgetter
In [40]: trends_list.sort(key=itemgetter('tweet_volume'), reverse=True)
1

Для этого, согласно документации модуля woeid, понадобится ключ API Yahoo!

12.11. Выявление тенденций: Twitter Trends API   549

Теперь выведем названия первых пяти актуальных тем:
In [41]: for trend in trends_list[:5]:
...:
print(trend['name'])
...:
#HBDJanaSenaniPawanKalyan
#BackToHogwarts
Khalil Mack
#ItalianGP
Alisson

Актуальные темы для Нью-Йорка
Получим первые пять актуальных тем для Нью-Йорка (WOEID 2459115). Этот
код выполняет те же операции, что и выше, но с другим значением WOEID:
In [42]: nyc_trends = api.trends_place(id=2459115)

# WOEID Нью-Йорка

In [43]: nyc_list = nyc_trends[0]['trends']
In [44]: nyc_list = [t for t in nyc_list if t['tweet_volume']]
In [45]: nyc_list.sort(key=itemgetter('tweet_volume'), reverse=True)
In [46]: for trend in nyc_list[:5]:
...:
print(trend['name'])
...:
#IDOL100M
#TuesdayThoughts
#HappyBirthdayLiam
NAFTA
#USOpen

12.11.3. Создание словарного облака
по актуальным темам
В главе 11 библиотека WordCloud использовалась для построения словарных
облаков. Сейчас мы снова используем ее для визуализации актуальных тем
Нью-Йорка, содержащих более 10 000 твитов. Начнем с создания словаря
пар «ключ-значение», содержащих названия актуальных тем и значений
tweet_volume:
In [47]: topics = {}
In [48]: for trend in nyc_list:
...:
topics[trend['name']] = trend['tweet_volume']
...:

550   Глава 12. Глубокий анализ данных Twitter
Создадим объект WordCloud на основе пар «ключ-значение» словаря topics,
а затем сохраним словарное облако в графическом файле TrendingTwitter.png (см.
иллюстрацию после кода). Аргумент prefer_horizontal=0.5 предполагает,
что 50% слов должны выводиться по горизонтали, хотя программа может
проигнорировать его для размещения содержимого:
In [49]: from wordcloud import WordCloud
In [50]: wordcloud = WordCloud(width=1600, height=900,
...:
prefer_horizontal=0.5, min_font_size=10, colormap='prism',
...:
background_color='white')
...:
In [51]: wordcloud = wordcloud.fit_words(topics)
In [52]: wordcloud = wordcloud.to_file('TrendingTwitter.png')

Полученное словарное облако показано ниже — ваш результат будет отличаться в зависимости от состава актуальных тем на день выполнения кода:

12.12. Очистка / предварительная обработка
твитов для анализа
Очистка данных — одна из самых распространенных задач, выполняемых
специалистами data science. В зависимости от того, как вы собираетесь обрабатывать твиты, вам придется использовать обработку естественного языка для
их нормализации с выполнением операций очистки данных (всех или части) из
следующей таблицы. Многие операции могут выполняться с использованием
библиотек, представленных в главе 11.

12.12. Очистка / предварительная обработка твитов для анализа   551

Операции очистки данных
ØØПриведение всего текста к одному регистру.
ØØУдаление символа # из хештегов.
ØØУдаление @-упоминаний.
ØØУдаление дубликатов.
ØØУдаление лишних пропусков.
ØØУдаление хештегов.
ØØУдаление знаков препинания.
ØØУдаление игнорируемых слов.
ØØУдаление RT (ретвит) и FAV (избранное).
ØØУдаление URL-адресов.
ØØВыделение основы.
ØØЛемматизация.
ØØРазбиение на лексемы.

Библиотека tweet-preprocessor и вспомогательные
функции TextBlob
В этом разделе библиотека tweet-preprocessor:
https://github.com/s/preprocessor

будет использоваться для выполнения базовой очистки данных твитов. Она
может автоматически удалять любые комбинации следующих элементов:
ØØURL-адреса;
ØØ@-упоминания (например, @nasa);
ØØхештеги (например, #mars);
ØØзарезервированные слова Twitter (например, RT = ретвит или FAV = из-

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

552   Глава 12. Глубокий анализ данных Twitter
В табл. 12.4 перечислены константы модуля, представляющие каждый из
вариантов.
Таблица 12.4. Константы модуля, представляющие варианты базовой очистки
данных твитов
Вариант

Константа

@-упоминания (например, @nasa)

OPT.MENTION

Эмодзи

OPT.EMOJI

Хештеги (например, #mars)

OPT.HASHTAG

Числа

OPT.NUMBER

Зарезервированные слова (RT и FAV)

OPT.RESERVED

Смайлики

OPT.SMILEY

URL

OPT.URL

Установка tweet-preprocessor
Для установки tweet-preprocessor откройте приглашение Anaconda (Windows),
терминал (macOS/Linux) или командную оболочку (Linux) и выполните
команду:
pip install tweet-preprocessor

Возможно, пользователям Windows придется запустить приглашение
Anaconda с правами администратора для получения необходимых привилегий для установки программного обеспечения. Щелкните правой кнопкой
мыши на команде Anaconda Prompt в меню Пуск и выберите команду MoreRun
as administrator.

Очистка твитов
Выполним базовую очистку твита, использующегося в последующем примере этой главы. Библиотеке tweet-preprocessor соответствует имя модуля
preprocessor. Документация рекомендует импортировать модуль следующим
образом:
In [1]: import preprocessor as p

12.13. Twitter Streaming API   553

Чтобы выбрать используемые режимы очистки, используйте функцию set_
options модуля. В данном примере мы хотим удалить URL и зарезервированные слова Twitter:
In [2]: p.set_options(p.OPT.URL, p.OPT.RESERVED)

А теперь очистим твит, содержащий зарезервированное слово (RT) и URLадрес:
In [3]: tweet_text = 'RT A sample retweet with a URL https://nasa.gov'
In [4]: p.clean(tweet_text)
Out[4]: 'A sample retweet with a URL'

12.13. Twitter Streaming API
Бесплатный Twitter Streaming API динамически передает вашему приложению
случайно выбранные твиты по мере их появления — до 1% твитов в день. По
данным InternetLiveStats.com, в секунду пишется приблизительно 6000 твитов, или свыше 500 миллионов твитов в день1. Таким образом, Streaming API
предоставляет доступ приблизительно к 5 миллионам твитов в день. Когдато сервис Twitter предоставлял бесплатный доступ к 10% потоковых твитов,
но сейчас этот сервис доступен только на платной основе. В этом разделе мы
используем определение класса и сеанс IPython для описания основных этапов обработки потоковых твитов. Обратите внимание: для получения потока
твитов необходимо создать пользовательский класс, наследующий от другого
класса. Эти темы, напомним, рассмотрены в главе 10.

12.13.1. Создание подкласса StreamListener
Streaming API возвращает появляющиеся твиты, подходящие по критерию
поиска. Вместо того чтобы подключаться к Twitter при каждом вызове метода,
поток использует постоянное подключение для отправки твитов вашему приложению. Скорость, с которой поступают твиты, очень сильно изменяется —
это зависит от критерия поиска. Чем популярнее тема, тем больше вероятность
того, что твиты будут поступать быстро.
Теперь создайте подкласс класса Tweepy StreamListener для обработки потока твитов. Объектом этого класса является слушатель, оповещаемый о по1

http://www.internetlivestats.com/twitter-statistics/.

554   Глава 12. Глубокий анализ данных Twitter
ступлении каждого нового твита (или другого сообщения, отправленного
Twitter1). Для каждого сообщения, отправляемого Twitter, вызывается метод
StreamListener. В табл. 12.5 приведена сводка нескольких таких методов.
StreamListener уже определяет каждый метод, так что вы переопределяете
только те методы, которые вам нужны, — это называется переопределением.
За информацией о других методах StreamListener обращайтесь по адресу:
https://github.com/tweepy/tweepy/blob/master/tweepy/streaming.py
Таблица 12.5. Сводка методов StreamListener
Метод

1

Описание

on_connect(self)

Вызывается при успешном подключении к потоку
Twitter. Используется для команд, которые должны
выполняться, только если приложение подключено
к потоку

on_status(self, status)

Вызывается при поступлении твита — status является объектом класса Tweepy Status

on_limit(self, track)

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

on_error(self, status_code)

Вызывается в ответ на отправку Twitter кодов ошибок

on_timeout(self)

Вызывается при тайм-ауте подключения, то есть
если сервер Twitter не отвечает

on_warning(self, notice)

Вызывается, если Twitter отправляет предупреждение об отключении, которое указывает, что подключение может быть закрыто. Например, Twitter
поддерживает очередь твитов, отправляемых вашему
приложению. Если приложение читает твиты
недостаточно быстро, аргумент notice функции
on_warning будет содержать сообщение с предупреждением о том, что подключение будет разорвано при
переполнении очереди

За подробной информацией о сообщениях обращайтесь по адресу https://developer.twitter.
com/en/docs/tweets/filter-realtime/guides/streaming-message-types.html.

12.13. Twitter Streaming API   555

Класс TweetListener
Наш подкласс StreamListener , которому присвоено имя TweetListener ,
определяется в файле tweetlistener.py. Рассмотрим компоненты TweetListener.
Строка 6 означает, что класс TweetListener является подклассом tweepy.
StreamListener, то есть наш новый класс содержит реализации по умолчанию
для методов StreamListener.
1
2
3
4
5
6
7
8

# tweetlistener.py
"""Подкласс tweepy.StreamListener для обработки поступающих твитов."""
import tweepy
from textblob import TextBlob
class TweetListener(tweepy.StreamListener):
"""Обрабатывает входной поток твитов."""

Класс TweetListener: метод __init__
В следующих строках определяется метод __init__ класса TweetListener,
который вызывается при создании нового объекта TweetListener. Параметр
api содержит объект Tweepy API, используемый TweetListener для взаимодействия с Twitter. Параметр limit содержит общее количество обрабатываемых
твитов (10 по умолчанию). Этот параметр добавлен для того, чтобы вы могли
управлять количеством получаемых твитов. Как вы вскоре увидите, при достижении этого ограничения поток завершается. Если присвоить limit значение None, то поток не будет завершаться автоматически. Строка 11 создает
переменную экземпляра для отслеживания количества твитов, обработанных
до настоящего момента, а строка 12 создает константу для хранения этого
ограничения. Если вы не знакомы с методами __init__ и super() из предыдущих глав, то строка 13 гарантирует, что объект api правильно сохранен для
использования объектом слушателя.
9
10
11
12
13
14

def __init__(self, api, limit=10):
"""Создает переменные экземпляров для отслеживания количества твитов."""
self.tweet_count = 0
self.TWEET_LIMIT = limit
super().__init__(api) # вызывает версию суперкласса

Класс TweetListener: метод on_connect
Метод on_connect вызывается при успешном подключении вашего приложения к потоку Twitter. Реализация по умолчанию переопределяется для вывода
сообщения «Connection successful».

556   Глава 12. Глубокий анализ данных Twitter
15
16
17
18
19

def on_connect(self):
"""Вызывается при успешной попытке подключения, чтобы вы могли
выполнить соответствующие операции приложения в этот момент."""
print('Connection successful\n')

Класс TweetListener: метод on_status
Метод on_status вызывается Tweepy при каждом поступлении твита. Во втором параметре этого метода передается объект Tweepy Status, представляющий
твит. В строках 23–26 извлекается текст твита. Сначала мы предполагаем,
что твит использует новое 280-символьное ограничение длины, и поэтому
пытаемся обратиться к свойству твита extended_tweet и получить его полный
текст full_text. Если твит не содержит свойства extended_tweet, то происходит исключение. В этом случае вместо него будет получено свойство text.
В строках 28–30 выводится экранное имя screen_name пользователя, отправившего твит, язык твита (lang) и tweet_text. Если язык отличен от английского
('en'), то в строках 32–33 объект TextBlob используется для перевода твита
и его вывода на английском языке. Мы увеличиваем self.tweet_count (строка 36), после чего сравниваем его с self.TWEET_LIMIT в команде return. Если
on_status возвращает True, то поток остается открытым. Если же on_status
возвращает False, то Tweepy отсоединяется от потока.
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

def on_status(self, status):
"""Вызывается, когда Twitter отправляет вам новый твит."""
# Получение текста твита
try:
tweet_text = status.extended_tweet.full_text
except:
tweet_text = status.text
print(f'Screen name: {status.user.screen_name}:')
print(f'
Language: {status.lang}')
print(f'
Status: {tweet_text}')
if status.lang != 'en':
print(f' Translated: {TextBlob(tweet_text).translate()}')
print()
self.tweet_count += 1

# Счетчик обработанных твитов

# При достижении TWEET_LIMIT вернуть False, чтобы завершить
# работу с потоком
return self.tweet_count != self.TWEET_LIMIT

12.13. Twitter Streaming API   557

12.13.2. Запуск обработки потока
Воспользуемся сеансом IPython для тестирования нового класса TweetListener.

Аутентификация
Начнем с аутентификации Twitter и создания объекта Tweepy API:
In [1]: import tweepy
In [2]: import keys
In [3]: auth = tweepy.OAuthHandler(keys.consumer_key,
...:
keys.consumer_secret)
...:
In [4]: auth.set_access_token(keys.access_token,
...:
keys.access_token_secret)
...:
In [5]: api = tweepy.API(auth, wait_on_rate_limit=True,
...:
wait_on_rate_limit_notify=True)
...:

Создание TweetListener
Затем создадим объект класса TweetListener и инициализируем его объектом api:
In [6]: from tweetlistener import TweetListener
In [7]: tweet_listener = TweetListener(api)

Аргумент limit не задан, поэтому TweetListener завершит работу после
10 твитов.

Создание Stream
Объект Tweepy Stream управляет подключением к потоку Twitter и передает
сообщения TweetListener. Ключевой аргумент auth конструктора Stream
получает свойство auth объекта api, содержащее ранее настроенный объект
OAuthHandler. Ключевой аргумент listener получает объект слушателя:
In [8]: tweet_stream = tweepy.Stream(auth=api.auth,
...:
listener=tweet_listener)
...:

558   Глава 12. Глубокий анализ данных Twitter

Запуск потока твитов
Метод filter объекта Stream начинает процесс потоковой передачи. Займемся
отслеживанием твитов о марсоходах NASA. В следующем примере параметр
track используется для передачи списка поисковых критериев:
In [9]: tweet_stream.filter(track=['Mars Rover'], is_async=True)

Streaming API возвращает полные JSON-объекты для твитов, подходящих
по критерию, не только в тексте твита, но также в @-упоминаниях, хештегах,
расширенных URL и другой информации, поддерживаемой Twitter в данных
JSON объекта твита. Следовательно, вы можете не увидеть искомые критерии,
если будете просматривать только текст твита.

Асинхронные и синхронные потоки
Аргумент is_async=True означает, что фильтр должен инициировать асинхронный поток твитов. Это позволяет программе продолжить выполнение, пока
слушатель ожидает получения твитов; данная возможность может пригодиться,
если вы решите заранее завершить поток. При выполнении асинхронного потока
твитов в IPython вы увидите следующее приглашение In [] и сможете завершить поток твитов, присваивая свойству running объекта Stream значение False:
tweet_stream.running=False

Без аргумента is_async=True фильтр запускает синхронный поток твитов.
В этом случае IPython выведет приглашение In [] после завершения потока.
Асинхронные потоки особенно полезны в GUI-приложениях, чтобы в процессе поступления твитов пользователи могли продолжать работу с другими
частями приложения. Ниже приведена часть вывода, состоящая из двух твитов:
Connection successful
Screen name: bevjoy:
Language: en
Status: RT @SPACEdotcom: With Mars Dust Storm Clearing, Opportunity
Rover Could Finally Wake Up https://t.co/OIRP9UyB8C https://t.co/
gTfFR3RUkG
Screen name:
Language:
Status:
she urgently
calling ou…
...

tourmaline1973:
en
RT @BennuBirdy: Our beloved Mars rover isn't done yet, but
needs our support! Spread the word that you want to keep

12.14. Анализ эмоциональной окраски твитов   559

Другие параметры метода filter
Метод filter также имеет другие параметры для уточнения критерия поиска
твитов по идентификаторам пользователей Twitter (для отслеживания твитов
от конкретных пользователей) и местоположению. Подробности — по адресу:
https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/basic-streamparameters

Ограничения Twitter
Маркетологи, исследователи и другие специалисты часто сохраняют твиты,
полученные от Streaming API. Twitter требует, чтобы при сохранении твитов
удалялись все сообщения или данные местоположения, для которых было
получено сообщение об удалении. Это происходит, если пользователь удалил твит или данные местоположения после того, как Twitter отправил этот
твит вам. В этом случае будет вызван метод on_delete вашего слушателя. За
информацией о правилах удаления и подробностях сообщений обращайтесь
по адресу:
https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-messagetypes

12.14. Анализ эмоциональной окраски твитов
В главе 11 был продемонстрирован анализ эмоциональной окраски для текста.
Многие аналитики и компании выполняют анализ эмоциональной окраски
твитов. Например, политические аналитики могут проверить эмоциональную
окраску во время выборов, чтобы понять, как люди относятся к конкретным политикам и темам. Компании могут проверять эмоциональную окраску твитов,
чтобы понять, что люди говорят об их продуктах и продуктах конкурентов.
В этом разделе мы воспользуемся методами, представленными ранее, для создания сценария (sentimentlistener.py), который позволяет проверить эмоциональную окраску конкретной темы. Сценарий подсчитывает сумму обработанных
положительных, отрицательных и нейтральных твитов и выводит результаты.
Сценарий получает два аргумента командной строки, представляющие тему
получаемых твитов и количество твитов, для которых должна проверяться
эмоциональная окраска, — подсчет ведется только для тех твитов, которые не
были исключены. Для вирусных тем характерно большое количество ретвитов,

560   Глава 12. Глубокий анализ данных Twitter
которые мы не подсчитываем, поэтому для получения заданного количества
твитов может потребоваться некоторое время. Сценарий запускается из каталога ch12 следующей командой:
ipython sentimentlistener.py football 10

Примерный вывод команды приведен ниже. Положительные твиты помечены
знаком +, отрицательные — знаком -, а нейтральные — пробелом:
- ftblNeutral: Awful game of football. So boring slow hoofball complete
waste of another 90 minutes of my life that I'll never get back #BURMUN
+ TBulmer28: I've seen 2 successful onside kicks within a 40 minute span.
I love college football
+ CMayADay12: The last normal Sunday for the next couple months. Don't
text me don't call me. I am busy. Football season is finally here?
rpimusic: My heart legitimately hurts for Kansas football fans
+ DSCunningham30: @LeahShieldsWPSD It's awsome that u like college
football, but my favorite team is ND - GO IRISH!!!
damanr: I'm bummed I don't know enough about football to roast
@samesfandiari properly about the Raiders
+ jamesianosborne: @TheRochaSays @WatfordFC @JackHind Haha.... just when
you think an American understands Football.... so close. Wat…
+ Tshanerbeer: @PennStateFball @PennStateOnBTN Ah yes, welcome back
college football. You've been missed.
- cougarhokie: @hokiehack @skiptyler I can verify the badness of that
football
+ Unite_Reddevils: @Pablo_di_Don Well make yourself clear it's football
not soccer we follow European football not MLS soccer
Tweet sentiment for "football"
Positive: 6
Neutral: 2
Negative: 2

Сценарий (sentimentlistener.py) приведен ниже. Мы подробно рассмотрим только
новую функциональность этого примера.

12.14. Анализ эмоциональной окраски твитов   561

Импортирование
Строки 4–8 импортируют файл keys.py и библиотеки, использованные в сценарии:
1
2
3
4
5
6
7
8
9

# sentimentlisener.py
"""Сценарий для поиска твитов, соответствующих строке поиска,
и суммирования количества положительных, отрицательных и нейтральных твитов."""
import keys
import preprocessor as p
import sys
from textblob import TextBlob
import tweepy

Класс SentimentListener: метод __init__
Кроме объекта API, обеспечивающего взаимодействие с Twitter, метод __init__
получает еще три параметра:
ØØsentiment_dict — словарь для хранения счетчиков эмоциональной окра-

ски;
ØØtopic — искомая тема, которая должна присутствовать в тексте твита;
ØØlimit — количество обрабатываемых твитов (без учета исключаемых тви-

тов).
Каждое из этих значений сохраняется в текущем объекта SentimentListener
(self).
10 class SentimentListener(tweepy.StreamListener):
11
"""Обрабатывает входной поток твитов."""
12
13
def __init__(self, api, sentiment_dict, topic, limit=10):
14
"""Инициализирует объект SentimentListener."""
15
self.sentiment_dict = sentiment_dict
16
self.tweet_count = 0
17
self.topic = topic
18
self.TWEET_LIMIT = limit
19
20
# Настройка tweet-preprocessor для удаления URL/зарезервированных слов
21
p.set_options(p.OPT.URL, p.OPT.RESERVED)
22
super().__init__(api) # вызывает версию суперкласса
23

562   Глава 12. Глубокий анализ данных Twitter

Метод on_status
При получении твита метод on_status:
ØØполучает текст твита (строки 27–30);
ØØпропускает твит в том случае, если он является ретвитом (строки 33–34);
ØØпроводит очистку твита с удалением URL и зарезервированных слов, та-

ких как RT и FAV (строка 36);
ØØпропускает твит, если искомая тема не входит в текст твита (строки 39–40);
ØØиспользует объект TextBlob для проверки эмоциональной окраски твита

и обновляет sentiment_dict (строки 43–52);
ØØвыводит текст твита (строка 55) с префиксом + для положительной эмо-

циональной окраски, пробелом для нейтральной или – для отрицательной, а также проверяет, было ли обработано заданное количество твитов
(строки 57–60).
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

def on_status(self, status):
"""Вызывается, когда Twitter отправляет вам новый твит."""
# Получение текста твита
try:
tweet_text = status.extended_tweet.full_text
except:
tweet_text = status.text
# Ретвиты игнорируются
if tweet_text.startswith('RT'):
return
tweet_text = p.clean(tweet_text)

# Очистка твита

# Игнорировать твит, если тема не входит в текст твита
if self.topic.lower() not in tweet_text.lower():
return
# Обновить self.sentiment_dict данными полярности
blob = TextBlob(tweet_text)
if blob.sentiment.polarity > 0:
sentiment = '+'
self.sentiment_dict['positive'] += 1
elif blob.sentiment.polarity == 0:
sentiment = ' '
self.sentiment_dict['neutral'] += 1
else:
sentiment = '-'
self.sentiment_dict['negative'] += 1

12.14. Анализ эмоциональной окраскитвитов   563
53
54
55
56
57
58
59
60
61

# Вывести твит
print(f'{sentiment} {status.user.screen_name}: {tweet_text}\n')
self.tweet_count += 1

# Счетчик обработанных твитов

# При достижении TWEET_LIMIT вернуть False, чтобы завершить работу
# с потоком
return self.tweet_count != self.TWEET_LIMIT

Основное приложение
Основное приложение определяется в функции main (строки 62–87; см. описание после листинга), которая вызывается в строках 90–91 при выполнении
файла как сценария. Это позволяет импортировать sentimentlistener.py в IPython
или другие модули для использования класса SentimentListener, как было
сделано с TweetListener в предыдущем разделе:
62 def main():
63
# Настройка OAuthHandler
64
auth = tweepy.OAuthHandler(keys.consumer_key, keys.consumer_secret)
65
auth.set_access_token(keys.access_token, keys.access_token_secret)
66
67
# Получить объект API
68
api = tweepy.API(auth, wait_on_rate_limit=True,
69
wait_on_rate_limit_notify=True)
70
71
# Создать объект подкласса StreamListener
72
search_key = sys.argv[1]
73
limit = int(sys.argv[2]) # Количество отслеживаемых твитов
74
sentiment_dict = {'positive': 0, 'neutral': 0, 'negative': 0}
75
sentiment_listener = SentimentListener(api,
76
sentiment_dict, search_key, limit)
77
78
# Создание Stream
79
stream = tweepy.Stream(auth=api.auth, listener=sentiment_listener)
80
81
# Начать фильтрацию твитов на английском языке, содержащих search_key
82
stream.filter(track=[search_key], languages=['en'], is_async=False)
83
84
print(f'Tweet sentiment for "{search_key}"')
85
print('Positive:', sentiment_dict['positive'])
86
print(' Neutral:', sentiment_dict['neutral'])
87
print('Negative:', sentiment_dict['negative'])
88
89 # Вызвать main, если файл выполняется как сценарий
90 if __name__ == '__main__':
91
main()

564   Глава 12. Глубокий анализ данных Twitter
Строки 72–73 получают аргументы командной строки. Строка 74 создает
словарь sentiment_dict для хранения данных эмоциональной окраски твитов.
В строках 75–76 создается объект SentimentListener. Строка 79 создает объект Stream. Поток, как и прежде, запускается вызовом метода filter класса
Stream (строка 82). Тем не менее в этом примере используется синхронный
поток, чтобы в строках 84–87 отчет об эмоциональной окраске выводился
только после обработки заданного количества твитов (limit). При вызове
filter также передается ключевой аргумент languages, в котором задается
список кодов языков. Единственный код языка 'en' означает, что Twitter
должен вернуть только твиты на английском языке.

12.15. Геокодирование и вывод информации
на карте
В этом разделе мы соберем потоковые твиты, а затем выведем для них данные
местоположения. Большинство твитов не включает данные широты и долготы, потому что Twitter по умолчанию отключает эту возможность для всех
пользователей. Тот, кто захочет включить в твит свое точное местоположение,
должен сознательно включить эту возможность. Хотя в большинстве твитов
точная информация о местоположении отсутствует, значительная их часть
включает данные о нахождении дома пользователя; впрочем, даже здесь часто
содержится недействительная информация — скажем, «Где-то далеко» или
название вымышленного места из любимого фильма пользователя.
В целях упрощения мы далее будем использовать свойство location объекта
User для нанесения местоположения пользователя на интерактивную карту,
позволяющую изменять масштаб и перетаскивать изображение, чтобы просматривать другие области. Для каждого твита на карте будет отображаться
маркер; если щелкнуть на нем, то появится временное окно с экранным именем
пользователя и текстом твита.
Ретвиты и твиты, не содержащие искомой темы, будут игнорироваться. Для
других твитов мы будем отслеживать процент твитов, содержащих информацию
о местоположении. При получении данных широты и долготы также будет отслеживаться процент твитов с недействительными данными местоположения.

Библиотека geopy
Библиотека geopy (https://github.com/geopy/geopy) будет использоваться для преобразования информации местоположения в широту и долготу (этот процесс

12.15. Геокодирование и вывод информации на карте   565

называется геокодированием), чтобы маркеры можно было разместить на карте.
Библиотека поддерживает десятки веб-сервисов геокодирования, многие из
которых имеют бесплатные или упрощенные уровни доступа. В данном примере будет использоваться сервис геокодирования OpenMapQuest (см. далее).
Библиотека geopy была установлена в разделе 12.6.

OpenMapQuest
Мы используем API геокодирования OpenMapQuest для преобразования местоположения (например, Boston, MA) в широту и долготу (например, 42,3602534
и –71,0582912) для нанесения на карту. В настоящее время OpenMapQuest разрешает выполнить на бесплатном уровне до 15 000 операций в месяц.
Чтобы пользоваться сервисом, сначала зарегистрируйтесь по адресу:
https://developer.mapquest.com/

После регистрации перейдите по адресу:
https://developer.mapquest.com/user/me/apps

Затем щелкните на кнопке Create a New Key, введите в поле App Name любое
имя на ваш выбор, оставьте поле Callback URL пустым и щелкните на кнопке
Create App для создания ключа API. Затем щелкните на имени приложения
на веб-странице, чтобы просмотреть ключ пользователя. Сохраните ключ
пользователя в файле keys.py, использованном ранее в этой главе; замените
ВашКлюч в строке
mapquest_key = 'ВашКлюч'

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

Библиотека Library и библиотека Leaflet.js
Для работы с картами в этом примере задействовалась библиотека folium:
https://github.com/python-visualization/folium,

использующая популярную картографическую JavaScript-библиотеку Leaflet.
js для вывода карт. Карты, построенные folium, сохраняются в файлах HTML,
которые можно просматривать в браузере. Чтобы установить folium, выполните команду:
pip install folium

566   Глава 12. Глубокий анализ данных Twitter

Карты OpenStreetMap.org
По умолчанию Leaflet.js использует находящиеся в свободном доступе карты
OpenStreetMap.org, права на которые принадлежат участникам одноименного
проекта. Чтобы использовать эти карты1, вы должны включить следующее
уведомление об авторском праве:
Map data © OpenStreetMap contributors

В условиях использования сервиса указано:
Вы обязаны ясно заявить, что данные предоставляются на условиях лицензии
Open Database License. Для этого можно предоставить ссылку «Лицензия» или
«Условия», ведущую на
www.openstreetmap.org/copyright или www.opendatacommons.org/licenses/odbl.

12.15.1. Получение твитов и нанесение их на карту
Выполним интерактивную разработку кода вывода местоположения твитов. Для этого будут использоваться вспомогательные функции из файла
tweetutilities.py и класс LocationListener из locationlistener.py. Вспомогательные
функции и класс будут более подробно рассмотрены в последующих разделах.

Получение объекта API
Как и в остальных примерах работы с потоками, выполним аутентификацию
Twitter и получим объект Tweepy API. На этот раз будет использоваться вспомогательная функция get_API из tweetutilities.py:
In [1]: from tweetutilities import get_API
In [2]: api = get_API()

Коллекции, необходимые для LocationListener
Нашему классу LocationListener требуются две коллекции: список (tweets)
для хранения собранных твитов и словарь (counts) для хранения общего количества собранных твитов и количества твитов с данными местоположения:
In [3]: tweets = []
In [4]: counts = {'total_tweets': 0, 'locations': 0}
1

https://wiki.osmfoundation.org/wiki/Licence/Licence_and_Legal_FAQ.

12.15. Геокодирование и вывод информации на карте   567

Создание LocationListener
В данном примере LocationListener собирает 50 твитов по теме 'football':
In [5]: from locationlistener import LocationListener
In [6]: location_listener = LocationListener(api, counts_dict=counts,
...:
tweets_list=tweets, topic='football', limit=50)
...:

LocationListener при помощи вспомогательной функции get_tweet_content

извлекает из каждого твита экранное имя, текст твита и местоположение и помещает эти данные в словарь.

Настройка и запуск потока твитов
Создадим объект Stream для поиска англоязычных твитов по теме 'football':
In [7]: import tweepy
In [8]: stream = tweepy.Stream(auth=api.auth, listener=location_listener)
In [9]: stream.filter(track=['football'], languages=['en'], is_async=False)

Дождитесь получения твитов. Хотя здесь результаты не показаны (для экономии места), LocationListener выводит для каждого твита экранное имя и текст,
чтобы вы видели живой поток твитов. Если твиты не появляются (например,
если сейчас не футбольный сезон), то завершите предыдущий фрагмент нажатием Ctrl + C и попробуйте снова с другим условием поиска.

Вывод статистики местоположения
Когда появится следующее приглашение In [], вы сможете проверить количество обработанных твитов, количество твитов с данными местоположения
и их процент:
In [10]: counts['total_tweets']
Out[10]: 63
In [11]: counts['locations']
Out[11]: 50
In [12]: print(f'{counts["locations"] / counts["total_tweets"]:.1%}')
79.4%

При запуске 79,4% твитов содержали данные местоположения.

568   Глава 12. Глубокий анализ данных Twitter

Геокодирование местоположения
Теперь воспользуемся вспомогательной функцией get_geocodes из tweetutilities.
py для геокодирования местоположения каждого твита из списка tweets:
In [13]: from tweetutilities import get_geocodes
In [14]: bad_locations = get_geocodes(tweets)
Getting coordinates for tweet locations...
OpenMapQuest service timed out. Waiting.
OpenMapQuest service timed out. Waiting.
Done geocoding

Иногда при использовании сервиса геокодирования OpenMapQuest происходит тайм-аут; это означает, что запрос не может быть обработан немедленно, и вам придется попробовать снова. В этом случае функция get_geocodes
выводит сообщение, делает короткую паузу, а затем снова пытается выдать
запрос геокодирования.
Как вы вскоре увидите, для каждого твита с действительным местоположением функция get_geocodes добавляет в словарь твитов в списке tweets два
новых ключа — 'latitude' и 'longitude'. Функция использует координаты
из твита, возвращенные OpenMapQuest.

Вывод статистики некорректных данных местоположения
Когда на экране появится следующее приглашение In [], вы сможете проверить процент твитов с некорректными данными местоположения:
In [15]: bad_locations
Out[15]: 7
In [16]: print(f'{bad_locations / counts["locations"]:.1%}')
14.0%

В данном случае из 50 твитов с данными местоположения 7 (14%) имели недействительные данные местоположения.

Очистка данных
До нанесения местоположения твита на карту воспользуемся коллекцией
Pandas DataFrame и выполним очистку данных. При создании DataFrame на
базе списка tweets коллекция будет содержать значение NaN в свойствах
'latitude' и 'longitude' любого твита, не имеющего действительного ме-

12.15. Геокодирование и вывод информации на карте   569

стоположения. Подобные строки можно удалить вызовом метода dropna
коллекции DataFrame:
In [17]: import pandas as pd
In [18]: df = pd.DataFrame(tweets)
In [19]: df = df.dropna()

Создание объекта Map
Создадим объект folium Map, на который будут наноситься данные местоположения твитов:
In [20]: import folium
In [21]: usmap = folium.Map(location=[39.8283, -98.5795],
...:
tiles='Stamen Terrain',
...:
zoom_start=5, detect_retina=True)
...:

Ключевой аргумент location задает последовательность с широтой и долготой центральной точки карты. Приведенные значения соответствуют географическому центру США (http://bit.ly/CenterOfTheUS). Возможно, у некоторых
твитов данные местоположения будут находиться за границами США. В этом
случае они не будут видны изначально при открытии карты. Вы можете изменять масштаб при помощи кнопок + и – в левом верхнем углу карты или
же панорамировать карту, перетаскивая изображение мышью, для просмотра
любой точки мира.
Ключевой аргумент zoom_start задает исходный масштаб карты; при меньших
значениях на карте помещается большая часть мира, а при больших — меньшая.
В нашей системе при масштабе 5 выводится вся континентальная часть США.
Ключевой аргумент detect_retina позволяет folium обнаруживать экраны
высокого разрешения. В этом случае folium запрашивает с OpenStreetMap.
org карты высокого разрешения, а уровень масштаба изменяется соответствующим образом.

Создание временных окон для местоположения твитов
На следующем этапе переберем DataFrame и добавим на объект Map объекты
folium Popup, содержащие текст каждого твита. В данном случае мы используем метод itertuples для создания кортежа на базе каждой строки DataFrame.
Каждый кортеж содержит свойство для каждого столбца DataFrame:

570   Глава 12. Глубокий анализ данных Twitter
In [22]: for t in df.itertuples():
...:
text = ': '.join([t.screen_name, t.text])
...:
popup = folium.Popup(text, parse_html=True)
...:
marker = folium.Marker((t.latitude, t.longitude),
...:
popup=popup)
...:
marker.add_to(usmap)
...:

Сначала создается строка (text) с экранным именем пользователя screen_name
и текстом твита (text), разделенными двоеточием. Эта строка будет выводиться на карте при щелчке на соответствующем маркере. Вторая команда создает
объект folium Popup для вывода текста. Третья команда создает объект folium
Marker с использованием кортежа для определения широты и долготы маркера.
Ключевой аргумент popup связывает объект Popup для твита с новым объектом
Marker. Наконец, последняя команда вызывает метод add_to объекта Marker,
чтобы задать объект Map, на котором должен отображаться маркер.

Сохранение карты
Последним шагом становится вызов метода save объекта Map для сохранения
карты в HTML-файле; двойной щелчок на этом файле откроет его в браузере:
In [23]: usmap.save('tweet_map.html')

Ниже приведена полученная карта (на вашей карте расположение маркеров
будет другим):

12.15. Геокодирование и вывод информации на карте   571

12.15.2. Вспомогательные функции tweetutilities.py
В этом разделе будут представлены вспомогательные функции get_tweet_
content и get_geo_codes, использованные в сеансе IPython из предыдущего
раздела. В каждом случае номера строк начинаются с 1 для удобства обсуждения. Обе функции определяются в файле tweetutilities.py, включенном в каталог
примеров ch12.

Вспомогательная функция get_tweet_content
Функция get_tweet_content получает объект Status (tweet) и создает словарь
с экранным именем твита (строка 4), текстом (строки 7–10) и местоположением (строки 12–13). Местоположение включается только в том случае, если
ключевой аргумент location равен True. Для текста твита попытаемся использовать свойство full_text объекта extended_tweet. Если оно недоступно, то
используется свойство text:
1 def get_tweet_content(tweet, location=False):
2
"""Возвращает словарь с данными из твита (объект Status)."""
3
fields = {}
4
fields['screen_name'] = tweet.user.screen_name
5
6
# Получение текста твита
7
try:
8
fields['text'] = tweet.extended_tweet.full_text
9
except:
10
fields['text'] = tweet.text
11
12
if location:
13
fields['location'] = tweet.user.location
14
15
return fields

Вспомогательная функция get_geocodes
Функция get_geocodes получает список словарей, содержащих твиты и геокоды их местоположений. Если геокодирование для твита прошло успешно,
то функция добавляет широту и долготу в словарь твита в tweet_list. Для
выполнения этого кода необходим класс OpenMapQuest из модуля geopy, который импортируется в файл tweetutilities.py следующим образом:
from geopy import OpenMapQuest
1 def get_geocodes(tweet_list):
2
"""Получает широту и долготу для местоположения каждого твита.

572   Глава 12. Глубокий анализ данных Twitter
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

Возвращает количество твитов с недействительными данными местоположения."""
print('Getting coordinates for tweet locations...')
geo = OpenMapQuest(api_key=keys.mapquest_key) # Геокодер
bad_locations = 0
for tweet in tweet_list:
processed = False
delay = .1 # Используется, если происходит тайм-аут OpenMapQuest
while not processed:
try: # Получить координаты для tweet['location']
geo_location = geo.geocode(tweet['location'])
processed = True
except: # Тайм-аут, сделать паузу перед повторной попыткой
print('OpenMapQuest service timed out. Waiting.')
time.sleep(delay)
delay += .1
if geo_location:
tweet['latitude'] = geo_location.latitude
tweet['longitude'] = geo_location.longitude
else:
bad_locations += 1 # Значение tweet['location'] недействительно
print('Done geocoding')
return bad_locations

Функция работает следующим образом:
ØØСтрока 5 создает объект OpenMapQuest, используемый для геокодирова-

ния местоположений. Ключевой аргумент api_key загружается из файла
keys.py, который был отредактирован ранее.
ØØСтрока 6 инициализирует переменную bad_locations, используемую для

отслеживания количества недействительных местоположений в собранных объектах твитов.
ØØВ цикле строки 9–18 пытаются выполнить геокодирование местополо-

жения текущего твита. Иногда сервис геокодирования OpenMapQuest
отказывает по тайм-ауту, что указывает на его временную недоступность.
Также это может произойти при выдаче слишком большого количества запросов. Цикл while продолжает выполняться, пока переменная processed
равна False. При каждой итерации цикл вызывает метод geocode объекта OpenMapQuest с передачей строки местоположения твита в аргументе.
В случае успеха processed присваивается значение True и цикл завершается. В противном случае строки 16–18 выводят сообщение о тайм-ауте,
делается пауза продолжительностью delay секунд и задержка увеличива-

12.15. Геокодирование и вывод информации на карте   573

ется на случай возникновения следующего тайм-аута. Строка 17 вызывает метод sleep модуля time стандартной библиотеки Python для приостановки выполнения кода.
ØØПосле завершения цикла while строки 20–24 проверяют, были ли возвра-

щены данные местоположения, и если да, то они добавляются в словарь
твита. В противном случае строка 24 увеличивает счетчик bad_locations.
ØØНаконец, функция выводит сообщение о завершении геокодирования

и возвращает значение bad_locations.

12.15.3. Класс LocationListener
Класс LocationListener выполняет многие операции, продемонстрированные
в предшествующих примерах работы с потоками, поэтому мы сосредоточимся
на отдельных строках класса:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# locationlistener.py
"""Получает твиты, соответствующие искомой строке, и сохраняет список
словарей с экранным именем/текстом/местоположением каждого твита."""
import tweepy
from tweetutilities import get_tweet_content
class LocationListener(tweepy.StreamListener):
"""Обрабатывает входной поток твитов для получения данных местоположения."""
def __init__(self, api, counts_dict, tweets_list, topic, limit=10):
"""Настройка LocationListener."""
self.tweets_list = tweets_list
self.counts_dict = counts_dict
self.topic = topic
self.TWEET_LIMIT = limit
super().__init__(api) # Вызов версии суперкласса
def on_status(self, status):
"""Вызывается, когда Twitter отправляет вам новый твит."""
# Получить экранное имя, текст и местоположение каждого твита.
tweet_data = get_tweet_content(status, location=True)
# Игнорировать ретвиты и твиты, не содержащие темы.
if (tweet_data['text'].startswith('RT') or
self.topic.lower() not in tweet_data['text'].lower()):
return
self.counts_dict['total_tweets'] += 1

# Исходный твит

574   Глава 12. Глубокий анализ данных Twitter
30
31
32
33
34
35
36
37
38
39

# Игнорировать твиты без данных местоположения.
if not status.user.location:
return
self.counts_dict['locations'] += 1 # Твит с местоположением.
self.tweets_list.append(tweet_data) # Сохранить твит.
print(f'{status.user.screen_name}: {tweet_data["text"]}\n')
# При достижении TWEET_LIMIT вернуть False, чтобы завершить работу
# с потоком
return self.counts_dict['locations'] != self.TWEET_LIMIT

В этом случае метод __init__ получает словарь counts, используемый для
отслеживания общего количества обработанных твитов, и список tweet_list,
в котором хранятся словари, возвращаемые вспомогательной функцией
get_tweet_content.
Метод on_status:
ØØВызывает get_tweet_content для получения экранного имени и местопо-

ложения каждого твита.
ØØИгнорирует твит, если он является ретвитом или его текст не содержит

искомой темы — при подсчете используются только исходные твиты, содержащие искомую строку.
ØØУвеличивает значение ключа 'total_tweets' в словаре counts на 1 для

подсчета количества исходных твитов.
ØØИгнорирует твиты без данных местоположения.
ØØУвеличивает значение ключа 'locations' в словаре counts на 1 для под-

счета количества твитов c данными местоположения.
ØØПрисоединяет к tweets_list словарь tweet_data, возвращенный вызовом

get_tweet_content.
ØØВыводит экранное имя и текст твита, показывая, что приложение не висит.
ØØПроверяет, было ли достигнуто ограничение TWEET_LIMIT, и если да, то

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

12.16. Способы хранения твитов
Предназначенные для анализа твиты обычно сохраняются в следующих
форматах:

12.18. Итоги   575
ØØCSV-файлы — формат CSV был представлен в главе 9.
ØØКоллекции Pandas DataFrame в памяти — CSV-файлы легко загружаются

в DataFrame для очистки и обработки.
ØØБазы данных SQL, такие как MySQL, — бесплатная реляционная система

управления базами данных (РСУБД) с открытым кодом.
ØØБазы данных NoSQL — Twitter возвращает твиты в форме документов

JSON и результаты будет хранить в документной базе данных JSON
NoSQL (например, MongoDB). Обычно Tweepy скрывает работу с JSON
от разработчика. Если вы предпочитаете работать с JSON напрямую, то
используйте методы, описанные в главе 16, когда мы займемся изучением
библиотеки PyMongo.

12.17. Twitter и временные ряды
Временной ряд представляет собой серию значений с временными метками.
Примеры временных рядов — котировки на момент закрытия операций на
бирже, ежедневные измерения температуры в заданной местности, количество
ежемесячно появляющихся рабочих мест в США, квартальная прибыль компании и т. д. Твиты отлично подходят для анализа временных рядов, потому
что они снабжены временными метками. В главе 14 метод простой линейной
регрессии будет использоваться для прогнозирования временных рядов. Мы,
кроме того, вернемся к временным рядам в главе 15 при обсуждении рекуррентных нейронных сетей.

12.18. Итоги
Эта глава была посвящена глубокому анализу данных Twitter — пожалуй,
самой открытой и доступной из всех социальных сетей и одним из самых
распространенных источников больших данных. Вы создали учетную запись
разработчика Twitter и подключились к Twitter с регистрационными данными своей учетной записи. Мы обсудили ограничения частоты использования
Twitter и некоторые дополнительные правила, а также важность их соблюдения.
Далее описано представление твита в формате JSON. Мы использовали
Tweepy — один из самых популярных клиентов Twitter API — для аутентификации Twitter и обращения к различным API. Было показано, что твиты,

576   Глава 12. Глубокий анализ данных Twitter
возвращаемые Twitter API, наряду с текстом твита содержат большое количество метаданных. Вы узнали, как определить подписчиков учетной записи
и ее друзей и как получить последние твиты пользователя.
Объект Tweepy Cursor был использован для удобного получения последовательных страниц результатов от различных Twitter API. Мы использовали
Twitter Search API для загрузки последних твитов, удовлетворяющих заданному критерию. Twitter Streaming API был использован для подключения к потоку твитов в процессе их появления, а Twitter Trends API — для определения
актуальных тем для различных мест; на основании информации о темах было
построено словарное облако.
Библиотека tweet-preprocessor была использована для очистки и предварительной обработки твитов в ходе подготовки их к анализу, после чего мы
провели анализ эмоциональной окраски твитов. При помощи библиотеки
folium была построена интерактивная карта местоположения твитов с возможностью просмотра твитов для конкретного места. Также мы рассмотрели
стандартные способы хранения твитов и отметили, что твиты представляют
собой естественную форму данных временных рядов. В следующей главе будет
представлен суперкомпьютер IBM Watson и его возможности когнитивных
вычислений.

13
IBM Watson
и когнитивные
вычисления
В этой главе…
•• Диапазон сервисов Watson и использование уровня Lite для бесплатного
знакомства с ними.
•• Демонстрация сервисов Watson.
•• Концепция когнитивных вычислений и их интеграция в ваши приложения.
•• Регистрация учетной записи IBM Cloud и получение регистрационных данных для использования различных сервисов.
•• Установка Watson Developer Cloud Python SDK для взаимодействия с сервисами Watson.
•• Разработка приложения-переводчика Python с построением гибрида сервисов Watson Speech to Text, Language Translator и Text to Speech.
•• Дополнительные ресурсы, упрощающие самостоятельную разработку приложений Watson.

578   Глава 13. IBM Watson и когнитивные вычисления

13.1. Введение: IBM Watson
и когнитивные вычисления
В главе 1 были рассмотрены некоторые ключевые достижения IBM в области
искусственного интеллекта, включая победу над двумя сильнейшими игроками Jeopardy! в матче с призовым фондом в миллион долларов. Суперкомпьютер Watson выиграл соревнование, а компания IBM пожертвовала призовые
деньги на благотворительность. Для поиска ответов суперкомпьютер Watson
одновременно выполнял сотни алгоритмов языкового анализа для поиска
правильных ответов в 200 миллионах страниц контента (включая всю «Википедию»), для хранения которых требовалось 4 терабайта пространства1,2.
Исследователи IBM обучали Watson с использованием методов машинного
обучения (рассматривается в следующей главе3) и обучения с подкреплением.
На ранней стадии работы над книгой мы признали стремительно растущую
важность Watson и поэтому установили оповещения Google Alerts для Watson
и сопутствующих тем. По этим оповещениям, рассылкам и блогам, которые мы
отслеживали, были собраны свыше 900 статей, документов и видеороликов, относящихся к Watson. Мы проанализировали много конкурирующих сервисов
и пришли к выводу, что политика Watson в стиле «кредитка необязательна»
и бесплатный уровень сервиса Lite4 наиболее удобны для разработчиков, желающих даром поэкспериментировать с сервисами Watson.
IBM Watson — облачная платформа когнитивных вычислений, применяемая
в широком спектре реальных сценариев. Системы когнитивных вычислений моделируют возможности распознавания закономерностей и принятия
решений человеческого мозга для «обучения» в ходе потребления больших
объемов данных5,6,7. Мы приведем сводку широкого спектра веб-сервисов
Watson, практические примеры ее использования и продемонстрируем многие
возможности платформы. В таблице на следующей странице представлены
некоторые примеры использования Watson в организациях.
1

2
3
4

5
6
7

https://www.techrepublic.com/article/ibm-watson-the-inside-story-of-how-the-jeopardy-winningsupercomputer-was-born-and-what-it-wants-to-do-next/.
https://en.wikipedia.org/wiki/Watson_(computer).
https://www.aaai.org/Magazine/Watson/watson.php, AI Magazine, осень 2010.

Всегда проверяйте новейшую версию условий на сайте IBM, так как условия и сервисы
могут изменяться.
http://whatis.techtarget.com/definition/cognitive-computing.
https://en.wikipedia.org/wiki/Cognitive_computing.
https://www.forbes.com/sites/bernardmarr/2016/03/23/what-everyone-should-know-about-cognitivecomputing.

13.1. Введение: IBM Watson и когнитивные вычисления   579

Watson предлагает замечательный набор функциональных возможностей,
которые могут быть встроены в приложения. В этой главе мы создадим учетную запись IBM Cloud1 и воспользуемся уровнем Lite и демонстрационными
примерами IBM Watson для экспериментов с различными веб-сервисами:
переводом естественного языка, преобразованием речи в текст и текста в речь,
пониманием естественных языков, чат-ботами, анализом текста на тональность
и распознаванием визуальных объектов в графике и видео. Далее будет приведен краткий обзор других сервисов и инструментов Watson.
Примеры практического применения Watson

Автономные автомобили

Музыка

Адресная реклама

Обработка естественного языка

Анализ голоса

Обработка изображений

Анализ эмоциональной окраски
и настроения

Образование

Безопасность рабочего места

Поддержка клиентов

Виртуальная реальность

Понимание естественного языка

Выявление виртуального запугивания

Предотвращение мошенничества

Выявление вредоносных программ

Прогнозное техническое обслуживание

Выявление угроз
Генетика
Диалоговый интерфейс

1

Перевод на другие языки

Прогнозирование погоды
Профилактика преступности
Разработка лекарств
Распознавание лиц

Дополненная реальность

Распознавание объектов

Дополненный интеллект

Рекомендации продуктов

Здравоохранение

Роботы и беспилотные аппараты

Искусственный интеллект

Спорт

Когнитивные вычисления

Субтитры

Компьютерные игры

Умные дома

Личные помощники

Управление цепочкой поставок

Машинное обучение

Финансы

Медицинская визуализация

Чат-боты

Медицинская диагностика и лечение

IoT (интернет вещей)

Технология IBM Cloud ранее называлась Bluemix. Во многих URL-адресах этой главы
все еще встречается название «bluemix».

580   Глава 13. IBM Watson и когнитивные вычисления
Для программного доступа к сервисам Watson из кода Python следует установить пакет Watson Developer Cloud Python Software Development Kit (SDK).
Затем в качестве практического примера мы разработаем приложение-переводчик, для чего построим гибрид нескольких сервисов Watson. Приложение
позволяет англоязычным и испаноязычным пользователям устно общаться
друг с другом, невзирая на языковые барьеры. Для этого аудиозаписи на
английском (испанском) языке переводятся в текст, который переводится
на второй язык, после чего на основании переведенного текста приложение
синтезирует английское и испанское аудио.
Сервисы Watson образуют динамичный, непрерывно расширяющийся набор
функциональных возможностей. В то время, когда мы работали над книгой,
появлялись новые сервисы, а существующие сервисы неоднократно обновлялись и/или удалялись. Описание сервисов Watson и выполняемых действий
было точным на момент написания книги. Обновления будут публиковаться
по мере необходимости на странице книги на сайте www.deitel.com.

13.2. Учетная запись IBM Cloud
и консоль Cloud
Для работы с сервисами Watson уровня Lite вам понадобится бесплатная
учетная запись IBM Cloud. На веб-странице с описанием каждого сервиса
перечислены его возможности на разных уровнях и указано, какие возможности предоставляет каждый уровень. Хотя возможности сервисов уровня
Lite ограниченны, обычно они достаточны для того, чтобы вы познакомились
с функциями Watson и начали разрабатывать приложения. Ограничения постоянно изменяются, и вместо упоминания их в тексте приводятся ссылки
на веб-страницы сервисов. Кстати, во время написания книги компания IBM
серьезно ослабила ограничения на многие сервисы. Платные уровни доступны для использования в коммерческих приложениях. Чтобы создать
бесплатную учетную запись IBM Cloud, выполните инструкции по адресу:
https://console.bluemix.net/docs/services/watson/index.html#about

Вы получите сообщение по электронной почте. Выполните содержащиеся
в нем инструкции для подтверждения учетной записи. После этого вы получите доступ к консоли IBM Cloud и сможете выйти на панель управления
Watson по адресу:
https://console.bluemix.net/developer/watson/dashboard

13.3. Сервисы Watson   581

Панель управления позволяет:
ØØПросмотреть список сервисов Watson.
ØØПодключиться к сервисам, для использования которых вы уже зареги-

стрированы.
ØØПросмотреть различные ресурсы для разработчиков, включая документа-

цию Watson, SDK и различные ресурсы для изучения Watson.
ØØПросмотреть приложения, созданные вами с использованием Watson.

После регистрации вы получите данные для использования различных сервисов Watson. Для просмотра и управления списком сервисов и ваших регистрационных данных используется панель управления IBM Cloud по адресу
(этот список можно открыть и щелчком по ссылке Existing Services на панели
управления Watson):
https://console.bluemix.net/dashboard/apps

13.3. Сервисы Watson
В этом разделе приведен обзор многих сервисов Watson и ссылки на их подробные описания. Непременно запустите демонстрационные приложения,
чтобы увидеть сервисы в действии. Ссылки на документации сервисов Watson
и справочник API доступны по адресу:
https://console.bluemix.net/developer/watson/documentation

В сносках приводятся ссылки на подробные описания всех сервисов. Когда вы
будете готовы использовать тот или иной сервис, щелкните по кнопке Create
на ее описании, чтобы создать свои регистрационные данные.

Watson Assistant
Сервис Watson Assistant1 помогает строить чат-ботов и виртуальных помощников, упрощающих взаимодействие пользователей с текстом на естественном языке. IBM предоставляет веб-интерфейс, при помощи которого можно
обучить сервис Watson Assistant для конкретных сценариев, относящихся
к вашему приложению. Например, метеорологический чат-бот можно научить
1

https://console.bluemix.net/catalog/services/watson-assistant-formerly-conversation.

582   Глава 13. IBM Watson и когнитивные вычисления
отвечать на запросы типа: «Покажи прогноз погоды в Нью-Йорке». В сервисе
клиентской поддержки можно построить чат-бот, отвечающий на вопросы
клиентов и при необходимости направляющий их в нужный отдел. Примеры
взаимодействий такого рода можно увидеть на сайте:
https://www.ibm.com/watson/services/conversation/demo/index.html#demo

Visual Recognition
Сервис Visual Recognition1 позволяет приложениям находить и воспринимать
информацию в форме графики и видео, включая цвета, объекты, лица, текст,
еду и неподходящий контент. IBM предоставляет готовые модели (используемые в демонстрационном приложении сервиса), но вы также можете обучить
и использовать собственную модель (см. главу 15). Опробуйте демонстрационное приложение с готовыми изображениями или загрузите собственную
графику:
https://watson-visual-recognition-duo-dev.ng.bluemix.net/

Speech to Text
Сервис Speech to Text2, используемый при построении приложения этой главы,
преобразует речевые аудиофайлы в текстовую запись. Сервису можно передать ключевые слова для «прослушивания», а он сообщит, где эти ключевые
слова были обнаружены, какова вероятность совпадения и где было совпадение
в аудио. Сервис может различать голоса разных людей и использоваться для
реализации голосовых приложений, преобразования живого аудио в текст
и т. д. Опробуйте демоприложение с готовыми аудиоклипами или загрузите
собственное аудио здесь:
https://speech-to-text-demo.ng.bluemix.net/

Text to Speech
Сервис Text to Speech3, также используемый при построении приложения этой
главы, синтезирует речь по тексту. В текст можно включать инструкции на
языке SSML (Speech Synthesis Markup Language) для управления голосовыми
1
2
3

https://console.bluemix.net/catalog/services/visual-recognition.
https://console.bluemix.net/catalog/services/speech-to-text.
https://console.bluemix.net/catalog/services/text-to-speech.

13.3. Сервисы Watson   583

модуляциями, ритмом, тональностью и т. д. В настоящее время сервис поддерживает английский язык (США и Великобритания), французский, немецкий, итальянский, испанский, португальский и японский языки. Опробуйте
демоприложение с готовым простым текстом, текстом с включениями SSML
и вашим собственным текстом здесь:
https://text-to-speech-demo.ng.bluemix.net/

Language Translator
Сервис Language Translator1, также используемый при построении приложения
этой главы, решает две ключевые задачи:
ØØперевод текста на другие языки;
ØØраспознавание текста, написанного на одном из более чем шестидесяти

языков.
Перевод поддерживается между английским и множеством других языков,
а также между другими языками. Попробуйте перевести текст на другие
языки здесь:
https://language-translator-demo.ng.bluemix.net/

Natural Language Understanding
Сервис Natural Language Understanding2 анализирует текст и выдает информацию с общей эмоциональной окраской и ключевыми словами, ранжированными по релевантности. Среди прочего, сервис может обнаруживать:
ØØлюдей, места, должности, организации, компании и количества;
ØØкатегории и концепции (спорт, правительство, политика и т. д.);
ØØчасти речи (например, глаголы).

Сервис также можно обучить с ориентацией на специфику конкретной отрасли, конкретного применения и т. д., с Watson Knowledge Studio (см. ниже).
Опробуйте демоприложение с готовым простым текстом, скопированным
текстом или со ссылкой на статью или документ в интернете здесь:
https://natural-language-understanding-demo.ng.bluemix.net/
1
2

https://console.bluemix.net/catalog/services/language-translator.
https://console.bluemix.net/catalog/services/natural-language-understanding.

584   Глава 13. IBM Watson и когнитивные вычисления

Discovery
Сервис Watson Discovery1 поддерживает ряд тех же возможностей, что и Natural
Language Understanding, предоставляя также корпоративные средства хранения и управления документами. Например, организации могут использовать
Watson Discovery для хранения всех своих текстовых документов и применять средства обработки естественного языка ко всей коллекции. Опробуйте
демоприложение сервиса, предоставляющее возможность поиска компаний
по текстовым новостям:
https://discovery-news-demo.ng.bluemix.net/

Personality Insights
Сервис Personality Insights2 анализирует текст на предмет индивидуальных черт.
Согласно описанию, сервис помогает «помочь получить представление о том,
как и почему люди думают, действуют и чувствуют именно так, а не иначе. Этот
сервис использует лингвистическую аналитику и теорию индивидуальности
для получения информации об атрибутах личности по неструктурированному
тексту автора». Полученная информация может использоваться для предоставления адресной рекламы людям, которые с наибольшей вероятностью
приобретут эти продукты. Опробуйте следующее демоприложение с твитами
от различных учетных записей Twitter, документами, встроенными в приложение, скопированными текстовыми документами или вашей собственной
учетной записью Twitter здесь:
https://personality-insights-livedemo.ng.bluemix.net/

Tone Analyzer
Сервис Tone Analyzer3 анализирует текст на тональность в трех категориях:
ØØэмоции — гнев, отвращение, страх, радость, печаль;
ØØсоциальные предрасположенности — открытость, добросовестность, до-

брожелательность и эмоциональный диапазон;
ØØстиль общения — аналитический, уверенный, осторожный.

1
2
3

https://console.bluemix.net/catalog/services/discovery.
https://console.bluemix.net/catalog/services/personality-insights.
https://console.bluemix.net/catalog/services/tone-analyzer.

13.3. Сервисы Watson   585

Анализ тональности проводится на уровне документа либо отдельных предложений. Опробуйте следующее демоприложение с примерами твитов, тестовым
обзором продукта, сообщением электронной почты или предоставленным
вами текстом здесь:
https://tone-analyzer-demo.ng.bluemix.net/

Natural Language Classifier
Сервис Natural Language Classifier1 обучается на предложениях и фразах, специфичных для конкретной области применения, классифицируя предложения
и фразы. Например, фразу «У меня возникли проблемы с вашим продуктом»
можно отнести к категории «Техническая поддержка», а фразу «Мне прислали неправильный счет» — к категории «Выставление счетов». Обучив
классификатор, вы сможете передавать сервису предложения и фразы, а затем использовать функциональность когнитивных вычислений Watson и ваш
классификатор для получения оптимальных классификаций и вероятностей
совпадения. Полученные классификации и вероятности могут использоваться
для определения следующих действий приложения. Так, если в приложении
технической поддержки пользователь обращается с вопросом о конкретном
продукте, то вы можете воспользоваться сервисом Speech to Text для преобразования вопроса в текст, использовать сервис Natural Language Classifier
для классификации текста, а затем передать обращение подходящему специалисту или отделу. Этот сервис не предоставляет уровень Lite. В следующем
демонстрационном приложении введите вопрос о погоде — сервис сообщит,
относится ваш вопрос к температуре или погодным условиям:
https://natural-language-classifier-demo.ng.bluemix.net/

Синхронная и асинхронная функциональность
Многие API, описанные в книге, являются синхронными — при вызове функции или метода программа дожидается, пока функция или метод вернет
управление, перед переходом к следующей задаче. Асинхронная программа
может запустить задачу, продолжить выполнение других действий, затем получить уведомление о том, что исходная задача завершилась и вернула свои
результаты. Многие сервисы Watson предоставляют как синхронные, так
и асинхронные API.

1

https://console.bluemix.net/catalog/services/natural-language-classifier.

586   Глава 13. IBM Watson и когнитивные вычисления
Демонстрационное приложение Speech to Text — хороший пример асинхронных API. Оно обрабатывает аудиоролик с разговором двух людей. В процессе
перевода аудио в текст сервис возвращает промежуточные результаты, даже
если он еще не смог различить говорящих. Эти промежуточные результаты
выводятся параллельно с продолжением работы сервиса. Иногда в процессе
определения говорящих демонстрационное приложение выводит сообщение «Detecting speakers». В конечном итоге сервис отправляет обновленные
результаты, после чего приложение заменяет предыдущие результаты преобразования.
Асинхронные API с современными многоядерными компьютерами и компьютерными кластерами могут повысить быстродействие программ. Впрочем,
программирование с использованием асинхронных API обычно сложнее программирования с синхронными. При описании установки Watson Developer
Cloud Python SDK мы предоставим ссылку на примеры кода SDK на GitHub,
в которых представлены синхронные и асинхронные версии ряда сервисов. За
полной информацией обращайтесь к справочнику API конкретных сервисов.

13.4. Другие сервисы и инструменты
В этом разделе приведена сводка других сервисов и инструментов Watson.

Watson Studio
Watson Studio1 — новый интерфейс Watson для создания и управления проектами Watson и взаимодействия с участниками команды этих проектов. Он позволяет добавлять данные, готовить данные для анализа, создавать документы
Jupyter Notebook для взаимодействия с вашими данными, создавать и обучать
модели, а также использовать функциональность глубокого обучения Watson.
Watson Studio предоставляет однопользовательский уровень Lite. После того
как вы настроите свой доступ к Watson Studio Lite щелчком на ссылке Create
на веб-странице сервиса:
https://console.bluemix.net/catalog/services/data-science-experience,

вы сможете обратиться к Watson Studio по адресу:
https://dataplatform.cloud.ibm.com/

1

https://console.bluemix.net/catalog/services/data-science-experience.

13.4. Другие сервисы и инструменты   587

Watson Studio содержит ряд предварительно настроенных проектов1. Чтобы
просмотреть их, щелкните на ссылке Create a project:
ØØStandard — «Работа с любыми активами. Сервисы для аналитических ак-

тивов добавляются по мере необходимости».
ØØData Science — «Анализ данных для выявления закономерностей и обмен

результатами с другими».
ØØVisual Recognition — «Пометка и классификация визуального контента

с использованием сервиса Watson Visual Recognition».
ØØDeep Learning — «Построение нейронных сетей и применение моделей

глубокого обучения».
ØØModeler — «Построение потоков моделирования для обучения моделей

SPSS или проектирование нейронных сетей».
ØØBusiness Analytics — «Создание визуальных панелей управления на осно-

ве данных для ускорения извлечения информации».
ØØData Engineering — «Объединение, очистка, анализ и формирование дан-

ных с использованием Data Refinery».
ØØStreams Flow — «Поглощение и анализ потоковых данных с использова-

нием сервиса Streaming Analytics».

Knowledge Studio
Многие сервисы Watson работают с предварительно определенными моделями,
но они также предоставляют возможность передачи пользовательских моделей,
обученных для конкретных отраслей или применений. Сервис Watson Knowledge
Studio2 упрощает построение пользовательских моделей. Он позволяет корпоративным командам совместно работать над созданием и обучением новых
моделей, которые затем могут использоваться различными сервисами Watson.

Machine Learning
Сервис Watson Machine Learning3 позволяет подключать прогностическую
функциональность через популярные библиотеки машинного обучения,
1
2
3

https://dataplatform.cloud.ibm.com/.
https://console.bluemix.net/catalog/services/knowledge-studio.
https://console.bluemix.net/catalog/services/machine-learning.

588   Глава 13. IBM Watson и когнитивные вычисления
включая Tensorflow, Keras, scikit-learn и др. В следующих двух главах будут
использоваться scikit-learn и Keras.

Knowledge Catalog
Watson Knowledge Catalog1,2 — средство корпоративного уровня для управления безопасностью, поиска и обмена данными вашейорганизации, обеспечивающее:
ØØцентрализованный доступ к локальным и облачным данным организации,

а также к моделям машинного обучения;
ØØподдержку Watson Studio, посредством которой пользователи могут на-

ходить данные, работать с ними и использовать их в проектах машинного
обучения;
ØØполитики безопасности, гарантирующие, что доступ к данным будет пре-

доставляться только доверенным пользователям;
ØØподдержку более 100 операций очистки и первичной обработки данных

и т. д.

Cognos Analytics
Сервис IBM Cognos Analytics3, имеющий 30-дневный пробный период, использует AI и методы машинного обучения для выявления и визуализации
информации в данных без какого-либо программирования с вашей стороны.
Он также предоставляет интерфейс естественного языка: пользователь задает вопросы, на которые Cognos Analytics отвечает на основании знаний,
собранных в данных.

13.5. Watson Developer Cloud Python SDK
В этом разделе будут установлены модули для полнофункциональной реализации практического примера Watson из следующего раздела. Для удобства
программирования IBM предоставляет Watson Developer Cloud Python SDK (пакет разработки программного обеспечения). Модуль watson_developer_cloud
1
2
3

https://medium.com/ibm-watson/introducing-ibm-watson-knowledge-catalog-cf42c13032c1.
https://dataplatform.cloud.ibm.com/docs/content/catalog/overview-wkc.html.
https://www.ibm.com/products/cognos-analytics.

13.6. Практический пример: приложение-переводчик   589

содержит классы, используемые для взаимодействия с сервисами Watson.
Мы создадим объекты для каждого необходимого сервиса, после чего будем
взаимодействовать с сервисом посредством вызова методов этих объектов.
Для установки SDK1 откройте приглашение Anaconda (Windows; с правами
администратора), терминал (macOS/Linux) или командную оболочку (Linux),
после чего выполните следующую команду2:
pip install --upgrade watson-developer-cloud

Модули, необходимые для записи и воспроизведения аудио
Вам также понадобятся два дополнительных модуля для записи аудио
(PyAudio) и воспроизведения (PyDub). Чтобы установить их, введите следующие команды3:
pip install pyaudio
pip install pydub

Примеры SDK
На GitHub компания IBM предоставляет пример кода, демонстрирующий использование сервисов Watson на базе классов Watson Developer Cloud Python
SDK. Пример можно найти по адресу:
https://github.com/watson-developer-cloud/python-sdk/tree/master/examples

13.6. Практический пример:
приложение-переводчик
Допустим, вы путешествуете по испаноязычной стране, но не говорите на
испанском языке и хотите поговорить с человеком, который не знает английского. Благодаря приложению-переводчику вы сможете говорить на англий1

2

3

За подробными инструкциями по установке и советами по диагностике проблем обращайтесь по адресу https://github.com/watson-developer-cloud/python-sdk/blob/develop/README.
md.
Возможно, пользователям Windows придется установить средства сборки Microsoft C++
по адресу https://visualstudio.microsoft.com/visual-cpp-build-tools/, а затем установить модуль
watson-developer-cloud.
Возможно, пользователям Mac придется сначала выполнить команду conda install
-c conda-forge portaudio.

590   Глава 13. IBM Watson и когнитивные вычисления
ском языке; приложение переведет текст и воспроизведет его на испанском
языке. Собеседник сможет ответить, а приложение переведет и воспроизведет
ответ на английском языке. В реализации приложения-переводчика будут
задействованы три ключевых сервиса IBM Watson1. Приложение позволит
людям, говорящим на разных языках, общаться друг с другом практически
в реальном времени. Подобное объединение сервисов называется гибридизацией. Приложение также использует простые средства работы с файлами,
представленные в главе 9.

13.6.1. Перед запуском приложения
Для построения приложения будут использоваться (бесплатные) уровни Lite
нескольких сервисов IBM. Прежде чем запускать приложение, не забудьте зарегистрироваться для создания учетной записи IBM Cloud, как обсуждалось
ранее в этой главе, чтобы получить регистрационные данные для всех трех
сервисов, используемых приложением. Когда у вас появятся регистрационные
данные (см. ниже), загрузите их в файл keys.py (из каталога примеров ch13),
который импортируется в данном примере. Никогда никому не передавайте
ваши регистрационные данные.
В процессе настройки сервисов на странице регистрационных данных каждого
сервиса также указывается URL-адрес сервиса. Это URL-адреса по умолчанию,
используемые Watson Developer Cloud Python SDK, так что копировать их не
нужно. В разделе 13.6.3 будет представлен сценарий SimpleLanguageTranslator.py
и подробное описание кода.

Регистрация для сервиса Speech to Text
Приложение использует сервис Watson Speech to Text для перевода английских и испанских аудиофайлов в английский и испанский текст соответственно. Для взаимодействия с сервисом необходимо знать имя пользователя
и пароль. Для этого:
ØØСоздайте экземпляр сервиса: перейдите на страницу https://console.bluemix.
net/catalog/services/speech-to-text и щелкните на кнопке Create в нижней части

страницы. Кнопка автоматически генерирует ключ API и открывает учебное руководство по взаимодействию с сервисом Speech to Text.
1

В будущем эти сервисы могут измениться. В таком случае мы опубликуем обновления
на веб-странице книги по адресу http://www.deitel.com/books/IntroToPython.

13.6. Практический пример: приложение-переводчик   591
ØØПолучите регистрационные данные сервиса: чтобы просмотреть ключ API,
щелкните на кнопке Manage в левом верхнем углу страницы. Щелкните на
ссылке Show credentials справа от Credentials, скопируйте ключ API и вставьте его в переменную speech_to_text_key в файле keys.py из каталога примеров ch13.

Регистрация для сервиса Text to Speech
Приложение использует сервис Watson Text to Speech для синтеза речи по
тексту. Для использования сервиса необходимо иметь имя пользователя
и пароль. Для этого:
ØØСоздайте экземпляр сервиса: перейдите на страницу https://console.bluemix.
net/catalog/services/text-to-speech и щелкните на кнопке Create в нижней части

страницы. Кнопка автоматически генерирует ключ API и открывает учебное руководство по взаимодействию с сервисом Text to Speech.
ØØПолучите регистрационные данные сервиса: чтобы просмотреть ключ API,
щелкните на кнопке Manage в левом верхнем углу страницы. Щелкните на
ссылке Show credentials справа от Credentials, скопируйте ключ API и вставьте его в переменную text_to_speech_key в файле keys.py из каталога примеров ch13.

Регистрация для сервиса Language Translator
Приложение использует сервис Watson Language Translator для передачи
текста Watson и получения текста, переведенного на другой язык. Для использования этого сервиса необходимо получить ключ API. Для этого:
ØØСоздайте экземпляр сервиса: перейдите на страницу https://console.bluemix.
net/catalog/services/language-translator и щелкните на кнопке Create в нижней

части страницы. Кнопка автоматически генерирует ключ API и открывает
страницу для управления экземпляром сервиса.
ØØПолучите регистрационные данные сервиса: щелкните на ссылке Show
credentials справа от Credentials, скопируйте ключ API и вставьте его в переменную translate_key в файле keys.py из каталога примеров ch13.

Получение регистрационных данных
Чтобы просмотреть регистрационные данные в любой момент, щелкните на
вкладке сервиса по адресу:
https://console.bluemix.net/dashboard/apps

592   Глава 13. IBM Watson и когнитивные вычисления

13.6.2. Пробный запуск приложения
После включения регистрационных данных в сценарий откройте приглашение Anaconda (Windows), терминал (macOS/Linux) или командную
оболочку (Linux), после чего запустите сценарий1 следующей командой из
каталога ch13:
ipython SimpleLanguageTranslator.py

Обработка вопроса
Логика приложения состоит из 10 основных шагов, выделенных комментариями в коде. Шаг 1 запрашивает и сохраняет вопрос — приложение выводит
приглашение:
Press Enter then ask your question in English,

ожидая нажатия Enter. Когда клавиша нажата, приложение выводит сооб­
щение:
Recording 5 seconds of audio

Пользователь произносит вслух свой вопрос — например, «Where is the closest
bathroom?». Через 5 секунд приложение выводит следующее сообщение:
Recording complete

На шаге 2 приложение взаимодействует с сервисом Watson Speech to Text для
перевода записанного аудио в текст и выводит результат:
English: where is the closest bathroom

На шаге 3 приложение при помощи сервиса Watson Language Translator переводит английский текст на испанский язык и выводит перевод, возвращенный
Watson:
Spanish: ¿Dónde está el baño más cercano?

1

Модуль pydub.playback, используемый в приложении, выдает предупреждение при запуске сценария. Это предупреждение относится к функциональности модуля, которую
мы не используем, поэтому на него можно не обращать внимания. Чтобы избавиться от
предупреждения, можно установить ffmpeg для Windows, macOS или Linux с сайта https://
www.ffmpeg.org.

13.6. Практический пример: приложение-переводчик   593

Шаг 4 передает испанский текст сервису Watson Text to Speech для преобразования в аудиофайл.
Шаг 5 воспроизводит полученный аудиофайл на испанском языке.

Обработка ответа
Теперь можно переходить к обработке ответа испаноговорящего пользователя.
На шаге 6 выводится сообщение:
Press Enter then speak the Spanish answer

Приложение ожидает нажатия Enter. Когда клавиша нажата, приложение выводит сообщение:
Recording 5 seconds of audio

Собеседник произносит свой ответ на испанском языке. Мы не говорим на
испанском, поэтому сервис Watson Text to Speech используется для воспроизведения заранее записанного ответа «El baño más cercano estáen el restaurante».
Ответ воспроизводится достаточно громко, чтобы он мог быть записан с микрофона вашего компьютера. Мы предоставили заранее записанное аудио
в файле SpokenResponse.wav в каталоге ch13. Если вы используете этот файл,
то быстро воспроизведите его после нажатия Enter, потому что приложение
ведет запись в течение всего 5 секунд1. Чтобы аудиофайл загружался и воспроизводился достаточно быстро, его стоит воспроизвести до нажатия Enter
для начала записи. Через 5 секунд приложение выводит сообщение:
Recording complete

На шаге 7 приложение взаимодействует с сервисом Watson Speech to Text для
перевода испанской речи в текст и выводит результат:
Spanish response: el baño más cercano está en el restaurante

1

Для простоты мы настроили приложение так, чтобы запись велась в течение 5 секунд. Для
управления продолжительностью записи можно воспользоваться переменной SECONDS
в функции record_audio. Можно создать систему записи, которая начинает запись при
обнаружении звука и завершает после паузы определенной продолжительности, но это
приведет к усложнению кода.

594   Глава 13. IBM Watson и когнитивные вычисления
На шаге 8 приложение взаимодействует с сервисом Watson Language Translator
для перевода испанского текста на английский язык и выводит результат:
English response: The nearest bathroom is in the restaurant

Шаг 9 передает английский текст сервису Watson Text to Speech для преобразования в аудиофайл.
Шаг 10 воспроизводит полученный аудиофайл на английском языке.

13.6.3. Сценарий SimpleLanguageTranslator.py
В этом разделе будет представлен исходный код сценария SimpleLanguageTranslator.
py, который мы разделили на небольшие фрагменты с последовательной нумерацией. При этом мы воспользуемся нисходящим методом, как было сделано
в главе 3. Верхний уровень выглядит так:
Создание приложения-переводчика для общения собеседников, говорящих на
английском и испанском языке.
Первое уточнение выглядит так:
Перевод вопроса, заданного на английском языке, в испанскую речь.
Перевод ответа на испанском языке в английскую речь.
Первая строка второй стадии разбивается на пять шагов:
Шаг 1: запрос и запись английской речи в аудиофайл.
Шаг 2: перевод английской речи в английский текст.
Шаг 3: перевод английского текста в испанский текст.
Шаг 4: синтез испанской речи по тексту и сохранение ее в аудиофайле.
Шаг 5: воспроизведение аудиофайла с испанской речью.
Вторая строка второй стадии также разбивается на пять шагов:
Шаг 6: запрос и запись испанской речи в аудиофайл.
Шаг 7: перевод испанской речи в испанский текст.
Шаг 8: перевод испанского текста в английский текст.

13.6. Практический пример: приложение-переводчик   595

Шаг 9: синтез английского текста в английскую речь и сохранение ее в аудио­
файле.
Шаг 10: воспроизведение аудиофайла с английской речью.
Методология нисходящей разработки наглядно демонстрирует все преимущества метода «разделяй и властвуй», позволяя сосредоточиться на меньших
частях более серьезной проблемы.
В сценарии этого раздела мы реализуем 10 шагов, указанных при втором
уточнении. Шаги 2 и 7 используют сервис Watson Speech to Text, шаги 3 и 8 —
сервис Watson Language Translator и шаги 4 и 9 — сервис Watson Text to Speech.

Импортирование классов Watson SDK
В строках 4–6 импортируются классы из модуля watson_developer_cloud,
установленного с Watson Developer Cloud Python SDK. Каждый из этих
классов использует регистрационные данные Watson, полученные ранее для
взаимодействия с соответствующими сервисами Watson:
ØØКласс SpeechToTextV11 позволяет передать аудиофайл сервису Watson

Speech to Text и получить документ JSON2 с результатом преобразования
речи в текст.
ØØКласс LanguageTranslatorV3 позволяет передать текст сервису Watson

Language Translator и получить документ JSON с переведенным текстом.
ØØКласс TextToSpeechV1 позволяет передать текст сервису Watson Text to

Speech и получить аудиофайл с текстом на заданном языке.
1 # SimpleLanguageTranslator.py
2 """Использование API IBM Watson Speech to Text, Language Translator и Text
to Speech
3
для общения на английском и испанском языке."""
4 from watson_developer_cloud import SpeechToTextV1
5 from watson_developer_cloud import LanguageTranslatorV3
6 from watson_developer_cloud import TextToSpeechV1

1

2

V1 в имени класса обозначает номер версии сервиса. В процессе обновления версий
компания IBM добавляет новые классы в модуль watson_developer_cloud вместо изменения существующих классов. Это гарантирует, что существующие приложения
сохранят работоспособность при обновлении сервисов. На момент написания книги
сервисы Speech to Text и Text to Speech существовали в версии 1 (V1), а сервис Language
Translator service — в версии 3 (V3).
Формат JSON был описан в главе 12.

596   Глава 13. IBM Watson и когнитивные вычисления

Другие импортируемые модули
В строке 7 импортируется файл keys.py с регистрационными данными Watson.
Строки 8–11 импортируют модули, обеспечивающие функциональность обработки аудио нашего приложения:
ØØМодуль pyaudio позволяет записывать аудио с микрофона.
ØØМодули pydub и pydub.playback позволяют загружать и воспроизводить

аудиофайлы.
ØØМодуль wave стандартной библиотеки Python позволяет сохранять фай-

лы в формате WAV (Waveform Audio File Format) — популярном звуковом формате, разработанном компаниями Microsoft и IBM. Приложение
использует модуль wave для сохранения записанного аудио в файле .wav,
который отправляется сервису Watson Speech to Text для преобразования
в текст.
7 import keys
8
9
10
11
12

import
import
import
import

# Содержит ключи API для обращения к сервисам Watson

pyaudio # Используется для записи с микрофона
pydub # Используется для загрузки файла WAV
pydub.playback # Используется для воспроизведения файла WAV
wave # Используется для сохранения файла WAV

Основная программа: функция run_translator
Рассмотрим основную часть программы, определенную в функции run_
translator (строки 13–54), которая вызывает функции, определяемые далее
в сценарии. Для целей нашего обсуждения функция run_translator была
разбита на 10 шагов. На шаге 1 (строки 15–17) выводится сообщение на английском языке с предложением нажать Enter и произнести вопрос. Функция
record_audio записывает аудио в течение 5 секунд, после чего сохраняет его
в файле english.wav:
13 def run_translator():
14
"""Вызывает функции, взаимодействующие с сервисами Watson."""
15
# Шаг 1: запрос и запись английской речи в аудиофайл.
16
input('Press Enter then ask your question in English')
17
record_audio('english.wav')
18

На шаге 2 вызывается функция speech_to_text, которой передается файл
english.wav для преобразования в текст, с использованием предопределенной

13.6. Практический пример: приложение-переводчик   597

модели 'en-US_BroadbandModel'1. Затем приложение выводит полученный
текст:
19
20
21
22
23

# Шаг 2: перевод английской речи в английский текст.
english = speech_to_text(
file_name='english.wav', model_id='en-US_BroadbandModel')
print('English:', english)

На шаге 3 вызывается функция translate, которой для перевода передается
текст с шага 2. Сервис Language Translator используется для перевода текста
с использованием предопределенной модели 'en-es' для перевода с английского (en) языка на испанский (es), после чего выводится испанский перевод:
24
25
26
27

# Шаг 3: перевод английского текста в испанский текст.
spanish = translate(text_to_translate=english, model='en-es')
print('Spanish:', spanish)

На шаге 4 вызывается функция text_to_speech, которой передается испанский
текст с шага 3, чтобы сервис Text to Speech зачитал этот текст с использованием
голоса 'es-US_SofiaVoice'. Также указывается файл для сохранения аудио:
28
29
30
31

# Шаг 4: синтез испанской речи по тексту и сохранение ее в аудиофайле.
text_to_speech(text_to_speak=spanish, voice_to_use='es-US_SofiaVoice',
file_name='spanish.wav')

На шаге 5 вызывается функция play_audio для воспроизведения файла
'spanish.wav', содержащего испанское аудио для текста, полученного на
шаге 3.
32
33
34

# Шаг 5: воспроизведение аудиофайла с испанской речью.
play_audio(file_name='spanish.wav')

Наконец, шаги 6–10 повторяют то, что делалось в шагах 1–5, но уже для перевода речи на испанском языке в речь на английском языке. Шаг 6 записывает
аудио на испанском языке.
1

Для большинства языков сервис Watson Speech to Text поддерживает широковещательные
и узковещательные модели. Термины относятся к качеству звука: для аудиоматериалов,
записанных при 16 кГц и выше, IBM рекомендует использовать широковещательные
модели. В данном приложении аудио записывалось на частоте 44,1 кГц.

598   Глава 13. IBM Watson и когнитивные вычисления
Шаг 7 преобразует аудио на испанском языке в испанский текст с использованием сервиса предопределенной модели 'es-ES_BroadbandModel' сервиса
Speech to Text.
Шаг 8 переводит испанский текст в английский текст с использованием модели 'es-en' сервиса Language Translator.
Шаг 9 синтезирует английское аудио с использованием голоса 'en-US_
AllisonVoice' сервиса Text to Speech. Шаг 10 воспроизводит аудио на английском языке.
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# Шаг 6: запрос и запись испанской речи в аудиофайл.
input('Press Enter then speak the Spanish answer')
record_audio('spanishresponse.wav')
# Шаг 7: перевод испанской речи в испанский текст.
spanish = speech_to_text(
file_name='spanishresponse.wav', model_id='es-ES_BroadbandModel')
print('Spanish response:', spanish)
# Шаг 8: перевод испанского текста в английский текст.
english = translate(text_to_translate=spanish, model='es-en')
print('English response:', english)
# Шаг 9: синтез английского текста в английскую речь и сохранение
# ее в аудиофайле.
text_to_speech(text_to_speak=english,
voice_to_use='en-US_AllisonVoice',
file_name='englishresponse.wav')
# Шаг 10: воспроизведение аудиофайла с английской речью.
play_audio(file_name='englishresponse.wav')

А теперь реализуем функции, вызываемые в коде шагов 1–10.

Функция speech_to_text
Чтобы обратиться к сервису Watson Speech to Text, функция speech_to_text
(строки 56–87) создает объект SpeechToTextV1 с именем stt (сокращение от
«Speech To Text»); при этом в аргументе передается ключ API, созданный
ранее. Команда with (строки 62–65) открывает аудиофайл, заданный параметром file_name, и присваивает полученный объект файла переменной
audio_file. Режим открытия файла 'rb' означает, что мы собираемся читать
(r) двоичные данные (b) — аудиофайлы хранятся в виде набора байтов в дво-

13.6. Практический пример: приложение-переводчик   599

ичном формате. Затем строки 64–65 используют метод recognize объекта
SpeechToTextV1 для обращения к сервису Speech to Text. Метод получает
три ключевых аргумента:
ØØaudio — файл (audio_file), передаваемый сервису Speech to Text;
ØØcontent_type — тип содержимого файла; 'audio/wav' означает, что это

­аудиофайл, хранящийся в формате WAV1;
ØØmodel определяет модель разговорного языка, которая будет использо-

ваться сервисом для распознавания речи и преобразования ее в текст.
Приложение использует предопределенные модели — либо 'en-US_Broad­
bandModel' (для английского языка), либо 'es-ES_BroadbandModel' (для
испанского языка).
56 def speech_to_text(file_name, model_id):
57
"""Использует сервис Watson Speech to Text для преобразования аудиофайла
в текст."""
58
# Создать клиента Watson Speech to Text
59
stt = SpeechToTextV1(iam_apikey=keys.speech_to_text_key)
60
61
# Открыть аудиофайл
62
with open(file_name, 'rb') as audio_file:
63
# Передать файл Watson для преобразования
64
result = stt.recognize(audio=audio_file,
65
content_type='audio/wav', model=model_id).get_result()
66
67
# Получить список 'results'. Список может содержать промежуточные
68
# или окончательные результаты. Нас интересуют только окончательные
69
# результаты, поэтому список состоит из одного элемента.
70
results_list = result['results']
71
72
# Получить окончательный результат распознавания текста - единственный
# элемент списка.
73
speech_recognition_result = results_list[0]
74
75
# Получить список 'alternatives'. Список может содержать несколько
76
# альтернативных вариантов. Нам альтернативы не нужны, поэтому
77
# список состоит из одного элемента.
78
alternatives_list = speech_recognition_result['alternatives']
79
80
# Получить единственный альтернативный текст из alternatives_list.
81
first_alternative = alternatives_list[0]

1

Типы аудиовизуального содержимого ранее назывались типами MIME (Multipurpose
Internet Mail Extensions) — стандарт, определяющий форматы данных, которые могут
использоваться программами для правильной интерпретации данных.

600   Глава 13. IBM Watson и когнитивные вычисления
82
83
84
85
86
87
88

# Получить значение ключа 'transcript', содержащее результат
# преобразования аудио в текст.
transcript = first_alternative['transcript']
return transcript

# Вернуть текстовую запись аудио

Метод recognize возвращает объект DetailedResponse. Его метод getResult
возвращает документ JSON с преобразованным текстом, который будет сохранен в result. Разметка JSON выглядит примерно так (ее конкретный вид
зависит от заданного вопроса):
{
"results": [
{
"alternatives": [
{
"confidence": 0.983,
"transcript": "where is the closest bathroom "
}
],
"final": true
}
],
"result_index": 0

Строка
Строка
Строка
Строка

70
73
78
81

Строка 85

}

Разметка JSON содержит вложенные словари и списки. Чтобы упростить
понимание структуры данных, в строках 70–85 используются отдельные небольшие конструкции для «отделения» отдельных частей вплоть до получения
преобразованного текста ("where is the closest bathroom "), который будет
возвращен в итоге. Прямоугольники вокруг отдельных частей JSON и номера строк в этих прямоугольниках соответствуют командам в строках 70–85.
Команды работают следующим образом:
ØØСтрока 70 присваивает results_list список, связанный с ключом

'results':
results_list = result['results']

В зависимости от аргументов, переданных методу recognize, этот список
может содержать промежуточные или окончательные результаты. Промежуточные результаты могут быть полезны, например, при преобразовании

13.6. Практический пример: приложение-переводчик   601

в текст живого аудио (скажем, новостного репортажа). Мы запросили
только окончательные результаты, поэтому список состоит только из одного элемента1.
ØØСтрока 73 присваивает переменной speech_recognition_result итоговый

результат распознавания речи — единственный элемент results_list:
speech_recognition_result

= results_list[0]

ØØСтрока 78:
alternatives_list = speech_recognition_result['alternatives']

присваивает alternatives_list список, связанный с ключом 'alternatives'.
Этот список может содержать несколько альтернативных вариантов преобразования в зависимости от аргументов метода recognize. С переданными
аргументами будет получен список из одного элемента.
ØØСтрока

81

присваивает

first_alternative

единственный

элемент

alternatives_list:
first_alternative = alternatives_list[0]

ØØСтрока 85 присваивает transcript значение, связанное с ключом

'transcript', которое содержит результат преобразования аудио в текст:
transcript = first_alternative['transcript']

ØØНаконец, строка 87 возвращает результат преобразования аудио в текст.

Строки 70–85 можно заменить более компактной записью:
return result['results'][0]['alternatives'][0]['transcript']

Однако мы предпочитаем более простые команды.

Функция translate
Чтобы обратиться к сервису Watson Language Translator, функция translate
(строки 89–111) сначала создает объект LanguageTranslatorV3 с именем
language_translator ; в аргументах передается версия сервиса ( '20181

За подробной информацией об аргументах метода recognize и подробным описанием
ответов JSON обращайтесь по адресу https://www.ibm.com/watson/developercloud/speech-totext/api/v1/python.html?python#recognize-sessionless.

602   Глава 13. IBM Watson и когнитивные вычисления
05-31'1), созданный ранее ключ API и URL-адрес сервиса. В строках 93–94
метод translate объекта LanguageTranslatorV3 используется для обращения

к сервису Language Translator с передачей двух ключевых аргументов:
ØØtext — строка для перевода на другой язык;
ØØmodel_id — предопределенная модель, используемая сервисом Language

Translator для понимания исходного текста и перевода его на нужный
язык. В этом приложении будет использоваться одна из предопределенных моделей IBM — 'en-es' (с английского на испанский) или 'es-en'
(с испанского на английский).
89 def
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112

translate(text_to_translate, model):
"""Использует сервис Watson Language Translator для перевода с английского
на испанский (en-es) или наоборот (es-en) по правилам модели."""
# Создать клиента Watson Translator
language_translator = LanguageTranslatorV3(version='2018-05-31',
iam_apikey=keys.translate_key)
# Выполнить перевод
translated_text = language_translator.translate(
text=text_to_translate, model_id=model).get_result()
# Получить список 'translations'. Если аргумент text метода translate
# содержит несколько строк, список будет содержать несколько элементов.
# Мы передали одну строку, поэтому в списке один элемент.
translations_list = translated_text['translations']
# Получить единственный элемент translations_list
first_translation = translations_list[0]
# Получить значение для ключа 'translation', то есть переведенный текст.
translation = first_translation['translation']
return translation

# Вернуть переведенную строку

Метод возвращает объект DetailedResponse. Метод getResult этого объекта
возвращает документ JSON следующего вида:
1

По данным справочника API сервиса Language Translator, '2018-05-31' является строкой
текущей версии на момент написания книги. IBM изменяет строку версии только при
внесении изменений API, не обладающих обратной совместимостью. Но даже в этом
случае сервис ответит на ваши вызовы, используя версию API, заданную в строке версии.
За дополнительной информацией обращайтесь по адресу https://www.ibm.com/watson/
developercloud/language-translator/api/v3/python.html?python#versioning.

13.6. Практический пример: приложение-переводчик   603
{

}

"translations": [
{
"translation": "¿Dónde está el baño más cercano?"
}
],
"word_count": 5,
"character_count": 30

Строка 103
Строка 106
Строка 109

Разметка JSON, которую вы получите в ответе, зависит от заданного вопроса; как и в предыдущем случае, она содержит вложенные словари и списки.
В строках 103–109 небольшие конструкции используются для выбора переведенного текста "¿Dónde está el baño más cercano?". Прямоугольники вокруг
отдельных частей JSON и номера строк в этих прямоугольниках соответствуют
командам в строках 103–109. Команды работают следующим образом:
ØØСтрока 103 получает список 'translations':
translations_list = translated_text['translations']

Если аргумент text метода translate содержит несколько строк, то список
будет содержать несколько элементов. Мы передали только одну строку,
поэтому список содержит только один элемент.
ØØСтрока 106 получает единственный элемент translations_list:
first_translation = translations_list[0]

ØØСтрока 109 получает значение, связанное с ключом 'translation', то есть

переведенный текст:
translation = first_translation['translation']

ØØСтрока 111 возвращает переведенную строку.

Строки 103–109 можно заменить более компактной записью:
return translated_text['translations'][0]['translation']

Но как и в предыдущем случае, мы предпочитаем более простые команды.

Функция text_to_speech
Чтобы обратиться к сервису Watson Text to Speech, функция text_to_speech
(строки 113–122) создает объект TextToSpeechV1 с именем tts (сокращение

604   Глава 13. IBM Watson и когнитивные вычисления
от «Text To Speech»); при этом в аргументе передается ключ API, созданный
ранее. Команда with открывает аудиофайл, заданный параметром file_name,
и присваивает объект файла переменной audio_file . Режим открытия
файла 'wb' открывает файл для записи (w) в двоичном формате (b). В файл
будет записано содержимое аудиоданных, возвращенных сервисом Speech
to Text.
113 def text_to_speech(text_to_speak, voice_to_use, file_name):
114
"""Использует сервис Watson Text to Speech для преобразования текста
115
в речь и сохранения результата в файле WAV."""
116
# Создать клиента Text to Speech
117
tts = TextToSpeechV1(iam_apikey=keys.text_to_speech_key)
118
119
# Открыть файл и записать синтезированный аудиоконтент в файл
120
with open(file_name, 'wb') as audio_file:
121
audio_file.write(tts.synthesize(text_to_speak,
122
accept='audio/wav', voice=voice_to_use).get_result().content)
123

В строках 121–122 вызываются два метода. Сначала приложение обращается
к сервису Speech to Text вызовом метода synthesize объекта TextToSpeechV1;
методу передаются три аргумента:
ØØtext_to_speak — строка с произносимым текстом;
ØØaccept — ключевой аргумент с типом аудиоданных, которые должен вер-

нуть сервис Speech to Text; как и прежде, 'audio/wav' обозначает аудиофайл в формате WAV;
ØØvoice — ключевой аргумент, определяющий один из предопределенных

голосов сервиса Speech to Text. В нашем приложении для английской речи
будет использоваться голос 'en-US_AllisonVoice', а для испанской — голос 'es-US_SofiaVoice'. Watson предоставляет много мужских и женских
голосов для разных языков1.
Объект ответа DetailedResponse содержит аудиофайл с синтезированной
речью, для получения которого можно воспользоваться вызовом get_result.
Обратимся к атрибуту content полученного файла, чтобы получить байты
аудиоданных и передать их методу write объекта audio_file для сохранения
в файле .wav.
1

Полный список доступен по адресу https://www.ibm.com/watson/developercloud/text-to-speech/
api/v1/python.html?python#get-voice. Попробуйте поэкспериментировать с другими голо­
сами.

13.6. Практический пример: приложение-переводчик   605

Функция record_audio
Модуль pyaudio позволяет записывать аудио с микрофона. Функция record_
audio (строки 124–154) определяет несколько констант (строки 126–130)
для настройки потока аудиоданных, поступающего с микрофона вашего компьютера. Мы воспользовались настройками из электронной документации
модуля pyaudio:
ØØFRAME_RATE: 44100 кадров соответствуют частоте 44,1 кГц, стандартной для

аудиоматериалов с CD-качеством.
ØØCHUNK: 1024 — количество кадров, передаваемых программе за один раз.
ØØFORMAT: pyaudio.paInt16 — размер каждого кадра (в данном случае 16 бит,

то есть 2-байтовые целые числа).
ØØCHANNELS: 2 — количество точек данных на кадр.
ØØSECONDS: 5 — продолжительность записи аудио в приложении (в секундах).
124 def record_audio(file_name):
125
"""Использует pyaudio для записи 5 секунд аудио в файл WAV."""
126
FRAME_RATE = 44100 # Количество кадров в секунду
127
CHUNK = 1024 # Количество кадров, читаемых за один раз
128
FORMAT = pyaudio.paInt16 # Каждый кадр - 16-разрядное (2-байтовое) целое
# число
129
CHANNELS = 2 # 2 точки данных на кадр
130
SECONDS = 5 # Общее время записи
131
132
recorder = pyaudio.PyAudio() # Открывает/закрывает аудиопотоки
133
134
# Настройка и открытие аудиопотока для записи (input=True)
135
audio_stream = recorder.open(format=FORMAT, channels=CHANNELS,
136
rate=FRAME_RATE, input=True, frames_per_buffer=CHUNK)
137
audio_frames = [] # Для хранения низкоуровневого ввода с микрофона
138
print('Recording 5 seconds of audio')
139
140
# Прочитать 5 секунд аудио блоками с размером CHUNK
141
for i in range(0, int(FRAME_RATE * SECONDS / CHUNK)):
142
audio_frames.append(audio_stream.read(CHUNK))
143
144
print('Recording complete')
145
audio_stream.stop_stream() # Остановить запись
146
audio_stream.close()
147
recorder.terminate() # Освободить ресурсы, используемые PyAudio
148
149
# Сохранить audio_frames в файле WAV
150
with wave.open(file_name, 'wb') as output_file:
151
output_file.setnchannels(CHANNELS)

606   Глава 13. IBM Watson и когнитивные вычисления
152
153
154
155

output_file.setsampwidth(recorder.get_sample_size(FORMAT))
output_file.setframerate(FRAME_RATE)
output_file.writeframes(b''.join(audio_frames))

Строка 132 создает объект PyAudio, от которого мы будем получать входной
поток для записи аудио с микрофона. В строках 135–136 метод open объекта
PyAudio используется для открытия входного потока, параметры которого
определяются константами FORMAT, CHANNELS, FRAME_RATE и CHUNK. Передача
ключевого аргумента input со значением True означает, что поток будет использоваться для получения входных аудиоданных. Метод open возвращает
объект Stream модуля pyaudio для взаимодействия с потоком.
В строках 141–142 метод read объекта Stream используется для получения
1024 (то есть CHUNK) кадров из входного потока, которые присоединяются
к списку audio_frames. Чтобы определить общее количество итераций цикла,
необходимых для производства 5 секунд аудио при CHUNK кадров за раз, мы
умножаем FRAME_RATE на SECONDS, а затем делим результат на CHUNK. После
того как чтение данных завершится, строка 145 вызывает метод stop_stream
объекта Stream для завершения записи, строка 146 закрывает объект Stream
вызовом метода close объекта Stream, а строка 147 вызывает метод terminate
объекта PyAudio для освобождения аудиоресурсов, задействованных в управлении аудиопотоком.
Команда with в строках 150–154 использует функцию open модуля wave для
открытия файла WAV, заданного file_name для записи в двоичном формате
('wb'). Строки 151–153 задают количество каналов файла WAV, размер данных
(полученный методом get_sample_size объекта PyAudio) и частоту дискретизации. Затем строка 154 записывает аудиоданные в файл. Выражение b''.
join(audio_frames) осуществляет конкатенацию всех байтов кадров в строку
байтов. Присоединение в начало строки префикса b показывает, что это строка
байтов, а не строка символов.

Функция play_audio
Чтобы воспроизвести аудиофайлы, возвращенные сервисом Watson Text to
Speech, воспользуемся функциональностью модулей pydub и pydub.playback.
Сначала строка 158 использует метод from_wav класса AudioSegment для загрузки файла WAV. Метод возвращает новый объект AudioSegment, представляющий аудиофайл. Чтобы воспроизвести AudioSegment, строка 159 вызывает
функцию play модуля pydub.playback и передает AudioSegment в аргументе.

13.7. Ресурсы Watson   607
156 def play_audio(file_name):
157
"""Использует модуль pydub (pip install pydub) для воспроизведения
файла WAV."""
158
sound = pydub.AudioSegment.from_wav(file_name)
159
pydub.playback.play(sound)
160

Выполнение функции run_translator
Функция run_translator вызывается при выполнении SimpleLanguageTranslator.
py в виде сценария:
161 if __name__ == '__main__':
162
run_translator()

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

13.7. Ресурсы Watson
IBM предоставляет разработчикам доступ к широкому спектру ресурсов для
ознакомления с сервисами и их применения при построении приложений.

Документация сервисов Watson
Документация сервисов Watson доступна по адресу:
https://console.bluemix.net/developer/watson/documentation

Для каждого сервиса доступна документация и ссылки на справочники API.
В документацию каждого сервиса обычно включаются:
ØØучебник для начинающих;
ØØвидеообзор сервиса;
ØØссылка на демонстрационное приложение сервиса;
ØØссылки на более конкретные инструкции и учебные документы;
ØØпримеры приложений;

608   Глава 13. IBM Watson и когнитивные вычисления
ØØдополнительные ресурсы (более подробные учебные руководства, видео-

ролики, посты в блогах и т. д.).
В справочнике API каждого сервиса приведены все подробности взаимодействия с сервисом на разных языках, включая Python. Щелкните на вкладке
Python, чтобы просмотреть документацию, относящуюся к Python, и соответствующие примеры кода для Watson Developer Cloud Python SDK. В справочнике API описаны все параметры обращения к сервису, разновидности
ответов, которые он может вернуть, примеры ответов и т. д.

Watson SDK
Для разработки сценария этой главы использовался пакет Watson Developer
Cloud Python SDK. Также существуют SDK для многих других языков и платформ. Полный список доступен по адресу:
https://console.bluemix.net/developer/watson/sdks-and-tools

Образовательные ресурсы
На странице Learning Resources
https://console.bluemix.net/developer/watson/learning-resources

приведены ссылки на следующие ресурсы:
ØØСообщения в блогах о функциональности Watson, а также об использова-

нии Watson и AI в отрасли.
ØØРепозиторий Watson на GitHub (средства разработчика, SDK и примеры

кода).
ØØКанал Watson на YouTube (см. ниже).
ØØПаттерны, которые IBM называет «ориентирами для решения сложных

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

Видеоролики о Watson
Канал Watson на YouTube:
https://www.youtube.com/user/IBMWatsonSolutions/

13.7. Ресурсы Watson   609

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

Публикации IBM Redbook
В следующих публикациях IBM Redbook приведены подробные описания
сервисов IBM Cloud и Watson, которые помогут вам повысить уровень владения Watson:
ØØОсновы разработки приложений для IBM Cloud:
http://www.redbooks.ibm.com/abstracts/sg248374.html
ØØПостроение когнитивных приложений с использованием сервисов IBM
Watson: том 1, «Getting Started»: http://www.redbooks.ibm.com/abstracts/
sg248387.html
ØØПостроение когнитивных приложений с использованием сервисов IBM

Watson: том 2, «Conversation» (теперь называется Watson Assistant):
http://www.redbooks.ibm.com/abstracts/sg248394.html
ØØПостроение когнитивных приложений с использованием сервисов IBM
Watson: том 3, «Visual Recognition»: http://www.redbooks.ibm.com/abstracts/
sg248393.html
ØØПостроение когнитивных приложений с использованием сервисов IBM
Watson: том 4, «Natural Language Classifier»: http://www.redbooks.ibm.com/
abstracts/sg248391.html
ØØПостроение когнитивных приложений с использованием сервисов IBM
Watson: том 5, «Language Translator»: http://www.redbooks.ibm.com/abstracts/
sg248392.html
ØØПостроение когнитивных приложений с использованием сервисов IBM
Watson: том 6, «Speech to Text and Text to Speech»: http://www.redbooks.ibm.
com/abstracts/sg248388.html
ØØПостроение когнитивных приложений с использованием сервисов IBM
Watson: том 7, «Natural Language Understanding»: http://www.redbooks.ibm.
com/abstracts/sg248398.html

610   Глава 13. IBM Watson и когнитивные вычисления

13.8. Итоги
В этой главе представлена платформа когнитивных вычислений IBM Watson
и приведен обзор широкого спектра ее сервисов. Вы узнали, что Watson предоставляет многие интересные возможности, которые могут интегрироваться
в ваши приложения. Для обучения и экспериментов IBM предоставляет бесплатные уровни доступа Lite. Чтобы воспользоваться ими, необходимо создать
учетную запись IBM Cloud. Мы использовали демонстрационные приложения
Watson для экспериментов с различными сервисами: переводом естественного
языка, преобразованием речи в текст и текста в речь, пониманием естественного языка, чат-ботами, анализом текста на тональность и распознаванием
визуальных объектов в графике и видео.
Мы установили пакет Watson Developer Cloud Python SDK для программного
доступа к сервисам Watson из кода Python. В приложении-переводчике несколько сервисов Watson объединены в гибридное приложение, при помощи
которого англоязычные и испаноязычные пользователи могут легко общаться
друг с другом. Записи аудио на английском и испанском языке преобразовывались в текст, текст переводился на другой язык, а затем английская и испанская
речь синтезировалась из переведенного текста. В завершающей части главы
описаны различные ресурсы Watson, включая документацию, блоги, репозиторий Watson на GitHub, канал Watson на YouTube, паттерны, реализованные
на Python (и других языках), и документы IBM Redbook.

14
Машинное обучение:
классификация, регрессия
и кластеризация
В этой главе…
•• Использование scikit-learn с популярными наборами данных для проведения исследований из области машинного обучения.
•• Визуализация и исследование данных средствами Seaborn и Matplotlib.
•• Машинное обучение с учителем на примере классификации методом k ближайших соседей и линейной регрессии.
•• Выполнение множественной классификации с набором данных Digits.
•• Разделение набора данных на обучающий, тестовый и проверочный наборы.
•• Настройка гиперпараметров модели с использованием k-проходной перекрестной проверки.
•• Измерение эффективности модели.
•• Вывод матрицы несоответствий с попаданиями и промахами классификационных прогнозов.
•• Выполнение множественной линейной регрессии с набором данных
California Housing.
•• Выполнение пространственной свертки с использованием PCA и t-SNE
с наборами данных Iris и Digits с целью подготовки их для двумерных визуализаций.
•• Выполнение машинного обучения без учителя кластеризацией методом k
средних с набором данных Iris.

612   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

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

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

Прогнозирование
Как было бы здорово, если бы мы могли повысить точность прогнозов погоды для спасения жизней, сведения к минимуму числа жертв и ущерба для
имущества! Или повысить точность диагностики рака и программы лечения
для спасения жизней либо бизнес-прогнозов для максимизации прибыли и защиты рабочих мест… А как насчет выявления мошенничества при покупках
по кредитным картам или обращениях за страховыми выплатами? Как насчет
прогнозирования оттока клиентов или новых цен на недвижимость, кассовой
прибыли новых фильмов, ожидаемого дохода от новых продуктов и сервисов?
Прогнозирования оптимальных стратегий для тренеров и игроков, которые
позволят им выигрывать больше игр и чемпионатов? Между тем уже сейчас
благодаря машинному обучению такие прогнозы строятся каждый день.

Области применения машинного обучения
В табл. 14.1 перечислены некоторые популярные области применения машинного обучения.

14.1. Введение в машинное обучение   613
Таблица 14.1. Некоторые популярные области применения машинного обучения
Автономные машины

Обнаружение объектов в сценах

Анализ эмоциональной окраски (например, классификация рецензий на
фильмы на отрицательные, положительные и нейтральные)

Перевод естественных языков (с английского на испанский, с французского
на японский и т. д.)

Выявление закономерностей вданных

Прогнозирование временных рядов —
например, предсказание будущих
котировок акций и прогнозы погоды

Выявление попыток мошенничества
с кредитными картами

Прогнозирование нарушений выплат
ипотечных кредитов

Выявление попыток страхового мошенничества

Прогнозирование оттока клиентов

Глубокий анализ данных в социальных
сетях (Facebook, Twitter, LinkedIn)

Распознавание лиц

Выявление аномалий

Распознавание голоса

Диагностическая медицина

Распознавание образов и классификация
изображений

Исследование данных

Распознавание рукописного текста

Классификация новостей: спорт, финансы, политика и т. д.

Рекомендательные системы («тем, кто
купил этот продукт, также понравились…»)

Классификация электронной почты
и выделение спама

Сжатие данных

Маркетинг: деление клиентов на группы

Фильтрация спама

Обнаружение вторжений в компьютерные системы

Чат-боты

14.1.1. Scikit-learn
В этой главе будет использоваться популярная библиотека машинного обучения
scikit-learn. Библиотека scikit-learn, также называемая sklearn, предоставляет
наиболее эффективные алгоритмы машинного обучения, удобно упакованные
в форме оценщиков (estimators). Все оценщики инкапсулированы, поэтому
подробности и математическое обоснование работы всех этих алгоритмов не
видны разработчику. И вас это не должно беспокоить — человек может вести
машину, не зная всех подробностей работы двигателя, системы передачи,
системы торможения или системы рулевого управления. Представьте, как
вы входите в лифт и выбираете нужный этаж или включаете телевизор и выбираете канал. Разбираетесь ли вы во всех подробностях того, как работает
это оборудование или, скажем, как функционирует программное обеспечение
вашего смартфона?

614   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
Со scikit-learn и небольшим объемом кода Python можно быстро создать мощные модели для анализа данных, извлечения закономерностей из данных и, что
самое важное, построения прогнозов. Мы будем использовать scikit-learn для
обучения моделей на подмножестве данных с последующим тестированием
для проверки того, как работает каждая модель. После того как ваши модели
пройдут обучение, мы применим их для построения прогнозов на основании
данных, которые им еще не встречались. Результаты часто поражают. Внезапно ваш компьютер, который использовался в основном для всяких рутинных
задач, начинает проявлять зачатки интеллекта.
Scikit-learn содержит инструменты, автоматизирующие процессы обучения
и тестирования моделей. И хотя вы можете задать параметры для настройки
моделей с возможным ростом их эффективности, в этой главе мы обычно
используем настройки моделей по умолчанию, добиваясь при этом впечатляющих результатов.
Также существуют такие инструменты, как auto-sklearn (https://automl.github.
io/auto-sklearn), автоматизирующие многие задачи, решаемые при помощи
scikit-learn.

Какого оценщика scikit-learn следует выбрать для проекта
Трудно заранее определить, какие модели лучше всего подойдут для ваших
данных, поэтому обычно аналитик опробует много моделей и выбирает ту,
которая покажет наилучшие результаты. Как вы вскоре увидите, scikit-learn
упрощает эту задачу. Популярный подход заключается в запуске многих
моделей и выборе наилучшего варианта(-ов). Как же оценить, какая модель
показывает наилучшие результаты?
Для этого нужно поэкспериментировать со множеством разных моделей
с разными видами наборов данных. Обычно знать подробности сложных
математических алгоритмов оценщиков sklearn не требуется, но с обретением опыта вы начнете представлять, какие алгоритмы лучше подходят для
определенных типов задач и наборов данных. Впрочем, даже располагая
определенным опытом, вряд ли вам удастся интуитивно угадать наилучшую
модель для каждого нового набора данных. По этой причине scikit-learn помогает легко «опробовать их все». Создание и использование каждой модели
занимает всего несколько строк кода. Модели выдают информацию о своей
эффективности, позволяющей сравнить результаты и выбрать модель(-и)
с лучшей эффективностью.

14.1. Введение в машинное обучение   615

14.1.2. Типы машинного обучения
В этом разделе рассматриваются две основные разновидности машинного обучения — машинное обучение с учителем, которое работает с помеченными данными,
и машинное обучение без учителя, которое работает с непомеченными данными.
Например, если разрабатываемое приложение должно распознавать собак
и кошек на изображениях, то вы будете обучать модели на множестве фотографий собак (с пометкой «собака») и фотографий кошек (с пометкой «кошка»). Если ваша модель эффективна, то она сможет распознать непомеченные
фотографиями собак и кошек, ранее никогда модели не встречавшиеся. Чем
больше фотографий использовано для обучения, тем больше вероятность
того, что модель точно определит, на каких новых фотографиях изображены
собаки, а на каких — кошки. В эпоху больших данных и огромных недорогих
компьютерных мощностей с теми методами, о которых мы собираемся рассказать, вы сможете строить довольно точные модели.
Какую пользу могут принести непомеченные данные? В интернете продается
огромное количество книг. Продавцы хранят огромные объемы (непомеченных) данных о покупке книг. Они быстро заметили, что люди, покупающие
определенные книги, с большой вероятностью будут приобретать другие книги
по тем же или схожим темам. Это привело к появлению рекомендательных систем. В поисках нужной книги на сайте продавца вы с большой вероятностью
будете видеть рекомендации вроде: «Люди, которые купили эту книгу, также
купили эти книги». В наши дни рекомендательные системы играют важную
роль, способствуя достижению максимальных продаж любых продуктов.

Машинное обучение с учителем
Машинное обучение с учителем делится на две категории — классификацию
и регрессию. Модели проходят обучение на наборах данных, состоящих из
строк и столбцов. Каждая строка представляет точку данных, а каждый столбец — некую характеристику этой точки. В машинном обучении с учителем
с каждой точкой данных связывается метка, называемая целевой меткой (например, «dog» или «cat»). Она определяет то значение, которое должно прогнозироваться для новых данных, передаваемых вашим моделям.

Наборы данных
Мы будем работать с «игрушечными» наборами данных, состоящими из небольшого количества точек данных и ограниченного набора характеристик.

616   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
Также будут использованы несколько полноценных, реальных наборов данных — один состоит из нескольких тысяч точек данных, а в другом их количество достигает десятков тысяч. Заметим, что в мире больших данных наборы
нередко содержат миллионы и миллиарды точек данных, и даже больше. Для
исследований в области data science существует огромное число бесплатных
и свободно распространяемых наборов данных. Такие библиотеки, как scikitlearn, включают популярные наборы данных для экспериментов, а также
предоставляют средства для загрузки данных из различных репозиториев
(например, openml.org). Правительственные учреждения, коммерческие и другие организации по всему миру предоставляют наборы данных по широкому
спектру областей. Мы будем работать с популярными бесплатными наборами
данных с применением различных средств машинного обучения.

Классификация
Для анализа набора данных Digits, включенного в поставку scikit-learn, будет
применен один из простейших классификационных алгоритмов — метод
k ближайших соседей. Классификационные алгоритмы прогнозируют дискретные классы (категории), к которым относятся точки данных. При бинарной
классификации используются два класса: например, «спам» или «не спам»
в приложении классификации электронной почты. В задачах множественной
классификации используется более двух классов — например, 10 классов (от
0 до 9) в наборе данных Digits. Схема классификации для описаний фильмов
может пытаться классифицировать их по жанру: «приключения», «фэнтези»,
«романтика», «исторический» и т. д.

Регрессия
Регрессионные модели прогнозируют непрерывный вывод — например, прогнозируемую температуру в анализе временных рядов из раздела «Введение
в data science» главы 10. В этой главе мы вернемся к примеру простой линейной регрессии, но на этот раз реализуем его с использованием оценщика
LinearRegression из scikit-learn. Затем оценщик LinearRegression будет
использован для выполнения множественной линейной регрессии с набором
данных California Housing, включенным в поставку scikit-learn. В этом примере будет прогнозироваться медианная стоимость дома в квартале по данным
переписи США с учетом следующих характеристик: среднего количества
комнат, медианного возраста дома, среднего количества спален и медианного
дохода. Оценщик LinearRegression по умолчанию использует все числовые

14.1. Введение в машинное обучение   617

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

Машинное обучение без учителя
Перейдем к рассмотрению машинного обучения без учителя с алгоритмами
кластеризации. Мы воспользуемся методом снижения размерности признакового пространства (с применением оценщика scikit-learn TSNE ) для
сжатия 64 признаков набора данных Digits до двух в целях визуализации.
Это позволит увидеть, как хорошо «группируются» данные Digits — наборы
данных с рукописными цифрами наподобие тех, что должны распознаваться
компьютерами в почтовых отделениях для отправки писем по указанным
почтовым индексам. Речь идет о сложной задаче из области распознавания
образов, если учесть неповторимость человеческого почерка. Тем не менее
эта модель кластеризации будет построена всего в нескольких строках кода,
а достигнутый результат окажется весьма впечатляющим. И все это не требует от вас знания внутреннего устройства алгоритма кластеризации. В этом
проявляется вся элегантность объектно-базированного программирования.
Другой пример удобного объектно-базированного программирования рассматривается в следующей главе, когда мы займемся построением мощных
моделей глубокого обучения с использованием библиотеки Keras.

Кластеризация методом k средних и набор данных Iris
Мы представим простейший алгоритм машинного обучения без учителя —
кластеризацию методом k средних и воспользуемся им для набора данных
Iris, также включенного в поставку scikit-learn. Снижение размерности признакового пространства (с оценщиком PCA из scikit-learn) сжимает четыре
признака набора данных Iris до двух с целью визуализации. Будет продемонстрирована кластеризация трех образцов Iris по набору данных и графическое представление центроида каждого кластера (то есть центральной точки
кластера). Наконец, мы применим несколько оценщиков кластеризации для
сравнения их эффективности по разбиению точек набора данных Iris на три
кластера.
Обычно аналитик задает желательное количество моделей k. Метод k средних
перебирает данные, стараясь разделить их на заданное количество кластеров.
Как и многие алгоритмы машинного обучения, метод k средних работает по
итеративному принципу и в конечном итоге сходится к кластерам в заданном
количестве.

618   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
Кластеризация методом k средних может выявить сходство в непомеченных
данных. Этот факт может помочь в назначении меток данным, чтобы оценщики для обучения с учителем смогли обработать его. С учетом того, насколько
монотонен и ненадежен процесс назначения меток непомеченным данным
(причем подавляющее большинство мировых данных не имеет меток), машинное обучение без учителя играет важную роль.

Большие данные и большие вычислительные
мощности компьютеров
Объем доступных данных в наши дни уже огромен, причем он продолжает
расти в экспоненциальном темпе. Только за последние годы было произведено
столько же данных, сколько появилось до этого момента от начала цивилизации. Мы часто говорим о больших данных, но прилагательное «большой»
недостаточно наглядно описывает, насколько огромен их объем. Когда-то люди
говорили: «Я тону в данных и не знаю, что с ними делать». С появлением машинного обучения мы теперь говорим: «Затопите меня большими данными,
и я воспользуюсь технологиями машинного обучения, чтобы извлечь из них
информацию и сделать прогнозы».
Все это происходит в то время, когда вычислительная мощность компьютеров стремительно растет, а компьютерная память и дисковое пространство
увеличиваются в объемах при значительном снижении стоимости. Все это
позволяет нам взглянуть на методологию поиска решения под другим углом.
Теперь мы можем программировать компьютеры так, чтобы они обучались на
данных, притом в колоссальных объемах. На первый план выходит прогнозирование на основе данных.

14.1.3. Наборы данных, включенные
в поставку scikit-learn
В табл. 14.2 перечислены наборы данных, включенные в поставку scikit-learn1.
Также предоставляется возможность загрузки наборов данных из других источников, включая 20 000+ наборов данных, доступных на сайте openml.org.

1

http://scikit-learn.org/stable/datasets/index.html.

14.1. Введение в машинное обучение   619
Таблица 14.2. Наборы данных, включенные в поставку scikit-learn
«Игрушечные» наборы данных

«Реальные» наборы данных

Цены на дома в Бостоне

Лица Оливетти

Ирисы

Тексты 20 новостных групп

Диабет

Помеченные лица для распознавания

Оптическое распознавание рукописных цифр

Типы лесопосадок

Linnerrud

RCV1

Распознавание вин

Kidcup 99

Диагностика рака груди (Висконсин)

California Housing

14.1.4. Последовательность действий в типичном
исследовании data science
Далее будут выполнены все основные шаги типичного практического сценария
машинного обучения:
ØØзагрузка набора данных;
ØØисследование данных с использованием pandas и визуализаций;
ØØпреобразование данных (нечисловых данных в числовые, потому что

scikit-learn требуются числовые данные; в главе 14 будут использоваться
«готовые» наборы данных, но мы еще вернемся к этой теме в главе 15);
ØØразбиение данных для обучения и тестирования;
ØØсоздание модели;
ØØобучение и тестирование модели;
ØØнастройка параметров модели и оценка ее точности;
ØØформирование прогнозов на основании «живых» данных, которые еще не-

известны модели.
В разделах «Введение в data science» глав 7 и 8 обсуждается решение проблемы отсутствующих и ошибочных значений средствами pandas. Эти операции
играют важную роль при очистке данных перед их применением в машинном
обучении.

620   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

14.2. Практический пример: классификация
методом k ближайших соседей и набор
данных Digits, часть 1
Чтобы почта обрабатывалась эффективно, а каждое письмо передавалось
по правильному адресу, компьютеры почтовой службы должны сканировать рукописные имена, адреса и почтовые индексы, распознавая цифры
и буквы. Как будет показано в этой главе, благодаря мощным библиотекам,
таким как scikit-learn, даже начинающий программист способен справиться
с подобной задачей из области машинного обучения. В следующей главе еще
более мощная функциональность распознавания образов будет использоваться при представлении технологии глубокого обучения для сверточных
нейронных сетей.

Задачи классификации
В этом разделе будет рассмотрена задача классификации в области машинного
обучения с учителем, где требуется спрогнозировать класс1, к которому относится образец. Например, если у вас имеются изображения собак и кошек, то
каждое изображение должно классифицироваться как «собака» или «кошка».
Подобная задача называется бинарной, поскольку в ней задействованы всего
два класса.
Воспользуемся набором данных Digits2, входящим в поставку scikit-learn. Набор
состоит из изображений 8 × 8 пикселов и представляет 1797 рукописных цифр
(от 0 до 9). Требуется определить, какую цифру представляет изображение.
Так как существует 10 возможных цифр (классов), данная задача является
задачей множественной классификации. Для обучения модели используются
помеченные данные — класс каждой цифры известен заранее. В этом примере
для распознавания рукописных цифр будет применен один из простейших
алгоритмов классификации — метод k ближайших соседей (k-NN).
Следующая визуализация цифры 5 в низком разрешении была получена
в результате вывода одной цифры в виде матрицы 8 × 8. Вскоре мы покажем,
как выводить такие изображения средствами Matplotlib:

1

2

В данном случае под термином «класс» понимается «категория», а не концепция класса
в языке Python.
http://scikit-learn.org/stable/datasets/index.html#optical-recognition-of-handwritten-digits-dataset.

14.2. Практический пример, часть 1   621

Исследователи создали изображения этого набора данных на основе базы
данных MNIST с десятками тысяч изображений 32 × 32 пиксела, полученных
в начале 1990-х. С современными камерами и сканерами высокого разрешения
такие изображения можно записать с более высоким качеством.

Наш подход
Описание этого примера занимает два раздела. Начнем с основных этапов
реализации задач машинного обучения:
ØØВыбор данных для обучения модели.
ØØЗагрузка и анализ данных.
ØØРазбиение данных для обучения и тестирования.
ØØВыбор и построение модели.
ØØОбучение модели.
ØØФормирование прогнозов.

Как вы вскоре увидите, в scikit-learn каждый из этих шагов занимает лишь
несколько строк кода. В следующем разделе мы:
ØØпроведем оценку результатов;
ØØнастроим параметры модели;
ØØобработаем несколько классификационных моделей для выбора наилуч-

шей модели(-ей).
Для визуализации данных будут использоваться библиотеки Matplotlib
и Seaborn, поэтому IPython следует запустить с поддержкой Matplotlib:
ipython --matplotlib

622   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

14.2.1. Алгоритм k ближайших соседей
Scikit-learn поддерживает много алгоритмов классификации, включая простейший алгоритм k ближайших соседей (k-NN). Этот алгоритм пытается
спрогнозировать класс тестового образца, анализируя k обучающих образцов,
расположенных ближе всего (по расстоянию) к тестовому образцу. Для примера возьмем следующую диаграмму, на которой заполненные точки представляют четыре класса — A, B, C и D. В контексте нашего обсуждения эти
буквы будут использоваться как имена классов:

Y

ось y

А

D

B

Z
X

C

ось x
Требуется спрогнозировать классы, к которым принадлежат новые образцы X, Y и Z. Будем считать, что прогнозы должны формироваться по трем
бли­жайшим соседям каждого образца — k равно 3 в алгоритме k ближайших
соседей:
ØØВсе три ближайших соседа образца X являются точками класса D, поэтому

модель прогнозирует, что X относится к классу D.
ØØВсе три ближайших соседа образца Y являются точками класса B, поэтому

модель прогнозирует, что Y относится к классу B.
ØØДля Z выбор не очевиден, потому что образец находится между точками B

и C. Из трех ближайших соседей один принадлежит классу B, а два — клас-

14.2. Практический пример, часть 1   623

су C. В алгоритме k ближайших соседей побеждает класс с большинством
«голосов». Из-за двух голосов C против одного голоса B мы прогнозируем, что Z относится к классу C. Выбор нечетного значения k в алгоритме
k-NN предотвращает «ничьи» и гарантирует, что количество голосов никогда не будет равным.

Гиперпараметры и настройка гиперпараметров
В области машинного обучения модель реализует алгоритм машинного обучения. В терминологии scikit-learn модели называются оценщиками. Существуют
два типа параметров машинного обучения:
ØØвычисляемые оценщиком в ходе своего обучения на основании предо-

ставленных вами данных;
ØØзадаваемые заранее при создании объекта оценщика scikit-learn, представ-

ляющего модель.
Параметры, задаваемые заранее, называются гиперпараметрами.
В алгоритме k ближайших соседей k является гиперпараметром. Для простоты мы используем значения гиперпараметров по умолчанию для scikit-learn.
В реальном исследовании из области машинного обучения желательно поэкспериментировать с разными значениями k для получения наилучших возможных моделей для ваших исследований. Этот процесс называется настройкой
гиперпараметров. Позднее мы используем настройку гиперпараметров для
выбора значения k, которое позволяет алгоритму k ближайших соседей выдать лучшие прогнозы для набора данных Digits. Scikit-learn также содержит
средства автоматической настройки гиперпараметров.

14.2.2. Загрузка набора данных
Функция load_digits из модуля sklearn.datasets возвращает объект scikitlearn Bunch, содержащий данные цифр и информацию о наборе данных Digits
(так называемые метаданные):
In [1]: from sklearn.datasets import load_digits
In [2]: digits = load_digits()

Bunch представляет собой подкласс dict, содержащий дополнительные атри-

буты для взаимодействия с набором данных.

624   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

Вывод описания
Набор данных Digits, входящий в поставку scikit-learn, является подмножеством набора данных рукописных цифр UCI (Калифорнийский университет
в Ирвайне) ML:
http://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits

Исходный набор данных UCI содержит 5620 образцов — 3823 для обучения
и 1797 для тестирования. Версия набора данных, поставляемая со scikit-learn,
содержит только 1797 тестовых образцов. Атрибут DESCR объекта Bunch содержит описание набора данных. Согласно описанию набора данных Digits1,
каждый образец содержит 64 признака (Number of Attributes), представляющие изображение 8 × 8 со значениями пикселов в диапазоне 0–16 (Attribute
Information). Набор данных не содержит отсутствующих значений (Missing
Attribute Values). Создается впечатление, что 64 признака — это много, но
необходимо иметь в виду, что реальные наборы данных иногда содержат сотни,
тысячи и даже миллионы признаков.
In [3]: print(digits.DESCR)
.. _digits_dataset:
Optical recognition of handwritten digits dataset
-------------------------------------------------**Data Set Characteristics:**
:Number of Instances: 5620
:Number of Attributes: 64
:Attribute Information: 8x8 image of integer pixels in the range
0..16.
:Missing Attribute Values: None
:Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)
:Date: July; 1998
This is a copy of the test set of the UCI ML hand-written digits datasets
http://archive.ics.uci.edu/ml/datasets/
Optical+Recognition+of+Handwritten+Digits
...

Проверка атрибутов data и target
Атрибуты data и target объекта Bunch представляют собой массивы NumPy:
ØØМассив data содержит 1797 образца (изображения цифр), каждый из кото-

рых несет 64 признака со значениями в диапазоне 0–16, представляющие
1

Ключевая информация выделена жирным шрифтом.

14.2. Практический пример, часть 1   625

интенсивности пикселов. С Matplotlib можно визуализировать интенсивности в оттенках серого от белого (0) до черного (16):

ØØМассив target содержит метки изображений, то есть классы, указываю-

щие, какую цифру представляет каждое изображение. Массив называется
target, потому что при прогнозировании вы стремитесь «попасть в цель»
с выбором значений. Чтобы увидеть метки образцов в наборе данных, выведем значения target каждого 100-го образца:
In [4]: digits.target[::100]
Out[4]: array([0, 4, 1, 7, 4, 8, 2, 2, 4, 4, 1, 9, 7, 3, 2, 1, 2, 5])

Количество образцов и признаков (на один образец) подтверждается при
помощи атрибута shape массива data, который показывает, что набор данных
состоит из 1797 строк (образцов) и 64 столбцов (признаков):
In [5]: digits.data.shape
Out[5]: (1797, 64)

Размеры массива target подтверждают, что количество целевых значений
соответствует количеству образцов:
In [6]: digits.target.shape
Out[6]: (1797,)

Пример изображения цифры
Все изображения двумерны — они обладают шириной и высотой в пикселах.
Объект Bunch, возвращаемый load_digits, содержит атрибут images — массив, каждый элемент которого представляет собой двумерный массив 8 × 8
с интенсивностями пикселов изображения цифры. Хотя в исходном наборе
данных каждый пиксел представлен целочисленным значением в диапазоне 0–16, scikit-learn хранит эти значения в виде значений с плавающей точкой
(тип NumPy float64). Например, двумерный массив, представляющий изображение образца с индексом 13, выглядит так:
In [7]: digits.images[13]
Out[7]:
array([[ 0., 2., 9., 15., 14., 9.,
[ 0., 4., 13., 8., 9., 16.,

3.,
8.,

0.],
0.],

626   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
[
[
[
[
[
[

0.,
0.,
0.,
0.,
0.,
0.,

0., 0., 6., 14., 15.,
0., 0., 11., 14., 2.,
0., 0., 2., 15., 11.,
0., 0., 0., 2., 15.,
1., 5., 6., 13., 16.,
2., 12., 12., 13., 11.,

3.,
0.,
0.,
4.,
6.,
0.,

0.],
0.],
0.],
0.],
0.],
0.]])

Ниже показано изображение, представленное этим двумерным массивом —
вскоре мы приведем код вывода этого изображения:

Подготовка данных для использования со scikit-learn
Алгоритмы машинного обучения scikit-learn требуют, чтобы образцы хранились в двумерном массиве значений с плавающей точкой (или коллекции,
сходной с двумерным массивом, например списком списков или коллекцией
pandas DataFrame):
ØØкаждая строка представляет один образец;
ØØкаждый столбец заданной строки представляет один признак этого образца.

Для представления каждого образца в виде одной строки данных многомерные
данные (например, двумерный массив image из фрагмента [7]) должны быть
преобразованы в одномерный массив.
Если вы работаете с данными, содержащими категорийные признаки (обычно
представленные в виде строк — скажем, 'spam' и 'not-spam'), то вам также
придется провести предварительную обработку этих признаков и преобразовать их в числовые значения (так называемое прямое унитарное кодирование
будет рассматриваться в следующей главе). Модуль sklearn.preprocessing
библиотеки Scikit-learn предоставляет функциональность для преобразования
категорийных данных в числовые. Набор данных Digits не содержит категорийных признаков.

14.2. Практический пример, часть 1   627

Для вашего удобства функция load_digits возвращает предварительно обработанные данные, готовые к машинному обучению. Набор данных Digits
является числовым, поэтому load_digits просто сглаживает двумерный
массив в одномерный массив. Например, массив 8 × 8 digits.images[13] из
фрагмента [7] соответствует массиву 1 × 64 digits.data[13] следующего вида:
In [8]: digits.data[13]
Out[8]:
array([ 0., 2., 9., 15., 14.,
16., 8., 0., 0., 0.,
0., 11., 14., 2., 0.,
0., 0., 0., 0., 0.,
13., 16., 6., 0., 0.,

9., 3., 0., 0., 4., 13., 8.,
0., 6., 14., 15., 3., 0., 0.,
0., 0., 0., 0., 2., 15., 11.,
2., 15., 4., 0., 0., 1., 5.,
2., 12., 12., 13., 11., 0., 0.])

9.,
0.,
0.,
6.,

В этом одномерном массиве первые восемь элементов содержат элементы строки 0 двумерного массива, следующие восемь элементов — элементы строки 1
двумерного массива, и т. д.

14.2.3. Визуализация данных
Всегда старайтесь поближе познакомиться со своими данными. Этот процесс
называется исследованием данных. Например, изображения цифр (с тем чтобы
составить представление об их внешнем виде) можно просто вывести функцией implot библиотеки Matplotlib. На следующей иллюстрации изображены
первые 24 изображения набора данных. Чтобы понять, насколько трудна задача распознавания рукописных цифр, посмотрите, как сильно различаются
изображения цифры 3 в первой, третьей и четвертой строке, и взгляните на
изображения цифры 2 в первой, третьей и четвертой строке.

628   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

Создание диаграммы
А теперь рассмотрим код, которым были выведены эти 24 изображения цифр.
Следующий вызов функции subplots создает эту диаграмму 6 × 4 (размер задается ключевым аргументом figsize(6, 4)), которая состоит из 24 поддиаграмм, расположенных в 4 строки (nrows=4) и 6 столбцов (ncols=6). Каждая
поддиаграмма имеет собственный объект Axes, который используется для
вывода одного изображения цифры:
In [9]: import matplotlib.pyplot as plt
In [10]: figure, axes = plt.subplots(nrows=4, ncols=6, figsize=(6, 4))

Функция subplots возвращает объекты Axes в двумерном массиве NumPy.
Изначально диаграмма выглядит так, как показано ниже, — на ней выводятся
деления (которые мы вскоре уберем) на осях x и y каждой поддиаграммы:

Вывод изображений и удаление меток осей
Затем команда for в сочетании со встроенной функцией zip будет использоваться для параллельного перебора всех 24 объектов Axes, первых 24 изображений в digits.images и первых 24 значений в digits.target:
In [11]: for item in zip(axes.ravel(), digits.images, digits.target):
...:
axes, image, target = item
...:
axes.imshow(image, cmap=plt.cm.gray_r)
...:
axes.set_xticks([]) # Удаление делений на оси x
...:
axes.set_yticks([]) # Удаление делений на оси y

14.2. Практический пример, часть 1   629
...:
axes.set_title(target)
...: plt.tight_layout()
...:
...:

Напомним, метод массивов NumPy ravel создает одномерное представление
многомерного массива, а функция zip — кортежи, содержащие элементы всех
аргументов zip с одинаковыми индексами. Аргумент с наименьшим количеством элементов определяет количество возвращаемых кортежей. Каждая
итерация цикла:
ØØраспаковывает один кортеж на три переменные, представляющие объект

Axes, изображение и целевое значение;
ØØвызывает метод imshow объекта Axes для вывода одного изображения.

Ключевой аргумент cmap=plt.cm.gray_r определяет цвета, выводимые
в изображении. Значение plt.cm.gray_r представляет собой цветовую
карту — группу часто выбираемых цветов, хорошо сочетающихся друг
с другом. С этой конкретной цветовой картой пикселы изображения выводятся в оттенках серого: 0 соответствует белому цвету, 16 — черному,
а промежуточные значения — оттенкам серого с возрастанием темного. Названия цветовых карт Matplotlib приведены на странице https://
matplotlib.org/examples/color/colormaps_reference.html. К ним можно обращаться через объект plt.cm bkb или в строковом виде 'gray_r';
ØØвызывает методы set_xticks и set_yticks объекта Axes с пустыми спи-

сками, чтобы указать, что оси x и y должны выводиться без делений;
ØØвызывает метод set_title объекта Axes для вывода целевого значения над

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

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

630   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
ей пока неизвестны. Когда вы будете уверены в том, что модель работает эффективно, ее можно будет использовать для прогнозирования на новых данных.
Сначала данные разбиваются на два поднабора: обучающий и тестовый.
Функция train_test_split из модуля sklearn.model_selection осуществляет
случайную перестановку данных, а затем разбивает образцы в массиве data
и целевые значения в массиве target на обучающий и тестовый набор. Это
гарантирует, что обучающий и тестовый наборы обладают сходными характеристиками. Случайная перестановка и разбиение выполняются для вашего
удобства объектом ShuffleSplit из модуля sklearn.model_selection. Функция train_test_split возвращает кортеж из четырех элементов, в котором два
первых элемента содержат образцы, разделенные на обучающий и тестовый
набор, а два последних — соответствующие целевые значения, также разделенные на обучающий и тестовый набор. По общепринятым соглашениям
буква верхнего регистра X используется для представления образцов, а буква y
нижнего регистра — для представления целевых значений:
In [12]: from sklearn.model_selection import train_test_split
In [13]: X_train, X_test, y_train, y_test = train_test_split(
...:
digits.data, digits.target, random_state=11)
...:

Предполагается, что классы данных сбалансированы, то есть образцы равномерно распределены между классами. К слову, все классификационные наборы,
входящие в поставку scikit-learn, обладают этим свойством. Несбалансированность классов может привести к ошибочным результатам.
В главе 4 было показано, как инициализировать генератор случайных чисел для
получения воспроизводимых результатов. В исследованиях в области машинного обучения это позволяет другим пользователям проверить ваши результаты, так как они могут работать с теми же случайно выбранными данными.
Функция train_test_split предоставляет ключевой аргумент random_state
для воспроизводимости результатов. Если в будущем тот же код будет выполняться с тем же значением инициализации, train_test_split выберет
те же данные для обучающего и тестового наборов. Значение инициализации
в нашем примере (11) было выбрано произвольно.

Размеры обучающего и тестового наборов
Взглянув на размеры наборов X_train и X_test, мы видим, что по умолчанию
train_test_split резервирует 75% данных для обучения и 25% для тестирования:

14.2. Практический пример, часть 1   631
In [14]: X_train.shape
Out[14]: (1347, 64)
In [15]: X_test.shape
Out[15]: (450, 64)

Чтобы использовать другое соотношение, можно задать размеры тестового
и обучающего набора при помощи ключевых аргументов test_size и train_
size функции train_test_split. Используйте значения с плавающей точкой
в диапазоне от 0.0 до 1.0 для определения процентной доли каждого набора
в данных. Целочисленные значения задают точное количество образцов. Если
один из этих ключевых аргументов задается при вызове, то второй вычисляется
автоматически. Например, команда
X_train, X_test, y_train, y_test = train_test_split(
digits.data, digits.target, random_state=11, test_size=0.20)

сообщает, что 20% данных предназначены для тестирования, поэтому значение
train_size вычисляется равным 0.80.

14.2.5. Создание модели
Оценщик KNeighborsClassifier (модуль sklearn.neighbors ) реализует алгоритм k ближайших соседей. Сначала создается объект оценщика
KNeighborsClassifier:
In [16]: from sklearn.neighbors import KNeighborsClassifier
In [17]: knn = KNeighborsClassifier()

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

14.2.6. Обучение модели
Затем вызывается метод fit объекта KNeighborsClassifier, который загружает обучающий набор образцов (X_train) и обучающий набор целевых
значений (y_train) в оценщике:

632   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
In [18]: knn.fit(X=X_train, y=y_train)
Out[18]:
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=None, n_neighbors=5, p=2,
weights='uniform')

Для большинства оценщиков scikit-learn метод fit загружает данные в оценщика, а затем использует эти данные для выполнения «за кулисами» сложных
вычислений, в ходе которых происходит извлечение информации и обучение
модели. Метод fit объекта KNeighborsClassifier просто загружает данные
в оценщике, потому что алгоритм k-NN не имеет исходного процесса обучения.
Данный оценщик называется отложенным, потому что он выполняет свою
работу только тогда, когда он используется для построения прогнозов. В этой
и в следующей главе мы будем использовать множество моделей, имеющих
значительные фазы обучения. В реальных приложениях машинного обучения обучение моделей может занимать минуты, часы, дни и даже месяцы, но
(см. далее) специализированное высокопроизводительное оборудование —
графические процессоры (GPU) и тензорные процессоры (TPU) — могут
значительно сократить время обучения модели.
Как видно из вывода фрагмента [18], метод fit возвращает оценщика,
поэтому IPython выводит его строковое представление, включающее настройки по умолчанию. Значение n_neighbors соответствует k в алгоритме
k ближайших соседей. По умолчанию KNeighborsClassifier ищет пятерых
ближайших соседей для построения своих прогнозов. Для простоты мы используем оценки оценщика по умолчанию. Для KNeighborsClassifier они
описаны по адресу:
http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html

Рассмотрение большинства этих настроек выходит за рамки книги. В части 2
примера мы поговорим о том, как выбрать лучшее значение для n_neighbors.

14.2.7. Прогнозирование классов
для рукописных цифр
Итак, после загрузки данных в KNeighborsClassifier эти данные могут использоваться с тестовыми образцами для построения прогнозов. При вызове
метода predict оценщика с передачей X_test в аргументе возвращает массив,
содержащий прогнозируемый класс каждого тестового изображения:

14.2. Практический пример, часть 1   633
In [19]: predicted = knn.predict(X=X_test)
In [20]: expected = y_test

Сравним прогнозируемые цифры с ожидаемыми для первых 20 тестовых
образцов:
In [21]: predicted[:20]
Out[21]: array([0, 4, 9, 9, 3, 1, 4, 1, 5, 0, 4, 9, 4, 1, 5, 3, 3, 8, 5, 6])
In [22]: expected[:20]
Out[22]: array([0, 4, 9, 9, 3, 1, 4, 1, 5, 0, 4, 9, 4, 1, 5, 3, 3, 8, 3, 6])

Как видим, среди первых 20 элементов массивов predicted и expected не совпадают только значения с индексом 18. Здесь ожидалась цифра 3, но модель
предсказала 5.
Воспользуемся трансформацией списка для нахождения всех ошибочных прогнозов для всего тестового набора, то есть тех случаев, в которых значения из
массивов predicted и expected не совпадают:
In [23]: wrong = [(p, e) for (p, e) in zip(predicted, expected) if p != e]
In [24]: wrong
Out[24]:
[(5, 3),
(8, 9),
(4, 9),
(7, 3),
(7, 4),
(2, 8),
(9, 8),
(3, 8),
(3, 8),
(1, 8)]

Трансформация списка использует zip для создания кортежей, содержащих
соответствующие элементы predicted и expected. Кортеж включается в результат только в том случае, если его значение p (прогнозируемое значение)
и e (ожидаемое значение) различны, то есть спрогнозированное значение было
неправильным. В этом примере оценщик неправильно спрогнозировал только
10 из 450 тестовых образцов. Таким образом, точность прогнозирования для
этого оценщика составила впечатляющую величину 97,78% даже при том, что
мы использовали только параметры оценщика по умолчанию.

634   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

14.3. Практический пример: классификация
методом k ближайших соседей и набор
данных Digits, часть 2
В этом разделе мы продолжим работу над примером с классификацией цифр
и сделаем следующее:
ØØоценим точность оценщика для классификации методом k-NN;
ØØвыполним несколько оценщиков и сравним их результаты для выбора

наилучшего варианта(-ов);
ØØпродемонстрируем настройку гиперпараметра k метода k-NN для дости-

жения оптимальной эффективности KNeighborsClassifier.

14.3.1. Метрики точности модели
После того как модель пройдет обучение и тестирование, желательно оценить
ее точность. В этом разделе будут рассмотрены два способа оценки точности —
метод score оценщика и матрица несоответствий.

Метод score оценщика
Каждый оценщик содержит метод score, который возвращает оценку результатов, показанных с тестовыми данными, переданными в аргументах. Для
классификационных оценщиков метод возвращает точность прогнозирования
для тестовых данных:
In [25]: print(f'{knn.score(X_test, y_test):.2%}')
97.78%

Оценщик kNeighborsClassifier со своим значением k по умолчанию (то есть
n_neighbors=5) достигает точности прогнозирования 97,78%. Вскоре мы проведем настройку гиперпараметра, чтобы попытаться определить оптимальное
значение k и добиться еще более высокой точности.

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

14.3. Практический пример, часть 2   635

о правильно и неправильно спрогнозированных значениях (также называемых попаданиями и промахами) для заданного класса. Вызовите функцию
confusion_matrix из модуля sklearn.metrics и передайте в аргументах классы
expected и predicted:
In [26]: from sklearn.metrics import confusion_matrix
In [27]: confusion = confusion_matrix(y_true=expected, y_pred=predicted)

Ключевой аргумент y_true задает фактические классы тестовых образцов.
Люди просмотрели изображения в наборе данных и пометили их конкретными
классами (цифры). Ключевой аргумент y_pred определяет прогнозируемые
цифры для этих тестовых изображений.
Ниже приведена матрица несоответствий, полученная по итогам предшествующего вывода. Правильные прогнозы находятся на главной диагонали, проходящей от левого верхнего до правого нижнего угла. Ненулевые значения,
не находящиеся на главной диагонали, обозначают ошибочные прогнозы:
In [28]: confusion
Out[28]:
array([[45, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 45, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 54, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 42, 0, 1, 0, 1, 0, 0],
[ 0, 0, 0, 0, 49, 0, 0, 1, 0, 0],
[ 0, 0, 0, 0, 0, 38, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 42, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 45, 0, 0],
[ 0, 1, 1, 2, 0, 0, 0, 0, 39, 1],
[ 0, 0, 0, 0, 1, 0, 0, 0, 1, 41]])

Каждая строка представляет один класс, то есть одну из цифр от 0 до 9.
Столбцы обозначают количество тестовых образцов, классифицированных
в соответствующий класс. Например, строка 0:
[45,

0,

0,

0,

0,

0,

0,

0,

0,

0]

представляет класс цифры 0. Столбцы представляют 10 возможных целевых
классов 0–9. Так как мы работаем с цифрами, классы (0–9) и индексы строк
и столбцов (0–9) совпадают. По данным строки, 0, 45 тестового образца были
классифицированы как цифра 0, но ни один из тестовых образцов не был
ошибочно классифицирован как одна из цифр 1–9. Таким образом, все 100%
цифр 0 были спрогнозированы правильно.

636   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
Теперь возьмем строку 8, представляющую результат для цифры 8:
[ 0,

1,

1,

2,

0,

0,

0,

0, 39,

1]

ØØ1 в столбце с индексом 1 означает, что одна цифра 8 была неправильно

классифицирована как 1.
ØØ1 в столбце с индексом 2 означает, что одна цифра 8 была неправильно

классифицирована как 2.
ØØ2 в столбце с индексом 3 означает, что две цифры 8 были неправильно

классифицированы как 3.
ØØ39 в столбце с индексом 8 означает, что 39 цифр 8 были правильно класси-

фицированы как 8.
ØØ1 в столбце с индексом 9 означает, что одна цифра 8 была неправильно

классифицирована как 9.
Таким образом, алгоритм правильно спрогнозировал 88,63% (39 из 44) всех
цифр 8. Позднее было показано, что общая точность прогнозирования этого
оценщика составляла 97,78%. Более низкая точность прогнозирования для
цифры 8 означает, что она из-за своей формы труднее распознается, чем другие цифры.

Отчет по классификации
Модуль sklearn.metrics также предоставляет функцию classification_
report, которая выводит таблицу метрик классификации1, основанных на
ожидаемых и прогнозируемых значениях:
In [29]: from sklearn.metrics import classification_report
In [30]: names = [str(digit) for digit in digits.target_names]
In [31]: print(classification_report(expected, predicted,
...:
target_names=names))
...:
precision
recall f1-score
support
0
1
2
3
1

1.00
0.98
0.98
0.95

1.00
1.00
1.00
0.95

1.00
0.99
0.99
0.95

45
45
54
44

http://scikit-learn.org/stable/modules/model_evaluation.html#precision-recall-and-f-measures.

14.3. Практический пример, часть 2   637
4
5
6
7
8
9
micro avg
macro avg
weightedavg

0.98
0.97
1.00
0.96
0.97
0.98
0.98
0.98
0.98

0.98
1.00
1.00
1.00
0.89
0.95
0.98
0.98
0.98

0.98
0.99
1.00
0.98
0.93
0.96
0.98
0.98
0.98

50
38
42
45
44
43
450
450
450

В этом отчете:
ØØprecision — точность, то есть общее количество точных прогнозов для

заданной цифры, разделенное на общее количество прогнозов для этой
цифры. Точность можно проверить по столбцам матрицы несоответствий.
Например, взглянув на столбец с индексом 7, вы увидите значение 1
в строках 3 и 4: это означает, что одна цифра 3 и одна цифра 4 были ошибочно классифицированы как 7. Значение 45 в строке 7 показывает, что
45 изображений были правильно классифицированы как 7. Таким образом, точность для цифры 7 составляет 45/47, или 0,96;
ØØrecall — отклик, то есть общее количество правильных прогнозов для

заданной цифры, разделенное на общее количество образцов, которые
должны были прогнозироваться как эта цифра. Отклик можно проверить
по строкам матрицы несоответствий. Например, в строке с индексом 8
встречаются три значения 1 и значение 2; это означает, что некоторые
цифры 8 были ошибочно классифицированы как другие цифры, а также
значение 39, которое показывает, что 39 изображений были классифицированы правильно. Таким образом, отклик для цифры 8 составляет 39/44,
или 0,89;
ØØf1-score — среднее значение точности и отклика;
ØØsupport — количество образцов с заданным ожидаемым значением. На-

пример, 50 образцов были снабжены меткой 4, а 38 образцов — меткой 5.
Подробности о средних значениях в нижней части отчета можно найти здесь:
http://scikit-learn.org/stable/modules/generated/sklearn.metrics.classification_report.html

Визуализация матрицы несоответствий
На тепловой карте значения представлены цветами; обычно более высоким
значениям соответствуют более интенсивные цвета. Функции построения диаграмм Seaborn работают с двумерными данными. При использовании pandas

638   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
DataFrame в качестве источника данных Seaborn автоматически помечает свои

визуализации по именам столбцов и индексам строк. Преобразуем матрицу
несоответствий в коллекцию DataFrame, а затем построим ее визуальное представление:
In [32]: import pandas as pd
In [33]: confusion_df = pd.DataFrame(confusion, index=range(10),
...:
columns=range(10))
...:
In [34]: import seaborn as sns
In [35]: axes = sns.heatmap(confusion_df, annot=True,
...:
cmap='nipy_spectral_r')
...:

Функция heatmap библиотеки Seaborn строит тепловую карту по заданной коллекции DataFrame. Ключевой аргумент annot=True (сокращение от
«annotation») выводит справа от диаграммы цветную полосу, которая обозначает соответствие между значениями и цветами цветовой карты. Ключевой
аргумент cmap='nipy_spectral_r' определяет используемую цветовую карту.
При выводе матрицы несоответствий в форме цветовой карты главная диагональ и ошибочные прогнозы хорошо выделяются на общем фоне.

14.3. Практический пример, часть 2   639

14.3.2. K-проходная перекрестная проверка
K-проходная перекрестная проверка позволяет использовать все данные как
для обучения, так и для тестирования. Повторное обучение и тестирование
модели с разными частями набора данных помогают лучше понять, как модель справляется с прогнозированием для новых данных. Набор данных разбивается на k частей равного размера (параметр k в данном случае никак не
связан с k из алгоритма k ближайших соседей). После этого модель повторно
обучается на k – 1 частях и тестируется на оставшейся части. Для примера
возьмем k = 10 с нумерацией частей от 1 до 10. Со всеми частями будут выполнены 10 последовательных циклов обучения и тестирования:
ØØСначала выполняется обучение на частях 1–9, а затем тестирование с ча-

стью 10.
ØØЗатем выполняется обучение на частях 1–8 и 10, а затем тестирование

с частью 9.
ØØЗатем выполняется обучение на частях 1–7 и 9–10, а затем тестирование

с частью 8.
Цикл обучения и тестирования продолжается до тех пор, пока каждая часть
не будет использована для тестирования модели.

Класс KFold
Библиотека scikit-learn предоставляет класс KFold и функцию cross_val_score
(из модуля sklearn.model_selection) для выполнения описанных выше циклов обучения и тестирования. Выполним k-проходную перекрестную проверку с набором данных Digits и оценщиком KNeighborsClassifier, созданным
ранее. Начнем с создания объекта KFold:
In [36]: from sklearn.model_selection import KFold
In [37]: kfold = KFold(n_splits=10, random_state=11, shuffle=True)

Ключевые аргументы:
ØØn_splits=10 — количество частей;
ØØrandom_state=11 — значение инициализации генератора случайных чисел

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

640   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
ØØshuffle=True — объект KFold выполняет случайную перестановку данных

перед разбиением их на части. Этот шаг особенно важен, если образцы
могут быть сгруппированы или упорядочены. Например, набор данных
Iris, который будет использован позднее в этой главе, содержит 150 образцов трех разновидностей ирисов: первые пятьдесят относятся к Iris setosa,
следующие пятьдесят — к Iris versicolor, а последние 5 пятьдесят 0 — к Iris
virginica. Если не переставить образцы, то может оказаться, что в обучающих данных нет ни одного образца конкретного вида ирисов, а тестовые
данные состоят из данных одного вида.

Использование объекта KFold с функцией cross_val_score
Затем воспользуемся функцией cross_val_score для обучения и тестирования
модели:
In [38]: from sklearn.model_selection import cross_val_score
In [39]: scores = cross_val_score(estimator=knn, X=digits.data,
...:
y=digits.target, cv=kfold)
...:

Ключевые аргументы:
ØØestimator=knn — оценщик, который вы хотите проверить;
ØØX=digits.data — образцы, используемые для обучения и тестирования;
ØØy=digits.target — прогнозы целевых значений для образцов;
ØØcv=kfold — генератор перекрестной проверки, определяющий способ раз-

биения образцов и целевых значений для обучения и тестирования.
Функция cross_val_score возвращает массив показателей точности — по
одной для каждой части. Как видно из следующего вывода, модель была
достаточно точной. Наименьший показатель точности составил 0,97777778
(97,78%), а в одном случае при прогнозировании всей части была достигнута
100-процентная точность:
In [40]: scores
Out[40]:
array([0.97777778, 0.99444444, 0.98888889, 0.97777778, 0.98888889,
0.99444444, 0.97777778, 0.98882682, 1.
, 0.98324022])

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

14.3. Практический пример, часть 2   641

точности и стандартное отклонение по 10 показателям точности (или другому
выбранному вами количеству частей):
In [41]: print(f'Mean accuracy: {scores.mean():.2%}')
Mean accuracy: 98.72%
In [42]: print(f'Accuracy standard deviation: {scores.std():.2%}')
Accuracy standard deviation: 0.75%

В среднем модель обеспечивала точность 98,72%, то есть даже больше, чем
в предыдущем варианте, когда 75% данных использовалось для обучения,
а 25% — для тестирования.

14.3.3. Выполнение нескольких моделей для поиска
наилучшей
Трудно заранее определить, какая модель машинного обучения будет оптимальной для конкретного набора данных, особенно если подробности их
работы скрыты от пользователя. И хотя KNeighborsClassifier прогнозирует
изображения цифр с высокой точностью, может оказаться, что другие оценщики scikit-learn работают еще точнее. Scikit-learn предоставляет много моделей, позволяющих быстро провести обучение и тестирование данных. Это
позволяет запустить несколько разных моделей и определить, какая из них
лучше подходит для конкретного практического сценария.
Воспользуемся методами из предыдущего раздела для сравнения нескольких
классификационных оценщиков — KNeighborsClassifier, SVC и GaussianNB
(существуют и другие). И хотя оценщики SVC и GaussianNB ранее не описывались, scikit-learn позволяет легко опробовать их с настройками по умолчанию1.
Импортируем двух других оценщиков:
In [43]: from sklearn.svm import SVC
In [44]: from sklearn.naive_bayes import GaussianNB

Теперь необходимо создать оценщиков. Следующий словарь содержит пары
«ключ-значение» для существующего оценщика KNeighborsClassifier, созданного ранее, а также новых оценщиков SVC и GaussianNB:
1

Чтобы избежать предупреждения в текущей версии scikit-learn на момент написания книги
(версия 0.20), мы передали один ключевой аргумент при создании оценщика SVC. Значение
этого аргумента будет использоваться по умолчанию, начиная с scikit-learn версии 0.22.

642   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
In [45]: estimators = {
...:
'KNeighborsClassifier': knn,
...:
'SVC': SVC(gamma='scale'),
...:
'GaussianNB': GaussianNB()}
...:

После этого можно переходить к выполнению модели:
In [46]: for estimator_name, estimator_object in estimators.items():
...:
kfold = KFold(n_splits=10, random_state=11, shuffle=True)
...:
scores = cross_val_score(estimator=estimator_object,
...:
X=digits.data, y=digits.target, cv=kfold)
...:
print(f'{estimator_name:>20}: ' +
...:
f'mean accuracy={scores.mean():.2%}; ' +
...:
f'standard deviation={scores.std():.2%}')
...:
KNeighborsClassifier: mean accuracy=98.72%; standard deviation=0.75%
SVC: mean accuracy=99.00%; standard deviation=0.85%
GaussianNB: mean accuracy=84.48%; standard deviation=3.47%

Цикл перебирает элементы словаря estimators и для каждой пары «ключзначение» выполняет следующие операции:
ØØраспаковывает ключ в estimator_name, а значение — в estimator_object;
ØØсоздает объект KFold, осуществляющий случайную перестановку данных

и формирующий 10 частей. В данном случае ключевой аргумент random_
state особенно важен — он гарантирует, что все оценщики будут работать
с идентичными частями (чтобы эффективность сравнивалась по одним
исходным данным);
ØØоценивает текущий объект estimator_object с использованием cross_

val_score;
ØØвыводит имя оценщика, за которым следует математическое ожидание

и стандартное отклонение для оценок точности, вычисленных для всех
10 частей.
Судя по результатам, оценщик SVC обеспечивает лучшую точность — по
крайней мере, с настройками по умолчанию. Возможно, настройка некоторых
параметров позволит добиться еще более точных результатов.
Точности оценщиков KNeighborsClassifier и SVC почти идентичны, поэтому
стоит провести настройку гиперпараметров каждого оценщика для выбора
лучшего варианта.

14.3. Практический пример, часть 2   643

Диаграмма оценщиков scikit-learn
В документации scikit-learn приведена полезная диаграмма для выбора правильного оценщика в зависимости от размера и типа данных, а также поставленной задачи машинного обучения:
https://scikit-learn.org/stable/tutorial/machine_learning_map/index.html

14.3.4. Настройка гиперпараметров
Ранее в этом разделе мы упоминали, что k в алгоритме k ближайших соседей
является гиперпараметром алгоритма. Гиперпараметры задаются до того, как
алгоритм начнет использоваться для обучения модели. В реальных исследованиях в процессе настройки гиперпараметров должны быть выбраны значения
гиперпараметров, которые обеспечивают лучшие возможные прогнозы.
Чтобы определить лучшее значение k в алгоритме k-NN, поэкспериментируйте
с разными значениями k и сравните эффективность оценщика в каждом варианте. Для этого можно воспользоваться теми же методами, что и при сравнении
оценщиков. Следующий цикл создает объект KNeighborsClassifiers с нечетными значениями k от 1 до 19 (как упоминалось ранее, нечетные значения k
в k-NN предотвращают неоднозначные ситуации с «ничейными» результатами) и выполняет k-проходную перекрестную проверку для каждого варианта.
Как видно из оценок точности и стандартных отклонений, при значении k = 1
достигается наибольшая точность прогнозирования для набора данных Digits.
Рост значений k ведет к снижению точности:
In [47]: for k in range(1, 20, 2):
...:
kfold = KFold(n_splits=10, random_state=11, shuffle=True)
...:
knn = KNeighborsClassifier(n_neighbors=k)
...:
scores = cross_val_score(estimator=knn,
...:
X=digits.data, y=digits.target, cv=kfold)
...:
print(f'k={k:10}: {linear_regression.coef_[i]}')
...:
MedInc: 0.4377030215382206
HouseAge: 0.009216834565797713
AveRooms: -0.10732526637360985
AveBedrms: 0.611713307391811
Population: -5.756822009298454e-06
AveOccup: -0.0033845664657163703
Latitude: -0.419481860964907
Longitude: -0.4337713349874016
In [29]: linear_regression.intercept_
Out[29]: -36.88295065605547

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

14.5. Практический пример: множественная линейная регрессия   663

имеет отрицательный показатель степени (e-06), так что значение коэффициента в действительности равно -0.000005756822009298454. Величина близка
к нулю, то есть заселенность группы кварталов практически не влияет на
значение медианной стоимости. Полученные значения можно использовать
со следующим уравнением для прогнозирования:
y = m1x1 + m2x2 + … + mnxn + b,
где m1, m2, …, mn — коэффициенты признаков;
b — точка пересечения;
x1, x2, …, xn — значения признаков (то есть значения независимых переменных);
y — прогнозируемое значение (то есть зависимая переменная).

14.5.6. Тестирование модели
Теперь протестируем модель вызовом метода predict оценщика, передавая
тестовые образцы в аргументе. Как и в предыдущих примерах, массив прогнозов хранится в predicted, а массив ожидаемых значений — в expected:
In [30]: predicted = linear_regression.predict(X_test)
In [31]: expected = y_test

Рассмотрим первые пять прогнозов и соответствующие значения из expected:
In [32]: predicted[:5]
Out[32]: array([1.25396876, 2.34693107, 2.03794745, 1.8701254,
2.53608339])
In [33]: expected[:5]
Out[33]: array([0.762, 1.732, 1.125, 1.37, 1.856])

В задаче классификации прогнозы представляли собой дискретные классы,
совпадающие с существующими классами в наборе данных. При регрессии
получить точные прогнозы сложнее из-за непрерывности вывода. Каждое
возможное значение x1, x2 … xn в формуле
y = m1x1 + m2x2 + … mnxn + b
прогнозирует некоторое значение.

664   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

14.5.7. Визуализация ожидаемых
и прогнозируемых цен
Сравним ожидаемую медианную ценность дома с прогнозируемой для тестовых данных. Создадим коллекцию DataFrame со столбцами для ожидаемых
и прогнозируемых значений:
In [34]: df = pd.DataFrame()
In [35]: df['Expected'] = pd.Series(expected)
In [36]: df['Predicted'] = pd.Series(predicted)

Построим визуализацию данных в форме диаграммы разброса данных, у которой ось x представляет ожидаемую (целевую) стоимость, а ось y — прогнозируемую:
In [37]: figure = plt.figure(figsize=(9, 9))
In [38]: axes = sns.scatterplot(data=df, x='Expected', y='Predicted',
...:
hue='Predicted', palette='cool', legend=False)
...:

Затем установим ограничения по осям x и y, чтобы масштаб обеих осей был
одинаковым:
In [39]: start = min(expected.min(), predicted.min())
In [40]: end = max(expected.max(), predicted.max())
In [41]: axes.set_xlim(start, end)
Out[41]: (-0.6830978604144491, 7.155719818496834)
In [42]: axes.set_ylim(start, end)
Out[42]: (-0.6830978604144491, 7.155719818496834)

Теперь построим линию, которая представляет идеальные прогнозы (обратите
внимание: это не регрессионная прямая). Следующий фрагмент выводит
линию от левого нижнего угла диаграммы (start, start) до правого верхнего
угла (end, end). Третий аргумент ('k--') обозначает стиль линии. Буква k
представляет черный цвет, а -- обозначает, что выводимая линия должна
быть пунктирной:
In [43]: line = plt.plot([start, end], [start, end], 'k--')

14.5. Практический пример: множественная линейная регрессия   665

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

14.5.8. Метрики регрессионной модели
Scikit-learn предоставляет множество метрических функций для оценки того,
насколько качественно оценщики прогнозируют результаты, и для сравнения
оценщиков и выбора лучшего(-их) для вашего конкретного исследования. Эти
метрики зависят от типа оценщика. Например, функции confusion_matrix
и classification_report модуля sklearn.metrics, использованные в примере
с классификацией набора данных Digits, принадлежат к числу метрических
функций, предназначенных для оценки классификационных оценщиков.
К числу других метрик регрессионных оценщиков принадлежит коэффициент
детерминации модели, также называемый коэффициентом R2. Чтобы вычислить
коэффициент R2 оценщика, вызовите функцию r2_score модуля sklearn.metrics
с массивами, представляющими ожидаемые и прогнозируемые результаты:
In [44]: from sklearn import metrics
In [45]: metrics.r2_score(expected, predicted)
Out[45]: 0.6008983115964333

666   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
Значения R2 лежат в диапазоне от 0,0 до 1,0 (1,0 — лучшее значение). Коэффициент R2 = 1,0 означает, что оценщик идеально прогнозирует значение зависимой переменной по заданным значениям независимой(-ых)
переменной(-ых). Коэффициент R2 = 0,0 означает, что модель не способна
выдать прогноз с какой-либо точностью на основании значений независимых
переменных.
Другая распространенная метрика регрессионных моделей — среднеквадратичная ошибка — вычисляется следующим образом:
ØØвычисляется разность между каждым ожидаемым и прогнозируемым зна-

чением;
ØØкаждая разность возводится в квадрат;
ØØвычисляется среднее значение всех квадратов.

Чтобы вычислить среднеквадратичную ошибку, вызовите функцию mean_
squared_error (из модуля sklearn.metrics) с массивами, представляющими
ожидаемые и прогнозируемые результаты:
In [46]: metrics.mean_squared_error(expected, predicted)
Out[46]: 0.5350149774449119

При сравнении оценщиков по метрике среднеквадратичной ошибки тот
оценщик, у которого это значение находится ближе всего к 0, обеспечивает
наилучшую подгонку для ваших данных. В следующем разделе мы выполним
несколько регрессионных оценщиков с набором данных California Housing.
За списком метрических функций scikit-learn из категории оценщиков обращайтесь по адресу:
https://scikit-learn.org/stable/modules/model_evaluation.html

14.5.9. Выбор лучшей модели
Как и в примере с классификацией, опробуем несколько оценщиков и проверим, не дает ли какой-либо из них лучшие результаты, чем оценщик
LinearRegression. В этом примере используется уже созданный оценщик
linear_regression, а также регрессионные оценщики ElasticNet, Lasso и Ridge
(все они принадлежат модулю sklearn.linear_model). За дополнительной
информацией об этих оценщиках обращайтесь по адресу:
https://scikit-learn.org/stable/modules/linear_model.html

14.6. Практический пример: машинное обучение без учителя, часть 1   667
In [47]: from sklearn.linear_model import ElasticNet, Lasso, Ridge
In [48]: estimators = {
...:
'LinearRegression': linear_regression,
...:
'ElasticNet': ElasticNet(),
...:
'Lasso': Lasso(),
...:
'Ridge': Ridge()
...: }

Оценщики будут выполняться с применением k-проходной перекрестной проверки с объектом KFold и функцией cross_val_score. Здесь cross_val_score
передается дополнительный ключевой аргумент scoring='r2', который означает, что функция должна выдать коэффициенты R2 для каждой части, —
и снова 1.0 является наилучшим значением. Похоже, LinearRegression и Ridge
оказываются наилучшими моделями для этого набора данных:
In [49]: from sklearn.model_selection import KFold, cross_val_score
In [50]: for estimator_name, estimator_object in estimators.items():
...:
kfold = KFold(n_splits=10, random_state=11, shuffle=True)
...:
scores = cross_val_score(estimator=estimator_object,
...:
X=california.data, y=california.target, cv=kfold,
...:
scoring='r2')
...:
print(f'{estimator_name:>16}: ' +
...:
f'mean of r2 scores={scores.mean():.3f}')
...:
LinearRegression: mean of r2 scores=0.599
ElasticNet: mean of r2 scores=0.423
Lasso: mean of r2 scores=0.285
Ridge: mean of r2 scores=0.599

14.6. Практический пример: машинное обучение
без учителя, часть 1 — понижение размерности
В процессе обсуждения data science мы всегда подчеркивали то, насколько
важно хорошо знать данные. Машинное обучение без учителя и визуализация
помогут вам в этом, способствуя выявлению закономерностей и отношений
между непомеченными образцами.
Для таких наборов данных, как одномерные временные ряды, использованных
ранее в этой главе, визуализация данных выполняется достаточно просто.
При использовании двух переменных — дата и температура — данные были
выведены в двумерной форме, при этом каждая ось представляла одну переменную. При использовании Matplotlib, Seaborn и других библиотек визуали-

668   Глава 14. Машинное обучение: классификация, регрессия и кластеризация
зации можно выводить наборы данных с тремя переменными на трехмерных
диаграммах. Но как вывести данные с большим количеством измерений? Например, в наборе данных Digits каждый образец имеет 64 признака и целевое
значение. Между тем в больших данных образцы могут иметь сотни, тысячи
и даже миллионы признаков.
Чтобы представить набор данных со множеством признаков (то есть множеством измерений), сначала сократим его размерность до двух или трех. Для этого будет использоваться метод машинного обучения без учителя, называемый
понижением размерности. При выводе полученной информации на диаграмме
могут быть выявлены закономерности в данных, которые помогут выбрать
наиболее подходящие алгоритмы машинного обучения. Например, если визуализация содержит кластеры точек данных, это может указывать на наличие
обособленных классов информации в наборе; в такой ситуации классификационный алгоритм может оказаться уместным. Конечно, сначала необходимо
определить класс образцов в каждом кластере. Для этого может потребоваться
анализ образцов, входящих в кластер, и выявление сходства между ними.
Понижение размерности также служит другим целям. Обучение оценщиков
на больших данных со значительным количеством измерений может занять
часы, дни, недели и даже более. Кроме того, человеку трудно представить себе
данные с большим количеством измерений — это называется «проклятием
размерности». Если данные содержат сильно коррелированные признаки, то
некоторые из них могут быть исключены посредством понижения размерности для повышения эффективности обучения. Это, однако, может привести
к потере точности модели.
Вспомним, что набор данных Digits уже помечен 10 классами, соответствующими цифрам 0–9. Проигнорируем эти метки и воспользуемся понижением
размерности, чтобы свести признаки набора данных к двум измерениям для
визуализации результатов.

Загрузка набора данных Digits
Запустите IPython следующей командой:
ipython --matplotlib

и загрузите набор данных:
In [1]: from sklearn.datasets import load_digits
In [2]: digits = load_digits()

14.6. Практический пример: машинное обучение без учителя, часть 1   669

Создание оценщика TSNE для понижения размерности
Теперь воспользуемся оценщиком TSNE (из модуля sklearn.manifold) для
выполнения понижения размерности. Этот оценщик использует алгоритм,
называемый методом нелинейного понижения размерности и виртуализации
многомерных переменных (t-SNE)1 для анализа признаков набора данных
и сведения их к заданному числу измерений. Сначала мы попытались воспользоваться популярным оценщиком PCA (анализ главных компонент, Principal
Components Analysis), но полученные результаты нас не устроили, поэтому мы
переключились на TSNE (оценщик PCA продемонстрирован позднее).
Создадим объект TSNE для сведения признаков набора данных к двум измерениям, на что указывает ключевой аргумент n_components. Как и в случае
с другими оценщиками, упоминавшимися ранее, использован ключевой аргумент random_state, обеспечивающий воспроизводимость «последовательности
вывода» при выводе кластеров цифр:
In [3]: from sklearn.manifold import TSNE
In [4]: tsne = TSNE(n_components=2, random_state=11)

Сведение признаков набора данных Digits к двум измерениям
Понижение размерности в scikit-learn обычно состоит из двух шагов — обучения оценщика на наборе данных и последующего использования оценщика
для преобразования данных к заданному количеству измерений. Эти шаги
могут выполняться по отдельности методами fit и transform оценщика TSNE
или же одной командой с использованием метода fit_transform:2
In [5]: reduced_data = tsne.fit_transform(digits.data)

Метод fit_transform оценщика TSNE какое-то время обучает оценщика, а затем
выполняет понижение размерности. В нашей системе это заняло около 20 секунд. Когда метод завершает свою операцию, он возвращает массив с таким же
количеством строк, что и у digits.data, но только двумя столбцами. Чтобы
убедиться в этом, достаточно проверить размеры reduced_data:
In [6]: reduced_data.shape
Out[6]: (1797, 2)
1

2

Подробности устройства алгоритма выходят за рамки этой книги. За дополнительной
информацией обращайтесь по адресу https://scikit-learn.org/stable/modules/manifold.html#t-sne.
Каждый вызов fit_transform выполняет обучение оценщика. Если вы хотите многократно
использовать оценщика для сокращения размерности образцов, вызовите fit для однократного обучения оценщика, а затем вызывайте transform для понижения размерности
Этот прием будет использован с PCA позднее в этой главе.

670   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

Визуализация данных с пониженной размерностью
Итак, исходный набор данных был сведен до двух измерений, и мы можем построить диаграмму разброса данных. На этот раз вместо функции scatterplot
библиотеки Seaborn будет использоваться функция scatter библиотеки
Matplotlib, потому что она возвращает коллекцию элементов, нанесенных
на диаграмму. Вскоре она будет использована на второй диаграмме разброса:
In [7]: import matplotlib.pyplot as plt
In [8]: dots = plt.scatter(reduced_data[:, 0], reduced_data[:, 1],
...:
c='black')
...:

Первые два аргумента scatter содержат столбцы reduced_data (0 и 1) с данными по осям x и y. Ключевой аргумент c='black' задает цвет точек. Мы не
пометили оси, потому что они не соответствуют конкретным признакам исходного набора данных. Новые признаки, сгенерированные оценщиком TSNE,
могут заметно отличаться от исходных признаков набора данных.
Следующая диаграмма выводит полученную диаграмму разброса. Очевидно,
на диаграмме присутствуют кластеры взаимосвязанных точек данных, хотя
основных кластеров одиннадцать, а не десять. Также существуют «свободные»
точки данных, которые не являются частью конкретных кластеров. На основании предшествующего изучения набора данных Digits эта картина выглядит
разумно из-за трудностей с классификацией некоторых цифр.

14.6. Практический пример: машинное обучение без учителя, часть 1   671

Визуализация данных после понижения размерности
с разными цветами
Хотя на предыдущей диаграмме видны скопления точек, мы не знаем, представляют ли все точки в каждом кластере одну и ту же цифру. Если они
представляют разные цифры, то от кластеров особой пользы не будет. Воспользуемся известными целевыми значениями в наборе данных Digits и раскрасим все точки, чтобы было видно, действительно ли кластеры представляют
конкретные цифры:
In [9]: dots = plt.scatter(reduced_data[:, 0], reduced_data[:, 1],
...:
c=digits.target, cmap=plt.cm.get_cmap('nipy_spectral_r', 10))
...:
...:

В этом случае ключевой аргумент c=digits.target функции scatter указывает, что целевые значения определяют цвета точек. Добавленный ключевой
аргумент
cmap=plt.cm.get_cmap('nipy_spectral_r', 10)

задает цветовую карту, используемую для выбора цветов точек. В данном
случае известно, что окрашиваются 10 цифр, поэтому воспользуемся методом
get_cmap объекта cm библиотеки Matplotlib (из модуля matplotlib.pyplot)
для загрузки цветовой карты ('nipy_spectral_r') и выбора 10 четко различающихся цветов из цветовой карты.
Следующая команда добавляет справа от диаграммы цветную полоску для
расшифровки цветов, чтобы вы видели, какую цифру представляет каждый
цвет:
In [10]: colorbar = plt.colorbar(dots)

На диаграмме хорошо видны 10 кластеров, соответствующих цифрам 0–9.
И снова остаются меньшие группы точек, не входящие ни в один кластер.
На основании этого можно решить, что метод обучения с учителем, такой
как метод k ближайших соседей, хорошо подойдет для этих данных. Для
эксперимента также можно воспользоваться классом Axes3D библиотеки
Matplotlib, предоставляющим оси x, y и z для построения пространственных
диаграмм.

672   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

14.7. Практический пример: машинное обучение
без учителя, часть 2 — кластеризация методом
k средних
В этом разделе будет представлен, пожалуй, самый простой из алгоритмов машинного обучения без учителя — кластеризация методом k средних. Алгоритм
анализирует непомеченные образцы и пытается объединить их в кластеры.
Поясним, что k в «методе k средних» представляет количество кластеров, на
которые предполагается разбить данные.
Алгоритм распределяет образцы на заранее заданное количество кластеров,
используя метрики расстояния, сходные с метриками алгоритма кластеризации k ближайших соседей. Каждый кластер группируется вокруг центроида —
центральной точки кластера. Изначально алгоритм выбирает k случайных
центроидов среди образцов набора данных, после чего остальные образцы
распределяются по кластерам с ближайшим центроидом. Далее выполняется
итеративный пересчет центроидов, а образцы перераспределяются по кластерам, пока для всех кластеров расстояние от заданного центроида до образцов,
входящих в его кластер, не будет минимизировано. В результате выполнения
алгоритма формируется одномерный массив меток, обозначающих кластер,

14.7. Практический пример: машинное обучение без учителя, часть 2   673

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

Набор данных Iris
Поработаем с популярным набором данных Iris1, входящим в поставку scikitlearn. Этот набор часто анализируется при классификации и кластеризации.
И хотя набор данных помечен, мы не будем использовать эти метки, чтобы
продемонстрировать кластеризацию. Затем метки будут использованы для
определения того, насколько хорошо алгоритм k средних выполняет кластеризацию образцов.
Набор данных Iris относится к «игрушечным» наборам данных, поскольку состоит только из 150 образцов и четырех признаков. Набор данных описывает
50 образцов трех видов цветов ириса — Iris setosa, Iris versicolor и Iris virginica
(см. фотографии ниже). Признаки образцов: длина наружной доли около­
цветника (sepal length), ширина наружной доли околоцветника (sepal width),
длина внутренней доли околоцветника (petal length) и ширина внутренней
доли околоцветника (petal width), измеряемые в сантиметрах.

Iris setosa. Credit: Courtesy of Nation Park services
1

Fisher, R.A., «The use of multiple measurements in taxonomic problems», Annual Eugenics,
7, Part II, 179–188 (1936); также «Contributions to Mathematical Statistics» (John Wiley,
NY, 1950).

674   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

Iris versicolor. Credit: Courtesy of Jefficus

Iris virginica. Credit: Christer T Johansson

14.7.1. Загрузка набора данных Iris
Запустите IPython командой ipython --matplotlib, после чего воспользуйтесь
функцией load_iris модуля sklearn.datasets для получения объекта Bunch
с набором данных:

14.7. Практический пример: машинное обучение без учителя, часть 2   675
In [1]: from sklearn.datasets import load_iris
In [2]: iris = load_iris()

Атрибут DESCR объекта Bunch показывает, что набор данных состоит из 150 образцов (Number of Instances), каждый из которых обладает четырьмя признаками (Number of Attributes). В наборе данных нет отсутствующих значений.
Образцы классифицируются целыми числами 0, 1 и 2, представляющими Iris
setosa, Iris versicolor и Iris virginica соответственно. Проигнорируем метки и поручим определение классов образцов алгоритму кластеризации методом k
средних. Ключевая информация DESCR выделена жирным шрифтом:
In [3]: print(iris.DESCR)
.. _iris_dataset:
Iris plants dataset
-------------------**Data Set Characteristics:**
:Number of Instances: 150 (50 in each of three classes)
:Number of Attributes: 4 numeric, predictive attributes and the class
:Attribute Information:
- sepal length in cm
- sepal width in cm
- petal length in cm
- petal width in cm
- class:
- Iris-Setosa
- Iris-Versicolour
- Iris-Virginica
:Summary Statistics:
============== ==== ==== ======= ===== ====================
Min Max
Mean
SD
Class Correlation
============== ==== ==== ======= ===== ====================
sepal length:
4.3 7.9
5.84
0.83
0.7826
sepal width:
2.0 4.4
3.05
0.43
-0.4194
petal length:
1.0 6.9
3.76
1.76
0.9490 (high!)
petal width:
0.1 2.5
1.20
0.76
0.9565 (high!)
============== ==== ==== ======= ===== ====================

...

:Missing Attribute Values: None
:Class Distribution: 33.3% for each of 3 classes.
:Creator: R.A. Fisher
:Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
:Date: July, 1988

676   Глава 14. Машинное обучение: классификация, регрессия и кластеризация

Проверка количества образцов, признаков
и целевых значений
Количество образцов и признаков можно узнать из атрибута shape массива
data, а количество целевых значений — из атрибута shape массива target:
In [4]: iris.data.shape
Out[4]: (150, 4)
In [5]: iris.target.shape
Out[5]: (150,)

Массив target_names содержит имена числовых меток массива. Выражение
target — dtype=' '2016'""", connection)
Out[10]:
title edition copyright
0 Intro to Python for CS and DS
1
2020
1
Java How to Program
11
2018
2
Visual C# How to Program
6
2017
3
C++ How to Program
10
2017
4
Android How to Program
3
2017

Поиск по шаблону: нуль и более символов
Условие WHERE может содержать операторы , =, =, (не равно) и LIKE.
Оператор LIKE используется для поиска по шаблону — поиска строк, удовлет-

774   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
воряющих заданному условию. Шаблон, содержащий знак % (процент), ищет
все строки, содержащие 0 и более символов в позиции символа %. Например,
найдем всех авторов, у которых фамилия начинается с буквы D:
In [11]: pd.read_sql("""SELECT id, first, last
...:
FROM authors
...:
WHERE last LIKE 'D%'""",
...:
connection, index_col=['id'])
...:
Out[11]:
first
last
id
1
Paul Deitel
2
Harvey Deitel
3
Abbey Deitel

Поиск по шаблону: любой символ
Символ подчеркивания (_) в строке-шаблоне обозначает один символ в указанной позиции. Выберем строки всех авторов, имена которых начинаются
с произвольного символа, за которым следует буква b, после чего следует любое
количество дополнительных символов (обозначаемое символом %):
In [12]: pd.read_sql("""SELECT id, first, last
...:
FROM authors
...:
WHERE first LIKE '_b%'""",
...:
connection, index_col=['id'])
...:
Out[12]:
first
last
id
3
Abbey Deitel

16.2.4. Условие ORDER BY
Условие ORDER BY сортирует результаты запроса по возрастанию (от меньших
значений к большим) или по убыванию (от больших значений к меньшим);
способ сортировки задается ключевым словом ASC или DESC соответственно.
По умолчанию используется сортировка по возрастанию, так что ключевое
слово ASC необязательно. Отсортируем названия книг по возрастанию:
In [13]: pd.read_sql('SELECT title FROM titles ORDER BY title ASC',
...:
connection)
Out[13]:

16.2. Реляционные базы данных и язык структурированных запросов (SQL)   775

0
1
2
3
4
5
6
7
8
9

title
Android 6 for Programmers
Android How to Program
C How to Program
C++ How to Program
Internet & WWW How to Program
Intro to Python for CS and DS
Java How to Program
Visual Basic 2012 How to Program
Visual C# How to Program
Visual C++ How to Program

Сортировка по нескольким столбцам
Чтобы отсортировать данные по нескольким столбцам, укажите разделенный запятыми список столбцов после ключевых слов ORDER BY. Отсортируем
таблицу authors по фамилии, а затем по имени для авторов с одинаковыми
фамилиями:
In [14]: pd.read_sql("""SELECT id, first, last
...:
FROM authors
...:
ORDER BY last, first""",
...:
connection, index_col=['id'])
...:
Out[14]:
first
last
id
3
Abbey Deitel
2
Harvey Deitel
1
Paul Deitel
4
Dan
Quirk
5
Alexander
Wald

Порядок сортировки может изменяться на уровне отдельных столбцов. Отсор­
тируем данные authors по убыванию фамилии и по возрастанию имени для
авторов с одинаковыми фамилиями:
In [15]: pd.read_sql("""SELECT id, first, last
...:
FROM authors
...:
ORDER BY last DESC, first ASC""",
...:
connection, index_col=['id'])
...:
Out[15]:
first
last
id
5
Alexander
Wald

776   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
4
3
2
1

Dan
Abbey
Harvey
Paul

Quirk
Deitel
Deitel
Deitel

Объединение условий WHERE и ORDER BY
Условия WHERE и ORDER BY могут объединяться в одном запросе. Получим
значения isbn, title, edition и copyright для каждой книги из таблицы
titles, название которой завершается строкой 'How to Program', после чего
отсортируем их по возрастанию title:
In [16]: pd.read_sql("""SELECT isbn, title, edition, copyright
...:
FROM titles
...:
WHERE title LIKE '%How to Program'
...:
ORDER BY title""", connection)
Out[16]:
isbn
title edition copyright
0 0134444302
Android How to Program
3
2017
1 0133976890
C How to Program
8
2016
2 0134448235
C++ How to Program
10
2017
3 0132151006
Internet & WWW How to Program
5
2012
4 0134743350
Java How to Program
11
2018
5 0133406954 Visual Basic 2012 How to Program
6
2014
6 0134601548
Visual C# How to Program
6
2017
7 0136151574
Visual C++ How to Program
2
2008

16.2.5. Слияние данных из нескольких таблиц:
INNER JOIN
Вспомните, что таблица author_ISBN в составе БД books связывает авторов
с соответствующими названиями книг. Если бы эта информация не была разделена по разным таблицам, то в каждую запись таблицы titles пришлось бы
включать информацию об авторе. Это привело бы к хранению повторяющейся
информации об авторах, написавших несколько книг.
Конструкция INNER JOIN позволяет объединить данные из нескольких таблиц.
Построим список авторов с кодами ISBN книг, написанных каждым автором:
запрос возвращает слишком много результатов, поэтому мы приводим только
начало вывода:
In [17]: pd.read_sql("""SELECT first, last, isbn
...:
FROM authors
...:
INNER JOIN author_ISBN

16.2. Реляционные базы данных и язык структурированных запросов (SQL)   777
...:
...:
Out[17]:
first
0
Abbey
1
Abbey
2 Harvey
3 Harvey
4 Harvey

ON authors.id = author_ISBN.id
ORDER BY last, first""", connection).head()
last
Deitel
Deitel
Deitel
Deitel
Deitel

isbn
0132151006
0133406954
0134289366
0135404673
0132151006

Условие ON конструкции INNER JOIN использует столбец первичного ключа одной таблицы и столбец внешнего ключа другой таблицы для определения того,
какие строки следует объединять из каждой таблицы. Этот запрос объединяет
столбцы first и last таблицы authors со столбцом isbn таблицы author_ISBN
и сортирует результаты по возрастанию сначала last, а затем first.
Обратите внимание на синтаксис authors.id (table_name.column_name) в условии ON. Синтаксис уточнения имен необходим в том случае, если столбцы
имеют одинаковые имена в обеих таблицах. Этот синтаксис может использоваться в любой команде SQL, для того чтобы различать одноименные столбцы
в разных таблицах. В некоторых системах имена таблиц, уточненные именами
баз данных, могут использоваться для выполнения запросов к другим базам
данных. Как обычно, запрос может содержать условие ORDER BY.

16.2.6. Команда INSERT INTO
До настоящего момента мы обращались с запросами на выборку существующих данных. Иногда выполняются команды SQL, которые изменяют БД. Для
этого мы воспользуемся объектом sqlite3 Cursor, для получения которого
применим метод cursor объекта Connection:
In [18]: cursor = connection.cursor()

Метод read_sql библиотеки Pandas использует Cursor во внутренней реализации для выполнения запросов и обращения к строкам результатов.
Команда INSERT INTO вставляет строку в таблицу. Вставим в таблицу authors
нового автора Sue Red; для этого вызовем метод execute объекта Cursor, который выполняет свой аргумент SQL и возвращает Cursor:
In [19]: cursor = cursor.execute("""INSERT INTO authors (first, last)
...:
VALUES ('Sue', 'Red')""")
...:

778   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
За ключевыми словами SQL INSERT INTO следует таблица, в которую вставляется новая строка, и разделенный запятыми список имен столбцов в круглых
скобках. За списком имен столбцов следует ключевое слово SQL VALUES
и разделенный запятыми список значений в круглых скобках. Передаваемые значения должны соответствовать заданным именам столбцов по типу
и порядку.
Значение столбца id не указывается, потому что это автоматически увеличиваемый столбец таблицы authors (см. сценарий books.sql, который создавал
таблицу). Для каждой новой строки SQLite присваивает уникальное значение
id, которое является следующим значением в автоматически увеличиваемой
последовательности (1, 2, 3 и т. д.). В данном случае Sue Red присваивается
идентификатор 6. Чтобы убедиться в этом, создадим запрос на выборку содержимого таблицы authors:
In [20]: pd.read_sql('SELECT id, first, last FROM authors',
...:
connection, index_col=['id'])
...:
Out[20]:
first
last
id
1
Paul Deitel
2
Harvey Deitel
3
Abbey Deitel
4
Dan
Quirk
5
Alexander
Wald
6
Sue
Red

Строки, содержащие внутренние одинарные кавычки
В SQL строки заключаются в одинарные кавычки ('). Если в строке присутствует внутренняя одинарная кавычка (например, O'Malley), то в этой позиции должны стоять две одинарные кавычки ('O''Malley'). Первая кавычка
интерпретируется как служебный символ для экранирования второй. Если
вы забудете экранировать символы одинарной кавычки в строке, которая
является частью команды SQL, то получите сообщение о синтаксической
ошибке SQL.

16.2.7. Команда UPDATE
Команда UPDATE обновляет существующие значения. Допустим, фамилия Red
была введена в БД неправильно, и ее нужно заменить на 'Black':

16.2. Реляционные базы данных и язык структурированных запросов (SQL)   779
In [21]: cursor = cursor.execute("""UPDATE authors SET last='Black'
...:
WHERE last='Red' AND first='Sue'""")

За ключевым словом UPDATE следует таблица, содержимое которой требуется
обновить, ключевое слово SET и разделенный запятыми список пар имя_столбца = значение, определяющий изменяемые столбцы и их новые значения.
Если условие WHERE не указано, то изменения будут внесены в каждую строку.
Условие WHERE в этом запросе указывает, что обновляться должны только те
строки, которые содержат фамилию 'Red' и имя 'Sue'.
Конечно, в БД может упоминаться несколько людей с одинаковым именем
и фамилией. Чтобы внести изменение только в одну строку, лучше использовать в условии WHERE уникальный первичный ключ. В данном примере это
может выглядеть так:
WHERE id = 6

Для команд, изменяющих БД, атрибут rowcount объекта Cursor содержит
целочисленное значение, представляющее количество измененных строк.
Если значение равно 0, то изменения не вносились. Следующий фрагмент
подтверждает, что команда UPDATE изменила только одну строку:
In [22]: cursor.rowcount
Out[22]: 1

Чтобы убедиться в том, что обновление прошло успешно, выведем содержимое
таблицы:
In [23]: pd.read_sql('SELECT id, first, last FROM authors',
...:
connection, index_col=['id'])
...:
Out[23]:
first
last
id
1
Paul Deitel
2
Harvey Deitel
3
Abbey Deitel
4
Dan
Quirk
5
Alexander
Wald
6
Sue
Black

780   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT

16.2.8. Команда DELETE FROM
Команда SQL DELETE FROM удаляет строки из таблицы. Удалим строку Sue
Black из таблицы authors по идентификатору:
In [24]: cursor = cursor.execute('DELETE FROM authors WHERE id=6')
In [25]: cursor.rowcount
Out[25]: 1

Необязательное условие WHERE определяет удаляемые строки. Если условие
WHERE отсутствует, то будут удалены все строки таблицы. Вот как выглядит
таблица authors после выполнения операции DELETE:
In [26]: pd.read_sql('SELECT id, first, last FROM authors',
...:
connection, index_col=['id'])
...:
Out[26]:
first
last
id
1
Paul Deitel
2
Harvey Deitel
3
Abbey Deitel
4
Dan
Quirk
5
Alexander
Wald

Закрытие базы данных
После завершения работы с БД следует вызвать метод close объекта Connection
для отключения:
connection.close()

SQL в больших данных
Важность SQL возрастает в области больших данных. Позднее в этой главе мы воспользуемся Spark SQL для выборки данных из коллекции Spark
DataFrame, данные которой могут быть распределены по многим компьютерам
в кластере Spark. Как вы вскоре увидите, Spark SQL имеет много общего
с языком SQL.

16.3. Базы данных NoSQL и NewSQL: краткое введение   781

16.3. Базы данных NoSQL и NewSQL:
краткое введение
В течение десятилетий реляционные БД считались стандартом в области обработки данных. Однако они требуют структурированных данных, хорошо
укладывающихся в формат прямоугольных таблиц. С увеличением размера
данных, количества таблиц и отношений эффективно управлять подобными
базами данных становится намного сложнее. В современном мире больших
данных появились БД NoSQL и БД NewSQL, предназначенные для механизмов хранения данных и потребностей в обработке, с которыми не справляются
традиционные реляционные БД. Большие данные требуют огромных баз
данных, часто распределенных по многим компьютерным центрам по всему
миру в огромных кластерах серийных компьютеров. По сведениям statista.com,
в настоящее время в мире существует более 8 миллионов центров хранения
и обработки данных1.
Изначально термин NoSQL трактовался буквально, то есть как«без SQL».
Но с ростом важности SQL в области больших данных — например, SQL для
Hadoop и Spark SQL — принято считать, что термин NoSQL означает «не
только SQL». Базы данных NoSQL предназначены для неструктурированных
данных (фотографии, видео и тексты на естественном языке в сообщениях
электронной почты, текстовых сообщениях и публикациях в социальных сетях), а также полуструктурированных данных вроде документов JSON и XML.
Полуструктурированные данные часто представляют собой неструктурированные данные с включением дополнительной информации (метаданных).
Например, видео YouTube представляет собой неструктурированные данные,
но YouTube также хранит для каждого видео ролика метаданные: кто опубликовал ролик, дата публикации, название, описание, теги для упрощения поиска, настройки конфиденциальности и др., — все эти данные возвращаются
в данных JSON функциями YouTube API. Метаданные добавляют структуру
в неструктурированные видеоданные, в результате чего те становятся полуструктурированными.
В нескольких ближайших подразделах приведен обзор основных разновидностей баз данных NoSQL — базы данных «ключ-значение», документные, столбцовые и графовые. Кроме того, будет приведен обзор баз данных
NewSQL, объединяющих функциональность реляционных БД и БД NoSQL.
В разделе 16.4 представлен пример, в котором мы будем сохранять и обраба1

https://www.statista.com/statistics/500458/worldwide-datacenter-and-it-sites/.

782   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
тывать большое количество объектов твитов в формате JSON в документной
БД NoSQL. Затем данные будут отображены в интерактивной визуализации,
отображаемой на карте Folium.

16.3.1. Базы данных NoSQL «ключ-значение»
Как и словари Python, базы данных «ключ-значение»1 предназначены для
хранения пар «ключ-значение», но они оптимизированы для распределенных
систем и обработки больших данных. Для надежности обычно применяется
репликация данных по нескольким узлам кластера. Некоторые из них (такие, как БД Redis) по соображениям эффективности реализуются в памяти,
другие хранят данные на диске (например, HBase) и работают на базе распределенной файловой системы Hadoop HDFS. Среди популярных баз данных
«ключ-значение» можно выделить Amazon DynamoDB, Google Cloud Datastore
и Couchbase. БД DynamoDB и БД Couchbase — многомодельные базы данных,
которые также поддерживают работу с документами. HBase также является
столбцовой базой данных.

16.3.2. Документные базы данных NoSQL
В документной базе данных 2 хранятся полуструктурированные данные
(например, документы JSON и XML). Обычно в документных базах данных создаются индексы для отдельных атрибутов, позволяющие более
эффективно находить документы и работать с ними. Допустим, вы храните
документы JSON, производимые устройствами IoT, и каждый документ содержит атрибут типа. Вы можете добавить индекс для этого атрибута, чтобы
иметь возможность фильтровать документы по типу. Без индексов вы тоже
сможете выполнять эту операцию, но она будет выполняться медленнее,
потому что БД придется проводить поиск по каждому документу для получения нужного атрибута.
Самая популярная документная БД (и самая популярная база данных
NoSQL вообще3) — MongoDB. Ее название состоит из букв, входящих в слово «humongous» (то есть «огромный»). В этом примере мы сохраним в БД
MongoDB большое количество твитов для обработки. Напомним, Twitter
1
2
3

https://en.wikipedia.org/wiki/Key-value_database.
https://en.wikipedia.org/wiki/Document-oriented_database.
https://db-engines.com/en/ranking.

16.3. Базы данных NoSQL и NewSQL: краткое введение   783

API возвращает твиты в формате JSON, что позволяет сохранить их напрямую в БД MongoDB. После получения твитов будет построена их сводка
с использованием коллекции DataFrame библиотеки Pandas и карты Folium.
Другие популярные документные базы данных — Amazon DynamoDB (также является базой данных «ключ-значение»), Microsoft Azure Cosmos DB
и Apache CouchDB.

16.3.3. Столбцовые базы данных NoSQL
Одной из типичных операций, выполняемых с использованием РСУБД,
является получение значения конкретного столбца для каждой строки. Так
как данные упорядочены по строкам, запрос на выборку данных конкретного
столбца может выполняться неэффективно. СУБД должна найти каждую
подходящую строку, каждый нужный столбец, отбросив остальную информацию, не относящуюся к запросу. Столбцовая база данных1,2, также называемая столбцово-ориентированной, похожа на РСУБД, но структурированные
данные хранятся по столбцам, а не по строкам. Так как все элементы столбцов
хранятся вместе, выборка всех данных заданного столбца будет выполняться
более эффективно.
Возьмем таблицу authors в базе данных books:
id
1
2
3
4
5

first

last

Paul
Harvey
Abbey
Dan
Alexander

Deitel
Deitel
Deitel
Quirk
Wald

В РСУБД все данные строки хранятся вместе. Если рассматривать каждую
строку как кортеж Python, то строки будут представлены в виде (1, 'Paul',
'Deitel'), (2, 'Harvey', 'Deitel') и т. д. В столбцовой БД все значения заданного столбца будут храниться вместе: (1, 2, 3, 4, 5), ('Paul', 'Harvey',
'Abbey', 'Dan', 'Alexander') и ('Deitel', 'Deitel', 'Deitel', 'Quirk',
'Wald'). Элементы каждого столбца хранятся в порядке строк, так что значение
с заданным индексом в каждом столбце принадлежат одной строке. Самые
популярные столбцовые базы данных — MariaDB ColumnStore и HBase.
1
2

https://en.wikipedia.org/wiki/Columnar_database.
https://www.predictiveanalyticstoday.com/top-wide-columnar-store-databases/.

784   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT

16.3.4. Графовые базы данных NoSQL
Для знакомства с графовой БД NoSQL важно следующее. Граф моделирует
отношения (связи) между объектами1. Объекты называются узлами (или
вершинами), отношения — ребрами. Ребра обладают направленностью: так,
ребро, представляющее маршрут полета, направлено от места вылета к месту
посадки, но не наоборот. Таким образом, графовая БД2 содержит узлы, ребра
и их атрибуты.
Если вы пользуетесь социальными сетями — Instagram, Snapchat, Twitter
или Facebook, — то представьте свой социальный граф, состоящий из людей,
с которыми вы знакомы (узлы), и отношений между ними (ребра). Каждый
человек обладает собственным социальным графом, и эти графы взаимосвязаны. Знаменитая задача «шести рукопожатий» гласит, что любые два человека
в мире соединены друг с другом не более чем шестью ребрами во всемирном
социальном графе3. Алгоритмы Facebook используют социальные графы своих
миллиардов активных подписчиков4 для определения того, какие истории
должны включаться в ежедневную сводку новостей каждого пользователя.
Анализируя ваши интересы, ваших друзей, их интересы и т. д., Facebook определяет истории, которые должны вас заинтересовать5.
Многие компании используют аналогичные методы для создания рекомендательных систем. Когда вы просматриваете описание товара на Amazon, то
Amazon использует граф пользователей и товаров для отображения похожих
товаров, которые просматривались другими пользователями перед покупкой.
При просмотре фильмов в Netflix сервис на основании графа пользователей
и фильмов, которые им понравились, рекомендует фильмы, которые могут
понравиться вам.
Одна из самых популярных графовых БД — Neo4j (рекомендуем прочитать
бесплатную книгу Neo4j в формате PDF «Graph Databases»6). Многие реальные
примеры применения графовых баз данных доступны по адресу:
https://neo4j.com/graphgists/

1
2
3
4
5
6

https://en.wikipedia.org/wiki/Graph_theory.
https://en.wikipedia.org/wiki/Graph_database.
https://en.wikipedia.org/wiki/Six_degrees_of_separation.
https://zephoria.com/top-15-valuable-facebook-statistics/.
https://newsroom.fb.com/news/2018/05/inside-feed-news-feed-ranking/.
https://neo4j.com/graph-databases-book-sx2.

16.3. Базы данных NoSQL и NewSQL: краткое введение   785

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

16.3.5. Базы данных NewSQL
Среди основных преимуществ реляционных баз данных обычно указываются
безопасность и поддержка транзакций. В частности, реляционные БД обычно
используют транзакции, характеристики которых обозначаются сокращением
ACID (Atomicity, Consistency, Isolation, Durability)1:
ØØАтомарность (Atomicity) гарантирует, что БД изменяется только в том

случае, если все составляющие транзакции прошли успешно. Если вы намереваетесь снять в банкомате 100 долларов, то эти деньги будут выданы
только в случае, если у вас на счете хватает средств, а в банкомате — денег
для выполнения транзакции.
ØØЦелостность (Consistency) гарантирует, что БД всегда находится в кор-

ректном состоянии. В примере со снятием средств с банкомата новый
баланс вашего счета будет в точности соответствовать сумме, снятой со
счета (и, возможно, комиссионных платежей).
ØØИзолированность (Isolation) гарантирует, что параллельные транзакции

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

ность и при сбоях оборудования.
Если вы проанализируете достоинства и недостатки баз данных NoSQL, то
увидите, что они обычно не предоставляют поддержку ACID. Для приложений,
использующих базы данных NoSQL, обычно не нужны гарантии, предоставляемые ACID-совместимыми базами данных. Большинство баз данных NoSQL
обычно придерживается модели BASE (Basic Availability, Softstate, Eventual
consistency — «базовая доступность, гибкое состояние, согласованность в конечном счете»), которая в большей степени ориентируется на доступность
базы данных. Если базы данных ACID гарантируют целостность состояния

1

https://en.wikipedia.org/wiki/ACID_(computer_science).

786   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
при записи в базу данных, то базы данных BASE обеспечивают целостность
в какой-то момент в будущем.
Базы данных NewSQL объединяют преимущества как реляционных баз данных, так и баз данных NoSQL для задач обработки больших данных. Популярные базы данных NewSQL — VoltDB, MemSQL, Apache Ignite и Google Spanner.

16.4. Практический пример:
документная база данных MongoDB
MongoDB — документная БД, предоставляющая возможность хранения и загрузки документов в формате JSON. Twitter API возвращает твиты в виде объектов JSON, которые могут записываться напрямую в БД MongoDB. В этом
разделе мы:
ØØиспользуем Tweepy для потоковой выдачи твитов о 100 сенаторах США

и сохранения их в базе данных MongoDB;
ØØиспользуем Pandas для обобщения информации о 10 самых популярных

сенаторах по их активности в Twitter;
ØØиспользуем интерактивную карту Folium Соединенных Штатов с одной

меткой на каждый штат. На метке выводится название штата и имена сенаторов, их политические партии и счетчики твитов.
Мы используем бесплатный облачный кластер MongoDB Atlas, не требующий
установки и в настоящее время позволяющий хранить до 512 Мб данных.
Чтобы хранить больший объем данных, загрузите MongoDB Community
Server по адресу:
https://www.mongodb.com/download-center/community

и запустите его локально либо оформите подписку на платный сервис
MongoDB Atlas.

Установка библиотек Python, необходимых для взаимодействия
с MongoDB
Для взаимодействия с базами данных MongoDB из кода Python воспользуемся библиотекой pymongo. Также нам понадобится библиотека dnspython для
подключения к кластеру MongoDB Atlas. Для установки введите команды:

16.4. Практический пример: документная база данных MongoDB   787
conda install -c conda-forge pymongo
conda install -c conda-forge dnspython

keys.py
В подкаталоге TwitterMongoDB каталога ch16 находится код примера и файл
keys.py. Отредактируйте файл, включите в него свои регистрационные данные Twitter и ключ OpenMapQuest из главы 12. После того как мы обсудим
создание кластера MongoDB Atlas, вам также нужно будет добавить в файл
строку подключения MongoDB.

16.4.1. Создание кластера MongoDB Atlas
Чтобы создать бесплатную учетную запись, откройте страницу:
https://mongodb.com,

введите свой адрес электронной почты и щелкните на ссылке Get started free.
На следующей странице введите свое имя, создайте пароль и прочитайте
условия обслуживания. Если вы с ними согласны, то щелкните на ссылке
Get started free на этой же странице, и вы попадете на экран настройки своего
кластера. Щелкните на ссылке Build my first cluster, чтобы перейти к созданию
кластера.
Сервис проведет вас по серии «первых шагов» со всплывающими подсказками,
которые описывают и указывают на каждую задачу, которую необходимо завершить. Для бесплатного кластера Atlas (обозначен как M0) предоставляются
настройки по умолчанию; просто введите имя кластера в разделе Cluster Name
и щелкните на ссылке Create Cluster. Открывается страница Clusters и начинается
создание нового кластера, что может занять несколько минут.
Затем открывается учебное руководство Atlas со списком дополнительных
действий, которые необходимо выполнить до начала работы:
ØØСоздание первого пользователя базы данных — позволяет вам подклю-

читься к кластеру.
ØØВключение IP-адреса в «белый список» — мера безопасности, гарантирую-

щая, что с кластером смогут работать только проверенные вами IP-адреса.
Для подключения к этому кластеру из разных мест (дом, работа, учебное
учреждение и т. д.) необходимо включить в «белый список» все IP-адреса,
с которых вы собираетесь создавать подключение.

788   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
ØØПодключение к кластеру — на этом шаге задается строка подключения

к вашему кластеру, чтобы ваш код Python смог подключиться к серверу.

Создание первого пользователя базы данных
Во временном окне обучающего руководства щелкните на ссылке Create your
first database user, чтобы продолжить работу. Выполняйте подсказки, перейдите
на вкладку Security и щелкните на кнопке +ADD NEW USER. В диалоговом окне
Add New User создайте имя пользователя и пароль. Запишите свои регистрационные данные — вскоре они вам понадобятся. Щелкните на кнопке Add User,
чтобы вернуться к руководству Connect to Atlas.

Включение IP-адреса в «белый список»
Во временном окне обучающего руководства щелкните на ссылке Whitelist
your IP address для продолжения работы. Выполняйте подсказки, перейдите
на вкладку IP Whitelist и щелкните на кнопке +ADD IP ADDRESS. В диалоговом
окне Add Whitelist Entry вы можете либо добавить текущий IP-адрес своего
компьютера, либо разрешить доступ с любого адреса; второй вариант не
рекомендован для реальной эксплуатации баз данных, но для учебных целей
этого достаточно. Щелкните на кнопке ALLOW ACCESS FROM ANYWHERE, а затем на кнопке Confirm, чтобы вернуться к обучающему руководству Connect
to Atlas.

Подключение к кластеру
Во временном окне обучающего руководства щелкните на ссылке Connect to your
cluster для продолжения работы. Выполняйте подсказки, чтобы открыть диалоговое окно Connect to ИмяВашегоКластера. Для подключения к БД MongoDB
Atlas из Python необходима строка подключения. Чтобы получить строку подключения, щелкните на ссылке Connect Your Application, а затем на ссылке Short
SRV connection string. Ваша строка подключения появится внизу под полем Copy
the SRV address. Щелкните на кнопке COPY, чтобы скопировать строку. Вставьте
строку в файл keys.py как значение переменной mongo_connection_string. Замените "" в строке подключения вашим паролем, после чего замените
имя базы данных "test" на "senators" (имя базы данных в данном примере).
Щелкните на кнопке Close в нижней части окна Connect to ИмяВашегоКластера.
Теперь все готово для взаимодействия с кластером Atlas.

16.4. Практический пример: документная база данных MongoDB   789

16.4.2. Потоковая передача твитов в MongoDB
Сначала будет представлен интерактивный сеанс IPython, который подключается к БД MongoDB, загружает текущие твиты через систему потоковой
передачи Twitter и определяет 10 самых популярных сенаторов по количеству
твитов. Затем будет представлен класс TweetListener, который обрабатывает
входящие твиты и сохраняет их разметку JSON в MongoDB. Наконец, мы
продолжим сеанс IPython, создав интерактивную карту Folium для вывода
информации из сохраненных твитов.

Использование Tweepy для аутентификации Twitter
Сначала используем Tweepy для выполнения аутентификации Twitter:
In [1]: import tweepy, keys
In [2]: auth = tweepy.OAuthHandler(
...:
keys.consumer_key, keys.consumer_secret)
...: auth.set_access_token(keys.access_token,
...:
keys.access_token_secret)
...:

Затем настроим объект Tweepy API, чтобы он переходил в режим ожидания
при достижении ограничений частоты использования Twitter.
In [3]: api = tweepy.API(auth, wait_on_rate_limit=True,
...:
wait_on_rate_limit_notify=True)
...:

Загрузка данных сенаторов
Воспользуемся информацией из файла senators.csv (находящегося в подкаталоге TwitterMongoDB каталога ch16) для отслеживания твитов, адресованных,
отправленных и посвященных каждому сенатору США. Файл содержит
двухбуквенный код штата, имя, партию, имя пользователя (handle) Twitter
и идентификатор Twitter.
Twitter позволяет отслеживать конкретных пользователей по их числовым
идентификаторам Twitter, но они должны передаваться в виде строковых
представлений этих числовых значений. Итак, загрузим файл senators.csv
в Pandas, преобразуем значения идентификаторов Twitter в строки (при помощи метода astype коллекции Series) и выведем несколько строк данных.

790   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
В данном случае мы указываем, что максимальное количество отображаемых
столбцов равно 6. Позднее мы добавим в DataFrame еще один столбец, и этот
параметр гарантирует, что будут выведены все столбцы вместо их подмножества, разделенного знаком … :
In [4]: import pandas as pd
In [5]: senators_df = pd.read_csv('senators.csv')
In [6]: senators_df['TwitterID'] = senators_df['TwitterID'].astype(str)
In [7]: pd.options.display.max_columns = 6
In [8]: senators_df.head()
Out[8]:
State
Name Party
0
AL Richard Shelby
R
1
AL
Doug Jones
D
2
AK Lisa Murkowski
R
3
AK
Dan Sullivan
R
4
AZ
Jon Kyl
R

TwitterHandle
SenShelby
SenDougJones
lisamurkowski
SenDanSullivan
SenJonKyl

TwitterID
21111098
941080085121175552
18061669
2891210047
24905240

Настройка MongoClient
Чтобы сохранить разметку JSON твитов в базе данных MongoDB, необходимо сначала подключиться к кластеру MongoDB Atlas при помощи функции
MongoClient библиотеки pymongo, в аргументе которой передается строка подключения к вашему кластеру:
In [9]: from pymongo import MongoClient
In [10]: atlas_client = MongoClient(keys.mongo_connection_string)

Теперь получим объект Database библиотеки pymongo, представляющий БД
senators. Следующая команда создает базу данных, если она не существует:
In [11]: db = atlas_client.senators

Создание потока твитов
Укажем количество твитов для загрузки и создадим объект TweetListener.
При создании TweetListener передается объект db , представляющий БД
MongoDB, чтобы твиты были записаны в базу данных. В зависимости от

16.4. Практический пример: документная база данных MongoDB   791

скорости, с которой люди пишут твиты о сенаторах, сбор 10 000 твитов может
занять от нескольких минут до нескольких часов. Для тестовых целей можно
ограничиться меньшим числом:
In [12]: from tweetlistener import TweetListener
In [13]: tweet_limit = 10000
In [14]: twitter_stream = tweepy.Stream(api.auth,
...:
TweetListener(api, db, tweet_limit))
...:

Запуск потока твитов
Механизм потоковой передачи Twitter позволяет отслеживать до 400 ключевых слов и до 5000 идентификаторов Twitter одновременно. В данном примере мы будем отслеживать имена пользователей Twitter и идентификаторы
сенаторов. В результате мы должны получить твиты отправленные, адресованные и относящиеся к каждому сенатору. Для демонстрации прогресса
будем выводить экранное имя и временную метку для каждого полученного
твита, а также общее количество обработанных твитов. Для экономии места
приводим только один из результатов, заменив экранное имя пользователя
XXXXXXX:
In [15]: twitter_stream.filter(track=senators_df.TwitterHandle.tolist(),
...:
follow=senators_df.TwitterID.tolist())
...:
Screen name: XXXXXXX
Created at: Sun Dec 16 17:19:19 +0000 2018
Tweets received: 1
...

Класс TweetListener
Для этого примера мы слегка изменили класс TweetListener из главы 12. Большая часть нижеследующего кода Twitter и Tweepy идентична приводившемуся
ранее, поэтому мы сосредоточимся только на новых концепциях:
1
2
3
4
5
6

# tweetlistener.py
"""TweetListener скачивает твиты и сохраняет их в MongoDB."""
import json
import tweepy
class TweetListener(tweepy.StreamListener):

792   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

"""Обрабатывает входной поток твитов."""
def __init__(self, api, database, limit=10000):
"""Создает переменные экземпляров для отслеживания количества
твитов."""
self.db = database
self.tweet_count = 0
self.TWEET_LIMIT = limit # По умолчанию 10 000
super().__init__(api) # Вызвать метод init суперкласса
def on_connect(self):
"""Вызывается в том случае, если попытка подключения была успешной,
чтобы вы могли выполнить нужные операции в этот момент."""
print('Successfully connected to Twitter\n')
def on_data(self, data):
"""Вызывается, когда Twitter отправляет вам новый твит."""
self.tweet_count += 1 # Количество обработанных твитов.
json_data = json.loads(data) # Преобразование строки в JSON
self.db.tweets.insert_one(json_data) # Сохранение в коллекции твитов
print(f'
Screen name: {json_data["user"]["name"]}')
print(f'
Created at: {json_data["created_at"]}')
print(f'Tweets received: {self.tweet_count}')
# При достижении TWEET_LIMIT вернуть False для завершения передачи
return self.tweet_count != self.TWEET_LIMIT
def on_error(self, status):
print(status)
return True

Ранее класс TweetListener переопределил метод on_status для получения объектов Tweepy Status, представляющих твиты. На этот раз переопределяется
метод on_data (строки 21–31). Вместо объектов Status метод on_data получает
необработанную разметку JSON для объекта твита. В строке 24 строка JSON,
полученная методом on_data, преобразуется в объект Python JSON. Каждая БД
MongoDB содержит одну или несколько коллекций Collection документов.
В строке 25 выражение
self.db.tweets

обращается к коллекции tweets объекта Database, создавая ее в том случае,
если она не существует. В строке 25 метод insert_one коллекции используется
для сохранения объекта JSON в коллекции tweets.

16.4. Практический пример: документная база данных MongoDB   793

Подсчет твитов для каждого сенатора
Затем выполним полнотекстовый поиск по коллекции твитов и подсчитаем
количество твитов, содержащих имя пользователя Twitter каждого сенатора.
Для выполнения полнотекстового поиска в БД MongoDB необходимо создать
текстовый индекс1. Он указывает, в каком поле(-ях) документа следует вести
поиск. Каждый текстовый индекс определяется как кортеж из имени поля
для поиска и типа индекса ('text'). Универсальный спецификатор MongoDB
($**) означает, что каждое текстовое поле в документе (объекте твита JSON)
должно индексироваться для полнотекстового поиска:
In [16]: db.tweets.create_index([('$**', 'text')])
Out[16]: '$**_text'

После того как индекс будет определен, вы сможете воспользоваться методом
count_documents объекта Collection для подсчета общего количества документов в коллекции, содержащих указанный текст. Выполним поиск по коллекции
tweets БД для каждого имени пользователя Twitter из столбца TwitterHandle
коллекции senators_df (коллекция DataFrame):
In [17]: tweet_counts = []
In [18]: for senator in senators_df.TwitterHandle:
...:
tweet_counts.append(db.tweets.count_documents(
...:
{"$text": {"$search": senator}}))
...:

Объект JSON, передаваемый count_documents, в данном случае сообщает, что
индекс с именем text будет использоваться для поиска значения senator.

Вывод счетчиков твитов для каждого сенатора
Создадим копию коллекции senators_df с добавленным столбцом tweet_
counts, после чего выведем список из 10 сенаторов с наибольшими значениями
счетчика твитов:
1

За дополнительной информацией о разновидностях индексов в MongoDB, текстовых
индексах и операторах обращайтесь по адресам https://docs.mongodb.com/manual/indexes,
https://docs.mongodb.com/manual/core/index-text и https://docs.mongodb.com/manual/reference/
operator.

794   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
In [19]: tweet_counts_df = senators_df.assign(Tweets=tweet_counts)
In [20]: tweet_counts_df.sort_values(by='Tweets',
...:
ascending=False).head(10)
...:
Out[20]:
State
78
SC
41
MA
8
CA
20
HI
62
NY
24
IL
13
CT
21
HI
86
UT
77
RI

Name Party
Lindsey Graham
R
Elizabeth Warren
D
Dianne Feinstein
D
Brian Schatz
D
Chuck Schumer
D
Tammy Duckworth
D
Richard Blumenthal
D
Mazie Hirono
D
Orrin Hatch
R
Sheldon Whitehouse
D

TwitterHandle
LindseyGrahamSC
SenWarren
SenFeinstein
brianschatz
SenSchumer
SenDuckworth
SenBlumenthal
maziehirono
SenOrrinHatch
SenWhitehouse

TwitterID
432895323
970207298
476256944
47747074
17494010
1058520120
278124059
92186819
262756641
242555999

Tweets
1405
1249
1079
934
811
656
646
628
506
350

Получение координат штатов для вывода маркеров
Затем мы воспользуемся методами, описанными в главе 12, для получения
широты и долготы каждого штата (в том числе, несколько ниже, — для вывода на карте Folium маркеров с названиями и количеством твитов, в которых
упоминаются сенаторы каждого штата).
Файл state_codes.py содержит словарь state_codes, связывающий двухбуквенные обозначения штатов с их полными названиями. Полные названия
штатов используются в сочетании с функцией geocode объекта OpenMapQuest
библиотеки geopy для определения местонахождения каждого штата1. Начнем
с импортирования необходимых библиотек и словаря state_codes:
In [21]: from geopy import OpenMapQuest
In [22]: import time
In [23]: from state_codes import state_codes

Затем получим объект geocoder для преобразования названий штатов в объекты Location:
In [24]: geo = OpenMapQuest(api_key=keys.mapquest_key)

1

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

16.4. Практический пример: документная база данных MongoDB   795

Каждый штат представлен двумя сенаторами, поэтому мы можем определить
местонахождение для каждого штата однократно, а затем использовать объект Location для обоих сенаторов от этого штата. Мы будем использовать
уникальные названия штатов, отсортированные по возрастанию:
In [25]: states = tweet_counts_df.State.unique()
In [26]: states.sort()

Следующие два фрагмента используют код из главы 12 для определения
местоположения каждого штата. Во фрагменте [28] при вызове функции
geocode передается название штата с суффиксом ', USA', который гарантирует, что будут получены данные для США1, потому что за пределами США
существуют места, названия которых совпадают с названиями штатов. Чтобы
отображать информацию о ходе выполнения, выведем строку каждого нового
объекта Location:
In [27]: locations = []
In [28]: for state in states:
...:
processed = False
...:
delay = .1
...:
while not processed:
...:
try:
...:
locations.append(
...:
geo.geocode(state_codes[state] + ', USA'))
...:
print(locations[-1])
...:
processed = True
...:
except: # Тайм-аут, ожидаем перед повторной попыткой
...:
print('OpenMapQuest service timed out. Waiting.')
...:
time.sleep(delay)
...:
delay += .1
...:
Alaska, United States of America
Alabama, United States of America
Arkansas, United States of America
...

1

Когда мы в первый раз проводили геокодирование для штата Вашингтон, объект
OpenMapQuest вернул данные для города Вашингтон (округ Колумбия). Из-за этого мы
внесли изменения в файл state_codes.py, чтобы в нем использовалась строка «Washington
State».

796   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT

Группировка счетчиков твитов по штатам
Используем общее количество твитов двух сенаторов штата для назначения
цвета этого штата на карте. Более темными цветами обозначаются штаты
с большим количеством твитов. Для подготовки данных к нанесению на карту воспользуемся методом groupby коллекции DataFrame библиотеки Pandas
для группировки сенаторов по штатам и вычисления суммарного количества
твитов по штатам:
In [29]: tweets_counts_by_state = tweet_counts_df.groupby(
...:
'State', as_index=False).sum()
...:
In [30]: tweets_counts_by_state.head()
Out[30]:
State Tweets
0
AK
27
1
AL
2
2
AR
47
3
AZ
47
4
CA
1135

Ключевой аргумент as_index=False в фрагменте [29] показывает, что обозначения штатов должны быть значениями столбца полученного объекта GroupBy
(вместо индексов строк). Метод sum объекта GroupBy суммирует числовые
данные (количество твитов по штату). Фрагмент [30] выводит несколько строк
объекта GroupBy, чтобы вы могли просмотреть часть результатов.

Создание карты
Перейдем к созданию карты. Возможно, вы захотите отрегулировать масштаб.
В нашей системе приведенный ниже фрагмент создает карту, на которой изначально видна только континентальная часть США. Помните, карты Folium
интерактивны, так что после отображения карты вы сможете отрегулировать
масштаб или перетащить карту для просмотра других областей, включая
Аляску или Гавайи:
In [31]: import folium
In [32]: usmap = folium.Map(location=[39.8283, -98.5795],
...:
zoom_start=4, detect_retina=True,
...:
tiles='Stamen Toner')
...:

16.4. Практический пример: документная база данных MongoDB   797

Создание картограммы для определения цветов карты
На картограмме области карты раскрашиваются в разные цвета в соответствии со значениями, которые вы задали для определения карты. Создадим
картограмму, выбирающую окраску штатов по количеству твитов, в которых встречаются имена сенаторов. Сохраним файл Folium us-states.json по
адресу:
https://raw.githubusercontent.com/python-visualization/folium/master/examples/data/
us-states.json

в каталоге этого примера. Файл содержит разметку в диалекте JSON, который называется GeoJSON (Geographic JSON) и предназначается для описания
границ фигур — в данном случае границ всех штатов США. Картограмма использует эту информацию для определения окраски каждого штата. За более
подробной информацией о GeoJSON обращайтесь по адресу http://geojson.org/.1
Следующие фрагменты создают картограмму, а затем добавляют ее к карте:
In [33]: choropleth = folium.Choropleth(
...:
geo_data='us-states.json',
...:
name='choropleth',
...:
data=tweets_counts_by_state,
...:
columns=['State', 'Tweets'],
...:
key_on='feature.id',
...:
fill_color='YlOrRd',
...:
fill_opacity=0.7,
...:
line_opacity=0.2,
...:
legend_name='Tweets by State'
...: ).add_to(usmap)
...:
In [34]: layer = folium.LayerControl().add_to(usmap)

В данном случае были использованы следующие аргументы:
ØØgeo_data='us-states.json' — файл с разметкой GeoJSON, определяющей

границы окрашиваемых областей.
ØØname='choropleth' — Folium отображает картограмму Choropleth как

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

Folium предоставляет ряд других файлов GeoJSON в своем каталоге примеров по адресу
https://github.com/python-visualization/folium/tree/master/examples/data. Вы также можете создать собственный файл по адресу http://geojson.io.

798   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
управления вызываются щелчком на значке с изображением слоев (
на карте;

)

ØØdata=tweets_counts_by_state — коллекция pandas DataFrame (или Series)

со значениями, определяющими цвета картограммы;
ØØcolumns=['State', 'Tweets'] — если данные содержатся в коллекции Da­

ta­Frame, то аргумент содержит список двух столбцов, представляющих

ключи и соответствующие значения цветов для окраски;
ØØkey_on='feature.id' — переменная из файла GeoJSON, с которой карто-

грамма связывает значения в аргументе columns;
ØØfill_color='YlOrRd' — цветовая карта, определяющая цвета для запол-

нения штатов. Folium предоставляет 12 цветовых карт: 'BuGn', 'BuPu',
'GnBu', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'RdPu', 'YlGn', 'YlGnBu', 'YlOrBr'
и 'YlOrRd'. Поэкспериментируйте с разными картами, чтобы найти самую
эффективную и эстетическую подборку цветов для своего приложения;
ØØfill_opacity=0.7 — значение в диапазоне от 0.0 (полная прозрачность) до

1.0 (непрозрачность), определяющее степень прозрачности цветов залив-

ки, выводимой в границах штатов;
ØØline_opacity=0.2 — значение в диапазоне от 0.0 (полная прозрачность)

до 1.0 (непрозрачность), определяющее прозрачность линий, которые
обозначают границы штатов;
ØØlegend_name='Tweets by State' — в верхней части карты выводится цве-

товая шкала, обозначающая диапазон значений, представленных цветами.
Текст legend_name выводится под цветовой шкалой и сообщает, что представляют разные цвета.
Полный список ключевых аргументов Choropleth документирован по адресу:
http://python-visualization.github.io/folium/modules.html#folium.features.Choropleth

Создание маркеров для каждого штата
Затем создадим маркеры для каждого штата. Чтобы сенаторы отображались
в каждом маркере в порядке убывания количества твитов, отсортируем
tweet_counts_df по убыванию столбца 'Tweets':
In [35]: sorted_df = tweet_counts_df.sort_values(
...:
by='Tweets', ascending=False)
...:

16.4. Практический пример: документная база данных MongoDB   799

Цикл в приведенном ниже фрагменте создает объекты Marker. Сначала вызов
sorted_df.groupby('State')

группирует sorted_df по значению 'State' . Метод groupby коллекции
DataFrame поддерживает исходный порядок строк в каждой группе. В заданной группе сенатор с наибольшим количеством твитов будет стоять на
первом месте, так как сортировка выполнена по убыванию количества твитов
во фрагменте [35]:
In [36]: for index, (name, group) in enumerate(sorted_df.groupby('State')):
...:
strings = [state_codes[name]] # используется для сборки всплывающего
# текста
...:
...:
for s in group.itertuples():
...:
strings.append(
...:
f'{s.Name} ({s.Party}); Tweets: {s.Tweets}')
...:
...:
text = ''.join(strings)
...:
marker = folium.Marker(
...:
(locations[index].latitude, locations[index].longitude),
...:
popup=text)
...:
marker.add_to(usmap)
...:
...:

Передадим сгруппированную коллекцию DataFrame для перебора, чтобы получить индекс для каждой группы, по которой будет выбран объект Location для
каждого штата из списка locations. Каждая группа состоит из названия (обозначение штата, по которому выполнялась группировка) и коллекции элементов
этой группы (два сенатора от этого штата). Цикл работает следующим образом:
ØØПолное название штата ищется в словаре state_codes и сохраняется

в списке strings — этот список будет использоваться для формирования
текста подсказки Marker.
ØØВложенный цикл перебирает элементы коллекции group, возвращая

каждый элемент в виде именованного кортежа с данными конкретного сенатора. Для каждого сенатора строится отформатированная строка
с именем, партией и количеством твитов, которая затем присоединяется
к списку strings.
ØØВ тексте Marker может использоваться разметка HTML для форматиро-

вания. Мы объединяем элементы списка strings, разделяя их элементом
HTML , создающим новую строку в HTML.

800   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
ØØСоздается объект Marker. Первый аргумент определяет местоположение

объекта Marker в виде кортежа, содержащего широту и долготу. Ключевой
аргумент popup задает текст, выводимый по щелчку на маркере.
ØØОбъект Marker добавляется на карту.

Вывод карты
Наконец, карта сохраняется в файле HTML:
In [17]: usmap.save('SenatorsTweets.html')

Чтобы просмотреть карту и взаимодействовать с ней, откройте файл HTML
в своем браузере. Напомним, карту можно перетаскивать, чтобы вывести
Аляску и Гавайи. Текст маркера для Южной Каролины:

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

16.5. Hadoop   801

16.5. Hadoop
В нескольких ближайших разделах мы покажем, как технологии Apache
Hadoop и Apache Spark решают проблемы хранения и обработки больших
данных при помощи огромных компьютерных кластеров, массово-параллельной обработки, MapReduce-программирования Hadoop и средств Spark для
обработки данных в памяти. В этом разделе рассматривается Apache Hadoop —
ключевая технология больших данных, которая также лежит в основе многих
последних достижений в области обработки больших данных и всей экосистемы программных инструментов, постоянно развивающихся для современных
потребностей больших данных.

16.5.1. Обзор Hadoop
На момент запуска сервиса Google в 1998 году объем сетевых данных на
2,4 миллиона веб-сайтов1 уже был огромным. В наши дни количество сайтов
увеличилось почти до 2 миллиардов2 (почти тысячекратное увеличение),
а компания Google обрабатывает свыше 2 триллионов поисковых запросов
в год3! К слову, авторы этой книги пользовались поисковой системой Google
с момента ее появления, и, на их взгляд, скорость отклика в наши дни значительно выше.
Когда компания Google разрабатывала свою поисковую систему, она знала,
что система должна быстро возвращать результаты поиска. Существовал лишь
один реальный способ решения этой задачи — хранение и индексирование
всего интернета, что можно было сделать только с применением умного сочетания механизмов использования внешней и оперативной памяти. Компьютеры
того времени не могли хранить такие объемы данных или анализировать их
достаточно быстро для того, чтобы гарантировать быструю выдачу ответов.
Так компания Google разработала систему кластеризации, которая объединяла
огромные количества компьютеров в так называемые узлы (nodes). Поскольку
увеличение количества компьютеров и связей между ними означало более высокую вероятность аппаратных сбоев, в систему также были встроены высокие
уровни избыточности, которые гарантировали, что система продолжит функ1
2
3

http://www.internetlivestats.com/total-number-of-websites/.
http://www.internetlivestats.com/total-number-of-websites/.
http://www.internetlivestats.com/google-search-statistics/.

802   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
ционировать даже при сбое узлов внутри кластера. Данные распределялись
между множеством недорогих «серийных компьютеров». Для удовлетворения
поискового запроса все компьютеры кластера параллельно проводили поиск
в той части системы, которая была им доступна локально. Затем результаты
поиска собирались и возвращались пользователю.
Чтобы добиться этой цели, компания Google должна была разработать оборудование и программное обеспечение кластеризации, включая распределенное
хранение. Компания Google опубликовала описание своей архитектуры, но
не стала публиковать свой код. Затем программисты из Yahoo!, работавшие
с описанием архитектуры Google из статьи «Google File System»1, построили
собственную систему. Они опубликовали свою работу на условиях открытого
кода, а организация Apache реализовала систему в форме Hadoop (система
получила свое название в честь плюшевого слона, который принадлежал
ребенку одного из ее создателей).
Технологическому развитию Hadoop также способствовали две публикации Google — «MapReduce: Simplified Data Processing on Large Clusters» 2
и «Bigtable: A Distributed Storage System for Structured Data»3 — эта технология была заложена в основу Apache HBase (база данных «ключ-значение»
и столбцовая база данных NoSQL).4

HDFS, MapReduce и YARN
Ключевые компоненты Hadoop:
ØØHDFS (Hadoop Distributed File System) — файловая система для хранения

огромных объемов данных в кластере;
ØØтехнология MapReduce для реализации задач обработки данных.

Ранее в книге мы представили основы программирования в функциональном
стиле и парадигму «фильтрация/отображение/свертка». Hadoop MapReduce
использует похожую концепцию, но в массово-параллельном масштабе. Задача
MapReduce выполняет две операции — отображение и свертку. Шаг отобра1
2
3
4

http://static.googleusercontent.com/media/research.google.com/en//archive/gfs-sosp2003.pdf.
http://static.googleusercontent.com/media/research.google.com/en//archive/mapreduce-osdi04.pdf.
http://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf.

Много других авторитетных публикаций, относящихся к большим данным (включая
упомянутые), можно найти по адресу: https://bigdata-madesimple.com/research-papers-thatchanged-the-world-of-big-data/.

16.5. Hadoop   803

жения, который также может включать фильтрацию, обрабатывает исходные
данные по всему кластеру и отображает их на кортежи пар «ключ-значение».
Затем шаг свертки объединяет эти кортежи для получения результатов задачи MapReduce. Здесь принципиальное значение имеет то, как выполняются
операции MapReduce. Hadoop делит данные на пакеты, распределяемые по
узлам кластера — от нескольких узлов до кластеров Yahoo! с 40 000 узлов
и свыше 100 000 ядер1. Hadoop также распределяет код задачи MapReduce по
узлам кластера и выполняет этот код параллельно на всех узлах. Каждый узел
обрабатывает только пакет данных, хранящийся на этом узле. Шаг свертки
объединяет результаты всех узлов для получения итогового результата. Для
координации происходящего Hadoop использует механизм YARN («Yet Another
Resource Negotiator»), управляющий всеми ресурсами кластера и планирующий
выполнение задач.

Экосистема Hadoop
Хотя существование Hadoop начиналось с HDFS и MapReduce, за которыми
последовала технология YARN, в настоящее время на основе Hadoop сформировалась крупная экосистема, включающая Spark (см. разделы 16.6–16.7)
и многие другие проекты Apache2,3,4:
ØØAmbari (https://ambari.apache.org) — инструменты для управления кластера-

ми Hadoop.
ØØDrill (https://drill.apache.org) — SQL-запросы к нереляционным данным в ба-

зах данных Hadoop и NoSQL.
ØØFlume (https://flume.apache.org) — сервис сбора и хранения (в HDFS и дру-

гих системах хранения) потоковых событийных данных (серверные журналы большого объема, сообщения IoT и т. д.).
ØØHBase (https://hbase.apache.org) — БД NoSQL для больших данных с «мил-

лиардами строк и до 31 миллиона столбцов — на кластерах, построенных
из серийного оборудования».
ØØHive (https://hive.apache.org) — использование SQL для взаимодействия

с данными в хранилищах данных. Хранилище данных (data warehouse)
объединяет данные разных типов из разных источников. Основные опе1
2
3
4

https://wiki.apache.org/hadoop/PoweredBy.
https://hortonworks.com/ecosystems/.
https://readwrite.com/2018/06/26/complete-guide-of-hadoop-ecosystem-components/.
https://www.janbasktraining.com/blog/introduction-architecture-components-hadoop-ecosystem/.

804   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
рации — извлечение данных, их преобразование и загрузка (эти операции
обозначаются сокращением ETL) в другую БД, обычно для анализа и построения отчетов.
ØØImpala (https://impala.apache.org) — БД для SQL-запросов в реальном вре-

мени к распределенным данным, хранимым в Hadoop HDFS или HBase.
ØØKafka (https://kafka.apache.org) — передача сообщений в реальном времени,

обработка и хранение потоковых данных (обычно с целью преобразования и обработки потоковых данных большого объема — скажем, активности на веб-сайте или потоковой передачи данных IoT).
ØØPig (https://pig.apache.org) — сценарная платформа, преобразующая задачи

анализа данных с языка сценариев Pig Latin в задачи MapReduce.
ØØSqoop (https://sqoop.apache.org) — инструмент для перемещения структу-

рированных, полуструктурированных и неструктурированных данных
между базами данных.
ØØStorm (https://storm.apache.org) — система обработки потоковых данных

в реальном времени для таких задач, как аналитика данных, машинное
обучение, ETL и т. д.
ØØZooKeeper (https://zookeeper.apache.org) — сервис для управления конфигу-

рацией кластера и координацией между кластерами.

Провайдеры Hadoop
Многие провайдеры облачных сервисов предоставляют Hadoop как сервис —
Amazon EMR, Google Cloud DataProc, IBM Watson Analytics Engine, Microsoft
Azure HDInsight и др. Кроме того, такие компании, как Cloudera и Hortonworks
(которые на момент написания книги проходили слияние), предоставляют
интегрированные компоненты и инструменты экосистемы Hadoop через крупных провайдеров облачных сервисов. Они также предоставляют бесплатные
загружаемые среды, которые можно запустить на настольном компьютере1 для
обучения, разработки и тестирования до перехода на облачное размещение,
которое может быть сопряжено со значительными затратами. Программирование MapReduce будет представлено в примерах следующих разделов с использованием кластера на базе облачного сервиса Microsoft Azure HDInsight,
предоставляющего Hadoop как сервис.
1

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

16.5. Hadoop   805

Hadoop 3
Apache продолжает развивать Hadoop. В декабре 2017 года была выпущена версия Hadoop 31 с многочисленными усовершенствованиями, включая повышенное
быстродействие и значительно улучшенную эффективность хранения данных2.

16.5.2. Получение статистики по длине слов
в «Ромео и Джульетте» с использованием MapReduce
В нескольких ближайших подразделах мы создадим облачный многоузловой
кластер в Microsoft Azure HDInsight. Затем воспользуемся функциональностью сервиса для демонстрации выполнения задач Hadoop MapReduce в этом
кластере. Задача MapReduce будет определять длину каждого слова вфайле
RomeoAndJuliet.txt (из главы 11), а затем выводить статистику по количеству
слов каждой длины. После определения шагов отображения и свертки мы отправим задачу в кластер HDInsight, а Hadoop решит, как лучше использовать
компьютерный кластер для ее выполнения.

16.5.3. Создание кластера Apache Hadoop
в Microsoft Azure HDInsight
Большинство крупных провайдеров облачных сервисов поддерживают кластеры Spark и Hadoop, которые можно настраивать под потребности вашего
приложения. Многоузловые облачные кластеры обычно являются платными
сервисами, хотя многие провайдеры предоставляют бесплатные ознакомительные версии или кредиты, позволяющие опробовать эти сервисы.
Поэкспериментируем с процессом настройки кластеров и воспользуемся
ими для выполнения задач. В этом примере Hadoop воспользуемся сервисом
Microsoft Azure’s HDInsight для создания облачных кластеров компьютеров
для тестирования наших примеров. Откройте страницу
https://azure.microsoft.com/en-us/free

для создания учетной записи. Microsoft требует ввести данные кредитной
карты для проверки личности. Некоторые сервисы бесплатны бессрочно, и вы
1

2

За списком возможностей Hadoop 3 обращайтесь по адресу https://hadoop.apache.org/docs/
r3.0.0/.
https://www.datanami.com/2018/10/18/is-hadoop-officially-dead/.

806   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
можете продолжать пользоваться ими хоть круглый год. За информацией об
этих сервисах обращайтесь по адресу:
https://azure.microsoft.com/en-us/free/free-account-faq/

Microsoft также предоставляет кредит для экспериментов с платными сервисами, такими как сервис Spark и HDInsight Hadoop. По истечении кредита или
через 30 дней (в зависимости от того, что произойдет ранее) вы не сможете
продолжить пользоваться платными сервисами, пока не разрешите Microsoft
снимать средства с вашей карты.
Поскольку в наших примерах используется кредит новой учетной записи
Azure1, обсудим настройку низкозатратного кластера, использующего меньше вычислительных ресурсов по сравнению с тем, что Microsoft выделяет по
умолчанию2. Внимание: после выделения кластера плата будет взиматься
независимо от того, пользуетесь вы им или нет. Следовательно, после завершения этого примера обязательно удалите свой кластер(-ы) и другие
ресурсы во избежание лишних трат. За дополнительной информацией обращайтесь по адресу:
https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-portal

Документацию и видеоролики, относящиеся к Azure, можно найти здесь:
ØØhttps://docs.microsoft.com/en-us/azure/ — документация Azure.
ØØhttps://channel9.msdn.com/ — видеосеть Microsoft Channel 9.
ØØhttps://www.youtube.com/user/windowsazure

— канал Microsoft Azure на

YouTube.

Создание кластера Hadoop в HDInsight
По следующей ссылке доступно описание процесса создания кластера для
Hadoop с использованием сервиса Azure HDInsight:
1

2

За последней информацией о возможностях бесплатных учетных записей Microsoft обращайтесь по адресу https://azure.microsoft.com/en-us/free/.
Информация о рекомендуемых Microsoft конфигурациях кластеров доступна по адресу
https://docs.microsoft.com/en-us/azure/hdinsight/hdinsight-component-versioning#default-nodeconfiguration-and-virtual-machine-sizes-for-clusters. Если вы настроите кластер, размеры кото-

рого недостаточны для конкретного сценария, то при попытке развертывания кластера
будет получено сообщение об ошибке.

16.5. Hadoop   807
https://docs.microsoft.com/en-us/azure/hdinsight/hadoop/apache-hadoop-linux-createcluster-get-started-portal

Выполняя серию шагов Create a Hadoop cluster, обратите внимание на следующее:
ØØНа шаге 1 для обращения к порталу Azure вам следует ввести регистраци-

онные данные своей учетной записи по адресу:
https://portal.azure.com
ØØНа шаге 2 операция Data + Analytics теперь называется Analytics, а внешний

вид значка HDInsight и его текст отличаются от того, что показано в учебнике.
ØØНа шаге 3 необходимо выбрать свободное имя кластера. При вводе имени

Microsoft проверяет имя и выводит соответствующее сообщение, если оно
занято. Также вы должны создать пароль. В группе Resource также необходимо щелкнуть на кнопке Create new и ввести имя группы. Оставьте все
остальные настройки этого шага без изменений.
ØØНа шаге 5: в разделе Select a Storage account щелкните на кнопке Create new

и введите имя учетной записи хранения данных, содержащее только буквы нижнего регистра и цифры. Имя учетной записи хранения данных, как
и имя кластера, должно быть уникальным.
Когда вы доберетесь до раздела Cluster summary, то увидите, что компания
Microsoft изначально определила конфигурацию кластера Head (2 x D12
v2), Worker (4 x D4 v2). На момент написания книги оцениваемая почасовая стоимость такой конфигурации составляла 3,11 доллара. Кластер
использует шесть вычислительных узлов с 40 ядрами — много более, чем
необходимо для демонстрационных целей. Вы можете отредактировать эту
конфигурацию и сократить количество процессоров и ядер, что обеспечит
экономию средств. Изменим конфигурацию и переключимся на кластер из
четырех процессоров и 16 ядер, использующий менее мощные компьютеры.
В разделе Cluster summary:
1. Щелкните на кнопке Edit справа от поля Cluster size.
2. Измените количество рабочих узлов Number of Worker на 2.
3. Щелкните на кнопке Worker node size, затем на кнопке View all, выберите
вариант D3 v2 (минимальный размер для узлов Hadoop) и щелкните на
кнопке Select.

808   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
4. Щелкните на кнопке Head node size, затем на кнопке View all, выберите
вариант D3 v2 и щелкните на кнопке Select.
5. Щелкните на кнопке Next, затем снова на кнопке Next для возврата к разделу Cluster summary. Microsoft проверяет новую конфигурацию.
6. Когда кнопка Create станет доступной, щелкните на ней, чтобы развернуть
кластер.
«Развертывание» кластера занимает 20–30 минут. В это время Microsoft выделяет все ресурсы и программное обеспечение, необходимое кластеру.
После внесения изменений оценочная стоимость кластера составляла 1,18 доллара в час (при среднем уровне использования кластеров с аналогичной
конфигурацией). Наши фактические затраты были ниже оцениваемых. Если
вы столкнетесь с какими-либо проблемами в ходе настройки кластера, то
Microsoft предоставит техническую поддержку HDInsight в форме чата по
адресу:
https://azure.microsoft.com/en-us/resources/knowledge-center/technical-chat/

16.5.4. Hadoop Streaming
В языках, не имеющих встроенной поддержки в Hadoop, таких как Python,
для реализации задач приходится использовать технологию Hadoop Streaming.
В Hadoop Streaming сценарии Python, реализующие шаги отображения
и свертки, используют стандартные потоки ввода и вывода для взаимодействия с Hadoop. Обычно стандартный поток ввода читает данные с клавиатуры,
а стандартный поток вывода записывает данные в командную строку. Тем не
менее эти потоки могут быть перенаправлены (как это делает Hadoop) для чтения из других источников и записи в другие приемники. Hadoop использует
потоки следующим образом:
ØØHadoop поставляет ввод сценарию отображения. Этот сценарий читает

свои данные из стандартного потока ввода.
ØØСценарий отображения записывает свои результаты в стандартный поток

вывода.
ØØHadoop поставляет вывод сценария отображения на вход сценария сверт-

ки, который читает данные из стандартного потока ввода.
ØØСценарий свертки записывает свои результаты в стандартный поток вы-

вода.

16.5. Hadoop   809
ØØHadoop записывает вывод сценария свертки в файловую систему Hadoop

(HDFS).
Термины «сценарий отображения» и «сценарий свертки» должны быть вам
знакомы по предшествующему обсуждению программирования в функциональном стиле, а также фильтрации, отображения и свертки в главе 5.

16.5.5. Реализация сценария отображения
В этом разделе мы создадим сценарий отображения, который получает строки
текста в виде входных данных от Hadoop и отображает их на пары «ключзначение», в которых ключом является длина слова, а соответствующее значение равно 1. Сценарий отображения «видит» каждое слово по отдельности, так
что с его точки зрения каждое слово существует только в одном экземпляре.
В следующем разделе сценарий свертки обобщает эти пары «ключ-значение»
по ключу, сводя их к одному значению счетчика для каждого ключа. По умолчанию Hadoop ожидает, что вывод сценария отображения, а также ввод и вывод
сценария свертки существуют в форме пар «ключ-значение», разделенных
символом табуляции.
В сценарии отображения (length_mapper.py) синтаксис #! в строке 1 сообщает Hadoop, что код Python должен выполняться python3 вместо установки
Python 2 по умолчанию. Эта строка должна предшествовать всем остальным
комментариям и коду в файле. На момент написания книги использовались
Python 2.7.12 и Python 3.5.2. Если в кластере не установлена версия Python 3.6
и выше, то вы не сможете использовать форматные строки в коде.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#!/usr/bin/env python3
# length_mapper.py
"""Отображает строки текста на пары "ключ-значение" из длины слова и 1."""
import sys
def tokenize_input():
"""Каждая строка стандартного ввода разбивается на список строк."""
for line in sys.stdin:
yield line.split()
# Прочитать каждую строку в стандартном вводе и для каждого слова
# построить пару "ключ-значение" из длины слова, табуляции и 1
for line in tokenize_input():
for word in line:
print(str(len(word)) + '\t1')

810   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
Функция-генератор tokenize_input (строки 6–9) читает строки текста из
стандартного потока ввода, возвращая каждой список строк. В данном примере
мы не удаляем знаки препинания или игнорируемые слова, как это делалось
в главе 11.
Когда Hadoop выполняет сценарий, в строках 13–15 перебирают списки
строк, полученные от tokenize_input. Для каждого списка (строки входных
данных) и для каждого элемента (слова) этого списка строка 15 выводит пару
«ключ-значение», состоящую из длины слова (ключ), символа табуляции (\t)
и значения 1. Это означает, что (пока) существует только одно слово этой
длины. Конечно, наверняка существует много других слов с такой же длиной.
Алгоритм MapReduce на шаге свертки обобщает эти пары «ключ-значение»
и сводит все пары с одинаковым ключом к одной паре «ключ-значение» со
значением-счетчиком.

16.5.6. Реализация сценария свертки
В сценарии свертки (length_reducer.py) функция tokenize_input (строки 8–11)
представляет собой функцию-генератор, которая читает и разделяет пары
«ключ-значение», произведенные сценарием отображения. И снова алгоритм
MapReduce предоставляет стандартный ввод. Для каждой строки tokenize_
input отделяет все начальные и завершающие пропуски (в частности, завершающие символы новой строки) и строит список, содержащий ключ и значение.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

#!/usr/bin/env python3
# length_reducer.py
"""Подсчитывает количество слов каждой длины."""
import sys
from itertools import groupby
from operator import itemgetter
def tokenize_input():
"""Разбивает каждую строку стандартного ввода на ключ и значение."""
for line in sys.stdin:
yield line.strip().split('\t')
# Построить пары "ключ-значение" из длины слова и счетчика, разделенные
# табуляцией
for word_length, group in groupby(tokenize_input(), itemgetter(0)):
try:
total = sum(int(count) for word_length, count in group)
print(word_length + '\t' + str(total))
except ValueError:
pass # Если счетчик не является целым числом, то слово игнорируется

16.5. Hadoop   811

Когда алгоритм MapReduce выполняет этот сценарий свертки, в строках 14–19
функция groupby из модуля itertools используется для группировки всех длин
слов с одинаковыми значениями. Первый аргумент вызывает tokenize_input
для получения списков, представляющих пары «ключ-значение». Второй
аргумент означает, что пары «ключ-значение» должны группироваться на
основании элемента с индексом 0 в каждом списке (то есть ключа). Строка 16
суммирует все счетчики для заданного ключа. Строка 17 выводит новую
пару «ключ-значение», которая состоит из слова и его счетчика. Алгоритм
MapReduce берет все итоговые счетчики и записывает их в файл в HDFS —
файловой системе Hadoop.

16.5.7. Подготовка к запуску примера MapReduce
Файлы необходимо загрузить в кластер для выполнения примера. В приглашении командной строки, терминале или командной оболочке перейдите в каталог, содержащий сценарии отображения и свертки, а также файл RomeoAndJuliet.
txt. Программа предполагает, что все три файла находятся в каталоге ch16,
поэтому не забудьте скопировать файл RomeoAndJuliet.txt в каталог.

Копирование файлов сценариев в Hadoop-кластер HDInsight
Введите приведенную ниже команду для отправки файлов. Не забудьте заменить ИмяКластера тем именем, которое было задано при создании кластера
Hadoop, и нажмите клавишу Enter только после того, как будет введена вся
команда. Двоеточие в этой команде обязательно; оно означает, что пароль
кластера будет введен по запросу. Введите пароль, заданный при создании
кластера, и нажмите клавишу Enter:
scp length_mapper.py length_reducer.py RomeoAndJuliet.txt
sshuser@ИмяКластера-ssh.azurehdinsight.net:

При выполнении команды впервые в целях безопасности вам будет предложено подтвердить, что вы доверяете хосту (то есть Microsoft Azure).

Копирование файла RomeoAndJuliet.txt в файловую
систему Hadoop
Чтобы прочитать содержимое RomeoAndJuliet.txt и передать строки текста
сценарию отображения, необходимо сначала скопировать файл в файловую

812   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
систему Hadoop. Используйте ssh1, чтобы войти в кластер и получить доступ
к командной строке. В приглашении командной строки, терминале или командной оболочке введите приведенную ниже команду. Не забудьте заменить
ИмяКластера именем своего кластера. Вам снова будет предложено ввести
пароль кластера:
ssh sshuser@ИмяКластера-ssh.azurehdinsight.net

В данном примере мы воспользуемся следующей командой Hadoop для
копирования текстового файла в уже существующий каталог /examples/data,
который предоставляется кластером для учебных руководств Microsoft Azure
Hadoop. И снова клавиша Enter должна быть нажата только после того, как вы
введете всю команду:
hadoop fs -copyFromLocal RomeoAndJuliet.txt
/example/data/RomeoAndJuliet.txt

16.5.8. Выполнение задания MapReduce
Теперь вы можете запустить задание MapReduce для файла RomeoAndJuliet.
txt в вашем кластере, выполнив приведенную ниже команду. Для вашего
удобства мы включили текст этой команды в файл yarn.txt, так что вы можете
скопировать ее из файла. Мы, кроме того, переформатировали команду для
удобочитаемости:
yarn jar /usr/hdp/current/hadoop-mapreduce-client/hadoop-streaming.jar
-D mapred.output.key.comparator.class=
org.apache.hadoop.mapred.lib.KeyFieldBasedComparator
-D mapred.text.key.comparator.options=-n
-files length_mapper.py,length_reducer.py
-mapper length_mapper.py
-reducer length_reducer.py
-input /example/data/RomeoAndJuliet.txt
-output /example/wordlengthsoutput

Команда yarn запускает программу Hadoop YARN, предназначенную для
управления и координации доступа к ресурсам Hadoop, используемым
1

Пользователям Windows: если ssh у вас не работает, установите и активируйте ssh так,
как описано по адресу https://blogs.msdn.microsoft.com/powershell/2017/12/15/using-theopenssh-beta-in-windows-10-fall-creators-update-and-windows-server-1709/. После завершения
установки выйдите из системы и войдите снова или перезапустите систему, чтобы
активировать ssh.

16.5. Hadoop   813

задачей MapReduce. Файл hadoop-streaming.jar содержит утилиту Hadoop
Streaming, которая позволяет использовать Python для реализации сценариев отображения и свертки. Два параметра -D задают свойства Hadoop,
которые позволяют отсортировать итоговые пары «ключ-значение» по ключу (KeyFieldBasedComparator) по убыванию в числовом порядке (-n; минус
означает сортировку по убыванию) вместо алфавитного. Другие аргументы
командной строки:
ØØ-files — список имен файлов, разделенных запятыми. Hadoop копирует

эти файлы в каждый узел в кластере, чтобы они могли выполняться локально на каждом узле;
ØØ-mapper — имя файла сценария отображения;
ØØ-reducer — имя файла сценария свертки;
ØØ-input — файл или каталог с файлами, передаваемыми сценарию отобра-

жения в качестве входных данных;
ØØ-output — каталог HDFS, в который будет записываться весь вывод. Если

каталог уже существует, происходит ошибка.
В следующем листинге приведена часть результатов, которые выдает Hadoop
при выполнении задания MapReduce. Мы заменили части вывода многоточиями (... ) для экономии места, а также выделили жирным шрифтом
некоторые строки:
ØØОбщее количество обрабатываемых входных путей (Total input paths

to process) — единственным источником ввода в данном примере явля-

ется файл RomeoAndJuliet.txt.
ØØКоличество разбиений (number of splits) — два в данном примере; опре-

деляется количеством рабочих узлов в кластере.
ØØПроценты завершения.
ØØСчетчики файловой системы (File System Counters), включающие коли-

чество прочитанных и записанных байтов.
ØØСчетчики задания (Job Counters) с количеством использованных задач

отображения и свертки, а также различной хронометражной информа­
цией.
ØØСтруктура Map-Reduce (Map-Reduce Framework) с различной информаци-

ей об этапах выполнения.

814   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
packageJobJar: [] [/usr/hdp/2.6.5.3004-13/hadoop-mapreduce/hadoopstreaming-2.7.3.2.6.5.3004-13.jar] /tmp/streamjob2764990629848702405.jar
tmpDir=null
...
18/12/05 16:46:25 INFO mapred.FileInputFormat: Total input paths to
process : 1
18/12/05 16:46:26 INFO mapreduce.JobSubmitter: number of splits:2
...
18/12/05 16:46:26 INFO mapreduce.Job: The url to track the job: http://
hn0-paulte.y3nghy5db2kehav5m0opqrjxcb.cx.internal.cloudapp.net:8088/
proxy/application_1543953844228_0025/
...
18/12/05 16:46:35 INFO mapreduce.Job: map 0% reduce 0%
18/12/05 16:46:43 INFO mapreduce.Job: map 50% reduce 0%
18/12/05 16:46:44 INFO mapreduce.Job: map 100% reduce 0%
18/12/05 16:46:48 INFO mapreduce.Job: map 100% reduce 100%
18/12/05 16:46:50 INFO mapreduce.Job: Job job_1543953844228_0025
completed successfully
18/12/05 16:46:50 INFO mapreduce.Job: Counters: 49
File System Counters
FILE: Number of bytes read=156411
FILE: Number of bytes written=813764
...
Job Counters
Launched map tasks=2
Launched reduce tasks=1
...
Map-Reduce Framework
Map input records=5260
Map output records=25956
Map output bytes=104493
Map output materialized bytes=156417
Input split bytes=346
Combine input records=0
Combine output records=0
Reduce input groups=19
Reduce shuffle bytes=156417
Reduce input records=25956
Reduce output records=19
Spilled Records=51912
Shuffled Maps =2
Failed Shuffles=0
Merged Map outputs=2
GC time elapsed (ms)=193
CPU time spent (ms)=4440
Physical memory (bytes) snapshot=1942798336
Virtual memory (bytes) snapshot=8463282176

16.5. Hadoop   815
Total committed heap usage (bytes)=3177185280
...
18/12/05 16:46:50 INFO streaming.StreamJob: Output directory: /example/
wordlengthsoutput

Просмотр счетчиков
Hadoop MapReduce сохраняет вывод в HDFS, поэтому для просмотра счетчиков длин слов необходимо просмотреть файл в HDFS в кластере следующей
командой:
hdfs dfs -text /example/wordlengthsoutput/part-00000

Результаты выполнения предшествующей команды:
18/12/05 16:47:19 INFO lzo.GPLNativeCodeLoader: Loaded native gpl library
18/12/05 16:47:19 INFO lzo.LzoCodec: Successfully loaded & initialized
native-lzo library [hadoop-lzo rev
b5efb3e531bc1558201462b8ab15bb412ffa6b89]
1
1140
2
3869
3
4699
4
5651
5
3668
6
2719
7
1624
8
1062
9
855
10
317
11
189
12
95
13
35
14
13
15
9
16
6
17
3
18
1
23
1

Удаление кластера для предотвращения затрат
Внимание: обязательно удалите свой кластер(-ы) и связанные с ним ресурсы (такие, как пространство хранения данных) во избежание лишних
расходов. На портале Azure щелкните на кнопке All resources для просмотра
списка ресурсов, в котором будет присутствовать созданный кластер и учетная

816   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
запись хранилища. И то и другое может привести к списанию средств, если не
удалить эти ресурсы. Выберите каждый ресурс и удалите его кнопкой Delete.
Вам будет предложено подтвердить свое решение (введите yes). За дополнительной информацией обращайтесь по адресу:
https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-portal

16.6. Spark
В этом разделе приведен обзор Apache Spark. Мы воспользуемся библиотекой
PySpark для Python и средствами Spark в стиле функционального программирования (фильтрация/отображение/свертка) для реализации простого примера с подсчетом слов, который генерирует статистику по длине слов в пьесе
«Ромео и Джульетта».

16.6.1. Краткий обзор Spark
При обработке действительно больших данных эффективность становится
критически значимым фактором. Технология Hadoop адаптирована для
пакетной обработки на базе дисков — данные читаются с диска, обрабатываются, а результаты записываются обратно на диск. Во многих случаях
практическое применение больших данных требует эффективности более
высокой, чем та, которую можно достичь при интенсивной работе с диском.
В частности, быстрые потоковые приложения, требующие обработки в реальном времени или почти в реальном времени, не будут работать в дисковой
архитектуре.

История
Технология Spark была разработана в 2009 году в Университете Беркли (Калифорния, США) и финансировалась DARPA (Управление перспективных
исследований Министерства обороны США). Изначально она создавалась как
ядро распределенного выполнения для высокопроизводительного машинного
обучения1. Spark использует архитектуру обработки в памяти, которая «была
применена для сортировки 100 Тбайт данных в 3 раза быстрее, чем Hadoop
MapReduce, на 1/10 машин»2, а по скорости выполнения некоторых задач
1
2

https://gigaom.com/2014/06/28/4-reasons-why-spark-could-jolt-hadoop-into-hyperdrive/.
https://spark.apache.org/faq.html.

16.6. Spark   817

превосходила Hadoop до 100 раз1. Существенно более высокая эффективность
Spark в задачах пакетной обработки заставила многие компании перейти
с Hadoop MapReduce на Spark2,3,4.

Архитектура и компоненты
Хотя изначально технология Spark разрабатывалась для выполнения на базе
Hadoop и использовала такие компоненты Hadoop, как HDFS и YARN, Spark
может работать автономно: на одном компьютере (обычно для обучения
и тестирования), а также в кластере или с использованием разных менеджеров кластеров и распределенных систем хранения данных. Для управления
ресурсами Spark работает на базе Hadoop YARN, Apache Mesos, Amazon EC2
и Kubernetes, поддерживая массу распределенных систем хранения, включая
HDFS, Apache Cassandra, Apache HBase и Apache Hive5.
Центральное место в Spark занимают отказоустойчивые распределенные наборы данных (RDD, Resilient Distributed Datasets), используемые для обработки
распределенных данных с помощью программирования в функциональном
стиле. Кроме чтения данных с диска и записи данных на диск, Hadoop применяет репликацию для обеспечения отказоустойчивости, что создает дополнительные потери ресурсов из-за дисковых операций. RDD устраняют
эти потери за счет работы с памятью (диск используется только в том случае,
если данные не помещаются в памяти), а не за счет репликации. В Spark отказоустойчивость обеспечивается запоминанием действий, использованных
для создания каждого RDD, что позволяет заново построить RDD в случае
сбоя кластера6.
Spark распределяет операции, заданные в Python, по узлам кластера для
параллельного выполнения. Механизм Spark Streaming позволяет обрабатывать данные по мере получения. Коллекции Spark DataFrame , сходные
с коллекциями DataFrame библиотеки Pandas, позволяют просматривать RDD
как коллекцию именованных столбцов. Коллекции Spark DataFrame могут
использоваться в сочетании с Spark SQL для выполнения запросов к распределенным данным. Spark также включает библиотеку Spark MLlib (Spark
1
2
3
4
5
6

https://spark.apache.org/.
https://bigdata-madesimple.com/is-spark-better-than-hadoop-map-reduce/.
https://www.datanami.com/2018/10/18/is-hadoop-officially-dead/.
https://blog.thecodeteam.com/2018/01/09/changing-face-data-analytics-fast-data-displaces-big-data/.
http://spark.apache.org/.
https://spark.apache.org/research.html.

818   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
Machine Learning Library) для выполнения различных алгоритмов машинного
обучения, сходных с теми, которые были описаны в главах 14 и 15. RDD, Spark
Streaming, коллекции DataFrame и Spark SQL будут описаны в нескольких
ближайших примерах.

Провайдеры
Провайдеры Hadoop обычно предоставляют поддержку Spark. Кроме провайдеров, перечисленных в разделе 16.5, также существуют провайдеры,
специализирующиеся на Spark, например Databricks. Они предоставляют
«облачную платформу, построенную на базе Spark, с нулевыми потребностями в управлении»1. Кроме того, превосходным ресурсом для изучения Spark
является веб-сайт Databricks. Платная платформа Databricks работает на базе
Amazon AWS или Microsoft Azure. Databricks также предоставляет бесплатный
уровень Databricks Community Edition, идеально подходящий для изучения
Spark и среды Databricks.

16.6.2. Docker и стеки Jupyter Docker
В этом разделе мы покажем, как загрузить и выполнить стек Docker, содержащий Spark и модуль PySpark для работы со Spark из Python. Код примера
Spark будет написан в Jupyter Notebook. Начнем с обзора Docker.

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

https://databricks.com/product/faq.

16.6. Spark   819

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

Установка Docker
Docker для Windows 10 Pro или macOS можно установить по адресу:
https://www.docker.com/products/docker-desktop

В Windows 10 Pro вы должны разрешить установочной программе "Docker
for Windows.exe" вносить изменения в вашу систему для завершения процесса установки. Когда Windows спросит, хотите ли вы разрешить программе установки вносить изменения в вашу систему, щелкните на кнопке Yes 1.
Пользователям Windows 10 Home придется использовать Virtual Box так, как
описано по адресу:
https://docs.docker.com/machine/drivers/virtualbox/

Пользователи Linux должны установить Docker Community Edition согласно
следующему описанию:
https://docs.docker.com/install/overview/

Для получения общего представления о Docker прочитайте руководство
«Getting started» по адресу:
https://docs.docker.com/get-started/

Стеки Jupyter Docker
Команда Jupyter Notebook создала несколько готовых «стеков Docker» для
Jupyter, содержащих стандартные сценарии развертывания Python. Каждая
ситуация позволяет использовать документы Jupyter Notebook для экспериментов с функциональностью, не отвлекаясь на сложные аспекты настройки
программного обеспечения. В каждом случае вы можете открыть JupyterLab
1

Возможно, некоторым пользователям Windows придется выполнить инструкции из
раздела «Allow specific apps to make changes to controlled folders» по адресу https://docs.
microsoft.com/en-us/windows/security/threat-protection/windows-defender-exploit-guard/customizecontrolled-folders-exploit-guard.

820   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
в браузере, открыть документ Notebook в JupyterLab и начать программирование. JupyterLab также предоставляет окно терминала, которое можно
использовать в браузере по аналогии с окном терминала, приглашением
Anaconda или командной оболочкой. Все, что мы приводили для IPython
до настоящего момента, можно также выполнить с использованием IPython
в окне терминала JupyterLab.
Мы будем использовать стек Docker jupyter/pyspark-notebook, заранее настроенный для создания и тестирования приложений Apache Spark на вашем
компьютере. При установке других библиотек Python, использованных в книге, вы сможете реализовать большую часть примеров книги в этом контейнере.
За дополнительной информацией о доступных стеках Docker обращайтесь
по адресу:
https://jupyter-docker-stacks.readthedocs.io/en/latest/index.html

Запуск стека Jupyter Docker
Прежде чем выполнять следующий шаг, убедитесь в том, что JupyterLab в настоящее время не выполняется на вашем компьютере. Загрузим и запустим
стек Docker jupyter/pyspark-notebook. Чтобы ваша работа не была потеряна
при закрытии контейнера Docker, присоединим к контейнеру каталог локальной файловой системы и используем его для сохранения вашего документа
Notebook — пользователи Windows должны заменить \ на ^. :
docker run -p 8888:8888 -p 4040:4040 -it --user root \
-v полныйПутьКИспользуемомуКаталогу:/home/jovyan/work \
jupyter/pyspark-notebook:14fdfbf9cfc1 start.sh jupyter lab

При первом выполнении этой команды Docker загрузит контейнер Docker
с именем:
jupyter/pyspark-notebook:14fdfbf9cfc1

Запись ":14fdfbf9cfc1" обозначает конкретный контейнер jupyter/pysparknotebook для загрузки. На момент написания книги новейшая версия контейнера была равна 14fdfbf9cfc1. Указание конкретной версии, как это сделали
мы, помогает обеспечить воспроизводимость результатов. Без включения
":14fdfbf9cfc1" в команду Docker загрузит новейшую версию контейнера,
которая может содержать другие версии программных продуктов и может
оказаться несовместимой с выполняемым кодом. Размер контейнера Docker

16.6. Spark   821

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

Открытие JupyterLab в браузере
После того как контейнер будет загружен и заработает, в окне приглашения
командной строки, терминала или командной оболочки появится команда:
Copy/paste this URL into your browser when you connect for the first
time, to login with a token:
http://(bb00eb337630 or 127.0.0.1):8888/?token=
9570295e90ee94ecef75568b95545b7910a8f5502e6f5680

Скопируйте длинную шестнадцатеричную строку (в вашей системе она будет
выглядеть иначе)
9570295e90ee94ecef75568b95545b7910a8f5502e6f5680

откройте адрес http://localhost:8888/lab в своем браузере (localhost соответствует 127.0.0.1 в предшествующем выводе) и вставьте скопированный маркер
в поле Password or token. Щелкните на кнопке Log in, чтобы перейти в интерфейс
JupyterLab. Если вы случайно закроете окно браузера, то откройте адрес http://
localhost:8888/lab, чтобы продолжить сеанс.
При выполнении в контейнере Docker рабочий каталог на вкладке Files в левой
части JupyterLab представляет каталог, присоединенный к контейнеру при помощи параметра -v команды docker run. С этого момента вы можете открывать
файлы документов Notebook, предоставленные нами. Любые новые документы
Notebook или другие файлы, которые вы будете создавать, будут сохраняться
в этом каталоге по умолчанию. Так как рабочий каталог контейнера Docker
связан с каталогом на вашем компьютере, все файлы, созданные в JupyterLab,
останутся на вашем компьютере даже в том случае, если вы решите удалить
контейнер Docker.

Обращение к командной строке контейнера Docker
Каждый контейнер Docker имеет интерфейс командной строки, сходный с тем,
который использовался для запуска IPython в этой книге. Через этот интерфейс можно устанавливать пакеты Python в контейнере Docker и даже использовать IPython так, как это делалось ранее. Откройте отдельное приглашение

822   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
Anaconda, терминал или командную оболочку и выведите список контейнеров
Docker, работающих в настоящий момент, при помощи следующей команды:
docker ps

Вывод команды получается довольно длинным, поэтому текст с большой
вероятностью будет переноситься:
CONTAINER ID
IMAGE
COMMAND
CREATED
STATUS
PORTS
NAMES
f54f62b7e6d5
jupyter/pyspark-notebook:14fdfbf9cfc1
"tini -g -/bin/bash" 2 minutes ago
Up 2 minutes
0.0.0.0:8888->8888/tcp
friendly_pascal

В последней строке вывода в нашей системе под заголовком столбца NAMES из
третьей строки выводится имя, случайным образом присвоенное Docker работающему контейнеру — friendly_pascal; в вашей системе имя будет другим.
Чтобы обратиться к командной строке контейнера, выполните следующую
команду, заменив имя_контейнера именем работающего контейнера:
docker exec -it имя_контейнера /bin/bash

Контейнер Docker использует Linux во внутренней реализации, поэтому вы
увидите приглашение Linux, в котором сможете вводить команды.
Приложение из этого раздела будет использовать ту же функциональность
библиотек NLTK и TextBlob, что и в главе 11. Ни одна из этих библиотек не
устанавливается в стеках Jupyter Docker заранее. Чтобы установить NLTK
и TextBlob, введите команду:
conda install -c conda-forge nltk textblob

Остановка и перезапуск контейнера Docker
Каждый раз, когда вы запускаете контейнер командой docker run, Docker
предоставляет новый экземпляр, не содержащий ранее установленных библиотек. По этой причине вам следует отслеживать имя своего контейнера,
чтобы вы могли использовать его из другого окна командной оболочки,
приглашения Anaconda или терминала для остановки и перезапуска контейнера. Команда
docker stop container_name

16.6. Spark   823

завершает работу контейнера. Команда
docker restart container_name

перезапускает контейнер. Docker также предоставляет GUI-приложение
с именем Kitematic, которое может использоваться для управления контейнерами, включая их остановку и перезапуск. Приложение можно загрузить
на сайте https://kitematic.com/ и работать с ним из меню Docker. В следующем
руководстве пользователя приведена краткая инструкция по управлению
контейнерами из приложения:
https://docs.docker.com/kitematic/userguide/

16.6.3. Подсчет слов с использованием Spark
В этом разделе мы воспользуемся средствами фильтрации, отображения и свертки Spark для реализации простого примера, который строит сводку использования слов в «Ромео и Джульетте». Вы можете работать с существующим документом Notebook RomeoAndJulietCounter.ipynb из каталога SparkWordCount (в который
вам следует скопировать файл RomeoAndJuliet.txt из главы 11) или же создать
новый документ, а затем ввести и выполнить приведенные ниже фрагменты.

Загрузка игнорируемых слов NLTK
Воспользуемся методами, представленными в главе 11, для исключения игнорируемых слов из текста перед подсчетом частот слов. Сначала загрузим
список игнорируемых слов NLTK:
[1]: import nltk
nltk.download('stopwords')
[nltk_data] Downloading package stopwords to /home/jovyan/nltk_data...
[nltk_data]
Package stopwords is already up-to-date!
[1]: True

Затем загрузим игнорируемые слова в программе:
[2]: from nltk.corpus import stopwords
stop_words = stopwords.words('english')

Настройка SparkContext
Объект SparkContext (из модуля pyspark) предоставляет доступ к функциональности Spark из Python. Многие среды Spark создают SparkContext за
вас, но в стеке Docker Jupyter pyspark-notebook объект придется создать вам.

824   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
Сначала задайте параметры конфигурации, создав объект SparkConf (из
модуля pyspark). Следующий фрагмент вызывает метод setAppName объекта
для назначения имени приложения Spark и вызывает метод setMaster объекта для определения URL-адреса кластера Spark. URL-адрес 'local[*]'
означает, что Spark выполняется на вашем локальном компьютере (вместо
кластера на базе облака), а звездочка сообщает Spark, что код должен выполняться с количеством программных потоков, равным количеству ядер
на компьютере:
[3]: from pyspark import SparkConf
configuration = SparkConf().setAppName('RomeoAndJulietCounter')\
.setMaster('local[*]')

Потоки позволяют одноузловому кластеру совместно (concurrent) выполнять
части задач Spark, чтобы моделировать параллелизм, обеспечиваемый кластерами Spark. Когда мы говорим, что две задачи выполняются совместно, имеется в виду, что они продвигаются к завершению в одно время — как правило,
одна задача выполняется в течение короткого промежутка времени, а затем
дает возможность выполняться другой задаче. Под параллельным (parallel)
выполнением понимается, что задачи выполняются одновременно; это одно
из ключевых преимуществ выполнения Hadoop и Spark в компьютерных
кластерах на базе облака.
Затем создадим объект SparkContext, передавая объект SparkConf в аргументе:
[4]: from pyspark import SparkContext
sc = SparkContext(conf=configuration)

Чтение текстового файла и отображение его на слова
Для работы со SparkContext используются средства программирования
в функциональном стиле (фильтрация, отображение и свертка), применяемые
к отказоустойчивым распределенным наборам данных (RDD). RDD берет
данные, хранящиеся в кластере в файловой системе Hadoop, и позволяет задать серию шагов обработки для преобразования данных в RDD. Действия
по обработке выполняются в отложенном режиме (глава 5) — задание не выполняется до тех пор, пока вы не прикажете Spark приступить к его обработке.
Следующий фрагмент задает три шага:
ØØМетод textFile объекта SparkContext загружает строки текста из файла
RomeoAndJuliet.txt и возвращает их в виде RDD (из модуля pyspark) со стро-

ками, представляющими каждую строку.

16.6. Spark   825
ØØМетод map объекта RDD использует свой аргумент lambda для удаления

всех знаков препинания функцией strip_punc объекта TextBlob и для
преобразования каждой строки текста к нижнему регистру. Этот метод
возвращает новый объект RDD, с которым можно задать дополнительные
выполняемые операции.
ØØМетод flatMap объекта RDD использует свой аргумент lambda для отображе-

ния каждой строки текста на слова, и строит единый список слов вместо
отдельных строк текста. Результатом выполнения flatMap является новый
объект RDD, представляющий все слова «Ромео и Джульетты».
[5]: from textblob.utils import strip_punc
tokenized = sc.textFile('RomeoAndJuliet.txt')\
.map(lambda line: strip_punc(line, all=True).lower())\
.flatMap(lambda line: line.split())

Удаление игнорируемых слов
Теперь используем метод filter объекта RDD для создания нового объекта RDD,
из которого были исключены игнорируемые слова:
[6]: filtered = tokenized.filter(lambda word: word not in stop_words)

Подсчет всех оставшихся слов
Теперь в наборе остались только значимые слова, и мы можем подсчитать
количество вхождений каждого слова. Для этого каждое слово сначала отображается на кортеж, содержащий слово и значение счетчика 1. Здесь происходит примерно то же, что мы делали с Hadoop MapReduce. Spark распределяет
задачу свертки по узлам кластера. Для полученного объекта RDD вызывается
метод reduceByKey, которому в аргументе передается функция add модуля
operator. Тем самым вы приказываете методу reduceByKey просуммировать
счетчики для кортежей, содержащих одно значение word (ключ):
[7]: from operator import add
word_counts = filtered.map(lambda word: (word, 1)).reduceByKey(add)

Поиск слов со счетчиками, большими или равными 60
Поскольку в тексте «Ромео и Джульетты» встречаются сотни слов, отфильтруем набор RDD, чтобы в нем остались только слова с 60 и более вхождениями:
[8]: filtered_counts = word_counts.filter(lambda item: item[1] >= 60)

826   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT

Сортировка и вывод результатов
На данный момент заданы все операции для подсчета слов. При вызове
метода collect объекта RDD Spark инициирует все действия обработки, заданные выше, возвращая список с окончательными результатами — в данном
случае кортежи из слов и счетчиков. С вашей точки зрения все выглядит так,
словно все вычисления выполнялись на одном компьютере. Но если объект
SparkContext настроен для использования кластера, то Spark разделит задачи среди рабочих узлов кластера за вас. В следующем фрагменте список
кортежей упорядочивается по убыванию (reverse=True) значений счетчиков
(itemgetter(1)).
Вызовем метод collect для получения результатов и их сортировки по убыванию счетчика слов:
[9]: from operator import itemgetter
sorted_items = sorted(filtered_counts.collect(),
key=itemgetter(1), reverse=True)

Наконец, выведем результаты. Сначала определим слово с наибольшим количеством букв, чтобы выровнять все слова по полю этой длины, а затем выведем
каждое слово и его счетчик:
[10]: max_len = max([len(word) for word, count in sorted_items])
for word, count in sorted_items:
print(f'{word:>{max_len}}: {count}')
[10]:
romeo: 298
thou: 277
juliet: 178
thy: 170S
nurse: 146
capulet: 141
love: 136
thee: 135
shall: 110
lady: 109
friar: 104
come: 94
mercutio: 83
good: 80
benvolio: 79
enter: 75
go: 75
i'll: 71
tybalt: 69
death: 69

16.6. Spark   827
night:
lawrence:
man:
hath:
one:

68
67
65
64
60

16.6.4. Подсчет слов средствами Spark
в Microsoft Azure
В книге представлены как инструменты, которые могут использоваться бесплатно, так и инструменты для коммерческой разработки. Рассмотрим пример с подсчетом слов Spark средствами в кластере Microsoft Azure HDInsight.

Создание кластера Apache Spark в HDInsight с использованием
Azure Portal
О настройке кластера Spark с использованием сервиса HDInsight читайте
здесь:
https://docs.microsoft.com/en-us/azure/hdinsight/spark/apache-spark-jupyter-spark-sqluse-portal

На этапах Create an HDInsight Spark cluster следует помнить о проблемах, перечисленных при описании создания кластера Hadoop ранее; в поле Cluster type
выберите вариант Spark.
Как и прежде, конфигурация кластера по умолчанию предоставляет больше
ресурсов, чем требуется для наших примеров. В разделе Cluster summary выполните действия, описанные ранее для создания кластера Hadoop, чтобы
изменить количество рабочих узлов до двух и настроить рабочие и ведущие
узлы для использования компьютеров D3 v2. Когда вы щелкнете на кнопке
Create, стартует процесс настройки и развертывания кластера, который займет
от 20 до 30 минут.

Установка библиотек в кластере
Если для вашего кода Spark необходимы библиотеки, не установленные в кластере HDInsight, их нужно будет установить. Чтобы увидеть, какие библиотеки
установлены по умолчанию, воспользуйтесь ssh для входа в кластер (как было
показано ранее в этой главе) и выполните команду:
/usr/bin/anaconda/envs/py35/bin/conda list

828   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
Так как ваш код будет выполняться на нескольких узлах кластера, библио­
теки должны быть установлены на каждом узле. Azure требует, чтобы вы
создали сценарий командной оболочки Linux, который содержит команды
установки библиотек. Когда вы отправляете этот сценарий, Azure проверяет
сценарий, а затем выполняет его на каждом узле. Сценарии командной оболочки Linux выходят за рамки книги, и этот сценарий должен быть размещен
на веб-сервере, с которого Azure сможет загрузить файл. Мы создали за вас
сценарий установки, который устанавливает библиотеки, используемые
в примерах Spark. Выполните следующие действия, чтобы установить библиотеки:
1. На портале Azure выберите свой кластер.
2. В списке под полем поиска кластера щелкните на варианте Script Actions.
3. Щелкните на кнопке Submit new, чтобы настроить параметры сценария
установки библиотек. В поле Script type выберите значение Custom, в поле
Name введите libraries, а в поле Bash script URI введите адрес:
http://deitel.com/bookresources/IntroToPython/install_libraries.sh

4. Включите варианты Head и Worker, чтобы сценарий установил библиотеки
на всех узлах.
5. Щелкните на кнопке Create.
Когда кластер завершит выполнение сценария, в случае успешного выполнения появится зеленая метка рядом с именем сценария в списке действий.
В противном случае Azure сообщит об ошибках.

Копирование файла RomeoAndJuliet.txt в кластер HDInsight
Как и с демонстрационным приложением Hadoop, воспользуемся командой scp для отправки в кластер файла RomeoAndJuliet.txt, который использовался в главе 11. В приглашении командной строки, терминале или командной
оболочке перейдите в каталог с файлом (предполагается, что это каталог
ch16) и введите приведенную ниже команду. Замените ИмяКластера именем,
заданным при создании кластера, и нажмите клавишу Enter только после того,
как будет введена вся команда. Двоеточие в этой команде обязательно; оно
означает, что пароль кластера будет введен по запросу. Введите пароль, заданный при создании кластера, и нажмите клавишу Enter:
scp RomeoAndJuliet.txt sshuser@YourClus terName-ssh.azurehdinsight.net:

16.6. Spark   829

Затем используйте ssh, чтобы войти в кластер и получить доступ к командной
строке. В приглашении командной строки, терминале или командной оболочке введите приведенную ниже команду. Не забудьте заменить ИмяКластера
именем своего кластера. Вам снова будет предложено ввести пароль кластера:
ssh sshuser@YourClusterName-ssh.azurehdinsight.net

Для работы с файлом RomeoAndJuliet.txt в Spark в сеансе ssh скопируйте его
в файловую систему Hadoop кластера, выполнив нижеследующую команду.
Вновь возьмем существующий каталог /examples/data, включенный Microsoft
для использования с учебными руководствами HDInsight. Не забудьте, что
клавишу Enter следует нажимать только после того, как будет введена вся
команда:
hadoop fs -copyFromLocal RomeoAndJuliet.txt
/example/data/RomeoAndJuliet.txt

Обращение к документам Jupyter Notebook в HDInsight
На момент написания книги в HDInsight использовался старый интерфейс
Jupyter Notebook вместо более нового интерфейса, представленного ранее.
Краткий обзор старого интерфейса доступен по адресу:
https://jupyter-notebook.readthedocs.io/en/stable/examples/Notebook/Notebook%20
Basics.html

Чтобы обратиться к документам Jupyter Notebook в HDInsight, на портале
Azure выберите вариант Allresources, а затем свой кластер. На вкладке Overview
выберите вариант Jupyter notebook в разделе Cluster dashboards. При этом открывается окно веб-браузера, где предлагается ввести свои регистрационные
данные. Используйте имя пользователя и пароль, заданный при создании
кластера. Если вы не указали имя пользователя, то по умолчанию используется
имя admin. После того как данные будут приняты, Jupyter отображает каталог
с подкаталогами PySpark и Scala. В них находятся учебники Python и Scala Spark.

Отправка файла RomeoAndJulietCounter.ipynb
Чтобы создать новый документ Notebook, щелкните на кнопке New и выберите
вариант PySpark3 или же отправьте существующие документы Notebook со
своего компьютера. В нашем примере отправим файл RomeoAndJulietCounter.ipynb
из предыдущего раздела и модифицируем его для работы с Azure. Для этого
щелкните на кнопке Upload, перейдите в подкаталог SparkWordCount каталога

830   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
ch16, выберите файл RomeoAndJulietCounter.ipynb и щелкните на кнопке Open. На
экране отображается файл в каталоге с кнопкой Upload справа. Щелкните на

кнопке, чтобы поместить документ Notebook в текущий каталог. Затем щелк­
ните на имени файла, чтобы открыть его в новой вкладке браузера. Jupyter
открывает диалоговое окно Kernel not found. Выберите вариант PySpark3 и щелк­
ните на кнопке OK, после чего следуйте инструкциям следующего параграфа.

Модификация блокнота для работы с Azure
Выполните следующие действия (каждая ячейка выполняется при завершении шага):
1. Кластер HDInsight не позволит NLTK сохранить загруженные игнорируемые слова в каталог NLTK по умолчанию, потому что он входит в число
защищенных каталогов системы. В первой ячейке измените вызов nltk.
download('stopwords') так, чтобы игнорируемые слова сохранялись
в текущем каталоге ('.'):
nltk.download('stopwords', download_dir='.')

2. При выполнении первой ячейки под ячейкой появится сообщение
Starting Spark application, пока HDInsight создает объект SparkContext
с именем sc за вас. Когда это будет сделано, код ячейки загрузит игнорируемые слова.
3. Во второй ячейке перед загрузкой игнорируемых слов необходимо сообщить NLTK, что они находятся в текущем каталоге. Включите следующую команду после команды import, запустив NLTK на поиск данных
в текущем каталоге:
nltk.data.path.append('.')

4. Поскольку HDInsight создает объект SparkContext за вас, третья и четвертая ячейки исходного документа не нужны, поэтому их можно удалить. Для этого либо щелкните внутри ячейки и выберите команду Delete
Cells в меню Jupyter Edit, либо щелкните на белом поле слева от ячейки
и введите dd.
5. В следующей ячейке укажите местонахождение файла RomeoAndJuliet.
txt в файловой системе Hadoop. Замените строку 'RomeoAndJuliet.txt'
строкой
'wasb:///example/data/RomeoAndJuliet.txt'

16.7. Spark Streaming   831

6. Синтаксис wasb:/// означает, что файл RomeoAndJuliet.txt хранится в Win­
dows Azure Storage Blob (WASB) — интерфейсе Azure к файловой системе
HDFS.
7. Так как Azure в настоящее время использует Python 3.5.x, форматные
строки не поддерживаются, и в последней ячейке следует заменить форматную строку следующей конструкцией строкового форматирования
с вызовом метода format:
print('{:>{width}}: {}'.format(word, count, width=max_len))

Приложение выводит тот же результат, что и приложение из предыдущего
раздела.
Внимание: не забудьте удалить свой кластер и другие ресурсы после завершения работы с ними, чтобы избежать лишних затрат. За дополнительной
информацией обращайтесь по адресу:
https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-portal

Учтите, что при удалении ресурсов Azure также будут удалены ваши документы Notebook. Чтобы загрузить только что выполненный файл, выберите
команду FileDownload asNotebook (.ipynb) в Jupyter.

16.7. Spark Streaming: подсчет хештегов Twitter
с использованием стека Docker pysparknotebook
В этом разделе мы создадим и запустим приложение Spark Streaming, которое
будет получать поток твитов по заданной теме(-ам) и выводить сводку 20 самых популярных хештегов на гистограмме, обновляемой каждые 10 секунд.
Для решения задачи используется контейнер Docker Jupyter из первого примера Spark. Пример состоит из двух частей. Сначала с использованием методов
из главы 12 создадим сценарий, получающий поток твитов от Twitter. Затем
используем механизм Spark Streaming в Jupyter Notebook для чтения твитов
и обработки хештегов.
Эти две части будут общаться друг с другом через сетевые сокеты — низко­
уровневое представление сетевых взаимодействий «клиент/сервер», в котором
клиентское приложение взаимодействует с серверным приложением по сети
с использованием операций, сходных с операциями файлового ввода/вывода.

832   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
Программа может читать данные из сокета или записывать их в сокет почти
так же, как при чтении из файла или записи в файл. Сокет представляет одну
конечную точку сетевого соединения. В данном случае клиентом является
приложение Spark, а сервером будет сценарий, который получает потоковые
твиты и отправляет их приложению Spark.

Запуск контейнера Docker и установка Tweepy
Установим библиотеку Tweepy в контейнере Jupyter Docker. Выполните
инструкции по запуску контейнера и установке в нем библиотеку Python из
раздела 16.6.2. Установите Tweepy следующей командой:
pip install tweepy

16.7.1. Потоковая передача твитов в сокет
Сценарий starttweetstream.py содержит измененную версию класса TweetListener
(глава 12). Она получает заданное количество твитов и отправляет их в сокет
на локальном компьютере. По достижении лимита твитов сценарий закрывает
сокет. Вы уже использовали потоковую передачу Twitter, поэтому сосредоточимся только на новых возможностях. Убедимся, что файл keys.py (подкаталог
SparkHashtagSummarizer каталога ch16) содержит ваши регистрационные данные
Twitter.

Выполнение сценария в контейнере Docker
Используем окно терминала JupyterLab для выполнения starttweetstream.py
в одной вкладке, а затем Notebook — для выполнения задачи Spark в другой
вкладке. При работающем контейнере Docker pyspark-notebook откройте адрес
http://localhost:8888/lab

в своем браузере. В JupyterLab выберите команду FileNewTerminal, чтобы
открыть новую вкладку с терминалом (командной строкой Linux). Введите
команду ls и нажмите Enter для вывода содержимого текущего каталога.
По умолчанию выводится рабочий каталог контейнера.
Чтобы выполнить starttweetstream.py , необходимо перейти в каталог
SparkHashtagSummarizer следующей командой1:
cd work/SparkHashtagSummarizer
1

Пользователям Windows: в Linux для разделения имен каталогов используется символ /
вместо \, а в именах каталогов учитывается регистр символов.

16.7. Spark Streaming   833

Теперь сценарий можно выполнить командой вида
ipython starttweetstream.py количество_твитов условия_поиска

где количество_твитов задает общее количество твитов для обработки, а условия_поиска — одна или несколько разделенных пробелами строк, используемых для фильтрации твитов. Так, следующая команда передает 1000 твитов
о футболе:
ipython starttweetstream.py 1000 football

В этот момент сценарий выводит сообщение «Waiting for connection» и ожидает подключения к Spark, чтобы начать потоковую передачу твитов.

Команды импортирования starttweetstream.py
Для целей нашего обсуждения мы разделили starttweetstream.py на части.
Сначала импортируются модули, используемые в сценарии. Модуль socket
стандартной библиотеки Python предоставляет функциональность, которая
позволяет приложениям Python взаимодействовать через сокеты.
1
2
3
4
5
6
7
8

# starttweetstream.py
"""Сценарий для получения твитов по теме(-ам), заданной в аргументе(-ах),
и отправки текста твитов в сокет для обработки средствами Spark."""
import keys
import socket
import sys
import tweepy

Класс TweetListener
Большая часть кода класса TweetListener уже приводилась ранее, поэтому мы
снова сосредоточимся исключительно на новых аспектах:
ØØМетод __init__ (строки 12–17) теперь получает параметр connection,

представляющий сокет, и сохраняет его в атрибуте self.connection.
­Сокет используется для отправки хештегов приложению Spark.
ØØВ методе on_status (строки 24–44) строки 27–32 извлекают хештеги из

объекта Tweepy Status, преобразуя их к нижнему регистру и создавая разделенную пробелами строку хештегов для отправки Spark. Самое важное
происходит в строке 39:
self.connection.send(hashtags_string.encode('utf-8'))

834   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
Метод send объекта connection используется для отправки текста твита приложению, читающему данные из сокета. Метод send ожидает, что в первом аргументе передается последовательность байтов. Вызов метода encode('utf-8')
преобразует строку в байты; Spark автоматически читает байты и воссоздает
строки.
9 class TweetListener(tweepy.StreamListener):
10
"""Обрабатывает входной поток твитов."""
11
12
def __init__(self, api, connection, limit=10000):
13
"""Создает переменные экземпляров для отслеживания количества
твитов."""
14
self.connection = connection
15
self.tweet_count = 0
16
self.TWEET_LIMIT = limit # 10,000 by default
17
super().__init__(api) # call superclass's init
18
19
def on_connect(self):
20
"""Вызывается в том случае, если попытка подключения была успешной,
21
чтобы вы могли выполнить нужные операции в этот момент."""
22
print('Successfully connected to Twitter\n')
23
24
def on_status(self, status):
25
"""Вызывается, когда Twitter отправляет вам новый твит."""
26
# Получить хештеги
27
hashtags = []
28
29
for hashtag_dict in status.entities['hashtags']:
30
hashtags.append(hashtag_dict['text'].lower())
31
32
hashtags_string = ' '.join(hashtags) + '\n'
33
print(f'Screen name: {status.user.screen_name}:')
34
print(f'
Hashtags: {hashtags_string}')
35
self.tweet_count += 1 # Количество обработанных твитов
36
37
try:
38
# send необходимы байты, поэтому строка кодируется в формате utf-8
39
self.connection.send(hashtags_string.encode('utf-8'))
40
except Exception as e:
41
print(f'Error: {e}')
42
43
# При достижении TWEET_LIMIT вернуть False для завершения передачи
44
return self.tweet_count != self.TWEET_LIMIT
45
46
def on_error(self, status):
47
print(status)
48
return True
49

16.7. Spark Streaming   835

Главное приложение
Строки 50–80 выполняются при запуске сценария. Ранее мы уже устанавливали связь с Twitter, и здесь будут рассматриваться только новые аспекты.
Строка 51 получает количество твитов для обработки — аргумент командной
строки sys.argv[1] преобразуется в целое число. Напомним, что элемент 0
представляет имя сценария.
50 if __name__ == '__main__':
51
tweet_limit = int(sys.argv[1])

# Получить максимальное количество твитов

В строке 52 вызывается функция socket модуля socket; она возвращает объект socket, используемый для ожидания подключения от приложения Spark.
52
53

client_socket = socket.socket()

# Создать сокет

В строке 55 метод bind объекта socket вызывается с передачей кортежа, содержащего имя хоста или IP-адрес компьютера и номер порта на этом компьютере. Комбинация этих значений определяет параметры ожидания сценарием
исходного подключения от другого приложения:
54
55
56

# Приложение будет использовать порт 9876 локального хоста
client_socket.bind(('localhost', 9876))

Строка 58 вызывает метод listen сокета, который заставляет сценарий ожидать запрос на подключение. Именно эта команда не позволяет потоку Twitter
стартовать до подключения приложения Spark.
57
58
59

print('Waiting for connection')
client_socket.listen() # Ожидать подключения клиента

После того как приложение Spark подключится, строка 61 вызывает метод
accept сокета, принимающий подключение. Этот метод возвращает кортеж
с новым объектом socket, который будет использоваться сценарием для взаимодействия с приложением Spark, и IP-адресом компьютера с приложением Spark.
60
61
62
63

# При получении запроса на подключение получить подключение и адрес клиента
connection, address = client_socket.accept()
print(f'Connection received from {address}')

836   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
Затем выполняется аутентификация Twitter и запускается поток. Строки 73–
74 настраивают поток, передавая объекту TweetListener объект connection,
чтобы он мог использовать сокет для отправки хештегов приложению Spark.
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

# Настройка доступа к Twitter
auth = tweepy.OAuthHandler(keys.consumer_key, keys.consumer_secret)
auth.set_access_token(keys.access_token, keys.access_token_secret)
# Наcтройка Tweepy для перехода в ожидание при превышении ограничения
api = tweepy.API(auth, wait_on_rate_limit=True,
wait_on_rate_limit_notify=True)
# Создать объект Stream
twitter_stream = tweepy.Stream(api.auth,
TweetListener(api, connection, tweet_limit))
# sys.argv[2] - первый критерий поиска
twitter_stream.filter(track=sys.argv[2:])

Наконец, в строках 79–80 вызывается метод close для объектов сокета, чтобы
они освободили свои ресурсы.
79
80

connection.close()
client_socket.close()

16.7.2. Получение сводки хештегов и Spark SQL
В этом разделе воспользуемся Spark Streaming для чтения хештегов, отправленных через сокет сценарием starttweetstream.py, и обобщим результаты. Создайте новый документ Notebook и введите код, приведенный в тексте, либо
загрузите файл hashtagsummarizer.ipynb из подкаталога SparkHashtagSummarizer
каталога ch16.

Импортирование библиотек
Начнем с импортирования библиотек, используемых в документе Notebook.
Будем описывать классы pyspark по мере их использования. Мы импортировали из IPython модуль display, который содержит классы и вспомогательные
функции, которые могут использоваться в Jupyter. В частности, используем
функцию clear_output для удаления существующей диаграммы перед выводом новой:

16.7. Spark Streaming   837
[1]: from pyspark import SparkContext
from pyspark.streaming import StreamingContext
from pyspark.sql import Row, SparkSession
from IPython import display
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

Это приложение Spark обрабатывает хештеги 10-секундными пакетами. После обработки каждого пакета оно выводит гистограмму Seaborn. Магическая
команда IPython
%matplotlib inline

означает, что графика Matplotlib должна отображаться в документе Notebook,
а не в отдельном окне. Напомним, что Seaborn использует Matplotlib.
В этой книге мы использовали всего несколько магических команд IPython.
Между тем существует много магических команд, предназначенных для использования в документах Jupyter Notebook. Их полный список доступен по
адресу:
https://ipython.readthedocs.io/en/stable/interactive/magics.html

Вспомогательная функция для получения
объекта SparkSession
Как вы вскоре увидите, для запроса данных из наборов RDD может использоваться Spark SQL. Spark SQL использует коллекцию Spark DataFrame для
получения табличного представления базовых RDD. Объект SparkSession
(модуль pyspark.sql) используется для создания коллекции DataFrame из RDD.
В каждом приложении Spark может быть только один объект SparkSession.
Следующая функция, позаимствованная нами из руководства «Spark
Streaming Programming Guide»1, показывает, как правильно получить экземпляр SparkSession, если он уже существует, или создать его, если он еще не
был создан2:
1

2

https://spark.apache.org/docs/latest/streaming-programming-guide.html#dataframe-and-sqloperations.
Так как функция была позаимствована из документации (https://spark.apache.org/docs/latest/
streaming-programming-guide.html#dataframe-and-sql-operations), мы не стали переименовывать

838   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
[2]: def getSparkSessionInstance(sparkConf):
"""Рекомендованный способ получения существующего или создания
нового объекта SparkSession из Spark Streaming Programming Guide."""
if ("sparkSessionSingletonInstance" not in globals()):
globals()["sparkSessionSingletonInstance"] = SparkSession \
.builder \
.config(conf=sparkConf) \
.getOrCreate()
return globals()["sparkSessionSingletonInstance"]

Вспомогательная функция для вывода гистограммы по данным
коллекции Spark DataFrame
После обработки каждого пакета хештегов вызывается функция display_
barplot . Каждый вызов стирает предшествующую гистограмму Seaborn,
а затем строит новую гистограмму на основании полученной коллекции
Spark DataFrame. Сначала мы вызываем метод toPandas коллекции Spark
DataFrame, чтобы преобразовать коллекцию в Pandas DataFrame для использования с Seaborn. Затем вызывается функция clear_output из модуля IPython.
display. Ключевой аргумент wait=True означает, что функция должна удалить предыдущую диаграмму (если она есть), но только по готовности новой
диаграммы к выводу. В остальном коде функции используются стандартные средства Seaborn, продемонстрированные ранее. Вызов функции sns.
color_palette('cool', 20) выбирает 20 столбцов из цветовой палитры 'cool'
библиотеки Matplotlib:
[3]: def display_barplot(spark_df, x, y, time, scale=2.0, size=(16, 9)):
"""Возвращает содержимое Spark DataFrame в виде гистограммы."""
df = spark_df.toPandas()
# Удалить предыдущую диаграмму, когда новая будет готова к выводу
display.clear_output(wait=True)
print(f'TIME: {time}')
# Создать и настроить объект Figure с гистограммой Seaborn
plt.figure(figsize=size)
sns.set(font_scale=scale)
barplot = sns.barplot(data=df, x=x, y=y
palette=sns.color_palette('cool', 20))
# Повернуть метки оси x на 90 градусов для удобства чтения
for item in barplot.get_xticklabels():
item.set_rotation(90)

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

16.7. Spark Streaming   839
plt.tight_layout()
plt.show()

Вспомогательная функция для обобщения 20 самых
популярных хештегов
В Spark Streaming объект DStream — последовательность RDD, каждый из
которых представляет мини-пакет данных, предназначенных для обработки.
Вы можете задать функцию, вызываемую для выполнения операции с каждым
RDD в потоке. В этом пакете функция count_tags обобщает хештеги в заданном наборе RDD, добавляет их к текущим счетчикам (которые поддерживает
SparkSession), после чего выводит обновленную гистограмму 20 хештегов,
чтобы вы видели, как состав самых популярных хештегов меняется со временем1. Для целей нашего обсуждения функция разбита на несколько меньших
частей. Получим объект SparkSession вызовом вспомогательной функции
getSparkSessionInstance с данными конфигурации SparkContext. Каждый
набор RDD предоставляет доступ к SparkContext в атрибуте context:
[4]: def count_tags(time, rdd):
"""Подсчет хештегов и вывод начальных 20 в порядке по убыванию."""
try:
# Получить объект SparkSession
spark = getSparkSessionInstance(rdd.context.getConf())

Затем вызывем метод map объекта RDD для отображения данных в RDD на
объекты Row (из пакета pyspark.sql). RDD в данном примере содержат кортежи из хештегов и счетчиков. Конструктор Row использует имена ключевых
аргументов для определения имен столбцов каждого значения в этой строке.
В данном случае tag[0] — хештег из кортежа, а tag[1] — счетчик для данного
хештега:
# Отобразить кортежи с хештегом и счетчиком на Row
rows = rdd.map(
lambda tag: Row(hashtag=tag[0], total=tag[1]))

Следующая команда создает коллекцию Spark DataFrame с объектами Row. Она
используется в сочетании со Spark SQL для выборки 20 самых популярных
с их счетчиками:
1

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

840   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
# Создать DataFrame для объектов Row
hashtags_df = spark.createDataFrame(rows)

Чтобы сформировать запрос к коллекции Spark DataFrame, создайте табличное представление, позволяющее Spark SQL выдавать запросы к DataFrame
как к таблице реляционной БД. Метод createOrReplaceTempView коллекции
Spark DataFrame создает временное табличное представление для DataFrame
и присваивает представлению имя для использования в условии from запроса:
# Создать временное табличное представление для Spark SQL
hashtags_df.createOrReplaceTempView('hashtags')

Когда у вас появится табличное представление, можно запросить данные
с использованием Spark SQL1. Следующая команда использует метод sql
экземпляра SparkSession для выполнения запроса Spark SQL, который
выбирает столбцы hashtag и total из табличного представления hashtags,
упорядочивает выбранные строки по убыванию (desc) total, после чего возвращает первые 20 строк результата (limit 20). Spark SQL возвращает новую
коллекцию Spark DataFrame с результатами:
# Использовать Spark SQL для получения 20 начальных хештегов
# при сортировке по убыванию
top20_df = spark.sql(
"""выбрать хештег, итого
из общего количества итого
хештег limit 20""")

Наконец, передадим коллекцию Spark DataFrame вспомогательной функции
display_barplot. Хештеги и счетчики будут выводиться на осях x и у соответственно. Также приложение выводит время вызова count_tags:
display_barplot(top20_df, x='hashtag', y='total', time=time)
except Exception as e:
print(f'Exception: {e}')

Получение объекта SparkContext
Остальной код документа Notebook настраивает Spark Streaming для чтения
текста от сценария и указывает, как должны обрабатываться твиты. Сначала
создается объект SparkContext для подключения к кластеру Spark:
[5]: sc = SparkContext()
1

За подробной информацией о синтаксисе Spark SQL обращайтесь по адресу https://spark.
apache.org/sql/.

16.7. Spark Streaming   841

Получение объекта StreamingContext
Для Spark Streaming необходимо создать объект StreamingContext (модуль
pyspark.streaming), передав в аргументах объект SparkContext и частоту обработки пакетов потоковых данных в секундах (пакетный интервал). В нашем
приложении пакеты будут обрабатываться каждые 10 секунд:
[6]: ssc = StreamingContext(sc, 10)

В зависимости от скорости поступления данных пакетный интервал можно
увеличить или уменьшить. За обсуждением этого и других аспектов, связанных
с эффективностью, обращайтесь к разделу «Performance Tuning» руководства
Spark Streaming Programming Guide:
https://spark.apache.org/docs/latest/streaming-programming-guide.html#performance-tuning

Создание контрольной точки для хранения состояния
По умолчанию Spark Streaming не поддерживает информацию состояния
в процессе обработки RDD. Тем не менее можно воспользоваться механизмом
контрольных точек RDD для отслеживания состояния. Назовем важнейшие
возможности контрольных точек:
ØØотказоустойчивость для перезапуска потока в случае сбоя узлов кластера

или приложений Spark;
ØØпреобразования с состоянием — например, обобщение данных, получен-

ных к настоящему моменту, как это делается в нашем примере.
Метод checkpoint объекта StreamingContext создает каталог для контрольных
точек:
[7]: ssc.checkpoint('hashtagsummarizer_checkpoint')

Для приложения Spark Streaming в облачном кластере задается путь HDFS для
хранения каталога контрольных точек. Наш пример выполняется в локальном
образе Jupyter Docker, поэтому мы просто задаем имя каталога, который Spark
создаст в текущем каталоге (в нашем случае SparkHashtagSummarizer в каталоге ch16). За дополнительной информацией о контрольных точках обращайтесь
по адресу:
https://spark.apache.org/docs/latest/streaming-programming-guide.html#checkpointing

842   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT

Подключение к потоку через сокет
Метод socketTextStream объекта StreamingContext подключается к сокету,
от которого будет поступать поток данных, и возвращает объект DStream для
получения данных. В аргументах метода передается имя хоста и номер порта,
по которому должен создавать подключение объект StreamingContext, — они
должны соответствовать параметрам, по которым сценарий starttweetstream.py
ожидает подключения:
[8]: stream = ssc.socketTextStream('localhost', 9876)

Разбиение хештегов на лексемы
Используем вызовы программирования в функциональном стиле для DStream,
чтобы определить этапы обработки потоковых данных. Следующий вызов
метода flatMap объекта DStream осуществляет разбиение строки хештегов,
разделенных пробелами, и возвращает новый объект DStream, представляющий
отдельные теги:
[9]: tokenized = stream.flatMap(lambda line: line.split())

Отображение хештегов на кортежи
пар «хештег-счетчик»
Затем по аналогии со сценарием отображения Hadoop, приведенным ранее в этой главе, используем метод map объекта DStream для получения
нового объекта DStream, в котором каждый хештег отображается на пару
«хештег-счетчик» (в данном случае кортеж), в которой счетчик изначально
равен 1:
[10]: mapped = tokenized.map(lambda hashtag: (hashtag, 1))

Суммирование счетчиков хештегов
Метод updateStateByKey объекта DStreamп получает лямбда-выражение с двумя аргументами, которое суммирует счетчики для заданного ключа и прибавляет их к предыдущей сумме для этого ключа:
[11]: hashtag_counts = tokenized.updateStateByKey(
lambda counts, prior_total: sum(counts) + (prior_total or 0))

16.7. Spark Streaming   843

Определение метода, вызываемого для каждого RDD
Наконец, используем метод foreachRDD объекта DSteam для указания того, что
каждый обработанный набор RDD должен быть передан функции count_tags,
которая выделяет 20 самых популярных хештегов и строит гистограмму:
[12]: hashtag_counts.foreachRDD(count_tags)

Запуск потоковой передачи Spark
Определив процедуру обработки, вызовем метод start объекта Strea­
mingContext для подключения к сокету и запуска процесса потоковой передачи.
[13]: ssc.start()

# Запустить Spark Streaming

На следующей иллюстрации изображена гистограмма, полученная при обработке потока твитов о футболе. Так как в США и во всем остальном мире
словом «футбол» называются две разные игры, хештеги относятся как к американскому футболу, так и к игре, для которой в США используется название
«соккер», — закрасим серым фоном три хештега, не подходящих для публикации:

844   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT

16.8. «Интернет вещей»
В конце 1960-х годов интернет начинался в виде сети ARPANET, которая изначально объединяла четыре университета. Через 10 лет ее размер увеличился до 10 узлов1. Теперь же «глобальная паутина» разрослась до миллиардов
компьютеров, смартфонов, планшетов и множества других типов устройств,
подключенных к интернету по всему миру. Любое устройство, подключенное
к интернету, рассматривается как «вещь» в концепции «интернета вещей» (IoT).
Каждое устройство обладает уникальным адресом протокола интернета (IPадресом), который его идентифицирует. Стремительный рост количества
подключенных устройств исчерпал приблизительно 4,3 миллиарда доступных
адресов IPv4 (протокол IP версии 4)2, что привело к необходимости разработки
протокола IPv6, поддерживающего приблизительно 3,4 × 1038 адресов3.
«Ведущие исследовательские компании, такие как Gartner и McKinsey, прогнозируют скачок от 6 миллиардов подключенных устройств, существуТаблица 16.2. Основные типы устройств IoT
Устройства IoT

Amazon Echo (Alexa), Apple HomePod
(Siri), Google Home (Google Assistant)

Кнопки заказов Amazon Dash

Автономные автомобили

Сейсмические датчики

Датчики — химические, газовые, GPS,
влажности, давления, температуры, …

Системы охлаждения винных погребов

Датчики цунами
Устройства отслеживания активности —
Apple Watch, FitBit, …
Домашние устройства — печи, кофеварки,
холодильники, …
Здравоохранение — глюкометры для
диабетиков, датчики давления,
электрокардиограммы (ЭКГ), электроэнцефалограммы (ЭЭГ), кардиомониторы, системы внутреннего контроля,
кардио­стимуляторы, датчики сна, …

1
2
3

Оборудование беспроводных сетей

Умный дом — свет, системы открывания
гаража, видеокамеры, звонки, системы управления поливом, устройства
безопасности, умные замки, датчики
задымления, термостаты, вентиляция
Устройства наблюдения

https://en.wikipedia.org/wiki/ARPANET#History.
https://en.wikipedia.org/wiki/IPv4_address_exhaustion.
https://en.wikipedia.org/wiki/IPv6.

16.8. «Интернет вещей»   845

ющих в наши дни, до 20–30 миллиардов к 2020 году»1. Различные прогнозы
утверждают, что это число может достигнуть 50 миллиардов. Подключенные
к интернету устройства продолжают развиваться. В табл. 16.2 перечислены
лишь немногие типы устройств и варианты применения IoT.

Проблемы IoT
Хотя мир IoT открывает много интересных возможностей, не все они положительные. Существует множество проблем в области безопасности,
конфиденциальности и этики. Так, незащищенные устройства IoT использовались для проведения распределенных атак отказа в обслуживании
(DDOS) на компьютерные системы2. Если видеокамеры безопасности, которые должны защищать ваш дом, будут взломаны, то другие пользователи
получат доступ к вашему видеопотоку. Голосовые «шпионские» устройства
постоянно ведут прослушивание, чтобы распознать управляющие слова.
Дети случайно заказывали товары на Amazon, разговаривая с устройствами
Alexa, а компании создавали телевизионную рекламу, которая активизировала устройства Google Home управляющими словами и заставляла Google
Assistant зачитывать страницы «Википедии» с описанием товаров3. Люди
беспокоятся, что эти устройства могут быть использованы и для подслушивания. Наконец, сравнительно недавно стало известно о том, что один
судья запросил у Amazon записи Alexa для использования в криминальном
судебном процессе4.

Примеры этого раздела
В этом разделе рассматривается модель публикации/подписки, которая используется IoT и другими типами приложений для организации взаимодействий.
Сначала без написания какого-либо кода мы построим информационную
панель на базе Freeboard.io и подпишемся на поток от сервиса PubNub. Затем
будет построено приложение, моделирующее термостат с подключением к интернету; моделируемое устройство публикует сообщения в бесплатном сервисе
Dweet.io при помощи Python-модуля Dweepy. После этого будет создана визуализация данных на базе Freeboard.io. В завершение построим клиента Python,
1
2
3

4

https://www.pubnub.com/developers/tech/how-pubnub-works/.
https://threatpost.com/iot-security-concerns-peaking-with-no-end-in-sight/131308/.
https://www.symantec.com/content/dam/symantec/docs/security-center/white-papers/istr-securityvoice-activated-smart-speakers-en.pdf.
https://techcrunch.com/2018/11/14/amazon-echo-recordings-judge-murder-case/.

846   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
который подписывается на поток от сервиса PubNub и строит динамическую
визуализацию потока средствами Seaborn и Matplotlib FuncAnimation.

16.8.1. Публикация и подписка
Устройства IoT (и многие другие разновидности устройств и приложений)
обычно взаимодействуют друг с другом и с другими приложениями через
системы публикации/подписки. Публикатором является любое устройство
или приложение, отправляющее сообщение сервису на базе облака, который,
в свою очередь, отправляет это сообщение всем подписчикам. Как правило,
каждый публикатор указывает тему или канал, а каждый подписчик выбирает
одну или несколько тем или каналов, для которых они хотели бы получать
сообщения. В наше время существует много систем публикации/подписки.
В оставшейся части этого раздела будут использоваться PubNub и Dweet.
io. Мы также рекомендуем познакомиться с Apache Kafka — компонентом
экосистемы Hadoop, предоставляющим высокопроизводительный сервис
публикации/подписки, средства обработки потоков в реальном времени
и хранения потоковых данных.

16.8.2. Визуализация живого потока PubNub
средствами Freeboard
PubNub — сервис публикации/подписки, адаптированный для приложений
реального времени, в которых произвольные программные компоненты
и устройства, подключенные к интернету, общаются посредством малых
сообщений. Некоторые стандартные применения таких систем — IoT, чаты,
онлайновые многопользовательские игры, социальные сети и приложения
для совместной работы. PubNub предоставляет несколько живых потоков
для учебных целей, включая поток, моделирующий датчики IoT (другие перечислены в разделе 16.8.5).
Одно из распространенных применений живых потоков данных — визуализация для целей отслеживания. В этом разделе мы подключим смоделированный поток живых данных от датчика PubNub к информационной панели
Freeboard.io. Приборная панель автомобиля используется для наглядного
представления данных от датчиков машины: температуры окружающий среды, скорости, температуры двигателя, времени, объема оставшегося бензина
и т. д. Информационная панель решает ту же задачу для данных из разных
источников, включая устройства IoT.

16.8. «Интернет вещей»   847

Freeboard.io — средство динамической визуализации данных на информационных панелях для облачных сред. Даже без написания какого-либо кода
Freeboard.io можно легко соединить с различными потоками данных и визуализировать данные по мере поступления. На следующей информационной
панели визуализируются данные от трех из четырех датчиков IoT-потока,
моделируемого средствами PubNub:

С каждым датчиком для визуализации данных используются представления
Gauge (полукруг) и Sparkline (спарклайны). После выполнения инструкций из этого раздела вы увидите, что показания на полукруглых шкалах
и линии быстро двигаются, так как новые данные поступают несколько раз
в секунду.
Кроме платного сервиса, Freeboard.io предоставляет версию с открытым кодом
(с меньшим набором возможностей) на GitHub. Также имеются учебники, показывающие, как пользоваться плагинами расширения; все это позволит вам
самостоятельно разрабатывать визуализации для своих информационных
панелей.

Подписка на Freeboard.io
Зарегистрируйтесь на сайте Freeboard.io и получите 30-дневный пробный
период:
https://freeboard.io/signup

После этого откроется страница My Freeboards. При желании щелкните на кнопке Try a Tutorial и создайте визуализацию данных с вашего смартфона.

848   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT

Создание новой информационной панели
Введите в поле поиска Enter a name в правом верхнем углу страницы My Free­
boards строку Sensor Dashboard, после чего щелкните на кнопке Create New,
открывающей конструктор информационной панели.

Добавление источника данных
Если вы добавили свой источник(-и) данных перед построением информационной панели, то сможете настроить каждую визуализацию при ее создании:
1. В разделе DATASOURCES щелкните на кнопке ADD, чтобы выбрать новый
источник данных.
2. В раскрывающемся списке TYPE диалогового окна DATASOURCE выводится
список источников данных, поддерживаемых в настоящий момент, хотя
вы также можете разработать собственные плагины для новых источников
данных1. Выберите вариант PubNub. Открывается страница с параметрами
канала (Channel) и ключом подписки (Subscribe key) каждого живого потока
PubNub. Скопируйте значения со страницы PubNub Sensor Network по
адресу https://www.pubnub.com/developers/realtime-data-streams/sensor-network/,
после чего вставьте их значения в соответствующих полях диалогового
окна DATASOURCE. Введите имя своего источника данных (NAME) и щелк­
ните на кнопке SAVE.

Добавление панели для датчика влажности
Информационная панель Freeboard.io разделена на субпанели, используемые
для группировки визуализаций. Их можно перетаскивать мышью, располагая
так, как вам нужно. Щелкните на кнопке +Add Pane, чтобы добавить новую панель. У каждой панели может быть свой заголовок. Для установки щелкните
на значке с гаечным ключом, введите заголовок Humidity в поле TITLE, а затем
щелкните на кнопке SAVE.

Добавление шкалы на панель Humidity
Чтобы добавить визуализации на панель, щелкните на кнопке +. На экране появляется диалоговое окно WIDGET. В раскрывающемся списке TYPE содержатся
1

Некоторые из перечисленных источников данных доступны только через Freeboard.io,
но не поддерживаются версией Freeboard с открытым кодом на GitHub.

16.8. «Интернет вещей»   849

некоторые встроенные виджеты. Выберите вариант Gauge. Справа от поля VALUE
щелкните на кнопке +DATASOURCE, затем выберите имя своего источника данных.
Открывается список доступных значений из этого источника данных. Щелкните на варианте humidity, чтобы выбрать значение от датчика влажности. В поле
UNITS выберите вариант % и щелкните на кнопке SAVE. Появляется новая визуализация, которая немедленно начинает отображать данные из потока датчика.
Обратите внимание: значение отображается с четырьмя знаками в дробной
части. PubNub поддерживает выражения JavaScript, которые могут использоваться для выполнения вычислений или форматирования данных. Например,
вы можете воспользоваться функцией JavaScript Math.round для округления
влажности до ближайшего целого числа. Для этого наведите указатель мыши
на шкалу и щелкните на значке с гаечным ключом. Вставьте строку "Math.
round(" перед текстом в поле VALUE, вставьте ")" после текста, затем щелкните
на кнопке SAVE.

Добавление спарклайна на панель Humidity
Спарклайн представляет собой линейный график без осей, который дает представление об изменении значения данных со временем. Добавим спарклайн
для датчика влажности; щелкните на кнопке + панели Humidity и выберите
вариант Sparkline в раскрывающемся списке TYPE. В поле VALUE снова выберите
источник данных и humidity, после чего щелкните на кнопке SAVE.

Завершение информационной панели
Повторив описанные выше действия, добавьте еще две панели и перетащите их справа от первой. Присвойте им названия Radiation Level и Ambient
Temperature соответственно и разместите на каждой панели визуализации
Gauge и Sparkline так, как показано выше. Для шкалы Radiation Level задайте
в поле UNITS значение Millirads/Hour, а в поле MAXIMUM — значение 400. Для
шкалы Ambient Temperature выберите в поле UNITS значение Celsius, а в поле
MAXIMUM — значение 50.

16.8.3. Моделирование термостата, подключенного
к интернету, в коде Python
Моделирование — одно из самых важных применений компьютеров. В одной
из предыдущих глав моделировались результаты бросков кубиков. С IoT
моделирование часто применяется для тестирования приложений, особенно

850   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
при отсутствии доступа к реальным устройствам и датчикам при разработке
приложений. Сходные средства моделирования IoT предоставляются многими провайдерами облачных сервисов, например IBM Watson IoT Platform
и IOTIFY.io.
В этом разделе будет создан сценарий, который моделирует подключенный
к интернету термостат, публикующий периодические сообщения в формате
JSON для dweet.io. Многие современные системы безопасности, подключенные к интернету, оснащаются температурными датчиками, которые могут выдавать предупреждения о слишком низкой или высокой температуре. Смоделированный нами датчик отправляет сообщения с данными местонахождения
и температуры, а также оповещения о низкой или высокой температуре. Они
срабатывают только в том случае, если температура достигает 3° или 35° по
Цельсию соответственно. В следующем разделе мы воспользуемся freeboard.
io для создания простой информационной панели, на которой отображаются
изменения температуры при поступлении сообщений, а также индикаторы
предупреждений о низкой или высокой температуре.

Установка Dweepy
Чтобы публиковать сообщения в dweet.io из кода Python, сначала необходимо
установить библиотеку Dweepy:
pip install dweepy

Документацию библиотеки можно просмотреть по адресу:
https://github.com/paddycarey/dweepy

Запуск сценария simulator.py
Сценарий Python simulator.py, который моделирует термостат, находится
в подкаталоге iot каталога ch16. Сценарий моделирования запускается с двумя
аргументами командной строки, представляющими количество сообщений
и задержку в секундах между их отправкой:
ipython simulator.py 1000 1

Отправка сообщений
Код simulator.py приведен ниже. Он использует генератор случайных чисел и средства Python, упоминавшиеся в книге, поэтому мы сосредоточимся

16.8. «Интернет вещей»   851

на нескольких строках кода, публикующих сообщения в сервисе dweet.io
средствами Dweepy. Код сценария разбит на части для удобства рассмотрения.
По умолчанию dweet.io является общедоступным сервисом, так что любое
приложение может публиковать сообщения или подписываться на них.
При публикации сообщений необходимо указать уникальное имя вашего
устройства. Мы использовали имя 'temperature-simulator-deitel-python'
(строка 17)1. В строках 18–21 определяется словарь Python, в котором будет
храниться текущая информация от датчиков. Dweepy преобразует ее в формат
JSON при отправке сообщения.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# simulator.py
"""Модель подключенного к интернету термостата, который публикует
сообщения JSON в dweet.io"""
import dweepy
import sys
import time
import random
MIN_CELSIUS_TEMP = -25
MAX_CELSIUS_TEMP = 45
MAX_TEMP_CHANGE = 2
# Получить количество моделируемых сообщений и задержку между ними
NUMBER_OF_MESSAGES = int(sys.argv[1])
MESSAGE_DELAY = int(sys.argv[2])
dweeter = 'temperature-simulator-deitel-python'
thermostat = {'Location': 'Boston, MA, USA',
'Temperature': 20,
'LowTempWarning': False,
'HighTempWarning': False}

# Уникальное имя

Строки 25–53 производят заданное число смоделированных сообщений. При
каждой итерации цикла приложение:
ØØгенерирует случайное изменение температуры в диапазоне от –2 до +2

и изменяет температуру;
ØØпроверяет, что температура находится в разрешенном диапазоне;

1

Чтобы имя было гарантированно уникальным, dweet.io может создать его за вас. В документации Dweepy объясняется, как это сделать.

852   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
ØØпроверяет, сработал ли датчик низкой или высокой температуры, и об-

новляет словарь thermostat соответствующим образом;
ØØвыводит количество сообщений, сгенерированных до настоящего мо­

мента;
ØØиспользует Dweepy для отправки сообщения dweet.io (строка 52);
ØØиспользует функцию sleep модуля time для ожидания заданного проме-

жутка времени перед генерированием очередного сообщения.
23 print('Temperature simulator starting')
24
25 for message in range(NUMBER_OF_MESSAGES):
26
# Сгенерировать случайное число в диапазоне от -MAX_TEMP_CHANGE
27
# до MAX_TEMP_CHANGE и прибавить его к текущей температуре
28
thermostat['Temperature'] += random.randrange(
29
-MAX_TEMP_CHANGE, MAX_TEMP_CHANGE + 1)
30
31
# Убедиться в том, что температура остается в допустимом диапазоне
32
if thermostat['Temperature'] < MIN_CELSIUS_TEMP:
33
thermostat['Temperature'] = MIN_CELSIUS_TEMP
34
35
if thermostat['Temperature'] > MAX_CELSIUS_TEMP:
36
thermostat['Temperature'] = MAX_CELSIUS_TEMP
37
38
# Проверить предупреждение о низкой температуре
39
if thermostat['Temperature'] < 3:
40
thermostat['LowTempWarning'] = True
41
else:
42
thermostat['LowTempWarning'] = False
43
44
# Проверить предупреждение о высокой температуре
45
if thermostat['Temperature'] > 35:
46
thermostat['HighTempWarning'] = True
47
else:
48
thermostat['HighTempWarning'] = False
49
50
# Отправить сообщение dweet.io средствами dweepy
51
print(f'Messages sent: {message + 1}\r', end='')
52
dweepy.dweet_for(dweeter, thermostat)
53
time.sleep(MESSAGE_DELAY)
54
55 print('Temperature simulator finished')

Регистрация для использования сервиса необязательна. При первом вызове функции dweet_for библиотеки Dweepy для отправки сообщения (строка 52) dweet.io создает имя устройства. Функция получает в аргументе имя

16.8. «Интернет вещей»   853

устройства (dweeter) и словарь, представляющий отправляемое сообщение
(thermostat). После выполнения сценария вы можете немедленно приступить к отслеживанию сообщений на сайте dweet.io, открыв следующий адрес
в браузере:
https://dweet.io/follow/temperature-simulator-deitel-python

Если вы используете другое имя устройства, то замените "temperaturesimulator-deitel-python" использованным именем. Веб-страница состоит
из двух вкладок. На вкладке Visual показаны отдельные элементы данных со
спарклайном для любых числовых значений. На вкладке Raw показаны фактические сообщения JSON, которые Dweepy отправляет dweet.io.

16.8.4. Создание информационной панели
с Freeboard.io
Сайты dweet.io и freeboard.io принадлежат одной компании. На странице
dweet.io, описанной в предыдущем разделе, кнопка Create a Custom Dashboard
открывает новую вкладку браузера с уже реализованной информационной
панелью по умолчанию для датчика температуры. По умолчанию freeboard.
io настраивает источник данных с именем Dweet и автоматически генерирует
информационную панель с одной панелью для каждого значения в JSONразметке сообщения. Внутри каждой панели в текстовом виджете по мере
поступления сообщений выводится соответствующее значение.
Если вы предпочитаете создать собственную информационную панель, то создайте источник данных так, как описано в разделе 16.8.2 (на этот раз выберите
Dweepy): формируйте новые панели и виджеты или же внесите изменения
в автоматически сгенерированную информационную панель. Ниже приведены
три снимка экранов информационной панели, состоящей из четырех виджетов:
ØØВиджет Gauge с текущей температурой. Для параметра VALUE этого виджета мы выбрали поле Temperature источника данных. Параметру UNITS
задано значение Celsius, а параметрам MINIMUM и MAXIMUM — –25 и 45 градусов соответственно. Виджет Text предназначен для вывода текущей

температуры по шкале Фаренгейта. Для этого виджета мы присвоили параметрам INCLUDE SPARKLINE и ANIMATE VALUE CHANGES значение YES. Для
параметра VALUE виджета выбрано поле Temperature источникаданных,
а в конец поля VALUE добавлен текст
* 9 / 5 + 32

854   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
для выполнения вычислений, преобразующих значение по шкале Цельсия
к шкале Фаренгейта. Также для параметра UNITS было выбрано значение
Fahrenheit.
ØØНаконец, мы добавили виджеты Indicator Light. Для параметра VALUE первого виджета Indicator Light выбрано поле LowTempWarning источника данных,
для параметра TITLE — значение Freeze Warning; для параметра ON TEXT
выбрано значение LOW TEMPERATURE WARNING. ON TEXT показывает, какой
текст должен отображаться при истинном значении. Для параметра VALUE
второго виджета Indicator Light выбрано поле HighTempWarning источника
данных, для параметра TITLE — значение High Temperature Warning; наконец, для параметра ON TEXT выбрано значение HIGH TEMPERATURE WARNING.

16.8.5. Создание подписчика PubNub в коде Python
PubNub предоставляет модуль Python pubnub для удобного выполнения операций публикации/подписки. Также предоставляются семь пробных потоков
для экспериментов — четыре потока реального времени и три смоделированных потока1:
1

https://www.pubnub.com/developers/realtime-data-streams/.

16.8. «Интернет вещей»   855
ØØTwitter Stream — предоставляет до 50 твитов в секунду из живого потока

Twitter, не требуя регистрационных данных Twitter.
ØØHacker News Articles — последние статьи с сайта.
ØØState Capital Weather — метеорологические данные для столиц штатов

США.
ØØWikipedia Changes — поток правок «Википедии».
ØØGame State Sync — смоделированные данные для многопользовательской

игры.
ØØSensor Network — смоделированные данные датчиков радиации, влажно-

сти, температуры и освещения.
ØØMarket Orders — смоделированные заказы для пяти компаний.

В этом разделе мы используем модуль pubnub для подписки на смоделированный поток Market Orders, а затем построим визуализацию изменяющихся
данных заказов на диаграмме Seaborn:

Вы можете публиковать сообщения и в потоке. Подробности — в документации
модуля pubnub по адресу https://www.pubnub.com/docs/python/pubnub-python-sdk.
Чтобы подготовиться к использованию PubNub в Python, выполните следующую команду для установки новейшей версии модуля pubnub — часть

856   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
'>=4.1.2' гарантирует, что будет установлена как минимум версия 4.1.2
модуля pubnub:
pip install "pubnub>=4.1.2"

Сценарий stocklistener.py, который подписывается на поток и визуализирует
данные заказов, находится в подкаталоге pubnub каталога ch16. Код сценария
будет разбит на части для удобства обсуждения.

Формат сообщений
Смоделированный поток Market Orders возвращает объекты JSON, содержащие пять пар «ключ-значение» с ключами 'bid_price', 'order_quantity',
'symbol', 'timestamp' и 'trade_type'. В примере используются только ключи
'bid_price' и 'symbol'. Клиент PubNub возвращает данные JSON в формате
словаря Python.

Импортирование библиотек
Строки 3–13 импортируют библиотеки, использованные в примере. Типы
PubNub, импортированные в строках 10–13, будут рассматриваться по мере
использования.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# stocklistener.py
"""Визуализация живого потока PubNub."""
from matplotlib import animation
import matplotlib.pyplot as plt
import pandas as pd
import random
import seaborn as sns
import sys
from
from
from
from

pubnub.callbacks import SubscribeCallback
pubnub.enums import PNStatusCategory
pubnub.pnconfiguration import PNConfiguration
pubnub.pubnub import PubNub

Список и коллекция DataFrame для хранения названий
компаний и цен
Список companies содержит названия компаний, данные которых присутствуют в потоке Market Orders, а в коллекции DataFrame с именем companies_df

16.8. «Интернет вещей»   857

хранятся цены по каждой компании. Эта коллекция DataFrame будет использоваться для построения гистограммы средствами Seaborn.
15 companies = ['Apple', 'Bespin Gas', 'Elerium', 'Google', 'Linen Cloth']
16
17 # DataFrame для хранения последних цен
18 companies_df = pd.DataFrame(
19
{'company': companies, 'price' : [0, 0, 0, 0, 0]})
20

Класс SensorSubscriberCallback
Подписываясь на поток PubNub, вы должны добавить слушателя, который
будет получать уведомления о статусе и сообщения от канала — по аналогии
со слушателями Tweepy, которых вы определяли ранее. Для этого необходимо
определить подкласс SubscribeCallback (модуль pubnub.callbacks), который
будет рассмотрен ниже:
21 class SensorSubscriberCallback(SubscribeCallback):
22
"""SensorSubscriberCallback получает сообщения от PubNub."""
23
def __init__(self, df, limit=1000):
24
"""Создает переменные экземпляров для хранения количества твитов."""
25
self.df = df # DataFrame для хранения последних цен
26
self.order_count = 0
27
self.MAX_ORDERS = limit # 1000 по умолчанию
28
super().__init__() # Вызов версии init суперкласса
29
30
def status(self, pubnub, status):
31
if status.category == PNStatusCategory.PNConnectedCategory:
32
print('Connected to PubNub')
33
elif status.category == PNStatusCategory.PNAcknowledgmentCategory:
34
print('Disconnected from PubNub')
35
36
def message(self, pubnub, message):
37
symbol = message.message['symbol']
38
bid_price = message.message['bid_price']
39
print(symbol, bid_price)
40
self.df.at[companies.index(symbol), 'price'] = bid_price
41
self.order_count += 1
42
43
# При достижении MAX_ORDERS отменить подписку на канал PubNub
44
if self.order_count == self.MAX_ORDERS:
45
pubnub.unsubscribe_all()
46

Метод __init__ класса SensorSubscriberCallback сохраняет коллекцию
DataFrame, в которую будут помещаться все новые цены. Клиент PubNub вы-

858   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT
зывает переопределенный метод status каждый раз с поступлением нового
сообщения статуса. В данном случае мы проверяем уведомления о подписке
или отписке от канала.
Клиент PubNub вызывает переопределенный метод message (строки 36–45)
при поступлении нового сообщения из канала. Строки 37 и 38 получают из
сообщения название компании и цену, которые выводятся приложением,
чтобы пользователь видел принимаемые сообщения. Строка 30 использует
метод at коллекции DataFrame для нахождения строки соответствующей
компании и ее столбца 'price', а затем присваивает этому элементу новую
цену. После того как счетчик order_count достигнет MAX_ORDERS, строка 45
вызывает метод unsubscribe_all клиента PubNub, для того чтобы отписаться
от канала.

Функция Update
В этом примере для визуализации цен применяются средства анимации,
представленные в разделе «Введение в data science» главы 6. Функция update
определяет прорисовку одного кадра анимации; она многократно вызывается
объектом FuncAnimation, который будет определен ниже. Мы используем
функцию barplot библиотеки Seaborn для визуализации данных от коллекции DataFrame companies_df, используя значения столбца 'company' по оси x
и значения столбца 'price' по оси y.
47 def update(frame_number):
48
"""Настраивает содержимое гистограммы для каждого кадра анимации."""
49
plt.cla() # Стереть старую гистограмму
50
axes = sns.barplot(
51
data=companies_df, x='company', y='price', palette='cool')
52
axes.set(xlabel='Company', ylabel='Price')
53
plt.tight_layout()
54

Настройка объекта Figure
В основной части сценария сначала назначается стиль оформления диаграммы,
после чего создается объект Figure, на котором будет выводиться гистограмма:
55 if __name__ == '__main__':
56
sns.set_style('whitegrid') # Белый фон с серыми линиями сетки
57
figure = plt.figure('Stock Prices') # Объект Figure для анимации
58

16.8. «Интернет вещей»   859

Настройка объекта FuncAnimation и отображение окна
Теперь настроим объект FuncAnimation, вызывающий функцию update, а затем
и метод show библиотеки Matplotlib для отображения Figure. Обычно метод
блокирует продолжение сценария до того, как вы закроете Figure. В данном
случае передается ключевой аргумент block=False, чтобы сценарий продолжил
выполнение, а мы могли настроить клиента PubNub и подписаться на канал.
59
60
61
62
63

# Настроить и запустить анимацию с вызовом функции update
stock_animation = animation.FuncAnimation(
figure, update, repeat=False, interval=33)
plt.show(block=False) # display window

Настройка клиента PubNub
Затем мы настраиваем ключ подписки PubNub, используемый клиентом
PubNub в сочетании с именем канала для подписки на канал. Ключ задается как атрибут объекта PNConfiguration (модуль pubnub.pnconfiguration),
передаваемый в строке 69 новому объекту клиента PubNub (модуль pubnub.
pubnub). Строки 70–72 создают объект SensorSubscriberCallback и передают его методу add_listener клиента PubNub, чтобы зарегистрировать его для
получения сообщений от канала. Аргумент командной строки используется
для определения количества обрабатываемых сообщений.
64
65
66
67
68
69
70
71
72
73

# Настроить ключ потока pubnub-market-orders
config = PNConfiguration()
config.subscribe_key = 'sub-c-4377ab04-f100-11e3-bffd-02ee2ddab7fe'
# Создать клиента PubNub и зарегистрировать SubscribeCallback
pubnub = PubNub(config)
pubnub.add_listener(
SensorSubscriberCallback(df=companies_df,
limit=int(sys.argv[1] if len(sys.argv) > 1 else 1000))

Подписка на канал
Следующая команда завершает процесс подписки, указывая, что мы хотим
получать сообщения от канала с именем 'pubnub-market-orders'. Метод
execute запускает поток:
74
75
76

# Подписаться на канал pubnub-sensor-network и начать потоковую передачу
pubnub.subscribe().channels('pubnub-market-orders').execute()

860   Глава 16. Большие данные: Hadoop, Spark, NoSQL и IoT

Сохранение объекта Figure на экране
Второй вызов метода show библиотеки Matplotlib гарантирует, что объект
Figure останется на экране, пока не будет закрыто окно.
77

plt.show()

# Диаграмма остается на экране, пока не будет закрыто окно

16.9. Итоги
В этой главе мы представили концепцию больших данных, рассказали, как
развивается эта отрасль, и описали программную и аппаратную инфраструктуру для работы с большими данными. Были представлены традиционные
реляционные базы данных и язык SQL, а модуль sqlite3 использовался для
создания и работы с базой данных books в SQLite. Также была продемонстрирована загрузка результатов запросов SQL в коллекции Pandas DataFrame.
Рассмотрены четыре основные разновидности баз данных NoSQL — базы
данных «ключ-значение», документные, столбцовые и графовые базы данных,
а также базы данных NewSQL. Объекты твитов JSON сохранялись в виде
документов в кластере MongoDB Atlas на базе облака, а затем обобщались
в интерактивной визуализации, отображаемой на карте Folium.
Далее была представлена технология Hadoop и ее применение в области больших данных. Мы настроили многоузловой кластер Hadoop с использованием
сервиса Microsoft Azure HDInsight, после чего создали и выполнили задачу
Hadoop MapReduce с применением потоковой передачи Hadoop.
Затем мы перешли к технологии Spark и ее применению для создания высокопроизводительных приложений больших данных, работающих в реальном
времени. Мы использовали возможности программирования Spark в функциональном стиле «фильтрация/отображение/свертка» — сначала в стеке Jupyter
Docker, работающем локально на вашем компьютере, а затем с многоузловым
кластером Spark c использованием Microsoft Azure HDInsight. Затем был
представлен механизм Spark Streaming для обработки данных мини-пакетами:
в примере Spark SQL использовался для запросов на выборку данных, хранящихся в коллекции Spark DataFrame.
Глава завершается кратким введением в «интернет вещей» (IoT) и модель
публикации/подписки. Средства Freeboard.io использовались для создания
визуализации живого потока данных от сервиса. Мы смоделировали термо-

16.9. Итоги   861

стат с подключением к интернету, который публикует сообщения на бесплатном сервисе dweet.io с использованием модуля Python Dweepy, а затем
воспользовались Freeboard.io для визуализации данных смоделированного
устройства. Наконец, мы подписались на поток PubNub при помощи модуля
Python.
Спасибо всем читателям. Хочется верить, что книга вам понравилась, а материал показался интересным и содержательным. Надеемся, что благодаря
прочитанному вы обретете необходимую уверенность для успешного применения технологий, рассмотренных в книге, и решения задач, с которыми
столкнетесь в своей карьере.

Пол Дейтел, Харви Дейтел
Python: Искусственный интеллект,
большие данные и облачные вычисления
Перевел с английского Е. Матвеев

Заведующая редакцией
Ведущий редактор
Литературные редакторы
Художественный редактор
Корректоры
Верстка

Ю. Сергиенко
К. Тульцева
М. Петруненко, М. Рогожин
В. Мостипан
Н. Викторова, М. Молчанова
Л. Егорова

Изготовлено в России. Изготовитель: ООО «Прогресс книга».
Место нахождения и фактический адрес: 194044, Россия, г. Санкт-Петербург,
Б. Сампсониевский пр., д. 29А, пом. 52. Тел.: +78127037373.
Дата изготовления: 03.2020. Наименование: книжная продукция. Срок годности: не ограничен.
Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12 — Книги печатные профессиональные, технические и научные.
Импортер в Беларусь: ООО «ПИТЕР М», 220020, РБ, г. Минск, ул. Тимирязева, д. 121/3, к. 214, тел./факс: 208 80 01.
Подписано в печать 19.03.20. Формат 70×100/16. Бумага офсетная. Усл. п. л. 69,660. Тираж 1000. Заказ 0000.