знакомство с
PY THON
Дэн Бейдер, Дэвид Эймос
Джоанна Яблонски, Флетчер Хейслер
2023
ББК 32.973.2-018.1
УДК 004.43
Б41
Бейдер Дэн, Эймос Дэвид, Яблонски Джоанна, Хейслер Флетчер
Б41Знакомство с Python. — СПб.: Питер, 2023. — 512 с.: ил. — (Серия «Библиотека программиста»).
ISBN 978-5-4461-1924-0
Пытаетесь найти что-нибудь для начинающих о языке Python в интернете? Не можете решить,
с чего начать? Как структурировать это море информации? В каком порядке изучать?
Если вы задаетесь подобными вопросами, потому что хотите заложить фундамент будущей
карьеры питониста, — эта книга для вас!
Вместо скучного перечисления возможностей языка авторы рассказывают, как сочетать разные
структурные элементы Python, чтобы сразу создавать скрипты и приложения.
Книга построена по принципу 80/20: большую часть полезной информации можно усвоить,
изучив несколько критически важных концепций. Освоив самые популярные команды и приемы,
вы сразу сосредоточитесь на решении реальных повседневных задач.
16+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.)
ББК 32.973.2-018.1
УДК 004.43
Права на издание получены по соглашению с DevAcademy Media Inc. Все права защищены. Никакая часть
данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения
владельцев авторских прав.
Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как
надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не
может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за
возможные ошибки, связанные с использованием книги. Издательство не несет ответственности за доступность материалов, ссылки на которые вы можете найти в этой книге. На момент подготовки книги к изданию
все ссылки на интернет-ресурсы были действующими.
ISBN 978-1775093329 англ.
ISBN 978-5-4461-1924-0
© Real Python (realpython.com)
© Перевод на русский язык ООО «Прогресс книга», 2022
© Издание на русском языке, оформление ООО «Прогресс книга»,
2022
© Серия «Библиотека программиста», 2022
Оглавление
Об авторах......................................................................................................................... 14
Предисловие......................................................................................... 15
Python как язык полного спектра............................................................................. 15
Глава 1. Введение.................................................................................. 21
1.1. Почему именно эта книга?................................................................................... 22
1.2. О Real Python........................................................................................................... 23
1.3. Как пользоваться книгой...................................................................................... 24
1.4. Дополнительный материал и учебные ресурсы........................................... 25
Глава 2. Установка и настройка Python ............................................... 27
2.1. О версиях Python.................................................................................................... 27
2.2. Windows...................................................................................................................... 28
2.3. macOS.......................................................................................................................... 31
2.4. Linux............................................................................................................................ 34
Глава 3. Первая программа Python ..................................................... 38
3.1. Написание программы Python........................................................................... 38
3.2. Ошибки....................................................................................................................... 42
3.3. Создание переменной............................................................................................ 44
3.4. Просмотр значений в интерактивном окне.................................................... 48
3.5. Заметки на память.................................................................................................. 50
3.6. Итоги и дополнительные ресурсы.................................................................... 52
6 Оглавление
Глава 4. Строки и строковые методы ................................................. 54
4.1. Что такое строка?.................................................................................................... 54
4.2. Конкатенация, индексирование и срезы......................................................... 60
4.3. Манипуляции со строками с использованием методов............................. 68
4.4. Взаимодействие с пользовательским вводом................................................ 73
4.5. Задача: разбор пользовательского ввода........................................................ 75
4.6. Работа со строками и числами............................................................................ 76
4.7. Упрощение команд вывода.................................................................................. 80
4.8. Поиск подстроки в строке.................................................................................... 82
4.9. Задача: преобразование текста........................................................................... 84
4.10. Итоги и дополнительные ресурсы.................................................................... 85
Глава 5. Числа и математические вычисления................................... 87
5.1. Целые числа и числа с плавающей точкой..................................................... 87
5.2. Арифметические операторы и выражения..................................................... 91
5.3. Задача: выполнение вычислений с пользовательским вводом............... 98
5.4. Когда Python говорит неправду......................................................................... 98
5.5. Математические функции и числовые методы..........................................100
5.6. Оформление чисел при выводе........................................................................104
5.7. Комплексные числа..............................................................................................107
5.8. Итоги и дополнительные ресурсы..................................................................109
Глава 6. Функции и циклы.................................................................. 111
6.1. Что же такое функция?.......................................................................................111
6.2. Написание ваших собственных функций.....................................................115
6.3. Задача: конвертер температур..........................................................................122
6.4. Циклическое выполнение..................................................................................123
6.5. Задача: отслеживание прибыли по вкладу...................................................130
Оглавление 7
6.6. Область видимости в Python............................................................................131
6.7. Итоги и дополнительные ресурсы..................................................................136
Глава 7. Поиск и исправление ошибок в коде.................................. 137
7.1. Использование окна Debug Control...............................................................137
7.2. Исправление ошибок...........................................................................................143
7.3. Итоги и дополнительные ресурсы..................................................................149
Глава 8. Условная логика и управление программой...................... 151
8.1. Сравнение значений.............................................................................................151
8.2. Добавим немного логики....................................................................................154
8.3. Управление последовательностью выполнения программы.................161
8.4. Задача: поиск множителей числа....................................................................170
8.5. Управление циклами............................................................................................171
8.6. Восстановление после ошибок.........................................................................174
8.7. Моделирование событий и вычисление вероятностей............................179
8.8. Задача: моделирование эксперимента с броском монеты.......................183
8.9. Задача: моделирование выборов......................................................................184
8.10. Итоги и дополнительные ресурсы..................................................................184
Глава 9. Кортежи, списки и словари.................................................. 186
9.1. Кортежи как неизменяемые последовательности.....................................186
9.2. Списки: изменяемые последовательности...................................................195
9.3. Вложение, копирование и сортировка кортежей и списков..................206
9.4. Задача: список списков.......................................................................................210
9.5. Задача: приступ вдохновения...........................................................................212
9.6. Храните отношения в словарях.......................................................................213
9.7. Задача: цикл по столицам...................................................................................222
9.8. Как выбрать структуру данных........................................................................223
8 Оглавление
9.9. Задача: коты в шляпах.........................................................................................223
9.10. Итоги и дополнительные ресурсы..................................................................224
Глава 10. Объектно-ориентированное программирование
(ООП).................................................................................................... 226
10.1. Определение класса...........................................................................................226
10.2. Создание экземпляров (инстанцирование)...............................................230
10.3. Наследование от других классов...................................................................235
10.4. Задача: модель фермы.......................................................................................242
10.5. Итоги и дополнительные ресурсы................................................................243
Глава 11. Модули и пакеты................................................................. 244
11.1. Работа с модулями..............................................................................................244
11.2. Работа с пакетами...............................................................................................253
11.3. Итоги и дополнительные ресурсы................................................................260
Глава 12. Операции ввода и вывода с файлами............................... 261
12.1. Файлы и файловая система.............................................................................261
12.2. Работа с путями к файлам в Python.............................................................265
12.3. Основные операции файловой системы.....................................................272
12.4. Задача: перемещение всех графических файлов
в новый каталог...................................................................................................285
12.5. Чтение и запись файлов...................................................................................286
12.6. Чтение и запись данных CSV.........................................................................298
12.7. Задача: создание списка рекордов................................................................307
12.8. Итоги и дополнительные ресурсы................................................................307
Глава 13. Установка пакетов с помощью pip.................................... 309
13.1. Установка сторонних пакетов с помощью pip...........................................309
13.2. Подводные камни сторонних пакетов.........................................................318
13.3. Итоги и дополнительные ресурсы................................................................320
Оглавление 9
Глава 14. Создание и изменение файлов PDF.................................. 321
14.1. Извлечение текста из файла PDF.................................................................321
14.2. Извлечение страниц из файлов PDF...........................................................327
14.3. Задача: класс PdfFileSplitter...........................................................................332
14.4. Конкатенация и слияние файлов PDF........................................................333
14.5. Поворот и обрезка страниц PDF...................................................................339
14.6. Шифрование и дешифрование файлов PDF............................................348
14.7. Задача: восстановление порядка страниц..................................................351
14.8. Создание файла PDF с нуля...........................................................................352
14.9. Итоги и дополнительные ресурсы................................................................357
Глава 15. Базы данных........................................................................ 359
15.1. Знакомство с SQLite..........................................................................................359
15.2. Библиотеки для работы с другими базами данных SQL......................369
15.3. Итоги и дополнительные ресурсы................................................................370
Глава 16. Веб-программирование..................................................... 372
16.1. Скрапинг и парсинг текста с веб-сайтов.....................................................372
16.2. Использование парсера HTML для извлечения веб-данных..............380
16.3. Работа с HTML-формами................................................................................385
16.4. Взаимодействие с веб-сайтами в реальном времени..............................390
16.5. Итоги и дополнительные ресурсы................................................................393
Глава 17. Научные вычисления и построение графиков................ 395
17.1. Использование NumPy для матричных вычислений.............................395
17.2. Построение графиков с помощью Matplotlib...........................................404
17.3. Итоги и дополнительные ресурсы................................................................422
Глава 18. Графические интерфейсы.................................................. 423
18.1. Добавление элементов GUI с помощью EasyGUI..................................423
18.2. Пример: программа для поворота страниц PDF......................................434
10 Оглавление
18.3. Задача: приложение для извлечения страницы PDF.............................439
18.4. Знакомство с Tkinter.........................................................................................440
18.5. Работа с виджетами............................................................................................443
18.6. Управление макетом при помощи менеджеров геометрии..................463
18.7. Интерактивность в приложениях.................................................................478
18.8. Пример приложения: конвертер температур............................................485
18.9. Пример приложения: текстовый редактор................................................489
18.10. Задача: возвращение поэта..............................................................................496
18.11. Итоги и дополнительные ресурсы................................................................498
Глава 19. Мысли напоследок и следующие шаги............................ 500
19.1. Еженедельные бесплатные советы для питонистов...............................501
19.2. Книга «Чистый Python»...................................................................................501
19.3. Библиотека видеокурсов Real Python.........................................................502
19.4. Благодарности......................................................................................................503
Что питонисты говорят о книге «Знакомство с Python»
«Обожаю [эту книгу]! Книга написана доступным языком, материал понятен,
а последовательность изложения выглядит логично. Я никогда не терялся
в материале, а интенсивность изложения не слишком велика, что позволяет
мне снова и снова возвращаться к предыдущим главам.
Я просмотрел более 10 разных учебников/книг/сетевых курсов о Python. Пожалуй, больше всего я узнал именно из материалов Real Python1!»
Thomas Wong (Томас Вон)
«Прошло три года, а я все еще возвращаюсь к книгам Real Python, когда требуется быстро освежить в памяти важные команды языка Python».
Роб Фаулер (Rob Fowler)
«Я долгое время заставлял себя заняться самообучением. Я продирался через
десятки неполных сетевых учебников. Я засыпал при просмотре многочасовых
скучных видеороликов. Я разочаровался в бесчисленных заумных книгах от
именитых издателей. А потом я открыл для себя Real Python.
Доступные пошаговые инструкции разделяют большие темы на легко усваиваемые части, написанные простым языком. Авторы никогда не забывают о своих
читателях, их объяснения неизменно обстоятельны и подробны. Сейчас я уже
перешел к самостоятельной работе, но постоянно возвращаюсь к книге за наставлениями».
Джаред Нильсен (Jared Nielsen)
«Мне нравится эта книга, потому что каждый урок завершают реальные и интересные задачи. Я только что написал программу для подсчета сбережений,
которая учитывает состояние моего сберегательного счета, — класс!»
Дрю Прескотт (Drew Prescott)
1
Real Python — проект Дэна Бейдера, одного из авторов книги «Знакомство с Python»
и автора бестселлера «Чистый Python. Тонкости программирования для профи».
Подробнее об этом ресурсе — в разделе «Об авторах». — Примеч. ред.
12 Что питонисты говорят о книге «Знакомство с Python»
«Чтобы потренироваться в том, что я узнал, я начал строить простые сценарии,
упрощающие повседневную работу моей команды. Когда руководство заметило
это, мне предложили новую должность разработчика. Я знаю, что мне еще многое
предстоит узнать и проблем будет немало, но я наконец-то начал заниматься
тем, что мне действительно нравится.
Еще раз: ОГРОМНОЕ СПАСИБО!»
Камил (Kamil)
«В курсах Real Python мне больше всего нравится то, что они объясняют все
на максимально простом уровне.
Для освоения многих учебных курсов — притом практически в любой дисциплине — необходимо изучать массу специальных терминов, тогда как на самом
деле материал можно объяснить быстро и лаконично. Авторы Real Python стараются использовать интересные примеры, и у них это отлично получается».
Стивен Грэди (Stephen Grady)
«После освоения первого курса Real Python я написал программу для автоматизации моих повседневных операций на работе. То, на что раньше требовалось
от трех до пяти часов, теперь выполняется менее чем за 10 минут!»
Брэндон Янгдейл (Brandon Youngdale)
«Честно говоря, осваивая материал книги, я усердно искал, что бы можно было
в ней добавить или улучшить, но учебник получился просто замечательный!
Вы прекрасно умеете объяснять и обучать Python на уровне, доступном даже
для людей вроде меня, то есть полных новичков.
Последовательность изложения материала работает идеально. Упражнения очень
сильно помогают, и после освоения материала книги вы справедливо ощущаете
гордость. Мне кажется, у вас есть особый дар — делать так, чтобы Python казался
более досягаемым для людей, не принадлежащих к миру программирования.
Я никогда не думал, что буду иметь дело с Python или изучать его. Но теперь
с небольшой поддержкой с вашей стороны я изучаю его — и вижу, что в будущем
мне это принесет только пользу!»
Ши Клусевич (Shea Klusewicz)
Что питонисты говорят о книге «Знакомство с Python»  13
«Авторы курсов НЕ забыли о том, каково быть новичком (а об этом не помнят
многие авторы!). Они ничего не требуют от своих читателей, и поэтому их
книги так хорошо читаются. К курсам прилагаются превосходные видеоролики,
а также множество ссылок для дополнительного обучения и домашней работы
и примеры кода, с которыми вы можете экспериментировать.
Мне очень понравилось, что все уроки сопровождались полными примерами
кода и каждая строка прокомментирована, чтобы вы понимали, что происходит.
У меня много книг о Python, но только книги Real Python я прочитал от корки
до корки: просто они однозначно лучшие на рынке. Если вы, как и я, не принадлежите к числу профессиональных программистов (я работаю в сетевом
маркетинге), эти книги станут для вас настоящим наставником благодаря доступным объяснениям, освобожденным от всего лишнего! В высшей степени
рекомендую!»
Крейг Эддиман (Craig Addyman)
14 Об авторах
ОБ АВТОРАХ
Ресурс Real Python предназначен для всех, кто хочет освоить навыки реального
программирования при поддержке сообщества профессиональных разработчиков Python со всего мира.
Веб-сайт realpython.com был запущен в 2012 году. В настоящее время он ежемесячно помогает более чем трем миллионам разработчиков Python своими
бесплатными учебными пособиями и курсами.
Все, кто работал над книгой «Знакомство с Python», — практики, имеющие
многолетний профессиональный опыт в программировании, члены преподавательской команды Real Python.
Дэвид Эймос — технический директор по контенту сайта Real Python. После
ухода из образовательной системы в 2015 году Дэвид работал на различных
технических должностях как программист и специалист по обработке данных.
В 2019 году он перешел в штат Real Python, чтобы развить свое увлечение образованием. Дэвид возглавил переработку и обновление материала книги для
Python 3.
Дэн Бейдер — владелец и старший редактор сайта Real Python, а также ведущий разработчик образовательной платформы realpython.com. Дэн занимается
программированием более 20 лет, он имеет степень магистра в области компьютерных технологий. А кроме того, Дэн написал «Python Tricks»1 — популярную
книгу для продвинутых разработчиков Python.
Джоанна Яблонски — главный редактор сайта Real Python. Она любит естественные языки в той же степени, что и языки программирования. Ее пристрастие к загадкам, закономерностям и нудным мелочам привело к тому, что
она выбрала карьеру переводчика. Прошло совсем немного времени, и она
влюбилась в новый язык — Python! Джоанна присоединилась к проекту Real
Python в 2018 году и с тех пор помогает программистам Python повышать профессиональную квалификацию.
Флетчер Хейслер — основатель проекта Hunter2, он обучает разработчиков
тонкостям программирования и построению безопасных современных вебприложений. Флетчер, один из основателей Real Python, в 2012 году написал
первую версию учебного курса Python, на котором основана эта книга.
1
Бейдер Д. Чистый Python. Тонкости программирования для профи. — СПб.: Питер.
Предисловие
Добро пожаловать! Надеюсь, вы готовы узнать, почему Python привлекает
столь многих разработчиков — как профессионалов, так и любителей — и как
вы можете применять его в ваших проектах, мелких и крупных.
Эта книга написана для новичков, которые либо немного владеют навыками
программирования, но не языком и экосистемой Python, либо начинают осваивать язык с нуля без какого-либо предварительного опыта программирования.
Если у вас нет диплома в области компьютерных технологий, не огорчайтесь.
Дэвид, Дэн, Джоанна и Флетчер разъяснят вам основные концепции программирования в процессе изложения основ Python; и что не менее важно — на первых
порах не будут отвлекаться на несущественные подробности.
PYTHON КАК ЯЗЫК ПОЛНОГО СПЕКТРА
При изучении нового языка программирования у вас еще нет опыта для того,
чтобы судить, насколько хорошо он послужит вам в долгосрочной перспективе.
Если вы намерены изучать Python, позвольте заверить, что вы сделали хороший
выбор. Одна из важнейших причин заключается в том, что Python является
языком полного спектра.
Что мы имеем в виду? Некоторые языки очень хорошо подходят для новичков.
Они ведут их за руку, и программирование становится невероятно простым.
Этот подход доводится до крайности в таких визуальных языках, как Scratch.
В Scratch вы имеете дело с блоками, представляющими концепции программирования: переменные, циклы, вызовы методов и т. д. Эти блоки перетаскиваются
на визуальной поверхности. Возможно, Scratch хорошо подходит для написания
первых, очень простых программ, но профессиональное приложение на нем не
построишь. Назовите хотя бы одну компанию из списка Fortune 500, которая
реализует свою основную бизнес-логику на Scratch.
Не получилось? У меня тоже, потому что это было бы полным безумием.
Другие языки обладают невероятной мощью — но в руках опытных разработчиков. Пожалуй, самым популярным в этой категории является C++ и его ближайший родственник C. Любой веб-браузер, которым вы пользовались сегодня,
16 Предисловие
с большой вероятностью был написан на C или C++. Операционная система,
в которой этот браузер работает, тоже, скорее всего, написана на C/C++. Ваша
любимая стрелялка или стратегическая видеоигра? Верно: C/C++.
На этих языках можно делать невероятные вещи, но они совершенно недружелюбны к новичкам, которые предпочитают спокойное знакомство с новой
областью.
Вы еще не видели код C++? Иногда от него начинают слезиться глаза. Приведу
пример (относительно сложный):
template <typename T>
_Defer<void(*(PID<T>, void (T::*)(void)))
(const PID<T>&, void (T::*)(void))>
defer(const PID<T>& pid, void (T::*method)(void))
{
void (*dispatch)(const PID<T>&, void (T::*)(void)) =
&process::template dispatch<T>;
return std::tr1::bind(dispatch, pid, method);
}
Пожалуйста, только не это…
И Scratch, и C++ определенно не являются тем, что я называю языками полного спектра. Scratch упрощает начало программирования, но для построения реальных приложений придется переключиться на «настоящий» язык.
И, наоборот, на C++ можно строить реальные приложения, но на деликатное
введение в тему лучше не рассчитывать. Вы с головой погружаетесь во всю
сложность языка, который создавался для поддержки полнофункциональных
приложений.
С другой стороны, Python отличается от обеих крайностей — это язык полного
спектра. Мы часто судим о простоте языка по программе Hello, World. Иначе
говоря, какой синтаксис и действия необходимы, чтобы программа вывела
сообщение Hello, World? На языке Python трудно представить что-то проще:
print("Hello, World")
И все! Тем не менее вряд ли это можно назвать полноценным критерием.
Тест Hello, World полезен, но его недостаточно для того, чтобы продемонстрировать всю мощь или сложность языка. Рассмотрим другой пример. Не
старайтесь понять все от начала до конца — просто следите за процессом,
чтобы уловить суть. Все эти (и многие другие) концепции будут рассмотрены
в книге. Следующий пример будет вам вполне по силам, когда вы доберетесь
до конца книги.
Python как язык полного спектра 17
Итак, новый критерий: насколько трудно написать программу, которая обращается к внешнему веб-сайту, загружает контент в память вашего приложения,
а затем выводит часть этого контента для пользователя? Для этого эксперимента мы воспользуемся Python 3 с пакетом requests (который вам необходимо
установить — см. главу 12):
import requests
resp = requests.get("http://olympus.realpython.org")
html = resp.text
print(html[86:132])
Вы не поверите, но это все! При запуске программа выводит результат следующего вида:
<h2>Please log in to access Mount Olympus:</h2>
Это простая, дружелюбная к начинающим часть спектра возможностей Python.
Всего несколько тривиальных строк раскрывают невероятную мощь. Поскольку для Python доступно много мощных, но удобных библиотек (таких, как
requests), часто говорят, что «батарейки входят в комплект Python».
Итак, вы увидели простой, но мощный пример. В реальном мире многие замечательные приложения были написаны на Python.
На Python написан YouTube, самый популярный сайт потокового видео, обрабатывающий более миллиона запросов в секунду. Instagram — еще один пример
приложения на Python. Примеры можно найти и ближе — realpython.com и мои
сайты, такие как talkpython.fm.
Тот факт, что Python является языком полного спектра, означает, что вы можете
начать с основ и осваивать расширенные возможности по мере роста потребностей вашего приложения.
Популярность Python
Вы наверняка слышали, что Python популярен. Может показаться, что по­
пулярность языка менее важна, чем то, что на нем можно построить нужное
вам приложение.
Как бы то ни было, популярность языка программирования многое говорит
о качестве доступных библиотек и о количестве опубликованных вакансий.
Короче говоря, лучше придерживаться более популярных технологий, потому
что в вашем распоряжении окажется больше вариантов применения полученных навыков.
18 Предисловие
Действительно ли Python настолько популярен? Да, настолько. Без шумихи и преувеличений дело не обходится, но обширная статистика подкрепляет это утверждение. Взгляните на аналитику, представленную на
stackoverflow.com — известном сайте вопросов и ответов для программистов.
Процент вопросов на Stack Overflow за этот месяц
Stack Overflow поддерживает сайт StackOverflowTrends, на котором можно
искать информацию о трендах различных технологий. Если сравнить Python
с другими возможными кандидатами для изучения программирования, вы
увидите, что Python выделяется на их фоне.
Язык
2
1
2
3
4
3
1
4
Год
Если вы захотите изучить эти тренды, воспользуйтесь данными
insights.stackoverflow.com/trends.
Обратите внимание на невероятный рост применения Python по сравнению
с графиками других кандидатов — горизонтальными и даже снижающимися!
Если ваше будущее зависит от успеха конкретной технологии, то какой язык
вы бы выбрали из этого списка?
Впрочем, это всего лишь один график — что он говорит нам? Рассмотрим другой. Stack Overflow проводит среди разработчиков ежегодный опрос, очень
исчерпывающий и очень хорошо продуманный. Полные результаты за 2020 год
можно найти на insights.stackoverflow.com/survey/2020.
Python как язык полного спектра 19
В этом описании обратите внимание на раздел «Самые любимые языки» («Most
Wanted»). В нем приведены данные о доле «разработчиков, которые не используют язык или технологию, но выразили интерес к разработке на этом языке».
И снова из диаграммы видно, что Python стоит на первом месте со значительным
отрывом даже от второго места.
Если вы согласны с тем, что относительная популярность языка программирования важна, то Python, очевидно, является хорошим вариантом.
Вам не надо знать теорию
Еще одно обстоятельство, которое я хочу подчеркнуть в начале вашего путешествия в мир Python: вам не обязательно быть специалистом по информатике.
Если вы к этому стремитесь — прекрасно. Изучение Python станет большим
шагом в этом направлении. Однако приглашение к изучению программирования
часто преподносится в виде: «У нас столько свободных вакансий! Нам нужны
разработчики!»
Это может быть правдой, а может и не быть. Но что гораздо важнее, программирование (даже простейшие навыки) может стать вашей персональной
суперсилой.
Представьте, что вы — биолог. Стоит ли бросать биологию и поступать на должность программиста, специализирующегося на построении веб-интерфейсов?
Возможно, нет. Но навыки вроде того, который был показан в начале предисловия (с использованием requests для получения данных с веб-сайта), могут
оказаться невероятно полезными и для биолога.
20 Предисловие
Вам не придется вручную экспортировать и извлекать данные с веб-сайтов или
из электронных таблиц, вы просто используете Python для обработки тысяч источников данных или электронных таблиц за время, необходимое для ручной
обработки всего одного источника. Владение языком Python переформатирует
вашу повседневную работу в качестве биолога, выведет ее на другой уровень —
значительно выше уровня ваших коллег, так что она станет вашей суперсилой.
Дэн и Real Python
Позвольте мне напоследок сказать пару слов об авторах. Дэн Бейдер и другие
авторы изо дня в день трудятся над тем, чтобы доступно и ярко объяснять
концепции Python на сайте realpython.com.
Они обладают неповторимыми взглядами на экосистему Python и ориентируются на то, что должны знать начинающие программисты.
Я со спокойной душой доверяю вас им в путешествии по миру Python. Отправляйтесь в путь и изучайте этот замечательный язык по отличной книге. А самое
главное — получайте удовольствие от процесса!
Майкл Кеннеди,
основатель Talk Python (@mkennedy)
От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.
com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию
о наших книгах.
ГЛАВА 1
Введение
Добро пожаловать! Встречайте новое издание книги «Знакомство с Python»,
полностью обновленное для Python 3.9. Книга посвящена методам программирования на языке Python, которые мы иллюстрируем интересными практическими примерами. Кем бы вы ни были — начинающим программистом
или профессио­налом, желающим освоить новый язык, — здесь вы найдете все
необходимое для того, чтобы начать самостоятельную работу на Python.
Если ваша деятельность связана с компьютером, то, каковы бы ни были ваши
цели, вы откроете для себя огромное количество возможностей упростить свою
жизнь за счет автоматизации задач и решения проблем в написанных вами программах Python. Достаточно изучить материал этой книги.
Но чем же так хорош Python как язык программирования? Прежде всего он
свободно распространяется с открытым кодом, а это означает, что вы можете
бесплатно загрузить его и использовать для любых целей (в том числе коммерческих).
Кроме того, приверженцы Python создали сообщество и разработали целый
ряд полезных инструментов, которыми вы можете пользоваться в своих программах. Понадобилось поработать с документами PDF? Для этого вам предлагается многогранный инструментарий. Извлечь данные с веб-страниц? Вам
не придется начинать с нуля!
Python создавался с таким расчетом, чтобы им было проще пользоваться, чем
любым другим языком программирования. Как правило, код на Python намного
легче читается и намного быстрее пишется, чем на других языках.
Вот простейшая программа на C, другом популярном языке программирования:
#include <stdio.h>
int main(void)
{
printf("Hello, World\n");
}
22 Глава 1 Введение
Эта программа просто выводит текст Hello, World на экран. Многовато работы
для вывода одной фразы! А вот как выглядит та же программа, написанная на
Python:
print("Hello, World")
Совсем просто, верно? Теперь вы сами убедились, что код на Python быстрее пишется и проще читается. И еще он выглядит более дружественным и доступным.
В то же время Python обладает всей функциональностью других языков — и не
только. Вы не поверите, сколько профессиональных продуктов построено на
базе Python: Instagram, YouTube, Reddit, Spotify… список можно продолжать.
Python не только доступен и интересен для изучения. Этот язык также положен
в основу технологий некоторых компаний мирового уровня, что открывает
фантастические возможности карьерного роста для любого программиста,
который им хорошо владеет.
1.1. ПОЧЕМУ ИМЕННО ЭТА КНИГА?
Будем откровенны: в интернете с лихвой хватает информации о Python. Но
многим новичкам, которые изучают язык самостоятельно, иногда трудно разобраться, что изучать и в какой последовательности.
Возможно, вас интересует вопрос: что следует в первую очередь узнать о Python,
чтобы заложить надежную базу для дальнейшего обучения? В таком случае эта
книга для вас независимо от того, абсолютный ли вы новичок или у вас уже
есть опыт работы на Python или других языках.
Книга написана просто. Базовые концепции, которые вам необходимы, излагаются доступно. Это означает, что вы быстро начнете добиваться успехов в Python.
Вместо перечисления возможностей языка я рассказываю, как разные структурные элементы сочетаются друг с другом и что необходимо для построения
реальных приложений и сценариев на языке Python.
Шаг за шагом вы освоите фундаментальные концепции, которые помогут вам
сделать первые шаги в применении Python.
Многие книги по программированию грешат описанием всех возможных вариаций каждой команды, из-за чего читатели быстро теряются в лабиринте
подробностей. Такой подход отлично годится для справочников, но не для
изучения языка программирования. Мало того, что вы тратите большую часть
1.2. О Real Python 23
времени, пытаясь уложить в голове множество деталей, которые вам никогда
не понадобятся, — это попросту скучно!
Книга построена по принципу 80/20: большую часть нужной информации можно усвоить, изучив несколько критически важных концепций. Мы рассмотрим
команды и приемы, используемые в большинстве ситуаций, и сосредоточимся
на решении реальных повседневных задач.
Тем самым я гарантирую, что вы:
zz
быстро освоите полезные приемы программирования;
zz
потратите меньше времени на борьбу с лишними сложностями;
zz
начнете применять Python на практике;
zz
получите больше удовольствия от процесса.
Книга дает вам возможность получить базовые знания, и дальнейшие ваши
вылазки на более сложную территорию будут проходить намного проще.
За основу мы взяли материал первой части исходного курса «Real Python Course»,
выпущенного в 2012 году. За прошедшие годы этот курс опробовали тысячи
программистов на Python, экспертов по работе с данными и разработчиков,
трудящихся в компаниях разных уровней, включая Amazon, Red Hat и Microsoft.
Для этой книги мы тщательно доработали, расширили и обновили материал,
чтобы вы могли быстро и эффективно развивать свои навыки работы на Python.
1.2. О REAL PYTHON
Сайт Real Python даст вам возможность освоить навыки реального программирования в сообществе профессиональных питонистов.
Веб-сайт realpython.com был запущен в 2012 году. В настоящее время он ежемесячно помогает более чем трем миллионам разработчиков на Python, предоставляя доступ к книгам, учебникам и другим учебным ресурсам.
Все, кто работал над этой книгой, — практикующие программисты из команды
Real Python с многолетним профессиональным опытом.
Контактные данные Real Python в интернете:
zz
realpython.com
zz
@realpython в Twitter (https://twitter.com/realpython)
24 Глава 1 Введение
zz
The Real Python Newsletter (https://twitter.com/newsletter)
zz
The Real Python Podcast (https://twitter.com/podcast)
1.3. КАК ПОЛЬЗОВАТЬСЯ КНИГОЙ
Первая половина книги — краткий, но разносторонний обзор всех фундаментальных возможностей Python. Никакой предшествующий опыт программирования вам для этого не понадобится. Вторая половина — практические решения
интересных реальных задач программирования.
Новичкам мы рекомендуем изучить первую половину книги от начала до конца.
Темы во второй половине книги в меньшей степени связаны друг с другом, так
что вам будет проще осваивать их по отдельности, однако имейте в виду: чем
дальше, тем материал сложнее.
Если у вас уже есть опыт программирования, возможно, вам стоит с ходу обратиться ко второй части книги. Но все же подумайте над тем, чтобы сначала
разобраться в основах, а потом уж заполнять информационные пробелы в процессе решения практических задач.
Большинство разделов каждой главы завершается упражнениями, которые
помогут вам убедиться в том, что вы хорошо усвоили учебный материал. Также
в книге предлагаются сложные задачи, для решения которых вам придется
воспользоваться знаниями, полученными из предыдущих глав.
В файлах, прилагаемых к книге, содержатся полные решения задач и самых
сложных упражнений. Но чтобы извлечь максимум пользы, постарайтесь решать
задачи самостоятельно, а не с ходу заглядывать в ответы.
Если у вас вообще нет опыта программирования, первые главы желательно подкрепить дополнительной практикой. Мы рекомендуем проработать учебники
начального уровня, которые можно бесплатно загрузить с сайта realpython.com
(https://realpython.com/python-basics), — они помогут убедиться в том, что материал вы усвоили.
А если у вас появятся вопросы или вы захотите поделиться своим мнением,
то всегда можете обратиться к нам напрямую (https://realpython.com/contact).
Обучение на практике
Принцип обучения на практике взят за основу в этой книге, поэтому обязательно
вводите вручную все фрагменты кода, которые вам встретятся. Для достижения
1.4. Дополнительный материал и учебные ресурсы 25
наилучших результатов мы рекомендуем избегать копирования/вставки примеров. Вы быстрее поймете концепции и усвоите синтаксис, если будете вводить
каждую строку самостоятельно. Кроме того, если вы совершите ошибку — что
абсолютно нормально и что частенько случается с любым разработчиком, — то
даже простое исправление опечаток поможет вам научиться отлаживать код.
Пробуйте выполнять упражнения и задачи самостоятельно, прежде чем обращаться за помощью к внешним ресурсам. При достаточной практике вы усвоите
материал, а попутно хорошо проведете время!
Сколько времени потребуется для изучения
материала книги?
Если вы уже знакомы с любым другим языком программирования, вам достаточно каких-нибудь 35–40 часов. Если же у вас нет опыта программирования,
вам может потребоваться 100 часов и более.
Не торопитесь, вас никто не подгоняет. Программирование — занятие благодарное, но непростое. Удачи на вашем пути в мир Python! Мы за вас болеем!
1.4. ДОПОЛНИТЕЛЬНЫЙ МАТЕРИАЛ
И УЧЕБНЫЕ РЕСУРСЫ
К книге прилагаются бесплатные дополнительные ресурсы и материалы, которые можно загрузить из интернета по приведенной ниже ссылке. Здесь же
опубликован и постоянно обновляется список опечаток с исправлениями:
realpython.com/python-basics/resources
Интерактивные тесты
Для многих глав книги были созданы бесплатные интерактивные тесты для
проверки того, как вы усвоили материал (на английском языке!). К ним можно
обратиться по ссылкам, приведенным в конце глав. Тесты размещаются на сайте
Real Python, и их можно просматривать с телефона или с компьютера.
В каждом тесте вам предлагается ответить на серию вопросов, относящихся
к конкретной главе книги. Иногда требуется выбрать один вариант из предлагаемого авторами набора, в других случаях вам придется напечатать ответ или
ввести код Python. Информация о том, на какие вопросы вы ответили правильно
в процессе тестирования, сохраняется.
26 Глава 1 Введение
В конце теста вам будет выставлена оценка, вычисленная по вашим результатам.
Если вы не набрали 100 процентов с первой попытки, не огорчайтесь! Эти тесты
и должны быть сложными. Предполагается, что вы пройдете их несколько раз,
каждый раз улучшая свою оценку.
Репозиторий кода упражнений
У книги существует репозиторий кода в интернете. Он содержит примеры
исходного кода, а также ответы на упражнения и задачи. Репозиторий разбит
по главам, так что вы можете сравнить свой код с нашими решениями после
изу­чения каждой главы. Ссылка на репозиторий:
realpython.com/python-basics/exercises
ПРИМЕЧАНИЕ
Код, приведенный в книге, был протестирован с Python 3.9 для Windows,
macOS и Linux.
Лицензия на примеры кода
Сценарии Python, имеющие отношение к книге, распространяются на условиях
лицензии CC0 (Creative Commons Public Domain). Это означает, что вы можете
свободно использовать в своих программах любые части кода для любых целей.
Обратная связь и опечатки
Мы охотно примем ваши идеи, предложения и даже критику. Какая-то тема
показалась вам непонятной? Вы нашли ошибку в тексте или в коде? Мы пропустили тему, о которой вам хотелось бы узнать побольше? Мы всегда рады
возможностям улучшить свои учебные материалы. Вы можете поделиться
с нами вашим мнением на:
realpython.com/python-basics/feedback
ГЛАВА 2
Установка и настройка Python
Эта книга посвящена программированию на языке Python. Вы можете прочитать ее от корки до корки, ни разу не прикоснувшись к клавиатуре, но так вы
упустите самое интересное — программирование!
Чтобы получить максимум пользы от книги, вам понадобится компьютер с установленной версией Python, а также средства для создания, редактирования
и сохранения файлов с кодом, который вы будете создавать.
В этой главе вы узнаете, как:
zz
установить последнюю версию Python 3 на вашем компьютере;
zz
запустить IDLE — интегрированную среду разработки и обучения
(Integrated Development and Learning Environment), встроенную в Python.
Итак, за дело!
2.1. О ВЕРСИЯХ PYTHON
Многие операционные системы, включая macOS и Linux, поставляются с предустановленной версией Python. Она называется системной версией.
Системная версия используется вашей операционной системой, и обычно она
уже устаревшая. Чтобы вы могли успешно воспроизводить примеры из книги,
важно установить последнюю версию Python.
ВАЖНО!
Не пытайтесь удалять системную версию Python!
На компьютере можно установить несколько версий этого языка. В этой главе
вы установите последнюю версию Python 3, не удаляя системную версию, которая уже может существовать на вашей машине.
28 Глава 2 Установка и настройка Python
ПРИМЕЧАНИЕ
Даже если у вас уже установлен Python 3.9, все равно стоит бегло просмотреть
эту главу и лишний раз убедиться, что окружение правильно настроено для
повторения примеров книги.
Глава разбита на три раздела: Windows, macOS и Ubuntu Linux. Найдите раздел,
посвященный вашей операционной системе, и выполните установку и настройку,
после чего можете перейти к следующей главе.
Если у вас установлена другая операционная система, обратитесь к разделу
Python 3 Installation & Setup Guide на сайте Real Python и посмотрите, поддерживается ли ваша ОС. Читатели, пользующиеся планшетами и мобильными
устройствами, могут заглянуть в раздел Online Python Interpreters, чтобы получить информацию о некоторых настройках для браузеров.
2.2. WINDOWS
Здесь описана процедура установки Python 3 и запуска IDLE в системе Windows.
ВАЖНО!
Код, приведенный в книге, тестировался только для копии Python, установленной так, как описано в этом разделе.
Учтите: если вы установили Python каким-то другим способом (например,
средствами Anaconda Python), могут возникнуть проблемы при запуске некоторых примеров.
Установка Python
Системная версия Python обычно не входит в поставку Windows. К счастью,
установка сводится лишь к загрузке и запуску программы установки Python
с сайта Python.org.
Шаг 1. Загрузите программу установки Python 3
Запустите браузер и перейдите на
https://www.python.org/downloads/windows/
2.2. Windows 29
Щелкните на ссылке Latest Python 3 Release - Python 3.x.x под заголовком Python
Releases for Windows в верхней части страницы. На момент написания книги
новейшей версией была Python 3.9.
Затем прокрутите страницу вниз и щелкните на ссылке Windows x86-64 executable
installer, чтобы начать загрузку.
ПРИМЕЧАНИЕ
Если ваша система оснащена 32-разрядным процессором, выберите 32-разрядную программу установки. Если вы не уверены в том, является ли ваш
компьютер 32-разрядным или 64-разрядным, выбирайте 64-разрядную программу установки, о которой мы говорили выше.
Шаг 2. Запустите программу установки
Откройте папку Загрузки в Проводнике Windows и дважды щелкните на файле,
чтобы запустить программу установки. На экране появляется диалоговое окно,
которое выглядит примерно так:
Если номер версии Python окажется больше 3.9.1, это нормально — главное,
чтобы он был не меньше 3.
30 Глава 2 Установка и настройка Python
ВАЖНО!
Обязательно включите флажок Add Python 3.x to PATH. Если вы установили
Python, не выбрав этот флажок, снова запустите программу установки и выберите его.
Щелкните на кнопке Install Now, чтобы установить Python 3. Дождитесь завершения установки и переходите к запуску IDLE.
Запуск IDLE
Чтобы запустить IDLE, выполните следующие действия.
1. Откройте меню Пуск и найдите папку Python 3.9.
2. Откройте папку и выберите IDLE (Python 3.9).
IDLE открывает командную оболочку (shell) Python в новом окне. Оболочка
Python — интерактивная среда, в которой можно ввести код Python и немедленно выполнить его. Она отлично подходит для изучения Python!
ПРИМЕЧАНИЕ
Хотя ничто не мешает вам использовать вместо IDLE другой редактор кода,
если он вам больше нравится, учтите, что в некоторых главах (особенно в главе 7 «Поиск и исправление ошибок в коде») работа построена исключительно
на использовании IDLE.
Окно командной оболочки Python выглядит примерно так:
2.3. macOS 31
В верхней части окна выводится номер версии Python и информация об операционной системе. Если номер меньше 3.9, возможно, вам стоит вернуться к инструкциям по установке из предыдущего раздела и установить нужную версию.
Символы >>> образуют так называемое приглашение (prompt). Когда вы видите
его, это означает, что Python ожидает от вас инструкций.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-setup
Итак, Python установлен в вашей системе, и мы можем написать первую программу Python! Переходите к главе 3.
2.3. macOS
Ниже описана процедура установки Python 3 и запуска IDLE в macOS.
ВАЖНО!
Код, приведенный в книге, тестировался только для копии Python, установленной так, как описано в разделе.
Учтите: если вы установили Python каким-то другим способом (например,
средствами Anaconda Python), могут возникнуть проблемы при запуске некоторых примеров.
Установка Python
Чтобы установить последнюю версию Python в macOS, загрузите и запустите
программу установки Python с сайта Python.org.
Шаг 1. Загрузите программу установки Python 3
Запустите браузер и перейдите на страницу:
https://www.python.org/downloads/mac-osx/
Щелкните на ссылке Latest Python 3 Release - Python 3.x.x под заголовком Python
Releases for macOS в верхней части страницы. На момент написания книги последней версией была Python 3.9.
32 Глава 2 Установка и настройка Python
Затем прокрутите страницу вниз и щелкните на ссылке macOS 64-bit installer,
чтобы начать загрузку.
Шаг 2. Запустите программу установки
Откройте Finder и дважды щелкните на файле, чтобы запустить программу
установки. На экране появится диалоговое окно, которое выглядит примерно так:
Несколько раз нажмите Continue, пока появится предложение подтвердить
лицензионное соглашение. Затем нажмите кнопку Agree.
На экране появится окно с информацией о том, в каком каталоге будет установлена копия Python и сколько места она займет. Скорее всего, изменять
каталог по умолчанию не понадобится; щелкните на кнопке Install, чтобы
начать установку.
Когда копирование файлов будет завершено, закройте окно программы установки кнопкой Close.
2.3. macOS 33
Запуск IDLE
Чтобы запустить IDLE, выполните следующие действия.
1. Откройте Finder и выберите категорию Приложения.
2. Дважды щелкните на папке Python 3.9.
3. Дважды щелкните на значке IDLE.
IDLE откроет командную оболочку (shell) Python в новом окне. Оболочка
Python — интерактивная среда, в которой можно ввести код Python и немедленно выполнить его. Она отлично подходит для изучения Python!
ПРИМЕЧАНИЕ
Хотя ничто не мешает вам использовать вместо IDLE другой редактор кода,
если он вам больше нравится, учтите, что в некоторых главах (особенно в главе 7 «Поиск и исправление ошибок в коде») работа построена исключительно
на использовании IDLE.
Окно командной оболочки Python выглядит примерно так:
В верхней части окна выводится номер версии Python и информация об операционной системе. Если номер меньше 3.9, возможно, вам стоит вернуться
к инструкциям по установке из предыдущего раздела.
34 Глава 2 Установка и настройка Python
Символы >>> образуют так называемое приглашение. Когда вы видите его, это
означает, что Python ожидает от вас инструкций.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-setup
Итак, Python установлен в вашей системе, и мы можем написать первую программу Python! Переходите к главе 3.
2.4. LINUX
Ниже описана процедура установки Python 3 и запуска IDLE в Ubuntu Linux.
ВАЖНО!
Код, приведенный в книге, тестировался только для копии Python, установленной так, как описано в разделе.
Учтите: если вы установили Python каким-то другим способом (например,
средствами Anaconda Python), могут возникнуть проблемы при запуске некоторых примеров.
Установка Python
Весьма вероятно, что в вашем Ubuntu уже установлен Python, но, скорее всего,
версия окажется не самой новой — например, Python 2 вместо Python 3.
Чтобы определить номер вашей версии, откройте окно терминала и попробуйте
выполнить следующие команды:
$ python --version
$ python3 --version
Одна или обе команды могут вывести номер версии:
$ python3 --version
Python 3.9.1
2.4. Linux 35
Если версия Python — 2.x или меньше 3.9, то вам стоит заняться установкой
Python. Способ установки Python в Ubuntu зависит от версии Ubuntu на вашем
компьютере. Для проверки локальной версии Ubuntu можно воспользоваться
следующей командой:
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:
Ubuntu 18.04.1 LTS
Release:
18.04
Codename:
bionic
Найдите номер версии в строке Release и выполните инструкции, приведенные
ниже.
Ubuntu 18.04 и выше
Ubuntu версии 18.04 по умолчанию не включает Python 3.9, но пакет доступен
в репозитории Universe. Чтобы установить его, выполните следующие команды
в окне терминала:
$ sudo apt-get update
$ sudo apt-get install python3.9 idle-python3.9 python3-pip
Учтите, что обновление репозитория Universe обычно отстает от графика выпуска Python. Возможно, загруженная версия Python 3.9 не будет новейшей.
Тем не менее для этой книги годится любая версия Python 3.9.
Ubuntu 17 и ниже
Для Ubuntu версий 17 и ниже Python 3.9 недоступен в репозитории Universe.
Его необходимо загрузить из архива PPA (Personal Package Archive). Чтобы установить Python из deadsnakes PPA (https://launchpad.net/~deadsnakes/+archive/
ubuntu/ppa), выполните следующие команды в окне терминала:
$ sudo add-apt-repository ppa:deadsnakes/ppa
$ sudo apt-get update
$ sudo apt-get install python3.9 idle-python3.9 python3-pip
Чтобы проверить, что установлена правильная версия Python, введите команду python3 --version. Если будет выведен номер версии меньше 3.9, возможно,
следует ввести команду python3.9 --version. Теперь вы можете запустить IDLE
и приготовиться к созданию вашей первой программы на языке Python.
36 Глава 2 Установка и настройка Python
Запуск IDLE
Чтобы запустить IDLE из командной строки, введите следующую команду:
$ idle-python3.9
В некоторых установках Linux можно запустить IDLE сокращенной командой:
$ idle3
IDLE открывает командную оболочку (shell) Python в новом окне. Оболочка
Python — интерактивная среда, в которой можно ввести код Python и немедленно выполнить его. Она отлично подходит для изучения Python!
ПРИМЕЧАНИЕ
Хотя ничто не мешает вам использовать вместо IDLE другой редактор кода,
если он вам больше нравится, учтите, что в некоторых главах (особенно в главе 7 «Поиск и исправление ошибок в коде») работа построена исключительно
на использовании IDLE.
Окно командной оболочки Python выглядит примерно так:
В верхней части окна выводится номер версии Python и информация об операционной системе. Если номер меньше 3.9, возможно, вам стоит вернуться
к инструкциям по установке из предыдущего раздела.
2.4. Linux 37
ВАЖНО!
Если вы запустили IDLE командой idle3 и в окне оболочки Python выводится номер версии меньше 3.9, значит, IDLE нужно запустить командой idlepython3.9 command.
Символы >>> образуют так называемое приглашение. Когда вы видите его, это
означает, что Python ожидает от вас инструкций.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-setup
Итак, Python установлен в вашей системе, и мы можем написать первую программу Python! Переходите к главе 3.
ГЛАВА 3
Первая программа Python
Итак, вы установили последнюю версию Python — пора браться за программирование!
В этой главе мы:
zz
напишем нашу первую программу на Python;
zz
запустим программу, содержащую ошибку, и посмотрим, что происходит;
zz
объявим переменную и просмотрим ее значения;
zz
напишем комментарии.
Готовы сделать первые шаги в мир Python? Вперед!
3.1. НАПИСАНИЕ ПРОГРАММЫ PYTHON
Если вы еще не открыли IDLE, сделайте это сейчас. В IDLE вы будете работать
с двумя основными окнами: интерактивным, которое открывается при запуске
IDLE, и окном редактора.
Код можно вводить в любом из этих окон. Но они отличаются тем, как в них
выполняется код. В этом разделе мы расскажем, как выполнять код Python
в обоих окнах.
Интерактивное окно
Интерактивное окно IDLE содержит командную оболочку Python — текстовый
интерфейс, используемый для взаимодействия с языком Python. Если ввести
фрагмент кода Python в интерактивном окне и нажать клавишу Enter, вы немедленно увидите результаты — отсюда и название «интерактивное окно».
Это окно открывается автоматически при запуске IDLE. В верхней его части
появляется следующий текст (с незначительными отличиями, которые зависят
от конфигурации):
3.1. Написание программы Python 39
Python 3.9.1 (tags/v3.9.1:1b293b6)
[MSC v.1916 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
В тексте указана версия Python, выполняемая в IDLE. Также приводится
­информация об операционной системе и некоторые команды, которыми
можно воспользоваться для получения справки и просмотра информации
о Python.
Символы >>> образуют так называемое приглашение. Здесь вы вводите свой код.
Введите после приглашения 1 + 1 и нажмите клавишу Enter:
>>> 1 + 1
2
>>>
Python обрабатывает выражение, выводит результат (2), а затем — следующее
приглашение. Каждый раз, когда вы выполняете код в интерактивном окне,
прямо под результатом появляется новое приглашение.
Выполнение кода Python в интерактивном окне можно описать циклом, состоящим из трех этапов.
1. Python читает код, введенный в приглашении.
2. Python вычисляет результат.
3. Python выводит результат и ожидает нового ввода.
Этот цикл обычно называют циклом «чтение — вычисление — печать» (read —
evaluate — print loop, или REPL).
Попробуем сделать что-то поинтереснее сложения чисел. У программистов есть
традиция начинать программирование на новом языке с программы, которая
выводит на экран фразу «Hello,World».
Введите после приглашения в интерактивном окне слово print, за которым
следует пара круглых скобок с текстом "Hello, World":
>>> print("Hello, World")
Hello, World
Функцией называется код, который выполняет некоторую операцию и может
вызываться по имени. Показанный код вызывает функцию print(), передавая
ей в качестве входных данных текст "Hello, World".
40 Глава 3 Первая программа Python
Круглые скобки приказывают Python вызвать функцию print(). В них также
заключено все, что должно передаваться функции на вход. Кавычки означают,
что "Hello, World" — обычный текст, а не что-то иное.
ПРИМЕЧАНИЕ
В процессе ввода IDLE выделяет части вашего кода разными цветами, чтобы
вам было проще их распознать. По умолчанию функции выделяются фиолетовым, а текст — зеленым цветом.
Интерактивное окно выполняет одну строку кода за раз. Такой режим выполнения удобен для небольших примеров и знакомства с языком Python, но
у него есть серьезный недостаток: вам приходится вводить код по одной строке!
Также возможен другой вариант: сохранить код Python в текстовом файле,
а затем выполнить сразу весь код из файла.
Окно редактора
Вы будете создавать ваши файлы Python в окне редактора IDLE. Окно редактора можно открыть командой FileNew File меню, находящегося в верхней части
интерактивного окна.
Когда открывается окно редактора, интерактивное окно остается открытым.
В нем отображается вывод, генерируемый кодом в окне редактора, поэтому окна
желательно расположить так, чтобы они были видны одновременно.
Введите в окне редактора тот же код, который использовался для вывода сообщения "Hello, World" в интерактивном окне:
print("Hello, World")
IDLE выделяет цветом код в окне редактора точно так же, как и в интерактивном окне.
ВАЖНО!
Если вы пишете код в файле Python, не нужно включать в код приглашение
>>>.
Прежде чем запускать программу, ее необходимо сохранить. Выберите в меню
команду FileSave и сохраните файл под именем hello_world.py.
3.1. Написание программы Python 41
ПРИМЕЧАНИЕ
В некоторых системах для сохранения файлов в IDLE по умолчанию используется каталог установки Python. Не сохраняйте свои файлы в этом каталоге.
Сохраните их на рабочем столе или в папке, расположенной в домашнем
каталоге пользователя.
Расширение .py означает, что файл содержит код Python. Более того, при сохранении файла с любым другим расширением цветовая разметка кода исчезнет.
IDLE применяет ее только в коде Python внутри файлов .py.
Запуск программ Python в окне редактора
Чтобы запустить программу, выберите команду RunRun Module в меню окна
редактора.
ПРИМЕЧАНИЕ
Нажатие клавиши F5 также запускает программу из окна редактора.
Вывод программы всегда отображается в интерактивном окне.
Каждый раз, когда вы запускаете код из файла, в интерактивном окне появляется
сообщение, которое выглядит примерно так:
>>> =================== RESTART ===================
IDLE перезапускает интерпретатор Python — компьютерную программу, которая
фактически выполняет ваш код при каждом запуске файла. Это гарантирует,
что программы будут каждый раз выполняться в одинаковых условиях.
Открытие файлов Python в окне редактора
Чтобы открыть существующий файл в IDLE, выберите в меню команду FileOpen,
а затем выделите файл, который вы хотите открыть. IDLE открывает каждый
файл в новом окне редактора, так что вы можете открыть несколько файлов
одновременно.
Файл можно также открыть из файлового менеджера — такого, как Проводник Windows или macOS Finder. Щелкните правой кнопкой мыши на
значке файла и выберите команду Edit with IDLE, чтобы открыть файл в окне
редактора IDLE.
42 Глава 3 Первая программа Python
Двойной щелчок на файле .py в файловом менеджере запускает программу.
Однако файл обычно выполняется системной версией Python, и окно программы исчезает сразу же после ее завершения — нередко до того, как вы увидите
какой-либо вывод.
А пока, если вы хотите запустить программу Python, лучше всего сделать это
в редакторе IDLE.
3.2. ОШИБКИ
Все совершают ошибки — особенно в программировании! Даже если вы еще не
сделали ни одной, мы сыграем на опережение и нарочно испортим код, чтобы
посмотреть, что из этого выйдет.
Ошибки в программах делятся на два основных типа: синтаксические ошибки
и ошибки времени выполнения.
Синтаксические ошибки
Синтаксическая ошибка возникает, если вы написали код, недопустимый
в Python.
Давайте намеренно создадим синтаксическую ошибку. Удалите последнюю
кавычку из кода в файле hello_world.py, созданного в предыдущем разделе:
print("Hello, World)
Сохраните файл и запустите его нажатием клавиши F5. Программа работать не
будет! IDLE отображает окно сообщения со следующим текстом:
EOL while scanning string literal.
(EOL при сканировании строкового литерала.)
В этом тексте встречаются два термина, которые могут быть вам незнакомы.
1. Строковый литерал (string literal) — текст, заключенный в кавычки.
"Hello, World" является строковым литералом.
2. EOL означает конец строки (End of Line).
Таким образом, IDLE сообщает, что Python достиг конца строки во время чтения строкового литерала. Строковые литералы должны завершаться кавычкой,
предшествующей концу строки.
3.2. Ошибки 43
IDLE выделяет красным цветом строку с командой print("Hello, World), чтобы
вы смогли быстро найти строку кода с синтаксической ошибкой. Без второй
кавычки все символы, следующие после первой, включая завершающую круглую
скобку, считаются частью строкового литерала.
Ошибки времени выполнения
IDLE обнаруживает синтаксические ошибки еще до того, как программа начнет
работу. Напротив, ошибки времени выполнения происходят только в работающих программах.
Чтобы сгенерировать ошибку времени выполнения, удалите обе кавычки
в файле hello_world.py:
print(Hello, World)
Вы заметили, что при удалении кавычек цвет кода меняется на черный? IDLE
уже не распознает Hello, World как текст. Как вы думаете, что произойдет при
запуске программы? Нажмите F5, чтобы узнать!
В интерактивном окне следующий текст выводится красным цветом:
Traceback (most recent call last):
File "/home/hello_world.py", line 1, in <module>
print(Hello, World)
NameError: name 'Hello' is not defined
Каждый раз, когда в программе происходит ошибка, Python останавливает
программу и выводит несколько строк текста — так называемую обратную
трассировку. Она содержит полезную информацию об ошибке.
Обратную трассировку лучше читать снизу вверх.
zz
В последней строке обратной трассировки содержится имя ошибки и сообщение об ошибке. В данном случае ошибка NameError возникла из-за
того, что имя Hello нигде не определено.
zz
В предпоследней строке выводится код, который стал причиной ошибки.
Файл hello_world.py состоит всего из одной строки, так что понять, где
возникла проблема, несложно. Но такая информация пригодится для
больших файлов.
zz
Третья строка с конца сообщает имя файла и номер строки, чтобы вы
могли перейти к строке кода, в которой произошла ошибка.
В следующем разделе вы узнаете, как определять имена для значений в коде.
Но прежде чем двигаться дальше, советую вам потренироваться в устранении
44 Глава 3 Первая программа Python
синтаксических ошибок и ошибок времени выполнения — в обзорных упражнениях.
Упражнения
1. Напишите программу, которая не будет запускаться в IDLE из-за синтаксической ошибки.
2. Напишите программу, в которой ошибка происходит только во время выполнения, потому что программа содержит ошибку времени выполнения.
3.3. СОЗДАНИЕ ПЕРЕМЕННОЙ
В языке Python переменной называется имя, с которым можно связать зна­
чение, чтобы в дальнейшем в коде программы ссылаться на значение по имени.
Переменные чрезвычайно важны для программирования по двум причинам.
1. Переменные предоставляют быстрый доступ к значениям. Например,
результат некоторой продолжительной операции можно присвоить
переменной, чтобы вашей программе не приходилось заново выполнять
операцию каждый раз, когда потребуется использовать результат.
2. Переменные наделяют значения контекстом. Число 28 может означать
что угодно: количество студентов в группе, количество обращений пользователя к веб-сайту и т. д. Если связать значение 28 с именем (например,
num_students), его смысл становится более понятным.
В этом разделе вы научитесь использовать переменные, а также освоите некоторые соглашения, которые соблюдаются программистами Python при выборе
имен переменных.
Оператор присваивания
Оператор представляет собой знак (например, +), выполняющий операцию
с одним или несколькими значениями. Например, оператор + получает два
числа (одно слева, а другое справа) и складывает их.
Для присваивания значений именам переменных используется специальный
знак, называемый оператором присваивания (=). Оператор = получает значение, указанное справа от оператора, и присваивает его имени, указанному слева.
Изменим файл hello_world.py из предыдущего раздела, чтобы программа присваивала текст переменной file, а затем выводила ее на экран:
3.3. Создание переменной 45
>>> greeting = "Hello, World"
>>> print(greeting)
Hello, world
В первой строке создается переменная с именем greeting, которой при помощи
оператора = присваивается значение "Hello, World".
Команда print(greeting) выводит текст Hello, World, потому что Python ищет
имя greeting, узнает, что ему было присвоено значение "Hello, World", и перед
вызовом функции заменяет имя переменной значением.
Если не выполнить команду greeting = "Hello, World" перед выполнением
команды print(greeting), вы получите ошибку NameError, как при попытке
выполнения команды print(Hello, World) в предыдущем разделе.
ПРИМЕЧАНИЕ
Хотя символ = известен нам из курса математики как знак равенства, в Python
он имеет другой смысл. Это важный момент, который может сбить с толку
многих начинающих программистов.
Просто запомните: когда вы видите оператор =, значение в правой части присваивается переменной в левой части.
В именах переменных важен регистр символов, так что greeting и Greeting —
две разные переменные. Например, при попытке выполнения следующего кода
будет выдана ошибка NameError:
>>> greeting = "Hello, World"
>>> print(Greeting)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'Greeting' is not defined
Если у вас возникнут сложности при выполнении какого-либо примера из книги,
проверьте каждый символ в вашем коде, включая пробелы, — ваш код должен
абсолютно соответствовать примеру в книге. Компьютер точно выполняет то,
что вы прикажете, так что «почти правильно» будет недостаточно!
Правила выбора имен переменных
Имена переменных могут быть сколь угодно длинными или короткими, но есть
несколько правил, которые необходимо соблюдать. Имена переменных могут
46 Глава 3 Первая программа Python
состоять из латинских букв верхнего и нижнего регистра (A–Z, a–z), цифр (0–9)
и символов подчеркивания (_), но они не могут начинаться с цифры.
Например, каждое из следующих имен является действительным именем переменной Python:
zz string1
zz _a1p4a
zz list_of_names
А вот такие имена не являются действительными именами переменных, потому
что они начинаются с цифры:
zz 9lives
zz 99_balloons
zz 2beOrNot2Be
Кроме латинских букв и цифр, имена переменных в Python могут содержать
многие символы Юникода.
Юникод (Unicode) — стандарт цифрового представления символов, используемых в большинстве мировых письменных систем. Это значит, что имена
переменных могут содержать буквы из других алфавитов, например буквы
с диакритическими знаками (é, ü и т. д.), и даже знаки китайской, японской
и арабской письменности.
Тем не менее не каждая система нормально отображает расширенные символы,
поэтому лучше избегать их, если только вы не собираетесь обмениваться своим
кодом с жителями других стран.
ПРИМЕЧАНИЕ
О Юникоде мы более подробно поговорим в главе 12.
Также о поддержке Юникода в Python можно узнать в официальной документации Python (https://docs.python.org/3/howto/unicode.html#python-s-unicodesupport).
Даже если имя переменной допустимо, оно не всегда может оказаться хорошим.
Выбрать хорошее имя для переменной иногда на удивление нелегко. К счастью,
существуют рекомендации, которые помогут вам.
3.3. Создание переменной 47
Содержательные имена лучше коротких
Содержательные имена переменных очень важны, особенно в сложных программах. Часто для записи содержательных имен требуется несколько слов.
Не бойтесь использовать длинные имена переменных.
В следующем примере значение 3600 присваивается переменной s:
s = 3600
Имя s не несет никакой информации. Если вы используете полное слово, вам
будет намного проще понять смысл переменной в коде:
seconds = 3600
Имя seconds лучше подходит для переменной, потому что оно предоставляет
больше информации. Но оно все еще не полностью передает смысл. Что такое
3600 секунд — время завершения некоторого процесса, продолжительность
фильма, еще что-нибудь? Непонятно.
А вот такое имя не оставляет ни малейших сомнений относительно того, что
хранится в переменной:
seconds_per_hour = 3600
Читая этот код, вы точно знаете, что 3600 — количество секунд в одном часе.
Ввод имени seconds_per_hour занимает больше времени, чем ввод одной буквы s
или слова seconds, но и выигрыш велик — полная ясность.
Хотя содержательные имена переменных обычно имеют большую длину, слишком длинных имен все же стоит избегать. Хорошее практическое правило —
имена переменных должны содержать не более трех-четырех слов.
Соглашения о выборе имен в Python
Во многих языках программирования имена переменных обычно записывают
в смешанном регистре. То есть с прописной буквы начинается каждое слово,
кроме первого, а все остальные буквы остаются строчными. Например, имена
numStudents и listOfNames записаны в смешанном регистре.
В Python имена переменных чаще записываются в нижнем регистре с подчеркиваниями. Здесь все буквы являются строчными, а слова разделяются
символами подчеркивания. Например, имена num_students и list_of_names
записаны именно по такому принципу.
48 Глава 3 Первая программа Python
Нет правил, которые бы требовали, чтобы имена переменных следовало записывать в нижнем регистре с подчеркиваниями. Однако эта практика закреплена
в документе, который называется PEP 8, и часто его считают официальным
руководством по стилю при написании кода Python.
ПРИМЕЧАНИЕ
Сокращение PEP означает «предложение по улучшению Python» (Python
Enhancement Proposal). PEP — проектировочный документ, используемый сообществом Python для предложений о включении новых возможностей в язык.
Соблюдение стандартов, описанных в PEP 8, гарантирует, что ваш код Python
будет понятен большинству Python-программистов. Это упрощает коммуникацию и сотрудничество при обмене кодом для всех участников разработки.
Упражнения
1. В интерактивном окне выведите какой-либо текст вызовом функции
print().
2. В интерактивном окне присвойте строковый литерал переменной. Затем
выведите содержимое переменной вызовом функции print().
3. Повторите первые два упражнения в окне редактора.
3.4. ПРОСМОТР ЗНАЧЕНИЙ
В ИНТЕРАКТИВНОМ ОКНЕ
Введите следующий фрагмент в интерактивном окне IDLE:
>>> greeting = "Hello, World"
>>> greeting
'Hello, World'
Когда вы нажмете Enter после ввода второй строки с greeting, Python выведет
строковый литерал, присвоенный greeting, хотя вы и не использовали функцию
print(). Этот механизм называется проверкой переменных.
Теперь выведите строку, присвоенную greeting, вызовом функции print():
>>> print(greeting)
Hello, World
3.4. Просмотр значений в интерактивном окне 49
Вы заметили отличия между выводом, полученным при использовании print(),
и выводом, полученным при простом вводе имени переменной и нажатием Enter?
Когда вы вводите имя переменной greeting и нажимаете Enter, Python выводит
значение, присвоенное переменной, в том виде, в котором оно отображается
в коде. Вы присвоили greeting строковый литерал "Hello, World", поэтому
'Hello, World' выводится в кавычках.
ПРИМЕЧАНИЕ
Строковые литералы Python могут создаваться в одинарных или двойных кавычках. На сайте Real Python мы используем двойные кавычки там, где это возможно, тогда как вывод IDLE по умолчанию заключается в одинарные кавычки.
В Python «Hello, World» и 'Hello, World' означают одно и то же — здесь важнее
всего последовательно подходить к использованию кавычек. О строках более
подробно мы расскажем в главе 4.
С другой стороны, print() выводит более удобочитаемое представление значения переменной, что для строковых литералов означает вывод текста без
кавычек.
Иногда вывод и проверка переменной выдают одинаковые результаты:
>>> x = 2
>>> x
2
>>> print(x)
2
Здесь переменной x присваивается число 2. Как при вызове print(x), так и при
проверке x результат выводится без кавычек, потому что 2 является числом,
а не текстом. В большинстве случаев проверка переменной дает более полезную
информацию, чем print().
Предположим, имеются две переменные: x, которой присваивается значение 2,
и y, которой присваивается строковый литерал "2". В этом случае print(x)
и print(y) выводят одно и то же:
>>> x = 2
>>> y = "2"
>>> print(x)
2
>>> print(y)
2
50 Глава 3 Первая программа Python
Однако при проверке x и y проявляются различия между значениями переменных:
>>> x
2
>>> y
'2'
Главный вывод таков: print() выводит удобочитаемое представление значения переменной, тогда как проверка переменной выводит значение в том виде,
в котором оно встречается в коде.
Учтите, что проверка переменной работает только в интерактивном окне. Например, попробуйте выполнить следующую программу из окна редактора:
greeting = "Hello, World"
greeting
Программа выполняется без ошибок, но ничего не выводит!
3.5. ЗАМЕТКИ НА ПАМЯТЬ
Программисты нередко читают код, написанный ими же некоторое время назад,
и спрашивают себя: «Что он делает?» Если вы давно не обращались к своему
коду, вам трудно будет вспомнить, почему вы написали его именно так, а не
иначе!
Чтобы избежать этой проблемы, вы можете оставить комментарии в своем коде.
Комментарии — это текст, который никак не влияет на выполнение программы. Они только описывают, что делает код или почему программист принял
конкретные решения.
Как пишутся комментарии
Самый простой способ вставить комментарий — это разместить символ #
в начале новой строки. Во время выполнения кода Python игнорирует строки,
начинающиеся с #.
Комментарии, начинающиеся с новой строки, называются блоковыми. Также
можно использовать встроенные комментарии, они размещаются в одной строке
с кодом, к которому относятся. Просто поставьте в конце строки символ #, а за
ним разместите текст комментария.
3.5. Заметки на память 51
Пример программы, содержащей комментарии обоих типов:
# Блоковый комментарий.
greeting = "Hello, World"
print(greeting) # Встроенный комментарий.
Символ # можно использовать и в строке. Например, Python не примет символ #
в этой строке за начало комментария:
>>> print("#1")
#1
В общем случае рекомендуется использовать как можно более короткие комментарии, но иногда требуется больше текста, чем помещается в одной строке.
В таком случае можно продолжить комментарий в новой строке, которую также
следует начать с символа #:
# Моя первая программа.
# Она выводит фразу "Hello, World".
# Комментарии в ней длиннее кода!
greeting = "Hello, World"
print(greeting)
Также комментарии используют для временного исключения кода в процессе
тестирования программы. Разместив символ # в начале строки, можно выполнить программу так, словно эта строка не существует, но без фактического
удаления кода.
Чтобы закомментировать фрагмент кода в IDLE, выделите этот фрагмент и нажмите соответствующую комбинацию клавиш:
zz
Windows: Alt+3;
zz
macOS: Ctrl+3;
zz
Ubuntu Linux: Ctrl+D.
Чтобы убрать комментарии, выделите закомментированные строки и нажмите:
zz
Windows: Alt+4;
zz
macOS: Ctrl+4;
zz
Ubuntu Linux: Ctrl+Shift+D.
Рассмотрим некоторые распространенные соглашения, относящиеся к комментариям в коде.
52 Глава 3 Первая программа Python
Соглашения и вредные привычки
Согласно PEP 8, комментарии надо всегда записывать в виде завершенных
предложений, а символ # следует отделять от первого слова комментария одним пробелом:
# Этот комментарий отформатирован по правилам PEP 8.
#А этот нет
Для встроенных комментариев PEP 8 рекомендует отделять код от символа #
как минимум двумя пробелами:
phrase = "Hello, World" # Этот комментарий соответствует PEP 8.
print(phrase)# А этот нет.
PEP 8 рекомендует осмотрительно использовать комментарии. Главной вредной
привычкой программисты считают комментарии, описывающие то, что и так
очевидно из чтения кода.
Например, комментарий в следующем коде не нужен:
# Вывести "Hello, World"
print("Hello, World")
Этот комментарий лишний, потому что сам код наглядно показывает, что
он делает. Комментарии лучше использовать для пояснения трудного для
понимания кода или объяснения того, почему что-то вы делаете именно так,
а не иначе.
3.6. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы написали и выполнили свою первую программу на языке
Python! Это была небольшая программа, которая выводит текст "Hello, World"
при помощи функции print().
Далее вы узнали о синтаксических ошибках, которые возникают до выполнения программы с недействительным кодом Python, и об ошибках времени
выполнения, происходящих только во время выполнения программы.
Вы научились присваивать значения переменным оператором присваивания
=, а также проверять значения переменных в интерактивном окне. Наконец,
вы узнали, как включать в код полезные комментарии на случай, если вы или
кто-то другой будете просматривать его в впоследствии.
3.6. Итоги и дополнительные ресурсы 53
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-first-program
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
zz
«11 Beginner Tips for Learning Python Programming» (https://realpython.
com/python-beginner-tips/)
zz
«Writing Comments in Python (Guide)» (https://realpython.com/pythoncomments-guide/)
ГЛАВА 4
Строки и строковые
методы
Многие программисты разных специализаций ежедневно имеют дело с текстом.
Например, веб-разработчики работают с текстовыми данными веб-форм. Эксперты по аналитическим данным обрабатывают текст для извлечения данных
и выполнения таких операций, как анализ эмоциональной окраски, для идентификации и классификации мнений в блоке текста.
Текстовые блоки в Python называются строками (strings). Для обработки строк
используются специальные функции, называемые строковыми методами. Существуют строковые методы для преобразования строки из нижнего регистра
в верхний, удаления пропусков в начале или конце строки, замены частей строки
другим текстом и многих других операций.
В этой главе вы научитесь:
zz
обрабатывать строки с использованием строковых методов;
zz
работать с пользовательским вводом;
zz
работать с числовыми данными в строках;
zz
форматировать строки для вывода.
За дело!
4.1. ЧТО ТАКОЕ СТРОКА?
В главе 3 вы создали строку "Hello, World" и вывели ее в интерактивном окне
IDLE при помощи функции print(). В этом разделе мы более детально разберемся в том, что собой представляют строки, а также изучим различные способы
их создания в Python.
4.1. Что такое строка? 55
Строковый тип данных
Строки принадлежат к числу фундаментальных типов данных Python. Тип
данных определяет, какого рода данные представляет значение. Строки используются для представления текста.
ПРИМЕЧАНИЕ
В Python существуют другие встроенные типы данных. Например, числовые
типы данных будут рассматриваться в главе 5, а логические типы данных —
в главе 8.
Строки называют фундаментальным типом данных, потому что они не могут
быть разбиты на меньшие значения другого типа. Не все типы данных являются
фундаментальными. В главе 9 я расскажу о составных типах данных, также называемых структурами данных.
Для строкового типа данных в Python есть специальное сокращенное имя: str.
Его можно вывести при помощи функции type(), которая используется для
определения типа данных заданного значения.
Введите следующие команды в интерактивном окне IDLE:
>>> type("Hello, World")
<class 'str'>
Вывод <class 'str'> означает, что значение "Hello, World" является экземпляром типа данных str. Другими словами, "Hello, World" — строка.
ПРИМЕЧАНИЕ
Пока слово class можно рассматривать как синоним для типа данных, хотя
на самом деле оно имеет более конкретный смысл. О том, что такое классы,
мы расскажем в главе 10.
Функция type() также работает для значений, которые были присвоены переменной:
>>> phrase = "Hello, World"
>>> type(phrase)
<class 'str'>
56 Глава 4 Строки и строковые методы
Строки обладают тремя важными свойствами.
1. Строки содержат отдельные буквы или знаки, которые называются
символами.
2. У строк есть длина — количество символов, содержащихся в строке.
3. Символы в строке образуют последовательность; это означает, что каждый
символ в строке занимает позицию с определенным номером.
Посмотрим, как создаются строки.
Строковые литералы
Как вы уже видели, строку можно создать, заключив текст в кавычки:
string1 = 'Hello, World'
string2 = "1234"
Для создания строки можно использовать как одинарные кавычки (string1),
так и двойные (string2) — важно лишь, чтобы в начале и в конце строки использовались кавычки одного вида.
Любая строка, созданная заключением текста в кавычки, называется строковым
литералом. Все строки, которые встречались вам до настоящего момента, были
строковыми литералами.
ПРИМЕЧАНИЕ
Не каждая строка является строковым литералом. Некоторые строки вводятся пользователем или читаются из файла. Так как в этом случае они
не ­заключаются в кавычки, такие строки не являются строковыми литералами.
Кавычки, в которые заключена строка, называются ограничителями, потому
что они сообщают Python, где начинается и где завершается строка. Если одну
разновидность кавычек вы используете в качестве ограничителя, другую разновидность можете использовать внутри строки:
string3 = "We're #1!"
string4 = 'I said, "Put it over by the llama."'
После того как Python прочитает первый ограничитель, он будет считать все
последующие символы частью строки, пока не достигнет второго парного
4.1. Что такое строка? 57
ограничителя. Вот почему вы можете использовать одинарную кавычку в строке,
заключенной в двойные кавычки, и наоборот.
Если же вы попытаетесь использовать двойные кавычки в строке, заключенной
в двойные кавычки, произойдет ошибка:
>>> text = "She said, "What time is it?""
File "<stdin>", line 1
text = "She said, "What time is it?""
^
SyntaxError: invalid syntax
Python выдаст ошибку SyntaxError, потому что решит, что строка завершается
после второй двойной кавычки ", и не будет знать, как интерпретировать остаток строки. Если вам понадобится включить в строку кавычку, совпадающую
с ограничителем, вы можете экранировать символ обратной косой чертой:
>>> text = "She said, \"What time is it?\""
>>> print(text)
She said, "What time is it?"
ПРИМЕЧАНИЕ
Когда вы работаете над проектом, рекомендуется использовать только одинарные кавычки (или только двойные) в качестве ограничителей для всех строк.
Помните, что здесь нет однозначно правильного или ошибочного варианта!
Нужно только действовать последовательно, потому что это упростит чтение
и понимание вашего кода.
Строки могут содержать произвольные символы Юникода. Например, строка
"We're #1!" содержит символ «решетка» (#), а строка "1234" содержит цифры.
"×Pýŧħøŋ×" также является действительной строкой Python!
Определение длины строки
Количество символов в строке, включая пробелы, называется длиной строки.
Например, длина строки "abc" — 3, а длина строки "Don't Panic" равна 11.
В Python существует встроенная функция len(), предназначенная для определения длины строки. Чтобы увидеть, как она работает, введите следующую
команду в интерактивном окне IDLE:
>>> len("abc")
3
58 Глава 4 Строки и строковые методы
Функция len() также может использоваться для получения длины строки,
присвоенной переменной:
>>> letters = "abc"
>>> len(letters)
3
Сначала строка "abc" присваивается переменной letters. Затем вызов функции
len() возвращает длину letters, которая равна 3.
Многострочный текст
Стилевое руководство PEP 8 рекомендует, чтобы каждая строка кода Python
содержала не более 79 символов, включая пробелы.
ПРИМЕЧАНИЕ
Длина строки в 79 символов, указанная в PEP 8, — рекомендация, а не правило.
Некоторые Python-программисты предпочитают несколько большую длину
строки.
В этой книге четко соблюдается длина строки, рекомендованная в PEP 8.
Независимо от того, следуете ли вы рекомендациям PEP 8 или нет, иногда
возникает необходимость создать строку, количество символов в которой превышает указанное ограничение.
Чтобы работать с более длинными строками, можно разбить их на несколько
«логических строк». Допустим, необходимо включить следующий текст в строковый литерал:
This planet has — or rather had — a problem, which was
this: most of the people living on it were unhappy for
pretty much of the time. Many solutions were suggested
for this problem, but most of these were largely concerned with the movements of small green pieces of
paper, which is odd because on the whole it wasn’t the
small green pieces of paper that were unhappy.
Дуглас Адамс. «Автостопом по галактике»
Абзац содержит намного более 79 символов, так что любая строка кода, содержащая этот текст в виде строкового литерала, нарушает PEP 8. Что же делать?
4.1. Что такое строка? 59
Есть пара возможных решений. Вы можете разбить строку на несколько частей
и поставить обратную косую черту \ в конце каждой части, кроме последней.
Для выполнения рекомендаций PEP 8 общая длина каждой части, включая \,
не должна превышать 79 символов.
Пример записи абзаца в виде многострочного текста с использованием \:
paragraph = "This planet has—or rather had—a problem, which was \
this: most of the people living on it were unhappy for pretty much \
of the time. Many solutions were suggested for this problem, but \
most of these were largely concerned with the movements of small \
green pieces of paper, which is odd because on the whole it wasn't \
the small green pieces of paper that were unhappy."
Обратите внимание: закрывать каждую строку кавычкой не нужно. В обычной
ситуации Python доберется до конца первой строки и пожалуется на то, что
строка не закрывается парной двойной кавычкой. Но если в конце стоит символ \, Python понимает, что вы просто продолжаете ту же строку в следующей
строке кода.
При выводе функцией print() многострочного текста, разбитого символами \,
вывод отображается в одну строку:
>>> long_string = "This multiline string is \
displayed on one line"
>>> print(long_string)
This multiline string is displayed on one line
Также можно создавать многострочный текст, используя в качестве ограничителей утроенные кавычки (""" или '''). Пример записи длинного абзаца
подобным способом:
paragraph = """This planet has—or rather had—a problem, which was
this: most of the people living on it were unhappy for pretty much
of the time. Many solutions were suggested for this problem, but
most of these were largely concerned with the movements of small
green pieces of paper, which is odd because on the whole it wasn’t
the small green pieces of paper that were unhappy."""
Строки в утроенных кавычках сохраняют символы пропуска (whitespace), которые включают пробелы и символы начала новой строки. Это означает, что
команда print(paragraph) выведет строку с разбиением на части — в том виде,
в котором она определяется в строковом литерале. Иногда это именно то, что
нужно, иногда — нет, поэтому вам стоит обдумать, какой вывод вас устроит, до
того, как выберете способ записи многострочного текста.
60 Глава 4 Строки и строковые методы
Чтобы увидеть, как пробелы сохраняются внутри строки в утроенных кавычках,
введите следующий фрагмент в интерактивном окне IDLE:
>>> print("""An example of a
...
string that spans across multiple lines
...
and also preserves whitespace.""")
An example of a
string that spans across multiple lines
and also preserves whitespace.
Обратите внимание: вторая и третья строки вывода содержат точно такие же
отступы, как в строковом литерале.
Упражнения
1. Выведите строку, внутри которой используется двойная кавычка.
2. Выведите строку, внутри которой используется апостроф.
3. Выведите многострочный текст с сохранением пробелов.
4. Выведите строку, которая определяется в многострочном формате, но
выводится в одной строке.
4.2. КОНКАТЕНАЦИЯ, ИНДЕКСИРОВАНИЕ
И СРЕЗЫ
Итак, теперь вы знаете, что такое строка и как объявляются строковые литералы в коде. Рассмотрим некоторые операции, которые часто выполняются со
строками.
В этом разделе рассматриваются три основные операции:
1) конкатенация, или объединение двух строк;
2) индексирование, или получение одного символа из строки;
3) срезы, или получение сразу нескольких символов из строки.
Конкатенация строк
Две строки можно объединить оператором + (эта операция называется конкатенацией):
>>> string1 = "abra"
>>> string2 = "cadabra"
4.2. Конкатенация, индексирование и срезы 61
>>> magic_string = string1 + string2
>>> magic_string
'abracadabra'
В этом примере конкатенация выполняется в третьей строке. Для конкатенации string1 и string2 используется знак +, после чего результат присваивается
переменной magic_string. Обратите внимание: при конкатенации строки не
разделяются пробелом.
Конкатенация часто используется для объединения двух взаимосвязанных
строк — например, отображения полного имени из имени и фамилии:
>>> first_name = "Arthur"
>>> last_name = "Dent"
>>> full_name = first_name + " " + last_name
>>> full_name
'Arthur Dent'
В этом примере конкатенация выполняется дважды в одной строке кода. Сначала
first_name объединяется с " ", чтобы в итоговой строке после имени следовал
пробел. В результате вы получаете строку "Arthur ", которая затем объединяется
с last_name для получения полного имени "Arthur Dent".
Индексация строк
Каждому символу в строке соответствует номер позиции, называемый индексом. Чтобы обратиться к символу в n-й позиции, укажите число n в квадратных
скобках [] сразу же после строки:
>>> flavor = "fig pie"
>>> flavor[1]
'i'
flavor[1] возвращает символ в позиции 1 строки "fig pie", то есть i.
Стоп! Разве первый символ "fig pie" не f?
В Python — как и в большинстве других языков программирования — отсчет
всегда начинается с нуля. Чтобы получить символ в начале строки, необходимо
задать позицию 0:
>>> flavor[0]
'f'
62 Глава 4 Строки и строковые методы
ВАЖНО!
Если вы забудете, что нумерация индексов начинается с нуля, и попробуете
обратиться к первому символу в строке по индексу 1, произойдет ошибка
смещения на 1.
Ошибки смещения на 1 часто создают проблемы как начинающим, так и опытным программистам!
На следующей диаграмме показаны индексы всех символов строки "fig pie".
|
f
|
i
0
|
1
g
|
2
|
3
p
|
i
4
|
e
5
|
6
При попытке обращения по индексу, значение которого больше, чем число
символов в строке, Python выдает ошибку IndexError:
>>> flavor[9]
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
flavor[9]
IndexError: string index out of range
Наибольший индекс символа в строке всегда на 1 меньше длины строки. Так
как длина "fig pie" равна 7, наибольший допустимый индекс равен 6.
В строках можно также использовать отрицательные индексы:
>>> flavor[-1]
'e'
Последнему символу в строке соответствует индекс –1; для строки "fig pie" это
буква e. Предпоследнему символу i соответствует индекс –2 и т. д.
На следующей диаграмме представлены отрицательные индексы всех символов
строки "fig pie".
|
f
-7
|
i
-6
|
g
-5
|
|
-4
p
-3
|
i
-2
|
e
-1
|
4.2. Конкатенация, индексирование и срезы 63
Как и в случае с положительными индексами, при попытке обращения по отрицательному индексу, значение которого меньше значения индекса первого
символа в строке, Python выдает ошибку IndexError:
>>> flavor[-10]
Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
flavor[-10]
IndexError: string index out of range
(Индекс строки вне диапазона.)
На первый взгляд отрицательные индексы кажутся бесполезными, но иногда
с ними удобнее работать, чем с положительными.
Допустим, строка, введенная пользователем, присваивается переменной user_
input. Если вы захотите получить последний символ строки, как узнать, какой
индекс для этого использовать?
Один из способов получения последнего символа строки основан на вычислении
его индекса при помощи функции len():
final_index = len(user_input) - 1
last_character = user_input[final_index]
Получение последнего символа с индексом –1 сокращает объем кода и не требует
промежуточного шага для вычисления индекса последнего символа:
last_character = user_input[-1]
Срезы
Допустим, вам нужна строка, содержащая первые три буквы строки "fig pie".
Можно обратиться к каждому символу по индексу и выполнить конкатенацию:
>>> first_three_letters = flavor[0] + flavor[1] + flavor[2]
>>> first_three_letters
'fig'
Если вам нужно больше символов, чем несколько первых букв строки, возврат
каждого символа по отдельности с последующей конкатенацией выглядит
громоздким и неудобным. К счастью, Python позволяет решить эту задачу
компактно.
64 Глава 4 Строки и строковые методы
Чтобы выделить часть строки, называемую подстрокой, поставьте двоеточие
между двумя индексами внутри квадратных скобок:
>>> flavor = "fig pie"
>>> flavor[0:3]
'fig'
Выражение flavor[0:3] возвращает первые три символа строки, присвоенной
flavor, начиная с символа с индексом 0 и заканчивая символом с индексом 3 (не
включая последний). Часть [0:3] выражения flavor[0:3] называется срезом.
В данном случае она возвращает срез строки "fig pie".
Понятие срезов нередко усваивается с трудом, потому что подстрока, возвращаемая срезом, включает символ с первым индексом, но не включает символ
со вторым индексом. Чтобы запомнить, как работают срезы, представьте себе
строку как последовательность квадратных ячеек. Левая и правая границы
каждой ячейки нумеруются последовательно от нуля до длины строки, и каждая
ячейка заполняется символом строки.
Вот как это выглядит для строки "fig pie":
|
0
f
|
1
i
|
2
g
|
|
3
4
p
|
5
i
|
6
e
|
7
Таким образом, для "fig pie" срез [0:3] возвращает строку "fig", а срез [3:7]
возвращает строку " pie".
Если опустить первый индекс в срезе, Python считает, что вы хотите начать
с индекса 0:
>>> flavor[:3]
'fig'
Срез [:3] эквивалентен [0:3], так что flavor[:3] вернет первые три символа
строки "fig pie".
Аналогичным образом, если опустить второй индекс в срезе, Python считает,
что вы хотите получить подстроку, начинающуюся с первого индекса и завершающуюся последним символом в строке:
>>> flavor[3:]
' pie'
4.2. Конкатенация, индексирование и срезы 65
Для строки "fig pie" срез [3:] эквивалентен срезу [3:7] . Так как символ
с индексом 3 является пробелом, flavor[3:7] возвращает подстроку, которая
начинается с пробела и завершается последней буквой: " pie".
Если в срезе опущен как первый, так и второй индекс, вы получите строку, которая начинается с символа с индексом 0 и заканчивается последним символом.
Иначе говоря, в этом случае возвращается вся строка:
>>> flavor[:]
'fig pie'
Важно заметить, что, в отличие от индексации строк, Python не выдает исключение IndexError при попытке определения среза, выходящего за начальную
и/или конечную границу строки:
>>> flavor[:14]
'fig pie'
>>> flavor[13:15]
''
В этом примере первая строка получает срез от начала строки до четырнадцатого
символа, не включая его. Длина строки, присвоенной flavor, равна 7; казалось
бы, Python выдаст ошибку. Вместо этого Python игнорирует несуществующие
индексы и возвращает всю строку "fig pie".
Третья строка показывает, что происходит при попытке получения среза,
в котором весь диапазон лежит за границами строки; flavor[13:15] пытается
получить 13-й и 14-й символы, которых нет. Вместо ошибки Python возвращает
пустую строку ("").
ПРИМЕЧАНИЕ
Пустая строка не содержит никаких символов. Чтобы создать пустую строку,
включите в программу две кавычки без каких-либо символов между ними:
empty_string = ""
Строка с любыми символами — даже с пробелом — пустой не является. Все
следующие строки не пусты:
non_empty_string1 = " "
non_empty_string2 = "
non_empty_string3 = "
"
"
Хотя эти строки не содержат видимых символов, пустыми они не считаются,
потому что содержат пробелы.
66 Глава 4 Строки и строковые методы
В срезах можно использовать отрицательные числа. Срезы с отрицательными
индексами подчиняются тем же правилам, что и срезы с положительными
числами. Попробуйте наглядно представить строку в виде последовательности
ячеек, границы которой помечены отрицательными числами.
|
f
-7
|
-6
i
|
-5
g
|
|
-4
-3
p
|
-2
i
|
e
|
-1
Как и прежде, срез [x:y] возвращает подстроку, которая начинается с индекса x
и идет до индекса y (не включая последний). Например, срез [-7:-4] возвращает
первые три буквы строки "fig pie":
>>> flavor[-7:-4]
'fig'
Однако следует учитывать, что крайняя правая граница строки не имеет отрицательного индекса. Логично предположить, что этой границе соответствует
число 0, но такая запись не работает.
Вместо всей строки [-7:0] возвращает пустую строку:
>>> flavor[-7:0]
''
Это происходит из-за того, что второе число в срезе должно обозначать границу,
которая находится справа от границы, соответствующей первому числу в срезе.
Но в нашем случае -7, и 0 относятся к крайней левой границе диаграммы.
Если вы хотите включить в срез последний символ строки, опустите второе
число:
>>> flavor[-7:]
'fig pie'
Конечно, использование flavor[-7:] для получения всей строки выглядит
немного странно — ведь для этого достаточно применить переменную flavor
без среза!
Тем не менее срезы с отрицательными индексами полезны для получения
нескольких последних символов строки. Например, выражение flavor[-3:]
возвращает "pie".
4.2. Конкатенация, индексирование и срезы 67
Неизменяемость строк
Прежде чем завершать этот раздел, обсудим важное свойство строк. Строки
являются неизменяемыми (immutable); это означает, что после создания их
невозможно изменить. Например, посмотрите, что происходит при попытке
присвоить новую букву в определенной позиции строки:
>>> word = "goal"
>>> word[0] = "f"
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
word[0] = "f"
TypeError: 'str' object does not support item assignment
Python выдает ошибку TypeError и сообщает, что объекты str не поддерживают
присваивание элементов.
Если вы захотите изменить строку, придется создать новую строку. Чтобы
преобразовать строку "goal" в строку "foal", можно воспользоваться срезом
и объединить букву "f" со всеми буквами слова "goal", кроме первой:
>>> word = "goal"
>>> word = "f" + word[1:]
>>> word
'foal'
Сначала строка "goal" присваивается переменной word. Затем срез word[1:],
то есть строка "oal", объединяется с буквой "f" для получения строки "foal".
Если вы получили другой результат, проверьте, что вы не забыли включить
в срез двоеточие (:).
Упражнения
1. Создайте строку и выведите ее длину при помощи функции len().
2. Создайте две строки, выполните их конкатенацию и выведите полученную строку.
3. Создайте две строки, воспользуйтесь конкатенацией для добавления
пробела между ними и выведите результат.
4. Выведите строку "zing", используя синтаксис срезов для определения
правильного диапазона символов в строке "bazinga".
68 Глава 4 Строки и строковые методы
4.3. МАНИПУЛЯЦИИ СО СТРОКАМИ
С ИСПОЛЬЗОВАНИЕМ МЕТОДОВ
В Python существуют специальные функции для работы со строками; они
называются строковыми методами. Их множество, но мы ограничимся лишь
наиболее часто используемыми.
В этом разделе вы научитесь:
zz
преобразовывать строку к верхнему или нижнему регистру;
zz
удалять пропуски из строк;
zz
определять, содержатся ли определенные символы в начале или конце
строки.
Преобразование регистра
Чтобы преобразовать все буквы строки к нижнему регистру, используйте метод
.lower(). Вызов метода .lower() добавляется непосредственно после преобразуемой строки:
>>> "Jean-Luc Picard".lower()
'jean-luc picard'
Точка (.) сообщает Python, что далее следует имя метода — в данном случае
метода lower().
ПРИМЕЧАНИЕ
В тексте строковые методы будут обозначаться точкой в начале имен. Например, .lower() записывается с начальной точкой вместо lower().
При таких обозначениях вам будет проще отличать строковые методы от
встроенных функций — например, print() или type().
Строковые методы работают не только со строковыми литералами. Также можно
вызвать .lower() для строки, присвоенной переменной:
>>> name = "Jean-Luc Picard"
>>> name.lower()
'jean-luc picard'
4.3. Манипуляции со строками с использованием методов 69
Для .lower() существует парный метод .upper(), который преобразует каждый
символ строки к верхнему регистру:
>>> name.upper()
'JEAN-LUC PICARD'
Сравните строковые методы .upper() и .lower() с функцией len(), описанной
ранее. Кроме того, что различаются результаты этих функций, важно обратить
внимание на то, как они используются.
len() — автономная функция. Если вы хотите определить длину строки name,
вы вызываете функцию len() напрямую:
>>> len(name)
15
А методы .upper() и .lower() должны вызываться в соединении со строкой.
Они не существуют независимо.
Удаление пропусков из строки
Пропусками (whitespace) называются любые символы, которые выводятся как
пустое место. К ним относятся пробел и новая строка — специальный символ,
продолжающий вывод со следующей строки.
Иногда возникает необходимость в удалении пропусков в начале или конце строки.
Эта операция особенно полезна при работе со строками, полученными от пользователя, так как в такие строки могут быть случайно включены лишние пропуски.
Три строковых метода, которые могут использоваться для удаления пропусков
из строки:
1. .rstrip()
2. .lstrip()
3. .strip()
.rstrip() удаляет пропуски в правой части строки:
>>> name = "Jean-Luc Picard
>>> name
'Jean-Luc Picard
'
>>> name.rstrip()
'Jean-Luc Picard'
"
70 Глава 4 Строки и строковые методы
В этом примере строка "Jean-Luc Picard " содержит пять пробелов в конце.
Метод .rstrip() используется для удаления таких пробелов. В результате вы
получаете новую строку "Jean-Luc Picard" — без пробелов.
Метод .lstrip() работает точно так же, как и .rstrip(), не считая того, что
пропуски удаляются в левой части строки:
>>> name = " Jean-Luc Picard"
>>> name
' Jean-Luc Picard'
>>> name.lstrip()
'Jean-Luc Picard'
Чтобы удалить пропуски и в левой, и в правой части строки, используйте метод
.strip():
>>> name = " Jean-Luc Picard "
>>> name
' Jean-Luc Picard '
>>> name.strip()
'Jean-Luc Picard'
Важно заметить, что ни один из методов .rstrip(), .lstrip() или .strip() не
удаляет пропуски из середины строки. В каждом из приведенных выше примеров сохраняется пробел между "Jean-Luc" и "Picard".
Проверка наличия символов в начале
или в конце строки
При работе с текстом иногда требуется определить, начинается ли (заканчивается ли) заданная строка некоторыми символами. Для решения этой задачи
используют два строковых метода: .startswith() и .endswith().
Допустим, имеется строка "Enterprise". Используем .startswith(), чтобы
определить, начинается ли строка с букв «e» и «n»:
>>> starship = "Enterprise"
>>> starship.startswith("en")
False
Чтобы сообщить .startswith(), какие символы следует искать, вы передаете
строку, содержащую эти символы. Таким образом, чтобы определить, начинается ли "Enterprise" с букв «e» и «n», мы вызываем .startswith("en"). Метод
возвращает False. Как вы думаете, почему?
4.3. Манипуляции со строками с использованием методов 71
Если вы предположили, что .startswith("en") возвращает False, потому что
"Enterprise" начинается с буквы «E» в верхнем регистре — вы абсолютно правы!
Метод .startswith() различает регистр символов. Чтобы вызов .startswith()
вернул True, необходимо передать ему строку "En":
>>> starship.startswith("En")
True
Также при помощи метода .endswith() можно определить, завершается ли
строка заданными символами:
>>> starship.endswith("rise")
True
Как и .startswith(), метод .endswith() различает регистр символов:
>>> starship.endswith("risE")
False
ПРИМЕЧАНИЕ
Значения True и False не являются строками. Они принадлежат особому типу
данных — логическому. О логических значениях более подробно мы поговорим в главе 8.
Строковые методы и неизменяемость
Вспомните, в предыдущем разделе мы говорили, что строки являются неизменяемыми — после того, как они будут созданы, изменить их уже не удастся.
Многие строковые методы, которые модифицируют строки, например .upper()
и .lower(), в действительности возвращают копии исходной строки с соответствующими изменениями.
Если действовать неосторожно, это может внести коварные ошибки в вашу
программу. Попробуйте выполнить следующий фрагмент в интерактивном
окне IDLE:
>>> name = "Picard"
>>> name.upper()
'PICARD'
>>> name
'Picard'
72 Глава 4 Строки и строковые методы
При вызове name.upper() в name ничего не изменяется. Если вы хотите сохранить
результат, его необходимо присвоить переменной:
>>> name = "Picard"
>>> name = name.upper()
>>> name
'PICARD'
name.upper() возвращает новую строку "PICARD", которая присваивается переменной name. Она заменяет исходную строку "Picard", которая была изначально
присвоена name.
Эксперименты с другими строковыми методами
в IDLE
Со строками связано множество методов, но здесь мы затронем эту тему кратко.
IDLE поможет вам изучить новые строковые методы. Чтобы увидеть, как это
делается, сначала присвойте строковый литерал переменной в интерактивном
окне:
>>> starship = "Enterprise"
Теперь введите starship и точку, но не нажимайте Enter. В интерактивном окне
должен появиться следующий текст:
>>> starship.
Подождите пару секунд. IDLE выводит список всех строковых методов; этот
список можно прокручивать клавишами управления курсором.
В IDLE реализована еще одна похожая возможность: клавиша Tab автоматически
заполняет текст без необходимости вводить длинные имена. Например, если
вы введете только starship.u и нажмете Tab , то IDLE автоматически дополнит
текст до starship.upper, потому что starship содержит только один метод, имя
которого начинается с u.
Этот прием работает даже с именами переменных. Попробуйте ввести несколько первых букв имени starship и нажать Tab. Если в программе не были
определены другие переменные, начинающиеся с тех же букв, IDLE завершит
имя starship за вас.
4.4. Взаимодействие с пользовательским вводом 73
Упражнения
1. Напишите программу, которая преобразует следующие строки к нижнему регистру: "Animals", "Badger", "Honey Bee", "Honey Badger". Каждый
результат преобразования должен выводиться с новой строки.
2. Повторите упражнение 1, но преобразуйте каждую строку к верхнему
регистру, а не к нижнему.
3. Напишите программу, которая удаляет пропуски из следующих строк,
а затем выводит полученные результаты:
string1 = "
Filet Mignon"
string2 = "Brisket
"
string3 = " Cheeseburger
"
4. Напишите программу, которая выводит результат вызова .start­
swith("be") для каждой из следующих строк:
string1 = "Becomes"
string2 = "becomes"
string3 = "BEAR"
string4 = " bEautiful"
5. Используя строки из упражнения 4, напишите программу, которая использует строковые методы для изменения каждой строки, чтобы вызов
.startswith("be") возвращал True для всех строк.
4.4. ВЗАИМОДЕЙСТВИЕ С ПОЛЬЗОВАТЕЛЬСКИМ
ВВОДОМ
Итак, вы научились работать со строковыми методами — теперь давайте зай­
мемся интерактивностью!
Сейчас вы узнаете, как получить информацию от пользователя при помощи
функции input(). Мы напишем программу, которая предлагает пользователю
ввести некоторый текст, после чего выводит текст в верхнем регистре.
Введите следующую команду в интерактивном окне IDLE:
>>> input()
74 Глава 4 Строки и строковые методы
Казалось бы, при нажатии клавиши Enter ничего не происходит. Курсор перемещается в новую строку, но приглашение >>> в ней не появляется. Python
ожидает, когда вы введете какой-нибудь текст!
Наберите какой-нибудь ответ и нажмите Enter:
>>> input()
Hello there!
'Hello there!'
>>>
Текст повторяется в новой строке в одинарных кавычках. Это объясняется
тем, что input() возвращает в виде строки любой текст, введенный пользователем. Чтобы функция input() стала чуть более удобной, можно задать приглашение, которое должно выводиться для пользователя. Оно представляет
собой обычную строку, которая заключается в круглые скобки input(). Это
может быть все что угодно: слово, знак, фраза — все, что является допустимой
строкой Python.
Функция input() выводит приглашение и ожидает, когда пользователь введет
какой-нибудь текст. Если пользователь нажмет Enter, input() возвратит текст
пользователя в виде строки, которую можно присвоить переменной и обработать в программе.
Чтобы увидеть, как работает input(), введите следующий код в окне редактора
IDLE:
prompt = "Hey, what's up? "
user_input = input(prompt)
print("You said: " + user_input)
Запустите программу клавишей F5. В интерактивном окне появится текст Hey,
what's up? с мигающим курсором.
Для чего нужен пробел в конце строки "Hey, what's up? "? Когда пользователь начнет вводить текст, он будет отделен от приглашения пробелом. Когда
пользователь введет ответ и нажмет Enter, его ответ присваивается переменной
user_input.
Пример выполнения программы:
Hey, what's up? Mind your own business.
You said: Mind your own business.
4.5. Задача: разбор пользовательского ввода 75
Получив от пользователя введенную им информацию, вы можете с ней чтонибудь сделать. Например, следующая программа получает текст, преобразует
его к верхнему регистру вызовом .upper(), а затем выводит результат:
response = input("What should I shout? ")
shouted_response = response.upper()
print("Well, if you insist..." + shouted_response)
Попробуйте напечатать код этой программы в окне редактора IDLE и запустить ее.
Какие еще операции можно выполнить с информацией, которую вводит пользователь?
Упражнения
1. Напишите программу, которая получает ввод от пользователя и воспроизводит его на экране.
2. Напишите программу, которая получает ввод от пользователя и выводит
его в нижнем регистре.
3. Напишите программу, которая получает ввод от пользователя и выводит
количество содержащихся в нем символов.
4.5. ЗАДАЧА: РАЗБОР ПОЛЬЗОВАТЕЛЬСКОГО
ВВОДА
Напишите программу first_letter.py, которая запрашивает у пользователя ввод
с приглашением "Tell me your password:". Затем программа определяет первую
букву пользовательского ввода, преобразует ее к верхнему регистру и выводит ее.
Например, если пользователь ввел "no", программа должна выдать следующий
результат:
The first letter you entered was: N
Если пользователь не ввел ничего (то есть просто нажал Enter), в вашей программе может возникнуть ошибка — пока не обращайте на это внимания.
В следующей главе я расскажу о паре возможных способов урегулирования
этой проблемы.
76 Глава 4 Строки и строковые методы
Решение этой задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources
4.6. РАБОТА СО СТРОКАМИ И ЧИСЛАМИ
Когда пользователь вводит что-то по запросу функции input(), результатом
всегда является строка. Существует много других ситуаций, когда ввод передается программе в виде строки. Иногда такие строки содержат числа, которые
должны быть задействованы в вычислениях.
В этом разделе вы научитесь работать со строками, содержащими числовые
данные. Вы увидите, как арифметические операции работают со строками и как
они часто приводят к странным результатам. Также вы научитесь выполнять
преобразования между строками и числовыми типами.
Использование строк с арифметическими
операторами
Вы уже видели, что строковые объекты могут содержать разные символы,
включая цифры. Однако не стоит путать числа в строках с обычными числами. Например, попробуйте выполнить следующий код в интерактивном
окне IDLE:
>>> num = "2"
>>> num + num
'22'
Оператор + выполняет конкатенацию двух строк; вот почему результат "2" +
"2" равен "22", а не "4".
Строку также можно умножать на число (при условии, что это число является
целым). Введите следующий фрагмент в интерактивном окне:
>>> num = "12"
>>> num * 3
'121212'
num*3 выполняет конкатенацию трех строк "12" и возвращает строку "121212".
Сравните эту операцию с арифметическими вычислениями. Когда вы умножаете число 12 на число 3, результат будет таким же, как при сложении трех
чисел 12. Этот принцип относится и к строкам. Иначе говоря, "12" * 3 можно
4.6. Работа со строками и числами 77
интерпретировать как "12" + "12" + "12". В общем случае умножение строки на
целое число n выполняет конкатенацию n копий этой строки.
Число в правой части выражения num*3 можно переместить в левую часть,
результат от этого не изменится:
>>> 3 * num
'121212'
Как вы думаете, что произойдет, если использовать оператор * с двумя строками?
Введите "12"*"3" в интерактивном окне и нажмите Enter:
>>> "12" * "3"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'
Python выдает ошибку TypeError и сообщает, что последовательность (sequence)
нельзя умножить на значение, которое не является целым числом.
ПРИМЕЧАНИЕ
Последовательностью называется любой объект Python, поддерживающий
обращение к элементам по индексу. Строки являются последовательностями. Другие разновидности последовательностей рассматриваются в главе 9.
При использовании оператора * со строкой Python всегда ожидает, что в другой
части оператора стоит целое число.
Как вы думаете, что произойдет при попытке сложить строку с целым числом?
>>> "3" + 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
Python выдает ошибку TypeError, потому что ожидается, что объекты с двух
сторон оператора + относятся к одному типу.
Если объект с одной стороны + является строкой, Python пытается выполнить
конкатенацию строк. Сложение выполняется только в том случае, если оба
объекта являются числами. Таким образом, чтобы выполнить сложение "3" + 3
и получить 6, необходимо сначала преобразовать строку "3" в число.
78 Глава 4 Строки и строковые методы
Преобразование строк в числа
Примеры с TypeError из предыдущего раздела подчеркивают типичную проблему несоответствия типов, которая возникает при передаче пользовательского
ввода операции, требующей числа, а не строки.
Рассмотрим пример. Сохраните и запустите следующую программу:
num = input("Enter a number to be doubled: ")
doubled_num = num * 2
print(doubled_num)
Если ввести число 2 после приглашения, можно ожидать, что результат будет
равен 4. Но в данном случае вы получите 22.
Напомню, что input() всегда возвращает строку, поэтому при вводе 2 переменной num присваивается строка "2", а не целое число 2. Следовательно, выражение
num*2 возвращает строку "2", объединенную с самой собой, то есть "22".
Чтобы выполнить арифметические действия с числами, содержащимися в строке, необходимо сначала преобразовать их из строкового типа в число. Для этой
цели существуют две функции: int() и float().
Функция int() преобразует объекты в целые числа, а float() преобразует
объекты в дробные числа. Примеры использования обеих функций в интерактивном окне:
>>> int("12")
12
>>> float("12")
12.0
Обратите внимание, что float() добавляет к числу дробную часть. Числа
с плавающей точкой всегда имеют хотя бы один знак в дробной части. По этой
причине вы не сможете преобразовать строку, содержащую представление
числа с плавающей точкой, в целое число, потому что вы потеряете всю
дробную часть.
Попробуйте преобразовать строку "12.0" в целое число:
>>> int("12.0")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '12.0'
4.6. Работа со строками и числами 79
И хотя лишний 0 в дробной части фактически не изменяет число, Python не
превратит 12.0 в 12, потому что это приведет к потере точности.
Вернемся к программе в начале раздела и посмотрим, как ее исправить. Повторим ее код:
num = input("Enter a number to be doubled: ")
doubled_num = num * 2
print(doubled_num)
Проблема возникает в строке doubled_num = num * 2, потому что num является
строкой, а 2 — целым числом.
Проблему можно решить передачей num функции int() или float(). Так как
приглашение предлагает пользователю ввести число, причем необязательно
целое, преобразуем num к формату с плавающей точкой:
num = input("Enter a number to be doubled: ")
doubled_num = float(num) * 2
print(doubled_num)
Попробуйте запустить программу и ввести 2. Вы получите 4.0, как и предполагалось. Убедитесь сами!
Преобразование чисел в строки
В некоторых случаях требуется преобразовать число в строку. Например, такая
необходимость может возникнуть при построении строки из существующих
переменных, которым присвоены числовые значения.
Как вы уже видели, при попытке выполнить конкатенацию числа со строкой
происходит ошибка TypeError:
>>> num_pancakes = 10
>>> "I am going to eat " + num_pancakes + " pancakes."
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
Так как num_pancakes является числом, Python не может выполнить конкатенацию переменной со строкой "I'm going to eat". Чтобы построить строку,
необходимо преобразовать num_pancakes в строку вызовом str():
>>> num_pancakes = 10
>>> "I am going to eat " + str(num_pancakes) + " pancakes."
'I am going to eat 10 pancakes.'
80 Глава 4 Строки и строковые методы
Также можно вызвать str() для числового литерала:
>>> "I am going to eat " + str(10) + " pancakes."
'I am going to eat 10 pancakes.'
Функция str() работает даже с арифметическими выражениями:
>>> total_pancakes = 10
>>> pancakes_eaten = 5
>>> "Only " + str(total_pancakes - pancakes_eaten) + " pancakes left."
'Only 5 pancakes left.'
В следующем разделе вы узнаете, как форматировать строки для вывода значений в удобном и понятном виде. Но прежде чем двигаться дальше, проверьте,
насколько вы поняли материал, на следующих упражнениях.
Упражнения
1. Создайте строку, содержащую целое число. Преобразуйте строку в целое
число вызовом int(). Чтобы убедиться в том, что новый объект действительно является числом, умножьте его на другое число и выведите
результат.
2. Повторите предыдущее упражнение, но используйте число с плавающей
точкой и функцию float().
3. Создайте строку и целое число. Выведите их рядом друг с другом одной
командой print при помощи str().
4. Напишите программу, которая дважды вызывает input() для получения
двух чисел от пользователя, перемножает эти числа и выводит результат.
Если пользователь вводит 2 и 4, программа должна вывести следующий
текст:
The product of 2 and 4 is 8.0.
4.7. УПРОЩЕНИЕ КОМАНД ВЫВОДА
Допустим, имеется строка name = "Zaphod" и две целочисленные переменные:
heads = 2 и arms = 3. Вы хотите вывести их в строке "Zaphod has 2 heads and 3 arms".
Такое включение целых чисел в строку называется строковой интерполяцией.
Одно из возможных решений основано на конкатенации строк:
>>> name + " has " + str(heads) + " heads and " + str(arms) + " arms"
'Zaphod has 2 heads and 3 arms'
4.7. Упрощение команд вывода 81
Код выглядит некрасиво, а следить за тем, что должно быть заключено в кавычки, а что не должно, иногда непросто. К счастью, существует другой способ
интерполяции строк — форматированные строковые литералы, чаще их называют f-строками.
Чтобы понять, как работают f-строки, проще всего посмотреть на них в действии.
Вот как выглядит приведенная выше строка при записи в виде f-строки:
>>> f"{name} has {heads} heads and {arms} arms"
'Zaphod has 2 heads and 3 arms'
В этом примере следует обратить внимание на два важных обстоятельства.
1. Строковый литерал начинается с буквы f, которая располагается перед
открывающей кавычкой.
2. Имена переменных, заключенные в фигурные скобки {}, заменяются
соответствующими значениями без использования str().
В фигурные скобки также можно заключить выражения Python. Эти выражения
заменяются в строке результатами их вычисления:
>>> n = 3
>>> m = 4
>>> f"{n} times {m} is {n*m}"
'3 times 4 is 12'
Выражения, используемые в f-строках, должны быть простыми, насколько это
возможно. Включив несколько сложных выражений в строковый литерал, вы
можете создать код, который трудно читать и сопровождать.
F-строки доступны только в Python версии 3.6 и выше. В более ранних версиях Python для получения тех же результатов используется метод .format().
В предыдущем примере форматирование строки методом .format() может
выглядеть примерно так:
>>> "{} has {} heads and {} arms".format(name, heads, arms)
'Zaphod has 2 heads and 3 arms'
F-строки короче кода с использованием .format(), а иногда и лучше читаются.
В книге мы будем использовать f-строки.
Подробное описание f-строк и сравнение их с другими способами форматирования строк вы найдете в руководстве «Python 3’s f-Strings: An Improved
String Formatting Syntax (Guide)» на сайте Real Python (https://realpython.com/
python-f-strings/).
82 Глава 4 Строки и строковые методы
Упражнения
1. Создайте переменную с плавающей точкой weight, содержащую значение
0.2, и строковый объект animal со значением "newt". Выведите следующую
строку с этими переменными, используя только конкатенацию строк:
0.2 kg is the weight of the newt.
2. Выведите ту же строку с использованием метода .format() и пустых
заполнителей {}.
3. Выведите ту же строку с использованием синтаксиса f-строк.
4.8. ПОИСК ПОДСТРОКИ В СТРОКЕ
Один из самых полезных строковых методов — .find() — позволяет найти
позицию одной строки внутри другой. Искомая строка обычно называется
подстрокой.
Чтобы использовать метод .find(), укажите его после имени переменной или
строкового литерала. Строку, которую вы хотите найти, указывают в круглых
скобках:
>>> phrase = "the surprise is in here somewhere"
>>> phrase.find("surprise")
4
Метод .find() возвращает индекс первого вхождения переданной строки.
В данном случае "surprise" начинается с пятого символа строки "the surprise
is in here somewhere", который имеет индекс 4, потому что нумерация начинается с 0.
Если .find() не находит нужную подстроку, вместо индекса возвращается -1:
>>> phrase = "the surprise is in here somewhere"
>>> phrase.find("eyjafjallajökull")
-1
Учтите, что проверка совпадений осуществляется символ за символом и с учетом
регистра. Например, если вы попытаетесь найти подстроку "SURPRISE", вызов
.find() вернет -1:
>>> "the surprise is in here somewhere".find("SURPRISE")
-1
4.8. Поиск подстроки в строке 83
Если подстрока встречается в строке более одного раза, .find() возвращает
индекс только первого вхождения от начала строки:
>>> "I put a string in your string".find("string")
8
В строке "I put a string in your string" подстрока "string" встречается дважды,
с индексами 8 и 23, но .find() возвращает только 8.
Метод .find() получает только строку. Если вы хотите найти целое число
в строке, придется передать .find() число в виде строки. В противном случае
Python выдаст ошибку TypeError:
>>> "My number is 555-555-5555".find(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: must be str, not int
>>> "My number is 555-555-5555".find("5")
13
Иногда требуется найти все вхождения конкретной подстроки и заменить их
другой строкой. Так как .find() возвращает индекс первого вхождения подстроки, этот метод не так просто использовать для замены. Но у строк есть метод
.replace(), который заменяет каждое вхождение подстроки другой строкой.
Как и в случае с .find(), вызов .replace() следует после имени переменной или
строкового литерала. Однако на этот раз в круглых скобках .replace() необходимо указать две строки, разделив их запятой. Первая строка содержит искомую
подстроку, а вторая — текст, которым заменяется каждое вхождение подстроки.
Например, следующий код заменяет каждое вхождение "the truth" в строке
"I'm telling you the truth; nothing but the truth" строкой "lies":
>>> my_story = "I'm telling you the truth; nothing but the truth!"
>>> my_story.replace("the truth", "lies")
"I'm telling you lies; nothing but lies!"
Так как строки являются неизменяемыми объектами, .replace() не изменяет
my_story.
Если немедленно ввести my_story в интерактивном окне после выполнения
приведенного примера, то появится исходная строка в неизменном виде:
>>> my_story
"I'm telling you the truth; nothing but the truth!"
84 Глава 4 Строки и строковые методы
Чтобы изменить значение my_story, необходимо присвоить переменной новое
значение, возвращаемое .replace():
>>> my_story = my_story.replace("the truth", "lies")
>>> my_story
"I'm telling you lies; nothing but lies!"
Метод .replace() заменяет каждое вхождение подстроки новым текстом.
Чтобы заменить несколько разных подстрок в строке, вызовите .replace()
несколько раз:
>>> text = "some of the stuff"
>>> new_text = text.replace("some of", "all")
>>> new_text = new_text.replace("stuff", "things")
>>> new_text
'all the things'
Мы поэкспериментируем с .replace() в следующем разделе.
Упражнения
1. В одной строке кода выведите результат вызова .find() для поиска подстроки "a" в строке "AAA". Результат должен быть равен –1.
2. Замените каждое вхождение символа "s" на "x" в строке "Somebody said
something to Samantha."
3. Напишите программу, которая запрашивает у пользователя ввод функцией input() и выводит результат вызова .find() для поиска конкретной
буквы во введенном тексте.
4.9. ЗАДАЧА: ПРЕОБРАЗОВАНИЕ ТЕКСТА
Напишите программу translate.py, которая предлагает пользователю ввести
текст со следующим приглашением:
Enter some text:
Используйте .replace(), чтобы зашифровать текст, введенный пользователем.
Для этого выполните следующие преобразования с буквами нижнего регистра.
zz
Буква a преобразуется в 4.
zz
Буква b преобразуется в 8.
zz
Буква e преобразуется в 3.
4.10. Итоги и дополнительные ресурсы 85
zz
Буква l преобразуется в 1.
zz
Буква o преобразуется в 0.
zz
Буква s преобразуется в 5.
zz
Буква t преобразуется в 7.
Затем ваша программа должна вывести полученную строку. Ниже приведен
пример выполнения программы:
Enter some text: I like to eat eggs and spam.
I 1ik3 70 347 3gg5 4nd 5p4m.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
4.10. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе мы рассказали о достоинствах и недостатках строковых объектов
Python. Вы узнали, как обращаться к разным символам строки по индексам
и с помощью срезов и как определить длину строки при помощи функции
len().
Строки также содержат различные методы:.upper() и .lower() преобразуют
все символы строки к верхнему и нижнему регистру соответственно,.rstrip(),
.lstrip() и .strip() удаляют пропуски из строк, а .startswith() и .endswith()
проверяют, начинается или завершается строка заданной подстрокой.
Также вы научились сохранять в строке текст, введенный пользователем, при
помощи функции input() и преобразовывать этот ввод в число, используя int()
и float(). Для преобразования чисел и других объектов в строки применяется
функция str().
Наконец, вы научились использовать методы .find() и .replace() для поиска
и замены подстрок новыми строками.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-strings
86 Глава 4 Строки и строковые методы
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
zz
«Python String Formatting Best Practices» (https://realpython.com/pythonstring-formatting/)
zz
«Splitting, Concatenating, and Joining Strings in Python» (https://realpython.
com/python-string-split-concatenate-join/)
ГЛАВА 5
Числа и математические
вычисления
Чтобы хорошо программировать, не обязательно быть гением в математике.
Откровенно говоря, лишь немногим программистам нужно иметь знания, выходящие за курс базовой алгебры.
Конечно, уровень необходимых математических знаний задает приложение,
над которым вы работаете. В общем же случае уровень математической подготовки, необходимый для программирования, ниже того, что можно было
бы ожидать.
И хотя математика и программирование связаны не так тесно, как считают
некоторые, числа являются неотъемлемой частью любого языка программирования, и Python не исключение.
В этой главе вы научитесь:
zz
создавать целые числа и числа с плавающей точкой;
zz
округлять числа до заданной точности;
zz
форматировать и выводить числа в строках.
Итак, за дело!
5.1. ЦЕЛЫЕ ЧИСЛА И ЧИСЛА
С ПЛАВАЮЩЕЙ ТОЧКОЙ
В Python поддерживаются три встроенных числовых типа данных: целые
числа, числа с плавающей точкой и комплексные числа. В этом разделе рассматриваются целые числа и числа с плавающей точкой — два наиболее часто
используемых типа. О комплексных числах я расскажу в разделе 5.7.
88 Глава 5 Числа и математические вычисления
Целые числа
Целое число не имеет дробной части. Например, 1 — целое число, а 1.0 — нет.
Целочисленный тип данных обозначается именем int, в чем нетрудно убедиться
при помощи функции type():
>>> type(1)
<class 'int'>
Чтобы создать целое число, достаточно его ввести. Например, следующий
фрагмент присваивает целое число 25 переменной num:
>>> num = 25
Когда вы создаете целое число подобным образом, значение 25 называется
целочисленным литералом. Значение целочисленного литерала задается непосредственно в коде.
Из главы 4 вы узнали, как преобразовать строковое представление целого числа в число при помощи функции int(). Например, следующий фрагмент кода
преобразует строку "25" в целое число 25:
>>> int("25")
25
int("25") не является целочисленным литералом, потому что целое число
создается на основе строки.
Когда вы записываете большие числа вручную, цифры обычно объединяются
в группы по три, разделяемые запятыми или точками1. Число 1,000,000 читается
намного проще, чем 1000000.
В языке Python запятые в целочисленных литералах запрещены, но вы можете
использовать символы подчеркивания (_). Оба следующих варианта являются
допустимыми способами представления одного миллиона в виде целочисленного литерала:
>>> 1000000
1000000
>>> 1_000_000
1000000
1
Так принято записывать числа в англоязычных странах. — Примеч. ред.
5.1. Целые числа и числа с плавающей точкой 89
Максимальное значение целого числа не ограничено, что удивительно, если
учесть, что компьютеры имеют лимитированный объем памяти. Попробуйте
ввести самое большое число, которое сможете придумать, в интерактивном
окне IDLE. Python справится с ним без проблем!
Числа с плавающей точкой
Число с плавающей точкой имеет дробную часть: 1.0 — число с плавающей точкой, как и –2.75. Тип данных с плавающей точкой обозначается именем float:
>>> type(1.0)
<class 'float'>
Числа с плавающей точкой, как и целые числа, могут создаваться в виде литералов с плавающей точкой или преобразованием строки вызовом float():
>>> float("1.25")
1.25
Существуют три способа представления литералов с плавающей точкой. Каждый из следующих вариантов записи создает литерал с плавающей точкой со
значением один миллион:
>>> 1000000.0
1000000.0
>>> 1_000_000.0
1000000.0
>>> 1e6
1000000.0
Первые два варианта похожи на два способа создания целочисленных литералов.
Третий вариант использует экспоненциальную запись для создания литерала
с плавающей точкой.
ПРИМЕЧАНИЕ
Возможно, экспоненциальную запись вы уже встречали в калькуляторах — она
используется для представления слишком больших чисел, не помещающихся
на экране.
Чтобы записать литерал с плавающей точкой в экспоненциальной форме,
введите число, за которым следует буква «e» и другое число. Python берет
число слева от «e» и умножает его на 10 в степени справа от «e». Таким образом, запись 1e6 эквивалентна 1 × 10⁶.
90 Глава 5 Числа и математические вычисления
Python также использует экспоненциальную запись для вывода больших чисел
с плавающей точкой:
>>> 200000000000000000.0
2e+17
Число 200000000000000000.0 выводится в виде 2e+17. Знак + указывает, что
показатель степени 17 является положительным числом. Также показатель
степени может быть отрицательным:
>>> 1e-4
0.0001
Литерал 1e-4 интерпретируется как 10 в степени –4, то есть 1/10000, или 0.0001.
Значение чисел с плавающей точкой, в отличие от целых чисел, ограничено.
Максимальное значение зависит от вашей системы, но число вроде 2e400 выходит за пределы возможностей большинства машин. Это выражение равно
2 × 10400, что намного больше общего количества атомов во Вселенной!
При достижении максимального значения числа с плавающей точкой Python
возвращает специальное значение, inf:
>>> 2e400
inf
inf (от infinity — бесконечность) означает лишь то, что число, которое вы по­
пытались создать, превышает максимальное значение числа с плавающей точкой,
допустимое для вашего компьютера. При этом inf имеет тип float:
>>> n = 2e400
>>> n
inf
>>> type(n)
<class 'float'>
В Python также используется значение -inf (отрицательная бесконечность).
Оно представляет отрицательное число с плавающей точкой, которое
меньше наименьшего числа с плавающей точкой, допустимого на вашем
компьютере:
>>> -2e400
-inf
5.2. Арифметические операторы и выражения 91
Скорее всего, вы будете нечасто сталкиваться с inf и -inf при программировании — если, конечно, вам не приходится регулярно работать с очень большими
числами.
Упражнения
1. Напишите программу, которая создает две переменные, num1 и num2. Присвойте каждой целочисленный литерал 25000000 — с разделителями-подчеркиваниями и без. Выведите num1 и num2 в разных строках.
2. Напишите программу, которая присваивает литерал с плавающей точкой
175000.0 переменной num с использованием экспоненциальной записи,
после чего выводит num в интерактивном окне.
3. В интерактивном окне IDLE попробуйте найти наименьший показатель
степени N, для которого 2e<N> (где <N> замените вашим числом) возвращает inf.
5.2. АРИФМЕТИЧЕСКИЕ ОПЕРАТОРЫ
И ВЫРАЖЕНИЯ
Сейчас вы научитесь выполнять в Python базовые арифметические операции
с числами (сложение, вычитание, умножение и деление). Заодно вы узнаете
некоторые соглашения для записи математических выражений в коде.
Сложение
Сложение выполняется оператором +:
>>> 1 + 2
3
Два числа по обе стороны от оператора + называются операндами. В приведенном примере оба операнда являются целыми числами, но операнды не
обязательно должны иметь одинаковый тип.
int можно сложить с float без каких-либо проблем:
>>> 1.0 + 2
3.0
92 Глава 5 Числа и математические вычисления
Обратите внимание: результат 1.0 + 2 равен 3.0, и он относится к типу float.
Каждый раз, когда float складывается с целым числом, результат также имеет
тип float. При сложении двух целых чисел вы всегда получаете int.
ПРИМЕЧАНИЕ
PEP 8 рекомендует отделять оба операнда от оператора пробелом.
Python прекрасно справится с вычислением 1+1, но формат 1 + 1 считается
предпочтительным, потому что он проще читается. Это правило применяется
ко всем операторам в этом разделе.
Вычитание
Чтобы вычесть одно число из другого, поставьте между ними оператор -:
>>> 1 - 1
0
>>> 5.0 - 3
2.0
Как и при сложении целых чисел, вычитание двух целых чисел всегда дает
результат типа int. Если же один из операндов имеет тип float, то результат
также относится к float.
Оператор - также используется для обозначения отрицательных чисел:
>>> -3
-3
Отрицательное число можно вычесть из другого числа, но, как видно из примера ниже, такие операции способны запутать читателя:
>>> 1 - -3
4
>>> 1 --3
4
>>> 1- -3
4
>>> 1--3
4
Из этих четырех примеров первый лучше всего соответствует рекомендациям
PEP 8. При этом вы можете заключить -3 в круглые скобки, чтобы еще нагляднее
показать, что второй знак - относится к 3:
5.2. Арифметические операторы и выражения 93
>>> 1 - (-3)
4
Использование круглых скобок — хорошая практика, потому что с ними код
более явно выражает намерения программиста. Компьютеры выполняют код,
а люди его читают. Все, что делается для упрощения чтения и понимания вашего
кода, можно только приветствовать.
Умножение
Для умножения двух чисел используется оператор *:
>>> 3 * 3
9
>>> 2 * 8.0
16.0
Тип числа, полученного в результате умножения, подчиняется тем же правилам,
что при сложении и вычитании. При умножении двух целых чисел вы получите результат int, а при умножении любого числа на float — результат float.
Деление
Оператор / предназначен для деления двух чисел:
>>> 9 / 3
3.0
>>> 5.0 / 2
2.5
В отличие от сложения, вычитания и умножения, деление оператором / всегда
возвращает float. Если вы хотите гарантированно получить после деления двух
чисел целое число, используйте int() для преобразования результата:
>>> int(9 / 3)
3
Помните, что функция int() отбрасывает дробную часть числа:
>>> int(5.0 / 2)
2
5.0 / 2 возвращает число с плавающей точкой 2.5, а int(2.5) возвращает целое
число 2 без дробной части .5.
94 Глава 5 Числа и математические вычисления
Целочисленное деление
Если запись int(5.0 / 2) кажется слишком навороченной, применяйте второй
оператор деления, предоставляемый Python, — оператор целочисленного деления:
>>> 9 // 3
3
>>> 5.0 // 2
2.0
>>> -3 // 2
-2
Оператор // сначала делит число слева на число справа, после чего округляет
результат в меньшую сторону до целого числа. Правда, если одно из чисел отрицательное, вы можете получить не тот результат, на который рассчитывали.
Например, -3 // 2 возвращает -2. Сначала -3 делится на 2, результат равен -1.5.
Затем -1.5 округляется в меньшую сторону до -2. С другой стороны, 3 // 2 возвращает 1, потому что оба числа положительные.
Приведенный пример также показывает, что если один из операндов имеет тип
float, то // возвращает число с плавающей точкой. Таким образом, 9 // 3 возвращает целое число 3, а 5.0 // 2 возвращает 2.0 в формате float.
Посмотрим, что произойдет при попытке разделить число на 0:
>>> 1 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Python выдает ошибку ZeroDivisionError, сообщая вам о том, что вы попытались
нарушить одно из основополагающих правил Вселенной — нельзя делить на ноль!
Возведение в степень
Для возведения числа в степень используется оператор **:
>>> 2 ** 2
4
>>> 2 ** 3
8
>>> 2 ** 4
16
5.2. Арифметические операторы и выражения 95
Показатель степени не обязательно должен быть целым числом. Он также может
относиться к типу с плавающей точкой:
>>> 3 ** 1.5
5.196152422706632
>>> 9 ** 0.5
3.0
Возведение числа в степень 0.5 эквивалентно извлечению квадратного корня,
но следует заметить, что, хотя квадратный корень из 9 является целым числом,
Python возвращает число с плавающей точкой 3.0.
Для положительных операндов оператор ** возвращает int, если оба они являются целыми числами, или float — если один из операндов является числом
с плавающей точкой.
Числа также можно возводить в отрицательную степень:
>>> 2 ** -1
0.5
>>> 2 ** -2
0.25
Возведение числа в отрицательную степень равносильно делению 1 на число,
возведенное в положительную степень. Таким образом, выражение 2 ** -1 эквивалентно 1 / (2 ** 1), то есть 1 / 2, или 0.5. Аналогичным образом выражение
2 ** -2 эквивалентно 1 / (2 ** 2), то есть 1 / 4, или 0.25.
Оператор вычисления остатка
Оператор % возвращает остаток от целочисленного деления левого операнда
на правый операнд:
>>> 5 % 3
2
>>> 20 % 7
6
>>> 16 % 8
0
Число 5 делится на 3 с остатком 2, поэтому 5 % 3 дает результат 2. Аналогичным
образом 20 делится на 7 с остатком 6. В последнем примере 16 делится на 8 без
остатка, поэтому 16 % 8 дает результат 0. Каждый раз, когда число в левой части
от % нацело делится на число в правой части, результат равен 0.
96 Глава 5 Числа и математические вычисления
Одно из стандартных применений % — определение того, делится ли одно число
на другое без остатка. Например, число n — четное в том и только в том случае,
если n % 2 равно 0. Как вы думаете, что возвращает 1 % 0? Попробуем:
>>> 1 % 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Результат выглядит логично: 1 % 0 вычисляет остаток от деления 1 на 0. Но 1
на 0 делить нельзя, поэтому Python выдает ошибку ZeroDivisionError.
ПРИМЕЧАНИЕ
При работе в интерактивном окне IDLE такие ошибки, как ZeroDivisionError,
не создают особых проблем. На экране появляется сообщение об ошибке,
а затем открывается новое приглашение, и вы можете продолжить работу.
Но когда Python обнаруживает ошибку в процессе работы программы, выполнение прерывается — другими словами, в вашей программе происходит
непредвиденный сбой. В главе 8 я покажу, как обрабатывать ошибки, чтобы
избежать преждевременного завершения программ.
При использовании оператора % с отрицательными числами ситуация немного
усложняется:
>>> 5 % -3
-1
>>> -5 % 3
1
>>> -5 % -3
-2
Хотя на первый взгляд эти результаты выглядят странно, они объясняются
четкой логикой выполнения оператора в Python. Для вычисления остатка r от
деления числа x на число y в Python используется формула r = x - (y * (x // y)).
Например, чтобы определить результат 5 % -3 , Python сначала вычисляет
(5 // -3). Так как результат 5 / -3 равен приблизительно -1.67, это означает,
что 5 // -3 дает -2. Теперь Python умножает это значение на -3, получается 6.
Наконец, Python вычитает 6 из 5 и получает -1.
5.2. Арифметические операторы и выражения 97
Арифметические выражения
Операторы могут объединяться для формирования сложных выражений. Выражение — это совокупность чисел, операторов и круглых скобок, которое
Python может вычислить для получения значения.
Примеры арифметических выражений:
>>> 2*3 - 1
5
>>> 4/2 + 2**3
10.0
>>> -1 + (-3*2 + 4)
-3
При вычислении выражений действуют те же правила, что и в классической
арифметике. Вероятно, вы изучали эти правила в школьном курсе математики,
где они назывались порядком действий.
Операторы *, /, // и % имеют одинаковый приоритет в выражениях, и каждый
из них обладает более высоким приоритетом, чем операторы + и -. Вот почему
выражение 2*3 - 1 возвращает 5, а не 4. Сначала вычисляется результат 2*3,
потому что * обладает более высоким приоритетом, чем оператор -.
Возможно, вы заметили, что в выражениях этого примера не соблюдается
правило о включении пробелов по обе стороны от оператора. В PEP 8 об этом
говорится следующее:
«Если используются операторы с разными приоритетами, рассмотрите
возможность включения пробелов рядом с операторами, обладающими
самым низким приоритетом(-ами). Руководствуйтесь здравым смыслом;
тем не менее никогда не используйте более одного пробела и всегда
используйте одинаковое количество пробелов с двух сторон бинарного
оператора».
PEP 8, «Other Recommendations» (https://pep8.org/)
Еще одна полезная практика — использование круглых скобок для обозначения
порядка выполнения операций, даже если эти скобки не являются необходимыми. Например, выражение (2 * 3) - 1 более понятно, чем 2*3 - 1.
98 Глава 5 Числа и математические вычисления
5.3. ЗАДАЧА: ВЫПОЛНЕНИЕ ВЫЧИСЛЕНИЙ
С ПОЛЬЗОВАТЕЛЬСКИМ ВВОДОМ
Напишите программу exponent.py, которая получает от пользователя два числа
и выводит результат возведения первого числа в степень, заданную вторым числом.
Результат выполнения программы должен выглядеть примерно так (с вводом
от пользователя):
Enter a base: 1.2
Enter an exponent: 3
1.2 to the power of 3 = 1.7279999999999998
Не забывайте
1. Прежде чем делать что-либо с пользовательским вводом, необходимо
присвоить результаты обоих вызовов input() новым переменным.
2. Функция input() возвращает строку, поэтому введенные значения необходимо преобразовать в числа для выполнения арифметических операций.
3. Для вывода результата можно задействовать f-строку.
4. Предполагается, что пользователь вводит числа.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
5.4. КОГДА PYTHON ГОВОРИТ НЕПРАВДУ
Как вы думаете, сколько будет 0.1 + 0.2? Получится 0.3, верно? Давайте посмотрим, что об этом думает Python. Попробуйте ввести следующий код в интерактивном окне:
>>> 0.1 + 0.2
0.30000000000000004
Выглядит… почти правильно. Что же происходит? Может, создатели Python
ошиблись?
Нет, это не ошибка! Это погрешность представления числа с плавающей точкой,
которая не имеет никакого отношения к Python. Она связана с особенностями
хранения чисел с плавающей точкой в памяти компьютера.
5.4. Когда Python говорит неправду 99
Число 0.1 может быть представлено в виде дроби 1/10. И число 0.1, и дробь
1/10 — десятичные представления, то есть представления в десятичной системе
счисления. Однако компьютеры хранят вещественные числа в двоичном представлении.
В двоичном представлении с десятичным числом 0.1 происходит нечто неожиданное. Дробь 1/3 не имеет конечного десятичного представления, то есть
1/3 = 0.3333... с бесконечным количеством троек в дробной части. То же самое
происходит с дробью 1/10 в двоичном представлении.
Двоичное представление 1/10 выглядит как бесконечная дробь:
0.00011001100110011001100110011...
Объем памяти компьютеров лимитирован, так что число 0.1 приходится хранить в приближенном виде, а не как точное значение. Приближенное значение
в памяти чуть больше точного и выглядит примерно так:
0.1000000000000000055511151231257827021181583404541015625
Но когда вы приказываете вывести значение 0.1, Python выводит 0.1, а не это
приближенное значение:
>>> 0.1
0.1
Не стоит думать, что Python просто отсекает цифры в двоичном представлении
0.1. На самом деле происходит нечто более сложное.
Так как 0.1 в двоичной записи является именно приближенной записью, нельзя
исключать, что несколько десятичных чисел будут иметь одинаковое двоичное
представление.
Например, как 0.1, так и 0.10000000000000001 имеют одинаковое двоичное
представление. Python выводит самое короткое десятичное число с таким
представлением.
Это объясняет, почему в первом примере этого раздела результат 0.1 + 0.2 не
равен 0.3. Python суммирует двоичные представления 0.1 и 0.2, и вы получаете
число, которое не является двоичным приближением 0.3.
У вас от этого, наверное, голова идет кругом, но не огорчайтесь! Если только вы
не пишете программы для финансовых или научных вычислений, вам не стоит
беспокоиться о погрешности вычислений с плавающей точкой.
100 Глава 5 Числа и математические вычисления
5.5. МАТЕМАТИЧЕСКИЕ ФУНКЦИИ
И ЧИСЛОВЫЕ МЕТОДЫ
Python содержит ряд встроенных функций для работы с числами. В этом разделе рассматриваются три наиболее распространенные функции:
1) round() для округления чисел до заданной точности;
2) abs() для получения абсолютного значения (модуля) числа;
3) pow() для возведения числа в степень.
Также вы узнаете о методе, который применяют к числам с плавающей точкой
для проверки того, содержат ли они целое значение.
Функция round()
Функцию round() применяют для округления числа до ближайшего целого:
>>> round(2.3)
2
>>> round(2.7)
3
Если дробная часть числа равна .5, функция round() ведет себя довольно неожиданно:
>>> round(2.5)
2
>>> round(3.5)
4
Число 2.5 округляется до 2, а 3.5 округляется до 4. Обычно мы ожидаем, что
число с дробной частью .5 округляется в большую сторону, поэтому разберемся
более детально, что здесь происходит.
Python 3 округляет числа в соответствии со стратегией, которая называется
округлением нейтральных чисел до ближайшего четного. Нейтральным называется любое число, последняя цифра которого равна 5. Так, 2.5 и 3.1415
являются нейтральными, а 1.37 — нет.
При округлении нейтральных чисел до ближайшего четного сначала проверяется предпоследняя цифра. Если она четная, то число округляется в меньшую
сторону, а если нечетная — в большую. Именно поэтому 2.5 округляется до 2,
а 3.5 — до 4.
5.5. Математические функции и числовые методы 101
ПРИМЕЧАНИЕ
Применение стратегии округления нейтральных чисел до ближайшего четного
рекомендовано IEEE (Institute of Electrical and Electronics Engineers — Институт
инженеров по электротехнике и электронике), потому что она помогает ограничить последствия округления для операций со многими числами.
IEEE поддерживает стандарт IEEE 754 для работы с числами с плавающей
точкой на компьютерах. Стандарт был опубликован в 1985 году, в настоящее
время он широко используется производителями оборудования.
Число также можно округлить до заданного количества знаков в дробной части,
для чего round() следует передать второй аргумент:
>>> round(3.14159, 3)
3.142
>>> round(2.71828, 2)
2.72
Число 3.14159 округляется до трех знаков в дробной части, и вы получаете
­результат 3.142, а число 2.71828 округляется до двух знаков, и вы получаете 2.72.
Второй аргумент round() должен быть целым числом. В противном случае
Python выдает ошибку TypeError:
>>> round(2.65, 1.4)
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
round(2.65, 1.4)
TypeError: 'float' object cannot be interpreted as an integer
Иногда round() дает неточный результат:
>>> # Expected value: 2.68
>>> round(2.675, 2)
2.67
Число 2.675 является нейтральным, потому что оно расположено ровно посередине между числами 2.67 и 2.68. Так как Python округляет нейтральные
числа до ближайшего четного, можно ожидать, что round(2.675, 2) вернет 2.68,
однако мы получаем 2.67.
Эта ошибка является результатом погрешности представления чисел с плавающей точкой, а не некорректной реализацией round().
102 Глава 5 Числа и математические вычисления
Работа с числами с плавающей точкой создает немало затруднений, но они
характерны не только для Python. Аналогичные проблемы возникают во всех
языках, поддерживающих стандарт чисел с плавающей точкой IEEE, включая
C/C++, Java и JavaScript.
Впрочем, в большинстве случаев незначительной погрешностью, встречающейся при работе с плавающей точкой, можно пренебречь, и результаты round()
вполне пригодны для использования.
Функция abs()
Абсолютное значение (модуль) числа n равно n, если n положительно, или –n,
если n отрицательно. Например, абсолютное значение 3 равно 3, а абсолютное
значение –5 равно 5.
Чтобы получить абсолютное значение числа в Python, используйте функцию
abs():
>>> abs(3)
3
>>> abs(-5.0)
5.0
Функция abs() всегда возвращает положительное число с таким же типом,
как у его аргумента. Другими словами, абсолютное значение целого числа
всегда является положительным целым числом, а абсолютное значение числа
с плавающей точкой всегда является положительным числом с плавающей
точкой.
Функция pow()
Из раздела 5.2 вы узнали, как возвести число в степень оператором **. Для
получения того же результата можно воспользоваться и функцией pow().
Функция pow() получает два аргумента. Первый определяет основание (число,
возводимое в степень), а второй — показатель степени.
Например, следующая команда использует pow() для возведения 2 в степень 3:
>>> pow(2, 3)
8
5.5. Математические функции и числовые методы 103
Как и в случае с оператором **, показатель степени в pow() может быть отрицательным:
>>> pow(2, -2)
0.25
Итак, чем же pow() отличается от **?
Функция pow() получает необязательный третий аргумент, который вычисляет
результат возведения первого числа в степень второго числа, после чего вычисляет остаток от деления на третье число. Иначе говоря, вызов pow(x, y, z)
эквивалентен (x ** y) % z.
В следующем примере x = 2, y = 3 и z = 2:
>>> pow(2, 3, 2)
0
Сначала 2 возводится в степень 3 и получается 8. Затем вычисляется выражение
8 % 2, результат которого равен 0, потому что 8 делится на 2 без остатка.
Проверка числа с плавающей точкой
на целочисленность
В главе 3 я рассказывал о таких строковых методах, как .lower(), .upper()
и .find(). Для целых чисел и чисел с плавающей точкой тоже существуют
методы.
Числовые методы используются не так часто, но среди них есть один, который может вам пригодиться. У чисел с плавающей точкой имеется метод
.is_integer(), который возвращает True, если число является целым, то есть
не имеет дробной части. В противном случае возвращается False:
>>> num = 2.5
>>> num.is_integer()
False
>>> num = 2.0
>>> num.is_integer()
True
Метод .is_integer() может оказаться полезным при проверке пользовательского ввода. Например, если вы пишете приложение для заказа пиццы через
интернет, нужно проверить, является ли количество заказанных порций целым
104 Глава 5 Числа и математические вычисления
числом или нет. О том, как выполнять подобные проверки, мы расскажем
в главе 8.
Упражнения
1. Напишите программу, которая предлагает пользователю ввести число,
а затем выводит его округленным до двух цифр. Выполнение вашей программы должно выглядеть примерно так:
Enter a number: 5.432
5.432 rounded to 2 decimal places is 5.43
2. Напишите программу, которая предлагает пользователю ввести число,
а затем выводит абсолютное значение этого числа. Выполнение вашей
программы должно выглядеть примерно так:
Enter a number: -10
The absolute value of -10 is 10.0
3. Напишите программу, которая предлагает пользователю ввести два числа, используя input() дважды, а затем сообщает, является ли разность
этих двух чисел целым числом. Выполнение вашей программы должно
выглядеть примерно так:
Enter a number: 1.5
Enter another number: .5
The difference between 1.5 and .5 is an integer? True!
4. Если пользователь вводит два числа, разность которых не является целым
числом, вывод должен выглядеть примерно так:
Enter a number: 1.5
Enter another number: 1.0
The difference between 1.5 and 1.0 is an integer? False!
5.6. ОФОРМЛЕНИЕ ЧИСЕЛ ПРИ ВЫВОДЕ
Чтобы вывести число для пользователя, необходимо вставить его в строку.
В главе 3 было показано, как это делать с f-строками, для чего переменная, содержащая число, заключается в фигурные скобки:
>>> n = 7.125
>>> f"The value of n is {n}"
'The value of n is 7.125'
Фигурные скобки поддерживают простой язык форматирования, позволяющий изменить внешний вид отформатированной строки. Например, чтобы
5.6. Оформление чисел при выводе 105
отформатировать значение n в приведенном примере до двух знаков, замените
содержимое фигурных скобок в f-строке на {n:.2f}:
>>> n = 7.125
>>> f"The value of n is {n:.2f}"
'The value of n is 7.12'
Двоеточие (:) после переменной n указывает, что все последующие символы
являются частью спецификации формата. В данном случае спецификация
формата имеет вид .2f.
Часть .2 в .2f округляет число до двух знаков в дробной части, а f приказывает
Python вывести n в виде числа с фиксированной точкой. Это означает, что число
выводится ровно с двумя знаками в дробной части, даже если в исходном числе
знаков было меньше.
Если n = 7.125, то результат {n:.2f} равен 7.12. Как и в случае с round(), Python
округляет нейтральные значения до четных даже при форматировании чисел
внутри строк. Таким образом, если заменить n = 7.125 на n = 7.126, то с {n:.2f}
будет выведен результат 7.13:
>>> n = 7.126
>>> f"The value of n is {n:.2f}"
'The value of n is 7.13'
Чтобы округлить выводимое число до одного знака, замените .2 на .1:
>>> n = 7.126
>>> f"The value of n is {n:.1f}"
'The value of n is 7.1'
Когда число форматируется как число с фиксированной точкой, оно всегда
выводится с заданным количеством знаков в дробной части:
>>> n = 1
>>> f"The value of n is {n:.2f}"
'The value of n is 1.00'
>>> f"The value of n is {n:.3f}"
'The value of n is 1.000'
Если мы хотим отформатировать большое число так, чтобы разряды в нем
разделялись запятыми, то нужно добавить запятую внутрь фигурных скобок:
>>> n = 1234567890
>>> f"The value of n is {n:,}"
'The value of n is 1,234,567,890'
106 Глава 5 Числа и математические вычисления
Чтобы округлить число до заданного количества знаков, а также сгруппировать
разряды, включите , перед . в спецификацию:
>>> n = 1234.56
>>> f"The value of n is {n:,.2f}"
'The value of n is 1,234.56'
Спецификатор ,.2f хорошо подходит для вывода денежных сумм:
>>> balance = 2000.0
>>> spent = 256.35
>>> remaining = balance - spent
>>> f"After spending ${spent:.2f}, I was left with ${remaining:,.2f}"
'After spending $256.35, I was left with $1,743.65'
Другой полезный спецификатор % используется для вывода значений в процентах. Число умножается на 100 и выводится в формате с фиксированной
точкой, а после него выводится знак процента.
Спецификатор % всегда следует располагать в конце спецификации формата,
кроме того, его нельзя использовать совместно с режимом f. Например, .1%
выводит число в процентах с одним знаком в дробной части:
>>> ratio = 0.9
>>> f"Over {ratio:.1%} of Pythonistas say 'Real Python rocks!'"
"Over 90.0% of Pythonistas say 'Real Python rocks!'"
>>> # Display percentage with 2 decimal places
>>> f"Over {ratio:.2%} of Pythonistas say 'Real Python rocks!'"
"Over 90.00% of Pythonistas say 'Real Python rocks!'"
Мини-язык форматирования — очень мощный и многообразный. Здесь мы
познакомили вас только с основными возможностями. За дополнительной
информацией обращайтесь к официальной документации (https://docs.python.
org/3/library/string.html#format-string-syntax_).
Упражнения
1. Выведите результат вычисления 3 ** .125 в формате с фиксированной
точкой с тремя знаками в дробной части.
2. Выведите число 150000 как денежную сумму с разделением групп разрядов запятыми. Денежные суммы должны выводиться с двумя знаками
в дробной части.
5.7. Комплексные числа 107
3. Выведите результат 2 / 10 в процентах без дробной части (то есть должно
выводиться значение 20%).
5.7. КОМПЛЕКСНЫЕ ЧИСЛА
Python — один из немногих языков программирования, имеющий встроенную
поддержку комплексных чисел. Хотя комплексные числа не так часто встречаются вне сферы научных вычислений и компьютерной графики, поддержка
их в Python — одна из сильных сторон языка.
ПРИМЕЧАНИЕ
Если тема работы с комплексными числами в Python вас не интересует, смело
пропускайте этот раздел. В других частях книги эта информация не используется.
Из курсов алгебры и начал анализа вы, вероятно, помните, что комплексное
число состоит из двух частей: вещественной и мнимой.
Чтобы создать комплексное число в Python, просто запишите его вещественную
часть, затем поставьте знак + и добавьте мнимую часть с суффиксом j:
>>> n = 1 + 2j
Если проверить значение n, вы увидите, что Python заключает число в круглые
скобки:
>>> n
(1+2j)
Это соглашение помогает не спутать выведенное значение со строкой или математическим выражением.
Комплексные числа содержат два свойства, .real и .imag, которые возвращают
вещественную и мнимую составляющие числа соответственно:
>>> n.real
1.0
>>> n.imag
2.0
Заметим, что Python возвращает вещественную и мнимую составляющие в виде
типа float, даже если они изначально задавались как целые числа.
108 Глава 5 Числа и математические вычисления
Для комплексных чисел также есть свой метод .conjugate(), который возвращает комплексно-сопряженное значение для числа:
>>> n.conjugate()
(1-2j)
Для любого комплексного числа сопряженным называется комплексное число
с такой же вещественной частью и такой же мнимой частью (по абсолютной
величине), но с обратным знаком. В данном примере сопряженным значением
для 1 + 2j является 1 - 2j.
ПРИМЕЧАНИЕ
После свойств .real и .imag не нужно ставить круглые скобки (в отличие от
.conjugate()).
Метод .conjugate() представляет собой функцию, которая выполняет действие
с комплексным числом, тогда как .real и .imag никаких действий не выполняют — они просто возвращают некоторую информацию о числе.
Различия между методами и свойствами являются важным аспектом объектно-ориентированного программирования. Вы узнаете о них в главе 10.
Кроме оператора целочисленного деления // все арифметические операторы,
работающие с числами с плавающей точкой и целыми числами, также работают
с комплексными числами.
Эта книга — не учебник по математике, поэтому мы не будем обсуждать механику вычислений с комплексными числами. Но мы познакомим вас с примерами
использования комплексных чисел с арифметическими операторами.
>>> a = 1 + 2j
>>> b = 3 - 4j
>>> a + b
(4-2j)
>>> a - b
(-2+6j)
>>> a * b
(11+2j)
>>> a ** b
(932.1391946432212+95.9465336603415j)
>>> a / b
5.8. Итоги и дополнительные ресурсы 109
(-0.2+0.4j)
>>> a // b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't take floor of complex number.
Интересно (хотя и неудивительно с математической точки зрения), что объекты
int и float содержат свойства .real и .imag, как и метод .conjugate():
>>> x = 42
>>> x.real
42
>>> x.imag
0
>>> x.conjugate()
42
>>> y = 3.14
>>> y.real
3.14
>>> y.imag
0.0
>>> y.conjugate()
3.14
Для чисел с плавающей точкой и целых чисел .real и .conjugate() всегда возвращают само число, а .imag всегда возвращает 0. Однако следует заметить, что
n.real и n.imag возвращают целое число, если n является целым числом, и число
с плавающей точкой, если n является числом с плавающей точкой.
После знакомства с основами комплексных вычислений может возникнуть вопрос: придется ли вам когда-нибудь использовать их? Если вы изучаете Python
для веб-программирования, обработки данных или программирования общего
назначения, скажу честно: скорее всего, никогда не придется.
С другой стороны, без комплексных чисел не обойтись в научных вычислениях
и компьютерной графике. Если вы подвизаетесь в этих областях, встроенная
поддержка комплексных чисел в Python может вам пригодиться.
5.8. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе мы рассказали, как работать с числами в Python. Вы узнали, что
существуют два основных типа чисел — целые и числа с плавающей точкой,
а еще в Python реализована встроенная поддержка комплексных чисел.
110 Глава 5 Числа и математические вычисления
Вы научились выполнять основные математические операции с числами
с использованием операторов +, -, *, / и %. Также вы освоили арифметические
выражения и приемы форматирования арифметических выражений в коде
программы, определенные в PEP 8.
Далее мы рассказали о числах с плавающей точкой и их представлении, которое не всегда бывает точным на 100%. Данное ограничение действует не
только в Python. Это особенность современных вычислительных технологий,
обусловленная способом представления чисел с плавающей точкой в памяти
компьютера.
Затем вы узнали, как округлять числа до заданной точности функцией round():
она округляет нейтральные числа до ближайшего четного, и такой способ округ­
ления несколько отличается от того, что мы изучали в школе. Также я показал
некоторые способы форматирования чисел для вывода.
В завершающей части главы была описана встроенная поддержка комплексных
чисел в Python.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-numbers
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
zz
«Basic Data Types in Python» (https://realpython.com/python-data-types/)
zz
«How to Round Numbers in Python» (https://realpython.com/pythonrounding/)
ГЛАВА 6
Функции и циклы
Функции являются строительными блоками практически любой программы
Python. Обычно именно в них происходят основные события!
Вы уже видели, как использовать некоторые функции, например print() ,
len() и round(). Все эти функции называются встроенными, потому что они
реализованы непосредственно в языке Python. Вы также можете создавать
пользовательские функции для решения конкретных задач.
Функции разбивают код на меньшие блоки. Их используют для определения
действий, которые должны неоднократно выполняться в программном коде. Вам
не придется писать один и тот же код каждый раз, когда программе потребуется
выполнить эту задачу, достаточно вызвать функцию!
Но иногда некоторую часть кода требуется повторить несколько раз подряд.
Для этого используются циклы.
В этой главе вы:
zz
создадите пользовательские функции;
zz
научитесь работать с циклами for и while;
zz
узнаете, что такое область видимости и почему она важна.
Итак, за дело!
6.1. ЧТО ЖЕ ТАКОЕ ФУНКЦИЯ?
В предыдущих главах мы использовали функции print() и len() для вывода
текста и определения длины строки. Давайте разберемся с функциями более
подробно.
В этом разделе на примере len() я покажу, что такое функция и как она выполняется.
112 Глава 6 Функции и циклы
Функции как значения
Одна из самых важных особенностей функций в языке Python состоит в том,
что функции — это значения, которые могут присваиваться переменным.
Проверим имя len в интерактивном окне IDLE. Для этого введите его после
приглашения:
>>> len
<built-in function len>
Python сообщает, что len является встроенной функцией. По аналогии с тем,
как целочисленные значения имеют тип int, а строки — тип str, значенияфункции также имеют тип:
>>> type(len)
<class 'builtin_function_or_method'>
Однако при желании с именем len можно связать другое значение:
>>> len = "I'm not the len you're looking for."
>>> len
"I'm not the len you're looking for."
Теперь имя len связано со строковым значением. Вы можете убедиться в том,
что оно относится к типу str, при помощи функции type():
>>> type(len)
<class 'str'>
И хотя значение, связанное с именем len, можно изменить, делать так обычно не
рекомендуется. Изменение значения len только усложнит чтение вашего кода,
потому что новое имя len легко перепутать со встроенной функцией. Сказанное
относится к любой встроенной функции.
ВАЖНО!
После выполнения этих примеров кода встроенная функция len станет недоступной в IDLE. Чтобы вернуть ее, введите команду:
>>> del len
Ключевое слово del используется для отмены присваивания переменной. Это
сокращение от delete (удалить), но значение не удаляется. Вместо этого связь
имени со значением разрывается и удаляется имя.
6.1. Что же такое функция? 113
Обычно после выполнения del при попытке использования имени удаленной
переменной происходит ошибка NameError. Однако в данном случае имя len
не удаляется:
>>> len
<built-in function len>
Так как len является именем встроенной функции, оно снова связывается с исходным значением-функцией.
Какой же вывод из этого можно сделать? У функций есть имена, но эти имена
не имеют жесткой связи с функцией, и им можно присваивать другие значения.
Когда вы пишете собственные функции, будьте внимательны и не присваивайте
им значения, используемые встроенными функциями.
Как Python работает с функциями
Давайте более детально разберемся в том, как Python выполняет функции.
Прежде всего следует заметить, что функцию невозможно выполнить, просто
указав ее имя. Необходимо вызвать функцию, чтобы сообщить Python, как она
должна выполняться.
Посмотрим, как работает механизм вызова на примере len():
>>> # Печать имени функции не приводит к ее исполнению.
>>> # IDLE проверяет переменную как обычно.
>>> len
<built-in function len>
>>> # Используем скобки для вызова функции.
>>> len()
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
len()
TypeError: len() takes exactly one argument (0 given)
В этом примере Python выдает ошибку TypeError при вызове len(), потому что
функция len() ожидает получить аргумент.
Аргумент представляет собой значение, которое передается функции. Некоторые
функции вызываются без аргументов, другие могут получать сколько угодно
аргументов. Функция len() должна получать только один аргумент. Завершив
выполнение, функция возвращает значение. Возвращаемое значение обычно —
хотя и не всегда — зависит от значений других аргументов, передаваемых функции.
114 Глава 6 Функции и циклы
Функция выполняется в три этапа.
1. Функция вызывается, и все аргументы передаются ей в качестве входных
значений.
2. Функция выполняется, и с ее аргументами выполняются некоторые
действия.
3. Функция возвращает управление, а исходный вызов функции заменяется
возвращаемым значением.
Давайте рассмотрим конкретный пример и разберемся, как Python выполняет
следующую строку кода:
>>> num_letters = len("four")
Сначала len() вызывается с аргументом "four". Вычисляется длина строки
"four", которая равна 4. Затем len() возвращает число 4 и заменяет вызов
функции полученным значением.
После выполнения функции строка кода будет выглядеть так:
>>> num_letters = 4
Затем Python присваивает значение 4 переменной num_letters и продолжает
выполнение остальных строк кода в программе.
Функции могут обладать побочными эффектами
Вы узнали, как вызывать функции и что функции возвращают значение при
завершении выполнения. Тем не менее иногда функции не ограничиваются
простым возвращением значения.
Если функция изменяет что-то в программе за пределами своего собственного
кода, говорят, что она имеет побочный эффект. Вы уже видели одну функцию
с побочным эффектом: print().
Когда вы вызываете print() со строковым аргументом, строка выводится в оболочке Python в текстовом виде. Однако print() не возвращает текстового значения.
Чтобы понять, что возвращает print(), присвойте возвращаемое значение
print() переменной:
>>> return_value = print("What do I return?")
What do I return?
>>> return_value
>>>
6.2. Написание ваших собственных функций 115
Когда вы присваиваете print("What do I return?") переменной return_value,
выводится строка "What do I return?". Но при проверке значения return_value
ничего не выводится.
Функция print() возвращает специальное значение None, которое указывает
на отсутствие данных; None относится к типу, который называется NoneType:
>>> type(return_value)
<class 'NoneType'>
>>> print(return_value)
None
При вызове print() выводимый текст не является возвращаемым значением,
это побочный эффект print().
6.2. НАПИСАНИЕ ВАШИХ СОБСТВЕННЫХ
ФУНКЦИЙ
При написании более сложных программ может оказаться, что вам нужно
многократно выполнять несколько строк кода — например, вычислять одну
и ту же формулу для разных входных значений.
Возможно, у вас появится искушение скопировать код в другие части программы и изменить его по мере надобности, но так поступать не стоит! Если
вы обнаружите ошибку в скопированном коде, исправление придется вносить
во все копии. А это огромная работа!
В этом разделе вы научитесь создавать собственные функции, чтобы избежать
дублирования кода при его многократном использовании.
Анатомия функции
Каждая функция состоит из двух частей.
1. Сигнатура функции определяет имя функции и все входные данные,
которые она ожидает получить.
2. Тело функции содержит код, который выполняется при каждом использовании функции.
Напишем функцию, которая получает два числа на входе и возвращает их произведение. Вот как может выглядеть функция (сигнатура и тело обозначены
в комментариях):
116 Глава 6 Функции и циклы
def multiply(x, y): # Сигнатура функции
# Тело функции
product = x * y
return product
Создание функции для чего-то настолько простого, как оператор *, выглядит
странно. Пожалуй, multiply() — не та функция, которую вы будете использовать в реальной программе. Но это хороший первый пример, чтобы понять, как
создаются функции!
ВАЖНО!
Когда вы определяете функцию в интерактивном окне IDLE, необходимо нажать Enter дважды после строки, содержащей команду return, чтобы функция
была зарегистрирована Python:
>>> def multiply(x, y):
... product = x * y
... return product
... # <--- Здесь нужно нажать Enter во второй раз.
>>>
Разобьем функцию на части и посмотрим, что в какой момент происходит.
Сигнатура функции
Первая строка кода функции называется сигнатурой функции. Она всегда начинается с ключевого слова def (сокращение от define — определить).
Рассмотрим повнимательнее сигнатуру multiply():
def multiply(x, y):
Сигнатура функции состоит из четырех частей.
1. Ключевое слово def.
2. Имя функции multiply.
3. Список параметров (x, y)
4. Двоеточие (:) в конце строки.
Когда Python читает строку, начинающуюся с ключевого слова def, он создает
новую функцию. Эта функция присваивается переменной, имя которой совпадает с именем функции.
6.2. Написание ваших собственных функций 117
ПРИМЕЧАНИЕ
Так как имена функций становятся переменными, они должны подчиняться
правилам имен переменных, о которых вы узнали в главе 3. Таким образом,
имя функции может содержать только цифры, буквы и подчеркивания и не
должно начинаться с цифры.
Список параметров представляет собой список имен, заключенный в круглые
скобки. Он определяет ожидаемые входные значения функции; (x, y) — список
параметров для multiply(), который определяет два параметра — x и y.
Параметр является разновидностью переменной, но не имеет собственного
значения. Параметр замещает фактические значения, которые будут переданы
при вызове функции с одним или несколькими аргументами.
Код в теле функции использует параметры так, как если бы они являлись переменными с реальными значениями. Например, тело функции может содержать
строку с выражением x * y.
Так как x и y значений не имеют, произведение x * y тоже не имеет значения.
Python сохраняет выражение в виде шаблона и подставляет недостающие значения при выполнении функции.
Функция может получать любое количество параметров, в том числе и ни одного.
Тело функции
Тело функции содержит код, который выполняется при использовании функции
в программе. Тело функции для multiply() выглядит так:
def multiply(x, y):
# Тело функции
product = x * y
return product
multiply — совсем простая функция. Ее тело содержит всего две строки кода.
Первая строка тела функции создает переменную с именем product и присваивает ей значение x * y. Так как x и y еще не содержат значений, эта строка
в действительности определяет шаблон для значения, которое будет присвоено
product при выполнении функции.
Вторая строка тела функции называется командой return. Она начинается
с ключевого слова return, за которым следует переменная product. Достигнув
118 Глава 6 Функции и циклы
команды return, Python прекращает выполнение функции и возвращает значение product.
Обратите внимание: обе строки кода в теле функции снабжены отступами.
Это очень важно! Каждая строка с отступом, расположенная под сигнатурой
функции, считается принадлежащей телу функции.
Например, вызов функции print() в следующем примере не входит в тело
функции, потому что эта строка не имеет отступа:
def multiply(x, y):
product = x * y
return product
print("Where am I?") # Не принадлежит телу функции
Если добавить отступ в строку с print(), эта строка станет частью тела функции, даже несмотря на пустую строку между print() и предыдущей строкой:
def multiply(x, y):
product = x * y
return product
print("Where am I?") # В теле функции
При добавлении отступов в тело функции необходимо соблюдать одно правило:
отступы всех строк должны содержать одинаковое количество пробелов.
Попробуйте сохранить следующий код в файле с именем multiply.py и выполнить его из IDLE:
def multiply(x, y):
product = x * y
return product
# С одним лишним пробелом
IDLE не будет выполнять этот код! На экране появится диалоговое окно с сообщением об ошибке «unexpeсted indent» («неожиданный отступ»). Python
ожидает, что команда return имеет такое же количество пробелов в отступе,
как и в строке над ней.
Другая ошибка возникает тогда, когда размер отступа в строке меньше, чем
у строки над ней и отступ не соответствует никакой из предшествующих строк.
Измените файл multiply.py, чтобы он выглядел так:
def multiply(x, y):
product = x * y
return product # Отступ меньше, чем в предыдущей строке
6.2. Написание ваших собственных функций 119
Сохраните и запустите файл. IDLE прерывает его выполнение с сообщением
об ошибке unindent does not match any outer indentation level (отступ не соответствует ни одному внешнему уровню). Количество пробелов в отступе
команды return отличается от любой строки в теле функции.
ПРИМЕЧАНИЕ
Хотя в Python нет правил, определяющих количество пробелов в отступах кода
в теле функции, PEP 8 рекомендует использовать отступы из четырех пробелов.
В книге соблюдается именно это правило.
После выполнения команды return функция останавливается и возвращает
значение. Если под командой return располагается код, отступ которого указывает на то, что он является частью тела функции, этот код выполнен не будет.
Например, в следующей функции команда print() никогда не выполняется:
def multiply(x, y):
product = x * y
return product
print("You can't see me!")
Эта версия multiply() никогда не выведет строку "You can't see me!".
Вызов функции, определенной пользователем
Пользовательская функция вызывается точно так же, как и любая другая: вы
вводите имя функции, за которым указываете список аргументов в круглых
скобках.
Например, чтобы вызвать multiply() с аргументами 2 и 4, просто введите следующую команду:
multiply(2, 4)
В отличие от встроенных, пользовательские функции становятся доступными
только после того, как они будут определены с ключевым словом def. Необходимо определить функцию, прежде чем вызывать ее.
Введите следующую программу в окне редактора IDLE:
num = multiply(2, 4)
print(num)
120 Глава 6 Функции и циклы
def multiply(x, y):
product = x * y
return product
Сохраните файл и нажмите F5. Так как функция multiply() вызывается до ее
определения, Python не распознает имя multiply и выдает ошибку NameError:
Traceback (most recent call last):
File "C:Usersdaveamultiply.py", line 1, in <module>
num = multiply(2, 4)
NameError: name 'multiply' is not defined
Чтобы исправить ошибку, переместите определение функции в начало файла:
def multiply(x, y):
product = x * y
return product
num = multiply(2, 4)
print(num)
Снова сохраните файл и нажмите F5, чтобы запустить программу. На этот раз
в интерактивном окне выводится значение 8.
Функции без команды return
Все функции в Python возвращают значение, даже если это None. Тем не менее
не всем функциям необходима команда return.
Например, следующая функция вполне допустима:
def greet(name):
print(f"Hello, {name}!")
greet() не содержит команды return, но прекрасно работает:
>>> greet("Dave")
Hello, Dave!
И несмотря на отсутствие команды return, greet() возвращает значение:
>>> return_value = greet("Dave")
Hello, Dave!
>>> print(return_value)
None
6.2. Написание ваших собственных функций 121
Строка "Hello, Dave!" выводится, хотя результат greet("Dave") присваивается
переменной. Может, вы не ожидали увидеть на экране "Hello, Dave!"? Тогда
вы столкнулись с одной из проблем побочных эффектов — они бывают неожиданными!
При создании функций всегда следует документировать, что они делают. Это
позволит другим разработчикам прочитать ваши заметки и понять, как пользоваться функциями и чего от них ожидать.
Документирование функций
Для получения справки о функции в интерактивном окне IDLE используется
функция help():
>>> help(len)
Help on built-in function len in module builtins:
len(obj, /)
Return the number of items in a container.
Функции help() передается имя переменной или функции, а она выводит полезную информацию об указанной переменной или функции. В данном случае
help() сообщает, что len() является встроенной функцией, которая возвращает
количество элементов в контейнере.
ПРИМЕЧАНИЕ
Контейнер — специальный термин для объекта, который содержит другие
объекты. Строка является контейнером, потому что она содержит символы.
О других типах контейнеров я расскажу в главе 9.
Посмотрим, что происходит при вызове help() для multiply():
>>> help(multiply)
Help on function multiply in module __main__:
multiply(x, y)
Функция help() выводит сигнатуру функции, но не предоставляет никакой
информации о том, что эта функция делает. Чтобы лучше документировать
функцию multiply(), следует предоставить doc-строку — строковый литерал
в утроенных кавычках в начале тела функции.
122 Глава 6 Функции и циклы
Doc-строка описывает, что делает функция и какие виды параметров она ожидает получить:
def multiply(x, y):
"""Возвращает произведение двух чисел x и y."""
product = x * y
return product
Перепишите функцию multiply() с doc-строкой. Теперь можно воспользоваться
функцией help() в интерактивном окне для просмотра doc-строки:
>>> help(multiply)
Help on function multiply in module __main__:
multiply(x, y)
Возвращает произведение двух чисел x и y.
В PEP 8 о doc-строках не сказано почти ничего, кроме того, что они должны
определяться в каждой функции (https://pep8.org/#documentation-strings).
Существует ряд стандартизированных форматов doc-строк, но мы не будем
в них углубляться. Некоторые общие рекомендации по написанию doc-строк
можно найти в PEP 257 (https://www.python.org/dev/peps/pep-0257/).
Упражнения
1. Напишите функцию cube(), которая получает один числовой параметр
и возвращает значение указанного числа в третьей степени. Протестируйте функцию — вызовите cube() для нескольких разных чисел и выведите результаты.
2. Напишите функцию greet(), которая получает один строковый параметр
с именем name и выводит текст «Hello <name>!», где <name> заменяется
значением параметра name.
6.3. ЗАДАЧА: КОНВЕРТЕР ТЕМПЕРАТУР
Напишите программу temperature.py, которая определяет две функции.
1. convert_cel_to_far() получает один параметр float, представляющий
температуру по шкале Цельсия, и возвращает значение float , представляющее ту же температуру по шкале Фаренгейта. Преобразование
выполняется по следующей формуле:
6.4. Циклическое выполнение 123
F = C * 9/5 + 32
2. convert_far_to_cel() получает один параметр float, представляющий
температуру по шкале Фаренгейта, и возвращает значение float, представляющее ту же температуру по шкале Цельсия. Преобразование выполняется по следующей формуле:
C = (F - 32) * 5/9
Программа должна делать следующее.
1. Запрашивать у пользователя температуру в градусах по шкале Фаренгейта, а затем выводить температуру, преобразованную к шкале Цельсия.
2. Запрашивать у пользователя температуру в градусах по шкале Цельсия,
а затем выводить температуру, преобразованную к шкале Фаренгейта.
3. Выводить все преобразованные температуры, округленные до двух знаков
в дробной части.
Пример выполнения программы:
Enter a temperature in degrees F: 72
72 degrees F = 22.22 degrees C
Enter a temperature in degrees C: 37
37 degrees C = 98.60 degrees F
Решение этой задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
6.4. ЦИКЛИЧЕСКОЕ ВЫПОЛНЕНИЕ
Одно из самых замечательных свойств компьютеров заключается в том, что им
можно приказать делать одно и то же снова и снова и они никогда не пожалуются! Цикл представляет собой блок кода, который выполняется многократно
заданное количество раз или до выполнения некоторого условия. В Python
поддерживаются две разновидности циклов: циклы while и циклы for. В этом
разделе я расскажу, как использовать обе разновидности.
Цикл while
Циклы while повторяют блок кода, пока некоторое условие не станет истинным.
Каждый цикл while состоит из двух частей.
124 Глава 6 Функции и циклы
1. Команда while начинается с ключевого слова while, за которым следует
проверяемое условие и двоеточие.
2. Тело цикла содержит код, который повторяется на каждом шаге цикла.
Каждая строка тела снабжается отступом из четырех пробелов.
В процессе выполнения цикла while Python проверяет условие и определяет,
истинно оно или ложно. Если условие истинно, то Python выполняет код в теле
цикла и снова возвращается к проверке условия. В противном случае код тела
пропускается и выполняется дальнейший код программы.
Рассмотрим пример. Введите следующий код в интерактивном окне:
>>> n = 1
>>> while n < 5:
...
print(n)
...
n = n + 1
...
1
2
3
4
Сначала целое число 1 присваивается переменной n. Затем создается цикл while
с условием n < 5. Это условие проверяет, что значение n меньше 5.
Если n меньше 5, выполняется тело цикла — две строки кода. В первой строке
значение n выводится на экран. Во второй строке n увеличивается на 1.
Выполнение цикла состоит из пяти шагов (итераций).
НОМЕР
ШАГА
ЗНАЧЕНИЕ N
УСЛОВИЕ
ЧТО ПРОИСХОДИТ
1
1
1 < 5 (true)
Выводит 1; увеличивается до 2
2
2
2 < 5 (true)
Выводит 2; увеличивается до 3
3
3
3 < 5 (true)
Выводит 3; увеличивается до 4
4
4
4 < 5 (true)
Выводит 4; увеличивается до 5
5
5
5 < 5 (false)
Ничего; цикл завершается
Если действовать неосторожно, можно создать бесконечный цикл. Такая ситуация возникает, когда условие цикла всегда истинно. Бесконечный цикл не
завершается никогда, а его тело выполняется снова и снова.
6.4. Циклическое выполнение 125
Пример бесконечного цикла:
>>> n = 1
>>> while n < 5:
...
print(n)
...
Единственное отличие этого цикла while от предыдущего в том, что n не увеличивается в теле цикла. На каждом шаге цикла переменная n остается равной 1.
Таким образом, условие n < 5 всегда будет истинным, а число 1 будет выводиться
снова и снова.
ПРИМЕЧАНИЕ
Бесконечные циклы не всегда плохи. Иногда это именно то, что нужно. Например, код для работы с оборудованием может в бесконечном цикле проверять,
была ли активизирована некоторая кнопка или переключатель.
Если вы запустили программу, которая вошла в бесконечный цикл, прикажите Python немедленно завершить программу, нажав сочетание клавиш Ctrl+C.
Python прекращает выполнение программы и выдает ошибку KeyboardInterrupt:
Traceback (most recent call last):
File "<pyshell#8>", line 2, in <module>
print(n)
KeyboardInterrupt
Рассмотрим пример практического использования цикла while. В одном из
возможных применений цикл while проверяет, удовлетворяет ли пользовательский ввод некоторому условию, и если нет — запрашивает у пользователя
новые данные, пока не будет получен корректный ввод.
Например, следующая программа многократно запрашивает у пользователя
положительное число, пока не будет введено именно положительное число:
num = float(input("Enter a positive number: "))
while num <= 0:
print("That's not a positive number!")
num = float(input("Enter a positive number: "))
Сначала программа выводит сообщение, предлагающее ввести положительное
число. Условие num <= 0 проверяет, что значение num меньше либо равно 0.
Если значение num положительно, условие нарушается. Тело цикла пропускается,
и программа завершается.
126 Глава 6 Функции и циклы
В противном случае, если значение num равно 0 или отрицательно, выполняется
тело цикла. Программа оповещает пользователя о том, что введенное значение
недопустимо, и предлагает повторить попытку.
Циклы while идеально подходят для повторения части кода, пока выполняется
заданное условие. С другой стороны, они плохо работают при повторении части
кода фиксированное количество раз.
Цикл for
Цикл for выполняет часть кода по одному разу для каждого элемента в коллекции. Количество выполнений кода определяется количеством элементов
в коллекции.
Цикл for, как и while, состоит из двух частей.
1. Команда for начинается с ключевого слова for, за которым следует выражение принадлежности, и завершается двоеточием:
2. Тело цикла for содержит код, выполняемый на каждом шаге цикла. Каждая
строка содержит отступ из четырех пробелов.
Рассмотрим пример. Следующий цикл for последовательно выводит каждую
букву строки "Python":
for letter in "Python":
print(letter)
В этом примере команда for имеет вид for letter in "Python", а выражение
принадлежности — letter in "Python".
На каждом шаге цикла переменной letter присваивается очередная буква
строки "Python", после чего выводится значение letter.
Цикл выполняется по одному разу для каждого символа строки "Python", так
что тело будет выполнено шесть раз. В следующей таблице кратко описано
выполнение цикла.
НОМЕР
ШАГА
ЗНАЧЕНИЕ LETTER
ЧТО ПРОИСХОДИТ
1
“P”
Выводит P
2
“y”
Выводит y
3
“t”
Выводит t
6.4. Циклическое выполнение 127
НОМЕР
ШАГА
ЗНАЧЕНИЕ LETTER
ЧТО ПРОИСХОДИТ
4
“t”
Выводит h
5
“o”
Выводит o
6
“n”
Выводит n
Чтобы понять, почему циклы for лучше подходят для перебора коллекций
элементов, перепишем цикл for из предыдущего примера в форме цикла while.
Для этого можно сохранить индекс следующего символа строки в переменной.
На каждом шаге цикла выводится символ с текущим индексом, после чего
индекс увеличивается.
Цикл остановится, как только значение индексной переменной достигнет длины строки. Помните, что индексы начинаются с 0, а следовательно, последний
индекс в строке "Python" равен 5.
Этот код можно было бы записать так:
word = "Python"
index = 0
while index < len(word):
print(word[index])
index = index + 1
Эта версия намного сложнее версии с for!
Впрочем, цикл for не только проще — он выглядит более естественно. Он ближе
к тому, как бы вы описали цикл на человеческом языке.
ПРИМЕЧАНИЕ
Иногда приходится слышать, как люди называют какой-нибудь код питоническим. Обычно этим термином обозначается код понятный, компактный
и широко использующий встроенные возможности Python.
Таким образом, использование цикла for для перебора коллекции элементов
следует признать более питоническим, чем цикл while.
Иногда требуется перебрать числа в некотором диапазоне. В Python существует
удобная встроенная функция range(), предназначенная именно для создания
диапазона чисел.
128 Глава 6 Функции и циклы
Например, range(3) возвращает диапазон целых чисел от 0 до 3 (не включая
последнее). Таким образом, range(3) создает диапазон чисел 0, 1 и 2.
Вызов range(n), где n — любое положительное число, может использоваться для
выполнения цикла ровно n раз. Например, следующий цикл выводит строку
"Python" три раза:
for n in range(3):
print("Python")
Также можно задать начальную точку диапазона. Например, вызов range(1, 5)
создает диапазон из чисел 1, 2, 3 и 4. Первый аргумент задает начальное число,
а второй — конечную точку, которая в диапазон не включается.
Следующий цикл for, использующий версию range() с двумя аргументами,
выводит квадраты всех чисел от 10 до 20 (не включая последнее):
for n in range(10, 20):
print(n * n)
Рассмотрим пример реальной программы. Она предлагает пользователю ввести
сумму счета, а потом показывает, как эта сумма будет делиться между двумя,
тремя, четырьмя и пятью людьми:
amount = float(input("Enter an amount: "))
for num_people in range(2, 6):
print(f"{num_people} people: ${amount / num_people:,.2f} each")
Цикл for перебирает числа 2, 3, 4 и 5 и выводит количество людей и сумму,
которую должен заплатить каждый участник. Форматный спецификатор ,.2f
форматирует сумму как число с фиксированной точкой, округленное до двух
знаков и с разделением групп разрядов запятыми.
Если запустить программу и ввести значение 10, будет получен следующий
результат:
Enter an amount: 10
2 people: $5.00 each
3 people: $3.33 each
4 people: $2.50 each
5 people: $2.00 each
Циклы for обычно используются в Python чаще, чем циклы while. Как правило,
циклы for более компактны и проще читаются, чем эквивалентные циклы while.
6.4. Циклическое выполнение 129
Вложенные циклы
Циклы могут вкладываться внутрь других циклов — при условии, что вы правильно используете отступы в коде.
Введите следующую команду в интерактивном окне IDLE:
for n in range(1, 4):
for j in range(4, 7):
print(f"n = {n} and j = {j}")
В ходе выполнения первого цикла for Python присваивает значение 1 переменной n. Затем выполняется второй цикл for и переменной j присваивается
значение 4. В первом выведенном результате n = 1 и j = 4.
После выполнения print() Python возвращается к внутреннему циклу for,
присваивает 5 переменной j, после чего выводит n = 1 и j = 5. Python не возвращается к внешнему циклу for, потому что внутренний цикл for, находящийся
внутри тела внешнего цикла for, еще не завершен.
Далее Python присваивает 6 переменной j, после чего выводит n = 1 и j = 6.
В этой точке внутренний цикл for завершил выполнение, поэтому управление
возвращается внешнему циклу for. Python присваивает значение 2 переменной n,
после чего внутренний цикл for выполняется во второй раз. Этот процесс повторяется в третий раз, когда n присваивается значение 3.
Окончательный вывод выглядит так:
n = 1 and j = 4
n = 1 and j = 5
n = 1 and j = 6
n = 2 and j = 4
n = 2 and j = 5
n = 2 and j = 6
n = 3 and j = 4
n = 3 and j = 5
n = 3 and j = 6
Цикл, находящийся внутри другого цикла, называется вложенным циклом.
Вложенные циклы встречаются достаточно часто. Разрешается вкладывать
циклы while в циклы for и наоборот. Более того, возможно вложение циклов
на глубину более двух уровней.
Циклы — мощный инструмент в арсенале программиста. Они используют одно
из самых больших достоинств компьютеров: повторять одни и те же действия
огромное количество раз, не уставая и не жалуясь.
130 Глава 6 Функции и циклы
ВАЖНО!
Вложение циклов неизбежно повышает сложность вашего кода. Об этом свидетельствует значительное увеличение числа шагов в приведенном примере
по сравнению с предыдущими примерами с одним циклом for.
Вложенные циклы — иногда единственный способ решения задачи, но слишком большое количество вложенных циклов может отрицательно сказаться
на быстродействии программы.
Упражнения
1. Напишите цикл for, который выводит целые числа от 2 до 10 с использованием range(). Каждое число должно выводиться в новой строке.
2. Напишите цикл while, который выводит целые числа от 2 до 10. (Подсказка: сначала необходимо создать новое целое число.)
3. Напишите функцию doubles(), которая получает число и увеличивает его
вдвое. Используйте doubles() в цикле, чтобы трижды увеличить вдвое
число 2, с выводом каждого результата в отдельной строке. Пример вывода:
4
8
16
6.5. ЗАДАЧА: ОТСЛЕЖИВАНИЕ ПРИБЫЛИ
ПО ВКЛАДУ
В этой задаче вам надо написать программу invest.py для отслеживания увеличения вклада со временем.
Исходная сумма вклада называется основной. Каждый год основная сумма
увеличивается на фиксированный процент — годовой процент прибыли.
Например, основная сумма $100.00 с годовым процентом 5 по истечении года
равна $105.00. Во второй год приращение составит 5 процентов от $105.00, или
$5.25, а общая сумма увеличится до $110.25.
Напишите функцию invest, которая выводит три параметра: основную сумму,
годовой процент прибыли и количество лет. Сигнатура функции может выглядеть примерно так:
def invest(amount, rate, years):
6.6. Область видимости в Python 131
Затем функция должна выводить сумму вклада, округленную до двух знаков
в дробной части, на конец каждого года в заданном диапазоне лет.
Например, вызов invest(100, .05, 4) должен выводить следующий результат:
year 1: $105.00
year 2: $110.25
year 3: $115.76
year 4: $121.55
Чтобы завершить программу, запросите у пользователя основную сумму, годовой процент прибыли и количество лет. Затем вызовите invest() для вывода
расчетов для значений, введенных пользователем.
Решение этой задачи, а также многие другие дополнительные ресурсы доступны
в интернете на realpython.com/python-basics/resources.
6.6. ОБЛАСТЬ ВИДИМОСТИ В PYTHON
Любое обсуждение функций и циклов в Python будет неполным без упоминания
областей видимости (scope).
Эта тема может оказаться одной из самых сложных для понимания в программировании, поэтому здесь мы будем рассказывать о ней, не углубляясь
в детали. К концу этого раздела вы уясните, что такое область видимости
и почему она важна. Также вы узнаете о правиле LEGB для разрешения областей видимости.
Что такое область видимости?
Присваивая значение переменной, вы тем самым связываете значение с именем.
Имена уникальны. Например, невозможно присвоить одно имя двум разным
числам:
>>> x = 2
>>> x
2
>>> x = 3
>>> x
3
Когда вы присваиваете переменной x значение 3, вы уже не сможете получить
значение 2 по имени x.
132 Глава 6 Функции и циклы
И такое поведение вполне логично. В конце концов, если бы переменная x имела
значения 2 и 3 одновременно, то как бы вы вычисляли x + 2? Сколько должно
получиться — 4 или 5?
Оказывается, присвоить одному имени два разных значения все же возможно.
Просто это потребует некоторых ухищрений.
Откройте новое окно редактора в IDLE и введите следующую программу:
x = "Hello, World"
def func():
x = 2
print(f"Inside 'func', x has the value {x}")
func()
print(f"Outside 'func', x has the value {x}")
В этом примере переменной x присваиваются два разных значения: "Hello,
World" в начале и 2 внутри func().
Вывод программы выглядит немного странно:
Inside 'func', x has the value 2
Outside 'func', x has the value Hello, World
Как получилось, что x все еще содержит значение "Hello, World" после вызова
func(), изменяющего значение x на 2?
Дело в том, что func() имеет другую область видимости, нежели код за пределами этой функции. Другими словами, внутри func() можно присвоить объекту
такое же имя, как у объекта за пределами func(), и Python будет хранить эти
два объекта по отдельности.
Тело функции обладает так называемой локальной областью видимости с собственным набором доступных имен. Код за пределами тела функции принадлежит глобальной области видимости.
Область видимости можно рассматривать как набор имен, связанных с объектами. Когда вы используете в своем коде конкретное имя (например, имя
переменной или функции), Python проверяет текущую область видимости,
чтобы определить, существует ли такое имя.
Разрешение областей видимости
Области видимости образуют иерархию. Например, возьмем следующий
фрагмент:
6.6. Область видимости в Python 133
x = 5
def outer_func():
y = 3
def inner_func():
z = x + y
return z
return inner_func()
ПРИМЕЧАНИЕ
Функция inner_func() называется внутренней, потому что она определяется
внутри другой функции. Оказывается, как и в случае с вложенными циклами,
функции также могут определяться внутри других функций!
О внутренних функциях можно больше узнать из статьи «Inner Functions—What
Are They Good For?» (https://realpython.com/inner-functions-what-are-they-goodfor/) на сайте Real Python.
Переменная z существует в локальной области видимости inner_func(). Когда
Python выполняет строку z = x + y, сначала осуществляется поиск переменных x
и y в локальной области видимости. Ни одна из них в локальной области видимости не существует, поэтому Python переходит вверх к области видимости
outer_func().
Область видимости outer_func() является внешней по отношению к inner_
func() . Это все еще не глобальная область видимости, но и не локальная
inner_func(). Она находится посередине между этими двумя областями.
Переменная y определена в области видимости outer_func(), и ей присвоено значение 3. Тем не менее x в этой области видимости не существует, по­
этому Python снова перемещается вверх к глобальной области видимости.
Здесь ­обнаруживается имя x со значением 5. Итак, имена x и y успешно разрешены, и Python может выполнить строку z = x + y, которая присваивает z
значение 8.
Правило LEGB
Чтобы запомнить, как в Python происходит разрешение областей видимости,
можно воспользоваться правилом LEGB. Это сокращение (Local, Enclosing,
Global, Built-in) описывает порядок, в котором Python производит поиск имен
по областям видимости.
134 Глава 6 Функции и циклы
Краткое описание поможет вам запомнить, как работает процесс поиска.
1. Локальная (Local): локальная, или текущая, область видимости может
быть телом функции или областью видимости верхнего уровня файла
с кодом. Она всегда представляет область видимости, в которой интерпретатор Python работает в настоящий момент.
2. Внешняя (Enclosing): внешняя область видимости находится на один
уровень выше локальной. Если локальная область видимости соответствует внутренней функции, то внешней становится область видимости
внешней функции. Если область видимости соответствует функции
верхнего уровня, то внешняя область видимости соответствует глобальной.
3. Глобальная (Global): глобальная область видимости находится на самом
верхнем уровне иерархии. Она содержит все имена, определенные в коде,
которые не содержатся в теле функции.
4. Встроенная (Built-in): встроенная область видимости содержит все имена,
встроенные в Python (например, ключевые слова). Такие функции, как
round() и abs(), принадлежат встроенной области видимости. Все, что
вы можете использовать, не определяя самостоятельно, содержится во
встроенной области видимости.
Области видимости — тема, сложная для понимания. Чтобы усвоить эту концепцию, вам потребуется некоторая практика. Не волнуйтесь, если с первого
раза вам не удалось в ней разобраться. Просто продолжайте практиковаться
и используйте правило LEGB.
Нарушение правил
Как вы думаете, что выведет следующий код?
total = 0
def add_to_total(n):
total = total + n
add_to_total(5)
print(total)
Программа должна вывести значение 5, не так ли? Попробуйте запустить ее
и посмотрите, что произойдет.
6.6. Область видимости в Python 135
А происходит нечто неожиданное! Вы получите ошибку:
Traceback (most recent call last):
File "C:/Users/davea/stuff/python/scope.py", line 6, in <module>
add_to_total(5)
File "C:/Users/davea/stuff/python/scope.py", line 4, in add_to_total
total = total + n
UnboundLocalError: local variable 'total' referenced before assignment
Минутку! Согласно правилу LEGB, Python должен был понять, что имя total
не существует в локальной области видимости add_to_total(), и перейти к глобальной области видимости для разрешения имени.
Проблема в том, что код пытается выполнить присваивание переменной total,
что приводит к созданию нового имени в локальной области видимости. Затем,
когда Python выполнит правую часть присваивания, он найдет в локальной области видимости имя total, которому еще ничего не присвоено.
Подобные ошибки весьма коварны. Это одна из причин для использования
уникальных имен переменных и функций независимо от того, какой области
видимости они принадлежат.
Проблему можно обойти при помощи ключевого слова global:
total = 0
def add_to_total(n):
global total
total = total + n
add_to_total(5)
print(total)
На этот раз выводится ожидаемый результат 5. Почему?
Строка global total сообщает Python о том, что имя total следует искать
в глобальной области видимости. И тогда строка total = total + n не создает
новую локальную переменную.
И хотя это «исправляет» программу, использование ключевого слова global
обычно считается признаком плохого стиля.
Если вы обнаружите, что вам приходится использовать global для решения
подобных проблем, остановитесь и подумайте: нельзя ли переписать ваш код
по-другому? Часто оказывается, что это возможно!
136 Глава 6 Функции и циклы
6.7. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы познакомились с двумя основополагающими концепциями
программирования: функциями и циклами.
Сначала вы научились определять собственные функции. Как было показано,
функция состоит из двух частей.
1. Сигнатура функции, обозначаемая ключевым словом def, включает имя
функции и параметры функции.
2. Тело функции содержит код, который выполняется при каждом использовании функции.
Функции помогают избежать дублирования одинакового кода в программе;
фактически они создают фрагменты, пригодные для повторного использования.
Они упрощают чтение и сопровождение вашего кода.
Далее вы познакомились с двумя разновидностями циклов Python.
1. Цикл while повторяет код, пока заданное условие остается истинным.
2. Циклы for повторяют код для каждого элемента в заданном наборе объектов.
И, наконец, вы узнали, что такое область видимости и как в Python организовано
разрешение имен по схеме LEGB.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-functions-loops
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
zz
«Python ‘while’ Loops (Indefinite Iteration)» (https://realpython.com/
python-while-loop/)
zz
«Python ‘for’ Loops (Definite Iteration)» (https://realpython.com/pythonfor-loop/)
ГЛАВА 7
Поиск и исправление
ошибок в коде
Все совершают ошибки — даже опытные профессиональные разработчики!
IDLE неплохо справляется с выявлением синтаксических ошибок и ошибок
времени выполнения, но существует третья разновидность ошибок, с которой
вы, возможно, уже сталкивались. Логические ошибки происходят тогда, когда
в целом правильная программа делает не то, что предполагал ее создатель.
Логические ошибки приводят к непредвиденному поведению программы.
Устранение ошибок называется отладкой, а специальный инструмент, который
помогает находить последствия логических ошибок и понять причины их возникновения, — отладчиком.
Умение находить и исправлять ошибки в коде — тот навык, которым вы будете
пользоваться на протяжении всей карьеры программиста!
В этой главе вы:
zz
научитесь использовать окно Debug Control в IDLE;
zz
отработаете навыки отладки на примере функции, содержащей ошибки.
Итак, за дело!
7.1. ИСПОЛЬЗОВАНИЕ ОКНА DEBUG CONTROL
Основным интерфейсом отладчика IDLE является окно Debug Control, которое
далее для краткости я буду называть окном Debug. Чтобы открыть окно Debug,
выберите команду DebugDebugger в меню интерактивного окна.
Каждый раз, когда окно Debug открыто, в интерактивном окне рядом с приглашением выводится сообщение [DEBUG ON]. Оно показывает, что отладчик активен.
138 Глава 7 Поиск и исправление ошибок в коде
ВАЖНО!
Если команда Debug отсутствует в строке меню, щелкните на интерактивном
окне, чтобы передать ему фокус ввода.
Теперь откройте новое окно редактора и разместите три окна на экране, чтобы
они были видны одновременно.
В этом разделе вы узнаете, как устроено окно Debug, научитесь выполнять код
в отладчике по строкам, а также устанавливать точки останова для ускорения
процесса отладки.
Окно Debug Control: обзор
Чтобы понять, как работает отладчик, начнем с простой программы, не содержащей ошибок. Введите следующий код в окне редактора:
for i in range(1, 4):
j = i * 2
print(f"i is {i} and j is {j}")
Сохраните файл, оставьте окно Debug открытым и нажмите F5. Выполнение
длится недолго.
Окно Debug выглядит примерно так:
7.1. Использование окна Debug Control 139
Обратите внимание: на панели Stack в верхней части окна выводится следующее
сообщение:
> '__main__'.<module>(), line 1: for i in range(1, 4):
Оно указывает, что строка 1 (содержащая код for i in range(1, 4):) готова
к выполнению, но еще не начала выполняться. Часть '__main__'.module() этого
сообщения указывает на тот факт, что вы сейчас находитесь в основном разделе
программы — в отличие от, допустим, определения функции перед основным
блоком кода.
Под панелью Stack располагается панель Locals, где перечислены всякие странные слова вроде __annotations__, __builtins__, __doc__ и т. д. Это внутренние
системные переменные, на которые пока можно не обращать внимания. При
выполнении программы переменные, объявленные в коде, будут отображаться
в этом окне, чтобы вы могли отслеживать их значения.
В левом верхнем углу окна Debug расположены пять кнопок: Go, Step, Over, Out
и Quit. Они управляют перемещением отладчика по вашему коду.
Сейчас мы исследуем назначение каждой из этих кнопок, начиная со Step.
Кнопка Step
Щелкните на кнопке Step в левом верхнем углу окна Debug. Окно Debug немного изменится, теперь оно выглядит примерно так:
140 Глава 7 Поиск и исправление ошибок в коде
Здесь стоит обратить внимание на два изменения. Во-первых, сообщение на
панели Stack заменяется следующим:
> '__main__'.<module>(), line 2: j = i * 2:
В этой точке строка 1 вашего кода была выполнена, а отладчик остановился
непосредственно перед выполнением строки 2.
Во-вторых, на панели Locals появилась новая переменная i, которой присвоено значение 1. Это объясняется тем, что цикл for в первой строке кода создал
переменную i и присвоил ей значение 1.
Продолжайте нажимать кнопку Step, чтобы выполнить код в пошаговом режиме, и понаблюдайте за происходящим в окне отладчика. Когда выполнение
достигнет строки print(f"i is {i} and j is {j}"), можно увидеть, что результаты
выводятся в интерактивном окне по частям.
Что еще важнее, вы можете отслеживать растущие значения i и j в процессе
перебора в цикле for. Наверное, вы представляете, как полезно это может быть
при поиске ошибок в программе. Информация о значении каждой переменной
в каждой строке кода поможет понять, где что-то пошло не так.
Точки останова и кнопка Go
Часто вы чувствуете, что ошибка находится в конкретной части вашего кода,
но не знаете, где именно. Вместо того чтобы щелкать на кнопке Step с утра до
ночи, можно установить точку останова. Тем самым вы приказываете отладчику
выполнять код, пока не будет достигнута точка останова.
Точки останова сообщают отладчику, когда следует приостановить выполнение, чтобы вы могли проанализировать текущее состояние программы. Так
что на самом деле выполнение программы не прерывается, а только приостанавливается.
Чтобы установить точку останова, щелкните правой кнопкой мыши (Ctrl+щелчок
на Mac) на той строке кода в окне редактора, где вы хотите приостановить выполнение, и выберите команду Set Breakpoint. IDLE выделяет строку желтым
цветом — это признак установленной точки останова. Чтобы удалить точку
останова, щелкните правой кнопкой мыши на строке с точкой останова и выберите команду Clear Breakpoint.
Нажмите кнопку Quit в верхней части окна Debug, чтобы временно отключить
отладчик. Окно при этом не закроется, и вам стоит и далее держать его открытым, потому что вскоре мы к нему вернемся.
7.1. Использование окна Debug Control 141
Установите точку останова в строке кода с командой print(). Окно редактора
должно выглядеть примерно так:
Сохраните и запустите файл. Как и прежде, панель Stack окна Debug показывает, что отладчик запустился и ожидает выполнения строки 1. Щелкните на
кнопке Go и понаблюдайте за тем, что происходит в окне Debug.
На панели Stack теперь выводится сообщение о том, что ожидается выполнение
строки 3:
> '__main__'.<module>(), line 3: print(f"i is {i} and j is {j}")
На панели Local мы видим, что переменные i и j теперь содержат значения 1 и 2
соответственно. Щелкнув на кнопке Go, вы приказываете отладчику выполнять
142 Глава 7 Поиск и исправление ошибок в коде
код, пока не будет достигнута точка останова или конец программы. Снова нажмите Go. Окно Debug теперь выглядит так:
Вы видите, что изменилось? На панели Stack выводится то же сообщение, что
и прежде: оно указывает, что отладчик собирается снова выполнить строку 3.
Однако переменные i и j теперь содержат 2 и 4. В интерактивном окне также
выводится результат выполнения строки с командой print() при первом проходе цикла.
Каждый раз, когда вы нажимаете кнопку Go, отладчик выполняет код в обычном
режиме, пока не достигнет следующей точки останова. Так как точка останова
была установлена в строке 3, находящейся внутри цикла for, отладчик останавливается в этой строке при каждом прохождении цикла.
Нажмите Go в третий раз. Теперь i и j содержат значения 3 и 6. Как вы думаете,
что произойдет, если нажать Go еще один раз? Так как цикл for выполняется
только три раза, при следующем нажатии Go программа завершится.
Кнопки Over и Out
Кнопка Over работает как комбинация кнопок Step и Go. Она выполняет код
без захода в функцию или цикл. Если вы дошли кнопкой Step в отладчике до
7.2. Исправление ошибок 143
какой-то функции, то вы сможете выполнить код этой функции без пошагового прохождения всех ее строк кнопкой Step. Кнопка Over переводит вас прямо
к результату выполнения функции. А если вы уже находитесь внутри функции
или цикла, кнопка Out выполняет оставшийся код в теле функции или цикла,
после чего приостанавливает выполнение.
В следующем разделе мы рассмотрим код, содержащий ошибку, и вы узнаете,
как исправить ее в IDLE.
7.2. ИСПРАВЛЕНИЕ ОШИБОК
Теперь, когда вы знаете, как работает окно Debug Control, давайте рассмотрим
программу, содержащую ошибки.
В следующем коде определяется функция add_underscores(), которая получает
в аргументе один строковый объект word и возвращает новую строку с копией
word, где каждая буква окружена символами подчеркивания. Например, вызов
add_underscores("python") должен возвращать "_p_y_t_h_o_n_".
Код с ошибкой:
def add_underscores(word):
new_word = "_"
for i in range(len(word)):
new_word = word[i] + "_"
return new_word
phrase = "hello"
print(add_underscores(phrase))
Введите этот код в окне редактора, сохраните файл и запустите программу нажатием F5. Программа должна вывести _h_e_l_l_o_, но вместо этого выводится
o_, то есть буква o, за которой следует один символ подчеркивания.
Если вы уже видите, в чем проблема этого кода, пока не исправляйте ее. Этот
раздел должен научить вас пользоваться отладчиком IDLE для поиска ошибок.
ПРИМЕЧАНИЕ
Отладка может потребовать значительных усилий и времени, а ошибки бывают
коварными и трудноуловимыми.
Хотя в этом разделе рассматривается относительно простая ошибка, описанная схема анализа кода и поиска ошибок остается неизменной и для более
сложных случаев.
144 Глава 7 Поиск и исправление ошибок в коде
Если же вы не опознали проблему, не огорчайтесь! К концу этого раздела вы
найдете ее и научитесь отыскивать похожие проблемы в рабочем коде.
Отладка — практический навык, и по мере накопления опыта вы научитесь разрабатывать собственные приемы отладки. В этом разделе представлен простой
алгоритм из четырех шагов, который поможет вам на начальном этапе.
1. Предположите, в какой части кода может находиться ошибка.
2. Установите точку останова и проанализируйте код, выполняя проблемную
часть кода в пошаговом режиме с отслеживанием важных переменных.
3. Найдите строку кода с ошибкой (если она есть) и внесите изменения для
решения проблемы.
4. Повторяйте шаги 1–3, пока код не заработает так, как ожидалось.
Шаг 1. Предположите, где находится ошибка
Прежде всего следует определить часть кода, которая с большой вероятностью
содержит ошибку. Возможно, вам не удастся сразу найти точную позицию
ошибки, но обычно можно предположить, в какой части кода ее следует искать.
Обратите внимание: наша программа делится на две части — определение
функции (где определяется add_underscores()) и основной блок кода, в котором определяется переменная phrase со значением "hello", а затем выводится
результат вызова add_underscores(phrase).
Взгляните на основную часть:
phrase = "hello"
print(add_underscores(phrase))
Как вы думаете, может ли проблема скрываться здесь? Маловероятно, не правда
ли? Все в этих двух строках кода выглядит нормально. А значит, проблема, возможно, содержится в определении функции:
def add_underscores(word):
new_word = "_"
for i in range(len(word)):
new_word = word[i] + "_"
return new_word
Первая строка кода внутри функции создает переменную new_word со значением "_". Здесь все хорошо, и мы можем заключить, что проблема возникла
где-то в теле цикла for.
7.2. Исправление ошибок 145
Шаг 2. Установите точку останова
и проанализируйте код
Итак, вы определили, где предположительно находится ошибка. Установите
точку останова в начале цикла for, чтобы отследить в окне Debug, что именно
происходит внутри кода.
Откройте окно Debug и запустите файл. Выполнение прерывается в первой
строке, то есть в определении функции.
Нажмите кнопку Go, чтобы выполнить код вплоть до точки останова. Окно
Debug должно выглядеть примерно так:
В этой точке код приостанавливается непосредственно перед входом в цикл
for в функции add_underscores() . Обратите внимание: на панели Locals
отображаются две локальные переменные, word и new_word . В настоящее
время word содержит значение "hello", а new_word — значение "_", как и ожидалось.
146 Глава 7 Поиск и исправление ошибок в коде
Щелкните на кнопке Step, чтобы войти в цикл for. Окно Debug изменяется, и на
панели Locals появляется новая переменная i со значением 0.
i — счетчик, используемый в цикле for. Значение i показывает, какая итерация
цикла выполняется в настоящий момент.
Щелкните на кнопке Step еще раз. Взглянув на панель Locals, вы увидите, что
переменная new_word приняла значение "h_".
Что-то не так. Изначально new_word содержит значение "_", а при второй итерации цикла переменная должна содержать значение "_h_". Если щелкнуть
на кнопке Step еще несколько раз, вы увидите, что new_word присваивается e_,
потом l_ и т. д.
7.2. Исправление ошибок 147
Шаг 3. Найдите ошибку и попытайтесь
исправить ее
На данный момент можно заключить, что при каждой итерации цикла for
переменная new_word перезаписывается следующим символом строки "hello"
и завершающим символом подчеркивания. Так как цикл for содержит всего
одну строку кода, проблема наверняка находится здесь:
new_word = word[i] + "_"
Повнимательнее присмотритесь к этой строке. Она приказывает Python получить следующий символ слова, присоединить к нему символ подчеркивания
и присвоить полученную строку переменной new_word. Именно такое поведение
вы наблюдали при пошаговом выполнении цикла for.
Чтобы исправить проблему, необходимо приказать Python выполнить конкатенацию строки word[i] + "_" с существующим значением new_word. Нажмите
кнопку Quit в окне Debug, но пока не закрывайте окно. Откройте окно редактора
и замените строку в цикле for следующей:
new_word = new_word + word[i] + "_"
Шаг 4. Повторяйте шаги 1–3, пока ошибка
не исчезнет
Сохраните изменения в программе и снова запустите ее. В окне Debug нажмите
кнопку Go, чтобы выполнить код до точки останова.
ПРИМЕЧАНИЕ
Если вы закрыли отладчик на предыдущем шаге без нажатия Quit, при повторном открытии окна Debug может появиться следующая ошибка:
You can only toggle the debugger when idle
(Вы можете переключать отладчик только в режиме ожидания)
При завершении сеанса отладки всегда щелкайте на кнопке Go или Quit, или
у вас возникнут проблемы с его повторным открытием. Чтобы избавиться от
ошибки, необходимо закрыть и снова запустить IDLE.
Программа приостанавливается перед входом в цикл for в add_underscores().
Нажимайте кнопку Step и следите за тем, что происходит с переменной new_word
при каждой итерации. Успех! Все работает, как и предполагалось!
148 Глава 7 Поиск и исправление ошибок в коде
Первая попытка исправления ошибки оказалась успешной, так что повторять
шаги 1–3 больше не нужно. Впрочем, так будет не всегда. Иногда процесс приходится повторять многократно, прежде чем ошибку удастся исправить.
Альтернативные способы поиска ошибок
Работа с отладчиком может быть нетривиальной и долгой, но это самый надежный способ выявления ошибок в коде. Впрочем, отладчики доступны не всегда.
Системы с ограниченными ресурсами — например, компактные устройства
«интернета вещей» — часто не содержат встроенных отладчиков.
В таких ситуациях для поиска ошибок можно воспользоваться отладочным
выводом. Он использует print() для вывода на консоль текста, который сообщает, где выполняется программа и в каком состоянии находятся переменные
программы в определенных точках кода.
Например, вместо отладки предыдущей программы в окне Debug можно включить следующую строку в конец цикла for в add_underscores():
print(f"i = {i}; new_word = {new_word}")
Измененный код будет выглядеть так:
def add_underscores(word):
new_word = "_"
for i in range(len(word)):
new_word = word[i] + "_"
print(f"i = {i}; new_word = {new_word}")
return new_word
phrase = "hello"
print(add_underscores(phrase))
При выполнении файла в интерактивном окне выводится следующий результат:
i = 0; new_word = h_
i = 1; new_word = e_
i = 2; new_word = l_
i = 3; new_word = l_
i = 4; new_word = o_
o_
Вывод показывает, какое значение принимает new_word при каждой итерации
цикла for . Последняя строка, содержащая только символ подчеркивания,
появилась в результате выполнения print(add_underscore(phrase)) в конце
программы.
7.3. Итоги и дополнительные ресурсы 149
Просматривая эти результаты, можно прийти к тому же заключению, как и при
работе с окном Debug. Проблема в том, что new_word перезаписывается при
каждой итерации.
Отладочный вывод работает, но у него есть несколько недостатков по сравнению
с отладчиком. Прежде всего, вам приходится выполнять всю программу каждый
раз, когда вы захотите проанализировать значения переменных. Это может привести к значительным затратам времени по сравнению с использованием точек
останова. Также придется не забывать об удалении вызовов функции print()
после завершения отладки!
Пример цикла в этом разделе неплохо поясняет процесс отладки, но это не
лучший пример питонического кода. Использование индекса i указывает на
то, что существует более удобный способ записи цикла. Одно из возможных
усовершенствований — прямой перебор символов word. Один из возможных
способов выглядит так:
def add_underscores(word):
new_word = "_"
for letter in word:
new_word = new_word + letter + "_"
return new_word
Процесс переработки существующего кода для того, чтобы он был более наглядным, удобочитаемым и понятным или лучше соответствовал стандартам, установленным для команды, называется рефакторингом. В этой книге
рефакторингу уделяется не много времени, но он очень важен для создания
профессионального кода.
7.3. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы освоили работу с окном Debug в IDLE. Я показал, как анализировать значения переменных, вставлять точки останова и использовать кнопки
Step, Go, Over и Out.
Вы немного потренировались в отладке функции, содержащей ошибку, для
чего применили четырехшаговый процесс выявления и устранения ошибок.
1. Вырабатываем предположение, где находится ошибка.
2. Устанавливаем точку останова и анализируем код.
3. Ищем ошибку и пытаемся ее исправить.
4. Повторяем шаги 1–3, пока ошибку не удастся исправить.
150 Глава 7 Поиск и исправление ошибок в коде
Отладка — не только наука, но и искусство. И освоить ее можно только одним
способом — побольше практиковаться!
Один из способов тренировки — открыть окно Debug Control и использовать
его для пошагового выполнения кода других упражнений и задач в книге.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-debugging
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
zz
«Python Debugging With pdb», статья (https://realpython.com/pythondebugging-pdb/)
zz
«Python Debugging With pdb», видеокурс (https://realpython.com/courses/
python-debugging-pdb/)
ГЛАВА 8
Условная логика
и управление программой
Почти весь код, встречавшийся вам в книге до сих пор, был безусловным. Другими словами, в коде не было вариантов выбора. Все строки кода выполнялись
в том порядке, в котором они были записаны, или в порядке вызова функций
с возможными повторениями внутри циклов.
В этой главе я научу вас пользоваться условной логикой для написания программ, выполняющих разные действия в зависимости от конкретных условий.
В сочетании с функциями и циклами условная логика позволяет писать сложные
программы, способные обрабатывать различные ситуации.
В этой главе вы научитесь:
zz
сравнивать значения двух и более переменных;
zz
использовать команды if для управления последовательностью выполнения вашей программы;
zz
обрабатывать ошибки конструкциями try и except;
zz
применять условную логику для создания простых моделей.
Итак, за дело!
8.1. СРАВНЕНИЕ ЗНАЧЕНИЙ
Условная логика основана на выполнении разных действий в зависимости от
того, истинно или ложно некоторое выражение, которое называется условием.
Эту идею используют не только компьютеры. Люди постоянно пользуются
условной логикой для принятия решений.
Например, в США по закону алкогольные напитки можно покупать только
с 21 года. Утверждение «если вам не менее 21 года, вы можете купить пиво»
152 Глава 8 Условная логика и управление программой
может рассматриваться как пример условной логики. Часть «если вам не менее
21 года» определяет условие (возраст не менее 21 года), которое может быть
либо истинным, либо ложным.
В программировании условия часто задаются в форме сравнения двух значений — например, что одно значение больше другого или что два значения
равны. Для выполнения сравнения используется стандартный набор условных
знаков, называемых булевскими операторами сравнения; вероятно, многие из
них вам уже знакомы.
Булевские операторы сравнения перечислены в следующей таблице.
ОПЕРАТОР
СРАВНЕНИЯ
ПРИМЕР
СМЫСЛ
>
a>b
a больше b
<
a<b
a меньше b
>=
a >= b
а больше или равно b
<=
a <= b
a меньше или равно b
!=
a != b
a не равно b
==
a == b
a равно b
Термин «булевский» происходит от фамилии английского математика Джорджа
Буля, работы которого легли в основу современных вычислительных технологий. В честь Буля условная логика иногда называется булевской логикой.
Существует фундаментальный логический тип данных bool (сокращение от
Boolean), который всегда принимает только одно из двух возможных значений.
В Python этим двум значениям присвоены имена True и False:
>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>
Обратите внимание: и True, и False начинаются с буквы верхнего регистра.
Результат вычисления условия всегда является логическим значением:
>>> 1 == 1
True
>>> 3 > 5
False
8.1. Сравнение значений 153
В первом примере, так как 1 равно 1, результат 1 == 1 равен True. Во втором
примере 3 не больше 5, поэтому результат равен False.
ВАЖНО!
Распространенная ошибка при записи условий — использование оператора
присваивания = вместо == для проверки двух значений на равенство.
К счастью, при обнаружении такой ситуации Python выдает ошибку SyntaxError —
и вы узнаете об этом еще до запуска программы.
Булевские операторы сравнения удобно рассматривать как вопросы, относящиеся к двум значением: a == b спрашивает, содержат ли a и b одинаковое значение.
Аналогичным образом a != b спрашивает, содержат ли a и b разные значения.
Условные выражения не ограничиваются сравнением чисел. Их также можно
использовать для сравнения других значений — например, строк:
>>> "a" == "a"
True
>>> "a" == "b"
False
>>> "a" < "b"
True
>>> "a" > "b"
False
Последние два примера могут показаться кому-то странными. Как одна строка
может быть больше или меньше другой?
С числами операторы сравнения < и > представляют понятия «больше» и «меньше», но в более общем смысле они представляют концепцию упорядочения.
В этом отношении "a" < "b" проверяет, что строка "a" предшествует строке "b".
Но как упорядочиваются строки?
В Python строки упорядочиваются лексикографически — этот хитроумный
термин означает, что они следуют в том порядке, в котором идут в словаре. Таким образом, можно считать, что "a" < "b" проверяет, предшествует ли буква a
букве b в словаре.
Лексикографическое упорядочение также действует и для строк из двух и более
символов, для чего проверяется каждая порядковая буква строки:
154 Глава 8 Условная логика и управление программой
>>> "apple" < "astronaut"
True
>>> "beauty" > "truth"
False
Так как строки могут содержать и другие символы, кроме алфавитных, упорядочение также должно распространяться и на эти символы. Каждому символу соответствует число, называемое кодовым пунктом Юникода. Чтобы сравнить два
символа, Python берет кодовые пункты этих символов и сравнивает два числа.
Мы не будем углубляться в подробности работы кодовых пунктов Юникода.
На практике операторы сравнения < и > чаще всего используются с числами,
а не со строками.
Упражнения
1. Для каждого из следующих условных выражений предположите, какой
результат будет получен при их обработке — True или False. Затем введите
их в интерактивном окне, чтобы проверить свои ответы:
1 <= 1
1 != 1
1 != 2
"good" != "bad"
"good" != "Good"
123 == "123"
2. Для каждого из следующих выражений заполните пустые места (обозначенные __) соответствующим булевским оператором сравнения, чтобы
выражение давало результат True:
3 __ 4
10 __ 5
"jack" __ "jill"
42 __ "42"
8.2. ДОБАВИМ НЕМНОГО ЛОГИКИ
Кроме булевских операторов сравнения Python использует специальные ключевые слова (которые называются логическими операторами) для объединения
булевских выражений. Существуют три логических оператора — and, or и not.
Логические операторы используются для конструирования составных логических выражений. По большей части их смысл аналогичен смыслу в естественном
языке, хотя в Python действуют намного более точные правила их использования.
8.2. Добавим немного логики 155
Ключевое слово and
Рассмотрим следующие утверждения.
1. У кошек четыре ноги.
2. У кошек есть хвост.
В общем случае оба эти утверждения истинны.
Если объединить эти два утверждения связкой and, то полученное утверждение «у кошек четыре ноги and у кошек есть хвост» также является истинным.
Если же изменить смысл обоих утверждений на противоположный, составное
утверждение «у кошек не четыре ноги and у кошек нет хвоста» будет ложным.
Даже если объединить истинное утверждение с ложным, составное утверждение будет ложным. Оба утверждения — «у кошек четыре ноги and у кошек нет
хвоста» и «у кошек не четыре ноги and у кошек есть хвост» — будут ложными.
Когда два утверждения, P и Q, объединяются связкой and, составное утверждение «P and Q» будет истинным в том и только в том случае, если истинно как
P, так и Q.
Оператор and в Python работает точно так же. Четыре примера составных выражений с использованием and:
>>> 1 < 2 and 3 < 4 # Оба выражения истинны (True)
True
Оба выражения истинны, поэтому их комбинация также истинна (True).
>>> 2 < 1 and 4 < 3 # Оба выражения ложны (False)
False
Оба выражения ложны, поэтому их комбинация ложна (False).
>>> 1 < 2 and 4 < 3 # Второе выражение ложно (False)
False
Выражение 1 < 2 истинно, но выражение 4 < 3 ложно, поэтому их комбинация
ложна (False).
>>> 2 < 1 and 3 < 4 # Первое выражение ложно (False)
False
Выражение 2 < 1 ложно, а выражение 3 < 4 истинно, поэтому их комбинация
ложна (False).
156 Глава 8 Условная логика и управление программой
В следующей таблице приведена краткая сводка использования оператора and.
КОМБИНАЦИЯ С AND
РЕЗУЛЬТАТ
True and True
True
True and False
False
False and True
False
False and False
False
Каждое из этих правил можно проверить в интерактивном окне:
>>> True and True
True
>>> True and False
False
>>> False and True
False
>>> False and False
False
Ключевое слово or
Когда мы используем слово «или» (or) в быту, иногда подразумевается «исключающее или» — то есть истинным может быть либо первый вариант, либо
второй, но не оба сразу.
Например, в утверждении «я могу остаться or я могу уйти» используется «исключающее или»: нельзя одновременно остаться и уйти. Истинным может быть
только что-то одно.
Однако в языке Python ключевое слово or исключающим не является. Иначе
говоря, если P и Q — два выражения, то выражение P or Q истинно в любой из
следующих комбинаций.
1. P истинно.
2. Q истинно.
3. Истинно как P, так и Q.
Рассмотрим несколько примеров со сравнениями чисел:
>>> 1 < 2 or 3 < 4 # Оба выражения истинны (True)
True
8.2. Добавим немного логики 157
>>> 2 < 1 or 4 < 3 # Оба выражения ложны (False)
False
>>> 1 < 2 or 4 < 3 # Второе выражение ложно (False)
True
>>> 2 < 1 or 3 < 4 # Первое выражение ложно (False)
True
Обратите внимание: если хотя бы одна часть составного выражения равна True,
то, даже если другая часть равна False, результат все равно равен True.
В следующей таблице приведена краткая сводка использования оператора or.
КОМБИНАЦИЯ С OR
РЕЗУЛЬТАТ
True or True
True
True or False
True
False or True
True
False or False
False
И снова все результаты можно проверить в интерактивном окне:
>>> True or True
True
>>> True or False
True
>>> False or True
True
>>> False or False
False
Ключевое слово not
Ключевое слово not вычисляет логическое отрицание одного операнда:
ИСПОЛЬЗОВАНИЕ NOT
РЕЗУЛЬТАТ
not True
False
not False
True
158 Глава 8 Условная логика и управление программой
В этом также можно убедиться в интерактивном окне:
>>> not True
False
>>> not False
True
Следует помнить, что в сочетании с операторами сравнения (такими, как ==)
not не всегда ведет себя так, как вы ожидаете.
Например, not True == False возвращает True, но False == not True выдаст
ошибку:
>>> not True == False
True
>>> False == not True
File "<stdin>", line 1
False == not True
^
SyntaxError: invalid syntax
Это происходит из-за того, что Python разбирает логические операторы в соответствии с приоритетом операторов, аналогично вычислениям с применением
арифметических операторов.
Приоритеты логических и булевских операторов в порядке убывания приведены в следующей таблице. Операторы в одной строке обладают одинаковыми
приоритетами.
ПРИОРИТЕТЫ ОПЕРАТОРОВ (В ПОРЯДКЕ УБЫВАНИЯ)
<, <=, ==, >=, >
not
and
or
Снова взгляните на выражение False == not True. Так как not обладает более
низким приоритетом, чем ==, Python сначала пытается вычислить False == not,
а это синтаксически некорректно.
Чтобы избежать ошибки SyntaxError, заключите not True в круглые скобки:
>>> False == (not True)
True
8.2. Добавим немного логики 159
Группировка выражений в круглых скобках помогает прояснить, какие операторы принадлежат той или иной части составного выражения. Даже если
круглые скобки не обязательны, желательно использовать их, чтобы составные
выражения проще читались.
Построение сложных выражений
Ключевые слова and, or или not можно объединять с True и False для создания
более сложных выражений. Вот пример такого выражения:
True and not (1 != 1)
Как вы думаете, какое значение будет получено при его вычислении?
Чтобы узнать это, разобьем выражение, начиная с правой стороны. Выражение 1 != 1 дает False, так как значение 1 равно самому себе. Следовательно,
приведенное выражение можно упростить до следующего вида:
True and not (False)
Теперь not (False) эквивалентно not False, то есть True. Следовательно, выражение можно упростить еще раз:
True and True
Наконец, True and True дает просто True. Итак, несколько итераций — и нам
становится понятно, что True and not (1 != 1) дает True.
При работе со сложными выражениями лучше всего начать с самой сложной
части выражения и продвигаться к более простым.
Например, попробуем вычислить следующее выражение:
("A" != "A") or not (2 >= 3)
Начните с двух выражений в круглых скобках."A" != "A" дает результат False,
потому что значение "A" равно самому себе. 2 >= 3 также дает False, потому
что 2 меньше 3. В результате мы получим эквивалентное, но более простое
выражение:
(False) or not (False)
Так как not обладает более высоким приоритетом, чем or, приведенное выше
выражение эквивалентно следующему:
False or (not False)
160 Глава 8 Условная логика и управление программой
not False дает результат True, поэтому выражение можно упростить дополни-
тельно:
False or True
Наконец, поскольку любое составное выражение с or истинно, если истинно
хотя бы одно из выражений с двух сторон от or, можно заключить, что ("A" !=
"A") or not (2 >= 3) дает результат True.
В составном условии группировка выражений при помощи круглых скобок
упрощает чтение кода. Иногда круглые скобки даже необходимы для получения
ожидаемого значения.
Например, на первый взгляд можно предположить, что следующая команда
выведет True, тогда как на самом деле она возвращает False:
>>> True and False == True and False
False
Дело в том, что оператор == обладает более высоким приоритетом, чем and, поэтому Python интерпретирует выражение в форме True and (False == True) and
False. Так как False == True дает False, это выражение эквивалентно True and
False and False, что дает результат False.
Можно добавить круглые скобки, чтобы выражение давало результат True:
>>> (True and False) == (True and False)
True
Логические операторы и булевские операторы сравнения не очень понятны
с первого раза, так что, если материал этого раздела вам не удастся усвоить
с первого раза, — не беспокойтесь!
После небольшой практики вы научитесь разбираться, что происходит в таких
выражениях, и строить собственные составные условные команды, когда они
вам потребуются.
Упражнения
1. Определите, какой результат (True или False) будет получен при вычислении следующих выражений. Введите их в интерактивном окне
и проверьте свои ответы:
(1 <= 1) and (1 != 1)
not (1 != 2)
8.3. Управление последовательностью выполнения программы 161
("good" != "bad") or False
("good" != "Good") and not (1 == 1)
2. Добавьте круглые скобки там, где необходимо, чтобы каждое из следующих выражений давало результат True:
False == not True
True and False == True and False
not True and "A" == "B"
8.3. УПРАВЛЕНИЕ ПОСЛЕДОВАТЕЛЬНОСТЬЮ
ВЫПОЛНЕНИЯ ПРОГРАММЫ
Вы узнали, как сравнивать значения булевскими операторами сравнения и строить условные команды с логическими операторами. Теперь добавим логику
в код, чтобы он мог выполнять разные действия для разных условий.
Команда if
Команда if приказывает Python выполнить блок кода только в том случае, если
выполняется некоторое условие.
Например, следующая команда if выводит 2 and 2 is 4 в том случае, если условие
2 + 2 == 4 дает результат True:
if 2 + 2 == 4:
print("2 and 2 is 4")
Команда if, как и цикл while, состоит из трех частей.
1. Ключевое слово if.
2. Условие, за которым следует двоеточие.
3. Блок кода (с отступом), который выполняется в том случае, если условие
истинно.
В этом примере проверяется условие 2 + 2 == 4. Так как это выражение истинно,
выполнение команды if в IDLE выводит сообщение 2 and 2 is 4.
Если условие ложно, например 2 + 2 == 5, Python пропускает блок с отступом
и продолжает выполнение со следующей строки без отступа.
Так, следующая команда if ничего не выводит:
if 2 + 2 == 5:
print("Is this the mirror universe?")
162 Глава 8 Условная логика и управление программой
Вселенная, в которой условие 2 + 2 == 5 оказывается истинным, была бы довольно странной!
ПРИМЕЧАНИЕ
При отсутствии двоеточия после условия в команде if выдается ошибка
SyntaxError:
>>> if 2 + 2 == 4
SyntaxError: invalid syntax
После выполнения блока с отступом в команде if Python продолжает выполнять дальнейший код.
Пример:
grade = 95
if grade >= 70:
print("You passed the class!")
print("Thank you for attending.")
Результат выглядит так:
You passed the class!
Thank you for attending.
(Вы сдали экзамен!
Спасибо за участие.)
Так как переменная grade содержит значение 95, условие grade >= 70 истинно
и выводится строка "You passed the class!". Затем Python выполняет остаток
кода и выводит строку "Thank you for attending."
Если изменить значение grade на 40, результат будет выглядеть так:
Thank you for attending.
Строка print("Thank you for attending.") выполняется независимо от того,
больше ли переменная grade значения 70 или нет, потому что она следует после
блока кода с отступом в команде if.
Студент не узнает о своей неудаче, если все, что он увидит, — сообщение "Thank
you for attending." Добавим другую команду if, которая будет сообщать студенту, что он не сдал экзамен, если его оценка grade меньше 70:
8.3. Управление последовательностью выполнения программы 163
grade = 40
if grade >= 70:
print("You passed the class!")
if grade < 70:
print("You did not pass the class :(")
print("Thank you for attending.")
Теперь результат выглядит так:
You did not pass the class :(
Thank you for attending.
В естественном языке альтернативу можно было бы описать словом «иначе».
Например: «Если вы набрали 70 и более баллов, экзамен сдан. Иначе экзамен
не сдан».
К счастью, существует ключевое слово, которое делает в Python то же, что
и слово «иначе».
Ключевое слово else
Ключевое слово else используется после команды if для выполнения кода
только в том случае, если условие команды if ложно.
В следующем примере else используется для вывода сообщения о том, сдал ли
студент экзамен, тем самым уменьшая объем кода:
grade = 40
if grade >= 70:
print("You passed the class!")
else:
print("You did not pass the class :(")
print("Thank you for attending.")
Эти команды if и else читаются так: «Если значение grade не менее 70, вывести сообщение "You passed the class!" Иначе вывести сообщение "You did
not pass the class :(".
Обратите внимание: ключевое слово else не имеет условия и за ним следует
двоеточие. Условие не нужно, потому что команда else выполняет код при нарушении условия команды if.
164 Глава 8 Условная логика и управление программой
ВАЖНО!
При отсутствии двоеточия после ключевого слова else Python выдаст ошибку
SyntaxError:
>>> if 2 + 2 == 5:
...
print("Who broke my math?")
... else
SyntaxError: invalid syntax
В этом примере выводится следующий результат:
You did not pass the class :(
Thank you for attending.
Даже при выполнении блока с отступом после else Python все равно выполнит
строку, которая выводит сообщение "Thank you for attending."
Ключевые слова if и else хорошо работают совместно, если вы проверяете
­условие, имеющее ровно два состояния. Однако иногда требуется проверить
три и более варианта. В таких случаях используется ключевое слово elif.
Ключевое слово elif
Ключевое слово elif (сокращение от else if) может использоваться для добавления дополнительных условий после команды if.
Команды elif, как и if, состоят из трех частей.
1. Ключевое слово elif.
2. Условие, за которым следует двоеточие.
3. Блок с отступом, который выполняется в том случае, если условие истинно.
ВАЖНО!
При отсутствии двоеточия в конце команды elif выдается ошибка SyntaxError:
>>> if 2 + 2 == 5:
...
print("Who broke my math?")
... elif 2 + 2 == 4
SyntaxError: invalid syntax
8.3. Управление последовательностью выполнения программы 165
Следующая программа объединяет if, elif и else для вывода буквенной оценки
успеваемости студента:
grade = 85 # 1
if grade >= 90: # 2
print("You passed the class with an A.")
elif grade >= 80: # 3
print("You passed the class with a B.")
elif grade >= 70: # 4
print("You passed the class with a C.")
else: # 5
print("You did not pass the class :(")
print("Thanks for attending.") # 6
Оба условия, grade >= 80 и grade >= 70, истинны, когда переменная grade равна
85, поэтому можно ожидать, что будут выполнены оба блока elif в строках #3
и #4. Однако выполнен будет только первый блок, для которого проверяемое
условие окажется истинным. Все остальные блоки elif и else пропускаются.
Программа выдает следующий результат:
You passed the class with a B.
Thanks for attending.
Проанализируем работу кода поэтапно.
1. В строке 1 grade присваивается значение 85.
2. Условие grade >= 90 ложно, поэтому команда if в строке 2 пропускается.
3. Условие grade >= 80 истинно, поэтому выполняется блок под командой
elif в строке 3 и выводится сообщение "You passed the class with a B.".
4. Команды elif и else в строках 4 и 5 пропускаются, так как было выполнено условие команды elif в строке 3.
5. Наконец, выполняется строка 6 и выводится сообщение "Thanks for
attending.".
Ключевые слова if, elif и else принадлежат к числу наиболее часто используемых в Python. Они позволяют писать код, который по-разному реагирует
на различные условия.
Команда if позволяет решать более сложные задачи, чем код без условной
логики. Команды if даже можно вкладывать друг в друга для реализации неимоверно сложной логики!
166 Глава 8 Условная логика и управление программой
Вложенные команды if
По аналогии с тем, как циклы while можно вкладывать друг в друга, также допустимо вложить команду if внутрь другой команды if, чтобы создать сложную
структуру принятия решений.
Рассмотрим следующий сценарий. Два человека играют в спортивную игру.
Требуется определить победителя на основании счета и игры.
zz
Если они играют в баскетбол, то побеждает игрок с большим счетом.
zz
Если они играют в гольф, то побеждает игрок с меньшим счетом.
zz
В любом виде спорта при равенстве счетов игра заканчивается вничью.
Следующая программа решает эту задачу с использованием вложенных команд
if, отражающих описанные выше условия:
sport = input("Enter a sport: ")
p1_score = int(input("Enter player 1 score: "))
p2_score = int(input("Enter player 2 score: "))
# 1
if sport.lower() == "basketball":
if p1_score == p2_score:
print("The game is a draw.")
elif p1_score > p2_score:
print("Player 1 wins.")
else:
print("Player 2 wins.")
# 2
elif sport.lower() == "golf":
if p1_score == p2_score:
print("The game is a draw.")
elif p1_score < p2_score:
print("Player 1 wins.")
else:
print("Player 2 wins.")
# 3
else:
print("Unknown sport")
Сначала программа предлагает пользователю ввести вид спорта и счета двух
игроков.
Команда if в разделе #1 выполняется в том случае, если переменная sport
содержит значение "basketball". Метод .lower() гарантирует, что такие варианты ввода, как "Basketball" или "BasketBall", будут интерпретироваться
одинаково.
8.3. Управление последовательностью выполнения программы 167
Если счета обоих игроков равны, то игра завершается вничью. В противном
случае игрок с более высоким счетом побеждает.
Команда if в разделе #2 выполняется в том случае, если переменная sport содержит значение "golf". Если счета равны, то игра завершается вничью. В противном случае игрок с меньшим счетом побеждает.
Команда if в разделе #3 выполняется в том случае, если переменная sport содержит какое-то другое значение, отличное от "basketball" или "golf". В этом
случае выводится сообщение "Unknown sport" (неизвестный вид спорта).
Результат выполнения программы зависит от входных значений. Пример выполнения со вводом "basketball":
Enter a sport: basketball
Player 1 score: 75
Player 2 score: 64
Player 1 wins.
А вот как выглядит результат с теми же счетами при вводе "golf":
Enter a sport: golf
Player 1 score: 75
Player 2 score: 64
Player 2 wins.
Если указать любой другой вид спорта, кроме баскетбола или гольфа, программа
выводит сообщение Unknown sport.
В итоге возможно семь вариантов выполнения программы.
ВИД СПОРТА
СЧЕТ ДВУХ ИГРОКОВ
“basketball”
p1_score == p2_score
“basketball”
p1_score > p2_score
“basketball”
p1_score < p2_score
“golf”
p1_score == p2_score
“golf”
p1_score > p2_score
“golf”
p1_score < p2_score
Любые другие
Любые комбинации
Вложенные команды if дают много возможных вариантов выполнения кода.
Если программа содержит более двух уровней вложенных команд if, число
возможных вариантов стремительно возрастает.
168 Глава 8 Условная логика и управление программой
ПРИМЕЧАНИЕ
При слишком большой глубине вложения команд if программа значительно
усложняется и становится трудно предсказать поведение программы при
заданных условиях.
По этой причине использовать вложенные команды if обычно не рекомендуется.
Давайте упростим приведенную выше программу, сократив число вложенных
команд if.
Прежде всего, независимо от вида спорта, игра завершается вничью, если
p1_score равно p2_score. Таким образом, проверку равенства можно вывести
из вложенных команд if для каждого вида спорта и преобразовать в одну
­команду if:
if p1_score == p2_score:
print("The game is a draw.")
elif sport.lower() == "basketball":
if p1_score > p2_score:
print("Player 1 wins.")
else:
print("Player 2 wins.")
elif sport.lower() == "golf":
if p1_score < p2_score:
print("Player 1 wins.")
else:
print("Player 2 wins.")
else:
print("Unknown sport.")
Осталось только шесть возможных вариантов выполнения.
Впрочем, и это немало. Нельзя ли еще как-то упростить программу?
Одно из возможных решений: игрок 1 выигрывает при выполнении любого из
следующих условий.
1. sport содержит "basketball", а p1_score больше p2_score.
2. sport содержит "golf", а p1_score меньше p2_score.
8.3. Управление последовательностью выполнения программы 169
Это можно описать составными условными выражениями:
sport = sport.lower()
p1_wins_bball = (sport == "basketball") and (p1_score > p2_score)
p1_wins_golf = (sport == "golf") and (p1_score < p2_score)
p1_wins = player1_wins_basketball or player1_wins_golf
Код достаточно плотный, поэтому мы разберем его последовательно.
Сначала строка, присвоенная sport, преобразуется к нижнему регистру, чтобы
значение можно было сравнивать с другими строками, не беспокоясь о возможных ошибках, связанных с расхождениями в регистре символов.
В следующей строке находится структура, которая на первый взгляд кажется
немного странной. За оператором присваивания = следует выражение с оператором проверки равенства ==. Эта строка вычисляет следующее составное
логическое выражение и присваивает результат переменной p1_wins_bball:
(sport == "basketball") and (p1_score > p2_score)
Если sport содержит "basketball", а p1_score больше p2_score, значение
p1_wins_bball истинно.
Затем аналогичная операция выполняется с переменной p1_wins_golf. Если
sport содержит "golf", а p1_score меньше p2_score, значение p1_wins_golf
истинно.
Наконец, переменная p1_wins содержит True, если игрок 1 выиграл в баскетбол
или гольф, и False в противном случае.
С этими изменениями программу можно довольно серьезно упростить:
sport = sport.lower()
if p1_score == p2_score:
print("The game is a draw.")
elif (sport == "basketball") or (sport == "golf"):
p1_wins_bball = (sport == "basketball") and (p1_score > p2_score)
p1_wins_golf = (sport == "golf") and (p1_score < p2_score)
p1_wins = p1_wins_bball or p1_wins_golf
if p1_wins:
print("Player 1 wins.")
else:
print("Player 2 wins.")
else:
print("Unknown sport")
170 Глава 8 Условная логика и управление программой
В обновленной версии программы есть всего четыре варианта выполнения и код
получается более понятным.
Иногда без вложенных команд if не обойтись. Но если вы заметите, что вам
приходится писать их слишком много, возможно, стоит остановиться и подумать, как упростить код.
Упражнения
1. Напишите программу, которая запрашивает у пользователя слово при
помощи функции input() и сравнивает длину слова с числом 5. Программа должна вывести один из следующих результатов в зависимости
от длины пользовательского ввода:
"Длина ввода меньше 5 символов"
"Длина ввода больше 5 символов"
"Длина ввода составляет 5 символов"
2. Напишите программу, которая выводит сообщение "Я загадал(а) число
от 1 до 10. Угадайте его". Получите число от пользователя при помощи
функции input(). Если пользователь вводит число 3, программа должна
вывести сообщение "Вы победили!", а для любого другого ввода программа
выводит сообщение "Вы проиграли".
8.4. ЗАДАЧА: ПОИСК МНОЖИТЕЛЕЙ ЧИСЛА
Множителем положительного целого числа n называется любое положительное
целое число, меньшее либо равное n, на которое n делится без остатка.
Например, 3 является множителем 12, потому что при делении 12 на 3 получается 4 без остатка. С другой стороны, 5 множителем 12 не является, потому
что 12 делится на 5 с остатком 2.
Напишите программу factors.py, которая запрашивает у пользователя положительное целое число, а затем выводит список множителей этого числа. Пример
запуска программы с выводом результата:
Enter a positive integer: 12
1 is a factor of 12
2 is a factor of 12
3 is a factor of 12
4 is a factor of 12
6 is a factor of 12
12 is a factor of 12
8.5. Управление циклами 171
Подсказка: вспомните, о чем мы говорили в главе 5, — оператор % может использоваться для получения остатка от деления двух чисел.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
8.5. УПРАВЛЕНИЕ ЦИКЛАМИ
В главе 6 я рассказывал, как выполнять блок кода в циклах for или while. Циклы
удобны для выполнения повторяющихся операций и для обработки большого
количества разных входных значений по одной схеме.
В этом разделе речь пойдет о команде if, вложенной в циклы for. Также я познакомлю вас с двумя ключевыми словами, break и continue, которые позволяют
управлять выполнением цикла.
Команды if и циклы for
Блок кода в цикле for не отличается от любого другого блока. Это означает, что
команда if может размещаться внутри цикла for точно так же, как и в любом
другом фрагменте кода.
В следующем примере цикл for с командой if используется для вычисления
и вывода суммы всех четных целых чисел, меньших 100:
sum_of_evens = 0
for n in range(101):
if n % 2 == 0:
sum_of_evens = sum_of_evens + n
print(sum_of_evens)
Сначала переменная sum_of_evens инициализируется значением 0. Затем программа в цикле перебирает числа от 0 до 100 и прибавляет четные значения
к sum_of_evens. Итоговое значение sum_of_evens равно 2550.
break
Ключевое слово break приказывает Python выйти из цикла. Другими словами,
цикл немедленно останавливается, а выполнение продолжается с кода, следующего после цикла.
172 Глава 8 Условная логика и управление программой
Например, следующий код в цикле перебирает числа от 0 до 3, но цикл прерывается при достижении числа 2:
for n in range(4):
if n == 2:
break
print(n)
print(f"Finished with n = {n}")
В результате выводятся только первые два числа:
0
1
Finished with n = 2
continue
Ключевое слово continue пропускает весь оставшийся код в теле цикла и переходит к следующей итерации.
Например, следующий код перебирает числа от 0 до 3 и выводит каждое число,
кроме 2:
for i in range(4):
if i == 2:
continue
print(i)
print(f"Finished with i = {i}")
В вывод включаются все числа, кроме 2:
0
1
3
Finished with i = 3
ПРИМЕЧАНИЕ
Всегда желательно присваивать переменным короткие, но содержательные
имена, по которым вы легко определите, что они должны представлять.
Буквы i, j, k и n являются исключениями, потому что они очень часто встречаются в программировании.
Эти буквы почти всегда используются для временных переменных с коротким
сроком жизни в качестве счетчиков в цикле.
8.5. Управление циклами 173
Подведем итог: ключевое слово break прерывает цикл при выполнении условия,
а ключевое слово continue пропускает оставшийся код текущей итерации цикла
при выполнении условия.
Циклы for … else
Хотя эта структура используется не так часто, циклы Python тоже могут включать секцию else. Рассмотрим пример:
phrase = "it marks the spot"
for character in phrase:
if character == "X":
break
else:
print("There was no 'X' in the phrase")
Цикл for в этом примере перебирает символы строки "it marks the spot" и останавливается при обнаружении буквы "X". Выполнив код примера, вы увидите,
что на консоль выводится сообщение There was no 'X' in the phrase.
Теперь измените текст на "X marks the spot". При выполнении того же кода
с новым текстом не будет выведено ничего. Что происходит?
Код в блоке else выполняется только в том случае, если предшествующий цикл
for завершился без выполнения команды break.
Таким образом, при выполнении кода с phrase = "it marks the spot" строка кода
с командой break выполнена не будет, потому что фраза не содержит символа X.
Вместо этого выполняется блок else и выводится строка "There was no 'X' in
the phrase".
С другой стороны, при выполнении кода с phrase = "X marks the spot" будет
выполнена строка с командой break; таким образом, блок else не будет выполняться и вывод отсутствует.
В следующем примере пользователю предоставляются три попытки для ввода
пароля:
for n in range(3):
password = input("Password: ")
if password == "I<3Bieber":
break
print("Password is incorrect.")
else:
print("Suspicious activity. The authorities have been alerted.")
174 Глава 8 Условная логика и управление программой
Программа перебирает числа от 0 до 2. При каждой итерации пользователю
предлагается ввести пароль. Если введенный пароль правилен, команда break
используется для выхода из цикла. В противном случае программа сообщает,
что пароль неправильный, и пользователь повторяет попытку.
После трех безуспешных попыток цикл for завершается без выполнения строки
кода, содержащей break. В этом случае выполняется блок else и пользователь
получает сообщение о подозрительной активности.
ПРИМЕЧАНИЕ
Этот раздел мы посвятили циклам for, потому что они используются чаще
других.
Тем не менее все упоминаемые возможности работают и в циклах while. Другими словами, команды break и continue можно выполнять и в циклах while.
Цикл while даже может иметь секцию else!
Условная логика в теле цикла открывает новые возможности управления
выполнением кода. Цикл можно остановить ключевым словом break или пропустить текущую итерацию ключевым словом continue. Можно даже сделать
так, что некоторый код будет выполняться только в том случае, если цикл был
завершен без выполнения команды break.
Это чрезвычайно мощные инструменты, которые пригодятся любому программисту.
Упражнения
1. Используя команду break, напишите программу, которая многократно
запрашивает у пользователя данные и завершается только тогда, когда
пользователь введет "q" или "Q".
2. Используя команду continue, напишите программу, которая перебирает
числа от 1 до 50 и выводит все числа, не кратные 3.
8.6. ВОССТАНОВЛЕНИЕ ПОСЛЕ ОШИБОК
Ошибки в коде неприятны, но абсолютно нормальны! Ошибки случаются даже
у лучших программистов.
Программисты часто называют ошибки времени выполнения «исключениями».
Таким образом, когда вы сталкиваетесь с ошибкой, поздравьте себя! Вы заставили свой код сотворить нечто исключительное.
8.6. Восстановление после ошибок 175
Чтобы создавать надежные программы, необходимо иметь возможность обрабатывать ошибки, вызванные некорректным пользовательским вводом или другим
непредсказуемым источником. В этом разделе я расскажу, как это делается.
Зоопарк исключений
Когда в вашей программе происходит исключение, полезно знать, что именно
пошло не так. В Python существует ряд встроенных типов исключений для
описания разных видов ошибок.
Некоторые уже встречались вам в этой книге. Для удобства я собрал их в этом
разделе, а также добавил к ним ряд новых представителей.
ValueError
Ошибка ValueError происходит, когда операция обрабатывает недействительное значение.
Например, попытка преобразования строки "not a number" в целое число приводит к ошибке ValueError:
>>> int("not a number")
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
int("not a number")
ValueError: invalid literal for int() with base 10: 'not a number'
Последняя строка выводит имя исключения и описание проблемы. Этот общий
формат используется для всех исключений Python.
TypeError
Ошибка TypeError происходит при выполнении операции со значением неправильного типа. Например, попытка суммирования строки и целого числа
приведет к ошибке TypeError:
>>> "1" + 2
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
"1" + 2
TypeError: can only concatenate str (not "int") to str
NameError
Ошибка NameError происходит при попытке использования имени переменной,
которое не было определено в программе:
176 Глава 8 Условная логика и управление программой
>>> print(does_not_exist)
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
print(does_not_exist)
NameError: name 'does_not_exist' is not defined
ZeroDivisionError
Ошибка ZeroDivisionError происходит в том случае, если делитель в операции
деления равен нулю:
>>> 1 / 0
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
1 / 0
ZeroDivisionError: division by zero
OverflowError
Ошибка OverflowError происходит, если результат арифметической операции
оказывается слишком большим. Например, при попытке возвести значение 2.0
в степень 1_000_000 вы получите ошибку OverflowError:
>>> pow(2.0, 1_000_000)
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
pow(2.0, 1_000_000)
OverflowError: (34, 'Result too large')
В главе 5 мы уже говорили, что целые числа в Python обладают неограниченной
точностью. Это означает, что ошибки OverflowError могут происходить только
с числами с плавающей точкой. Возведение целого числа 2 в степень 1_000_000
не приведет к ошибке OverflowError!
Полный список встроенных исключений Python приведен в официальной документации (https://docs.python.org/3/library/exceptions.html).
Ключевые слова try и except
Иногда можно предусмотреть возможность того или иного исключения. Вместо
того чтобы позволить программе аварийно завершиться, можно перехватить
ошибку в момент ее возникновения и попытаться скорректировать ее.
Например, можно предложить пользователю ввести целое число. Если он укажет значение, не являющееся целым числом (например, строку "a"), следует
сообщить ему, что введено недопустимое значение.
8.6. Восстановление после ошибок 177
Для предотвращения аварийного завершения программы можно воспользоваться ключевыми словами try и except. Рассмотрим пример:
try:
number = int(input("Enter an integer: "))
except ValueError:
print("That was not an integer")
Ключевое слово try обозначает начало блока try, и после него ставится двоеточие. Код с отступом, следующий за try, выполняется в программе. В данном
случае пользователю предлагается ввести целое число. Так как input() возвращает строку, пользовательский ввод преобразуется в целое число вызовом
int(), а результат присваивается переменной number.
Если введенное значение не является целым числом, операция int() выдает
ошибку ValueError. В таком случае будет выполнен код с отступом, расположенный под строкой except ValueError. Таким образом, вместо аварийного
завершения программы выводится строка "That was not an integer". Если пользователь введет допустимое целое значение, то код в блоке except ValueError
выполняться не будет.
Но при исключении другого типа (например, TypeError) программа аварийно
завершится. В приведенном выше примере обрабатывается только один тип
исключения — ValueError.
Однако секция except может обрабатывать сразу несколько типов исключений.
Для этого следует разделить имена исключений запятыми и заключить список
имен в круглые скобки:
def divide(num1, num2):
try:
print(num1 / num2)
except (TypeError, ZeroDivisionError):
print("encountered a problem")
В этом примере divide() получает два параметра, num1 и num2, и выводит результат деления num1 на num2.
Если divide() вызывается со строковым аргументом, то операция деления выдает ошибку TypeError. Кроме того, если аргумент num2 равен нулю, появляется
сообщение об ошибке ZeroDivisionError.
Строка except (TypeError, ZeroDivisionError) обрабатывает оба исключения
и выводит строку "encountered a problem" при возникновении любого из двух
исключений.
178 Глава 8 Условная логика и управление программой
Впрочем, во многих случаях полезно перехватывать ошибки по отдельности; это
позволит вывести более осмысленный текст. Для этого после блока try следует
определить несколько блоков except:
def divide(num1, num2):
try:
print(num1 / num2)
except TypeError:
print("Both arguments must be numbers")
except ZeroDivisionError:
print("num2 must not be 0")
В этом примере ошибки TypeError и ZeroDivisionError обрабатываются по отдельности. Это позволяет вывести более точное сообщение, если что-то пойдет
не так.
Если одно из значений num1 или num2 не является числом, то программа выдает
ошибку TypeError и выводит сообщение "Both arguments must be numbers". Если
значение num2 равно нулю, выдается ошибка ZeroDivisionError и выводится
сообщение "num2 must not be 0".
except без исключения
Ключевое слово except также может использоваться само по себе, без указания
конкретных исключений:
try:
# Много потенциально опасных операций
except:
print("Something bad happened!")
Если при выполнении кода в блоке try произойдет любое исключение, будет выполнен блок except и на экране появится сообщение "Something bad happened!"
Казалось бы, это отличный способ предотвратить аварийное завершение ваших
программ. Но на самом деле поступать так не рекомендуется!
Для этого есть несколько причин, но начинающим программистам важно знать,
что перехват всех исключений скроет ошибки в вашем коде и создаст ложное
чувство уверенности в том, что он работает, как ожидалось.
Если вы будете перехватывать только конкретные исключения, то при обнаружении непредвиденных ошибок Python выведет трассировку и подробности
ошибки — и вы получите больше информации для отладки вашего кода.
8.7. Моделирование событий и вычисление вероятностей 179
Упражнения
1. Напишите программу, которая предлагает пользователю ввести целое
число. Если пользователь ввел что-то другое, программа должна перехватить ошибку ValueError и вывести сообщение "Try again."
Когда пользователь задаст целое число, программа должна вывести это
число и завершить работу без сбоев.
2. Напишите программу, которая предлагает пользователю ввести строку
и целое число n, а затем выводит символ с индексом n во введенной строке.
Используйте механизм обработки ошибок для того, чтобы предотвратить
аварийное завершение программы, если пользователь задаст что-то кроме
целого числа или если индекс выходит за границы массива. Программа должна выводить разные сообщения об ошибках в зависимости от типа ошибки.
8.7. МОДЕЛИРОВАНИЕ СОБЫТИЙ
И ВЫЧИСЛЕНИЕ ВЕРОЯТНОСТЕЙ
В этом разделе я покажу, как применить некоторые концепции циклов и условной логики, о которых мы рассказали ранее, при решении реальной задачи —
моделировании событий и вычислении вероятностей.
Мы проведем простое моделирование, известное под названием эксперимента
Монте-Карло. Каждый эксперимент состоит из испытания — некоторого процесса,
который можно повторить (например, броска монеты). Каждое испытание генерирует результат (например, падение монеты орлом или решкой вверх). Испытание
повторяется многократно для вычисления вероятности того или иного исхода.
Для программирования этой схемы необходимо включить в код источник
случайности.
Модуль random
Python предоставляет набор функций для генерирования случайных чисел
в модуле random. Стандартная библиотека Python — организованная коллекция
модулей, которую можно импортировать в ваш код для решения различных задач.
Чтобы импортировать модуль random, введите следующую команду в интер­
активном окне IDLE:
>>> import random
Теперь вы можете использовать функции из модуля random в своем коде.
180 Глава 8 Условная логика и управление программой
ПРИМЕЧАНИЕ
Модули и команды import более подробно рассматриваются в главе 11 «Модули и пакеты».
Функция randint() модуля random имеет два обязательных параметра a и b, оба
должны быть целыми числами. Функция возвращает случайное целое число,
которое больше или равно a, но меньше или равно b. Например, следующий код
создает случайное целое число от 1 до 10:
>>> random.randint(1, 10)
9
Так как функция возвращает случайный результат, скорее всего, вы получите
другой вывод вместо 9. Если снова выполнить тот же код, он с большой вероятностью вернет иное число.
Так как функция randint() принадлежит модулю random, для ее использования
перед именем необходимо указать имя модуля random и точку.
При использовании randint() важно помнить, что оба параметра, a и b, должны
быть целыми числами, а вывод равен a, b или любому числу между ними. Например, random.randint(0, 1) возвращает одно из двух случайно выбранных
чисел — 0 или 1.
Все целые числа в диапазоне от a до b будут возвращаться randint() с равной
вероятностью. Таким образом, для randint(1, 10) все целые числа от 1 до 10
будут возвращаться с вероятностью 10%. Для randint(0, 1) вероятность возвращения 0 составляет 50%.
Симметричная монета
Воспользуемся функцией randint() для моделирования бросков симметричной монеты. Под симметричной понимается монета, при броске которой орел
и решка выпадают с равной вероятностью.
Одним испытанием в эксперименте будет один бросок монеты. В результате
должен быть получен либо орел, либо решка. Вопрос в том, каким окажется
соотношение падения монеты орлом и решкой вверх при очень большом количестве бросков монеты.
Давайте подумаем, как решать эту задачу. Необходимо хранить информацию
о том, сколько раз выпали орел или решка; для этого потребуются два счетчика.
Каждое испытание состоит из двух фаз.
8.7. Моделирование событий и вычисление вероятностей 181
1. Бросить монету.
2. Если монета упала орлом вверх, обновить счетчик орлов. Если монета
упала решкой вверх, обновить счетчик решек.
Испытание повторяется много раз — допустим, десять тысяч. Для подобных
операций хорошо подходят циклы for по диапазону range(10_000).
Итак, план готов. Теперь можно написать функцию coin_flip(), которая случайным образом возвращает строку "heads" или "tails". Эта задача решается
вызовом random.randint(0, 1). Значение 0 интерпретируется как орел ("heads"),
а 1 — как решка ("tails").
Код функции coin_flip():
import random
def coin_flip():
"""Возвращает случайно выбранную строку 'heads' или 'tails'."""
if random.randint(0, 1) == 0:
return "heads"
else:
return "tails"
Если random.randint(0, 1) возвращает 0, функция coin_flip() возвращает
"heads". В противном случае coin_flip() возвращает "tails".
Теперь можно написать цикл for, который десять тысяч раз моделирует бросок монеты и обновляет соответствующие счетчики падения монеты орлом
и решкой:
# Счетчики инициализируются нулями
heads_tally = 0
tails_tally = 0
for trial in range(10_000):
if coin_flip() == "heads":
heads_tally = heads_tally + 1
else:
tails_tally = tails_tally + 1
Сначала создаются две переменные-счетчики heads_tally и tails_tally, которые инициализируются целым числом 0.
Затем цикл for выполняется десять тысяч раз, при каждой итерации вызывается coin_flip(). Если функция coin_flip() вернула строку "heads", то
переменная heads_tally увеличивается на 1. В противном случае tails_tally
увеличивается на 1.
182 Глава 8 Условная логика и управление программой
Наконец, программа выводит соотношение падения монеты орлом и решкой:
ratio = heads_tally / tails_tally
print(f"The ratio of heads to tails is {ratio}")
Сохранив этот код в файле и выполнив его несколько раз, вы увидите, что
результат обычно оказывается в интервале от 0,98 до 1,02. Если увеличить
range(10_000) в цикле for, допустим, до range(50_000), то результаты приблизятся к 1,0.
Такое поведение выглядит разумно. Так как монета симметрична, следует ожидать, что после множества бросков количество падений монеты орлом вверх
будет примерно равно количеству падений решкой вверх.
Однако в жизни идеальная симметрия встречается редко. Монета может быть
слегка деформирована, из-за чего чаще будет выпадать орел (или, наоборот,
решка). Как же смоделировать такую несимметричную монету?
Несимметричные монеты
Функция randint() возвращает 0 или 1 с равной вероятностью. Если 0 представляет решку, а 1 — орла, то для моделирования несимметричной монеты
необходимо повысить вероятность возвращения 0 или 1.
Функция random() вызывается без аргументов и возвращает число с плавающей
точкой, большее или равное 0.0, но меньшее 1.0. Все возвращаемые значения
равновероятны. В теории вероятностей это называется равномерным распределением.
Одно из следствий равномерного распределения заключается в том, что для заданного числа n в интервале от 0 до 1 вероятность того, что random() вернет число
меньше n, равна n. Например, вероятность того, что random() вернет значение меньше 0.8, равна 0.8, а вероятность того, что результат random() меньше 0.25, равна 0.25.
Используя этот факт, можно написать функцию, которая моделирует бросок
монеты, но возвращает решку с заданной вероятностью:
import random
def unfair_coin_flip(probability_of_tails):
if random.random() < probability_of_tails:
return "tails"
else:
return "heads"
Например, unfair_coin_flip(.7) вернет "tails" c вероятностью 70%.
8.8. Задача: моделирование эксперимента с броском монеты 183
Перепишем программу, созданную ранее, используя в каждом испытании несимметричный бросок unfair_coin_flip():
heads_tally = 0
tails_tally= 0
for trial in range(10_000):
if unfair_coin_flip(.7) == "heads":
heads_tally = heads_tally + 1
else:
tails_tally = tails_tally + 1
ratio = heads_tally / tails_tally
print(f"The ratio of heads to tails is {ratio}")
После многократного моделирования вы увидите, что соотношение падений
монеты орлом к падениям решкой, которое было равно 1 в эксперименте с симметричной монетой, составляет около 0.43.
Итак, вы познакомились с функциями randint() и random() из модуля random
и узнали, как использовать условную логику и циклы для моделирования
бросков монеты. Подобные модели используются во многих дисциплинах для
прогнозирования и компьютерного моделирования реальных событий.
Модуль random предоставляет множество полезных функций для генерирования
случайных чисел и моделирования. За дополнительной информацией о random
обращайтесь к статье «Generating RandomData in Python (Guide)» (https://
realpython.com/python-random/) на сайте Real Python.
Упражнения
1. Напишите функцию roll(), которая использует randint() для моделирования броска симметричного кубика, возвращающего случайное целое
число от 1 до 6.
2. Напишите программу, которая моделирует 10 000 бросков игрального
кубика и выводит среднее значение из тех, что выпали.
8.8. ЗАДАЧА: МОДЕЛИРОВАНИЕ ЭКСПЕРИМЕНТА
С БРОСКОМ МОНЕТЫ
Допустим, вы многократно бросаете симметричную монету, пока орел и решка
не выпадут хотя бы по одному разу. Другими словами, после первого броска вы
продолжаете бросать монету, пока она не упадет другой стороной вверх.
184 Глава 8 Условная логика и управление программой
Бросая монету, вы получаете последовательность орлов и решек. Например,
при первом проведении эксперимента (в первой попытке) вы можете получить
последовательность «орел, орел, решка».
Сколько в среднем понадобится бросков, чтобы в последовательности оказались
как орел, так и решка?
Напишите программу для моделирования ситуации, когда эксперимент составляют 10 000 попыток и надо вывести среднее число подбрасываний монеты
в каждой попытке.
8.9. ЗАДАЧА: МОДЕЛИРОВАНИЕ ВЫБОРОВ
При помощи модуля random и использования условной логики можно построить
модель выборов, в которых участвуют два кандидата.
Допустим, два кандидата, A и B, претендуют на должность мэра города, в котором три избирательных участка. Последние опросы показывают, что кандидат A
имеет следующие шансы на победу в каждом участке.
zz
Участок 1: 87%
zz
Участок 2: 65%
zz
Участок 3: 17%
Напишите программу, которая моделирует выборы 10 000 раз и выводит процент выборов, когда победил кандидат A.
Для простоты считайте, что кандидат одержал верх на выборах, если он победил
как минимум на двух из трех участков.
8.10. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы познакомились с условными командами и условной логикой.
Вы научились сравнивать значения с такими операторами, как <, >, <=, >=, !=
и ==, и узнали, как строить сложные условные выражения с использованием
операторов and, or и not.
Затем вы освоили управление логикой выполнения программы при помощи
команд if. Вы узнали, как создавать ветви выполнения в программе командами if… else и if … elif … else, а также научились управлять выполнением кода
внутри блоков if командами break и continue.
8.10. Итоги и дополнительные ресурсы 185
Далее мы изучили синтаксис try … except для обработки ошибок, которые могут
происходить во время выполнения. Это важная конструкция, позволяющая
программам корректно обрабатывать неожиданные ситуации и не огорчать
пользователей сбоями.
Наконец, мы применили инструменты, о которых шла речь в этой главе, и воспользовались модулем random для построения простых моделей.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-conditional-logic
Дополнительные ресурсы
Один умный вулканец однажды сказал: «Логика — лишь начало мудрости, но
далеко не конец» (Спок. «Звездный путь»).
Дополнительную информацию об условной логике вы найдете на следующих
ресурсах:
zz
«Operators and Expressions in Python» (https://realpython.com/pythonoperators-expressions/)
zz
«Conditional Statements in Python» (https://realpython.com/pythonconditional-statements/)
ГЛАВА 9
Кортежи, списки и словари
До настоящего момента вы работали с фундаментальными типами данных —
такими, как str, int и float. Многие реальные задачи проще решаются при
объединении простых типов данных в более сложные структуры.
Структура данных моделирует такие наборы данных, как списки чисел, строки
электронной таблицы или записи в базе данных. Чтобы написать простой и эффективный код, выберите структуру данных, которая лучше всего моделирует
данные для вашей программы.
Python поддерживает три встроенные структуры данных, которым и посвящена
эта глава: кортежи, списки и словари.
В этой главе вы:
zz
научитесь работать с кортежами, списками и словарями;
zz
узнаете, что такое неизменяемость и почему она важна;
zz
узнаете, когда используются разные структуры данных.
Итак, за дело!
9.1. КОРТЕЖИ КАК НЕИЗМЕНЯЕМЫЕ
ПОСЛЕДОВАТЕЛЬНОСТИ
Пожалуй, простейшей составной структурой данных является последовательность элементов.
Последовательность представляет собой упорядоченный список значений.
Каждому элементу последовательности присваивается индекс — целое число,
обозначающее позицию элемента в последовательности. Как и в случае символов
в строках, индекс первого значения в последовательности равен 0.
9.1. Кортежи как неизменяемые последовательности 187
Например, буквы латинского алфавита образуют последовательность, первым
элементом которой является буква A, а последним — буква Z. Строки также
являются последовательностями. Строка "Python" состоит из 6 элементов, начиная с "P" (индекс 0) и заканчивая "n" (индекс 5).
Вот несколько примеров последовательностей из реальной жизни: последовательность значений, ежесекундно измеряемых датчиком; последовательность
оценок студента на экзаменах за год; последовательность ежедневных цен на
акции некоторой компании за некоторый период времени.
В этом разделе вы научитесь использовать встроенный тип данных tuple для
создания последовательностей значений.
Что такое кортеж?
Термин «кортеж» пришел из математики, где он используется для описания
конечной упорядоченной последовательности значений.
Обычно математики записывают кортежи, перечисляя элементы, разделенные
запятыми, в круглых скобках. Например, (1, 2, 3) — кортеж из трех целых чисел.
Кортежи упорядочены, потому что их элементы следуют в определенном порядке. Первым элементом кортежа (1, 2, 3) является 1, вторым — 2, а третьим — 3.
Python заимствует название и способ записи кортежей из математики.
Как создать кортеж
В Python предусмотрено несколько способов создания кортежа. Мы рассмотрим два из них.
1. Литералы кортежей.
2. Встроенная функция tuple().
Литералы кортежей
Как говорилось выше, строковый литерал представляет собой строку, которая явно создается посредством заключения некоторого текста в кавычки.
Аналогичным образом литерал кортежа представляет собой кортеж, который
явно записывается в виде заключенной в круглые скобки последовательности
значений, разделенных запятыми.
Пример литерала кортежа:
>>> my_first_tuple = (1, 2, 3)
188 Глава 9 Кортежи, списки и словари
Эта строка создает кортеж с целыми числами 1, 2 и 3 и присваивает его переменной с именем my_first_tuple.
Чтобы убедиться в том, что my_first_tuple содержит кортеж, можно воспользоваться функцией type():
>>> type(my_first_tuple)
<class 'tuple'>
В отличие от строк, которые состоят из последовательности символов, кортежи
могут содержать значения любых типов, в том числе и разных. Кортеж (1, 2.0,
"three") вполне допустим.
Также существует специальный кортеж, не содержащий никаких значений. Он
называется пустым кортежем, а для его создания следует ввести две круглые
скобки, между которыми нет ни одного символа:
>>> empty_tuple = ()
На первый взгляд пустой кортеж может показаться странным и бесполезным,
но на самом деле он имеет практический смысл.
Допустим, вам поручено предоставить кортеж со всеми целыми числами, которые одновременно являются четными и нечетными. Таких целых чисел не
существует, но пустой кортеж позволит вам выполнить требование.
А как создать кортеж, содержащий ровно один элемент?
Попробуйте выполнить следующий фрагмент в IDLE:
>>> x = (1)
>>> type(x)
<class 'int'>
Если заключить значение в круглые скобки, но не включить ни одной запятой,
Python интерпретирует его не как кортеж, а как значение в круглых скобках.
Таким образом, (1) становится всего лишь экзотическим способом записи
целого числа 1.
Чтобы создать кортеж, содержащий единственное значение 1, необходимо поставить после 1 запятую:
>>> x = (1,)
>>> type(x)
<class 'tuple'>
9.1. Кортежи как неизменяемые последовательности 189
Кортеж из одного элемента может показаться таким же странным, как и пустой.
Почему бы не отказаться от возни с кортежами, а просто использовать само
значение?
Это зависит от задачи.
Если вам понадобится создать кортеж всех четных простых чисел, то это будет
(2,), потому что 2 — единственное четное простое число. Значение 2 не подойдет, потому что это не кортеж.
На первый взгляд может показаться, что это проявление излишнего педантизма,
но программирование часто требует изрядной доли такого качества. В конце
концов, никто не сравнится с компьютером по части педантизма.
Встроенная функция tuple()
Для создания кортежа из последовательности другого типа — например, строки — можно воспользоваться встроенной функцией tuple():
>>> tuple("Python")
('P', 'y', 't', 'h', 'o', 'n')
Функция tuple() принимает только один параметр, поэтому вам не удастся
просто перечислить нужные значения как отдельные аргументы. Если вы попытаетесь это сделать, Python выдаст ошибку TypeError:
>>> tuple(1, 2, 3)
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
tuple(1, 2, 3)
TypeError: tuple expected at most 1 arguments, got 3
Ошибка TypeError выдается и в том случае, если переданный tuple() аргумент
не может быть интерпретирован как список значений:
>>> tuple(1)
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
tuple(1)
TypeError: 'int' object is not iterable
Слова not iterable (не итерируемый) в сообщении об ошибке означают, что
одно целое число не может использоваться для перебора, то есть целый тип
данных не содержит нескольких значений, которые можно перебирать одно
за одним.
190 Глава 9 Кортежи, списки и словари
Единственный параметр tuple() не является обязательным. Опустив его, вы
получите пустой кортеж:
>>> tuple()
()
Но программисты Python обычно предпочитают более короткую запись () для
создания пустых кортежей.
Сходство между кортежами и строками
У кортежей и строк много общего. Как кортежи, так и строки являются типами
последовательностей конечной длины, поддерживают индексацию и работу со
срезами, являются неизменяемыми, а их содержимое можно перебирать в цикле.
Главное отличие строк и кортежей заключается в том, что элементами кортежа
могут быть любые значения, а строки содержат только символы.
Рассмотрим сходства и различия строк и кортежей более подробно.
У кортежей есть длина
И строки, и кортежи обладают длиной. Длина строки равна количеству содержащихся в ней символов. Длина кортежа равна количеству содержащихся
в нем элементов.
Как и в случае со строками, для определения длины кортежа можно воспользоваться функцией len():
>>> numbers = (1, 2, 3)
>>> len(numbers)
3
Кортежи поддерживают индексацию и срезы
Вспомните, о чем говорилось в главе 4: для обращения к отдельным символам
строки можно воспользоваться индексом:
>>> name = "David"
>>> name[1]
'a'
Индекс [1] после имени переменной приказывает Python получить символ
с индексом 1 в строке "David". Так как нумерация индексов начинается с 0,
символом с индексом 1 является буква "a".
9.1. Кортежи как неизменяемые последовательности 191
Кортежи также поддерживают индексную запись:
>>> values = (1, 3, 5, 7, 9)
>>> values[2]
5
Еще одна особенность, общая для строк и кортежей, — работа со срезами. Напомню, что синтаксис срезов используется для извлечения подстрок:
>>> name = "David"
>>> name[2:4]
"vi"
Обозначение [2:4] после имени переменной создает новую строку, которая содержит символы name, начиная с символа в позиции 2 и до символа в позиции 4
(не включая его).
Создание среза из кортежа:
>>> values = (1, 3, 5, 7, 9)
>>> values[2:4]
(5, 7)
Синтаксис values[2:4] создает новый кортеж, содержащий все целые числа из
values, начиная с позиции 2 и до позиции 4 (не включая ее).
Правила, относящиеся к срезам строк, также распространяются на срезы кортежей. Возможно, вам стоит вернуться к примерам работы со срезами из главы 4
и попробовать применить их к кортежам.
Кортежи неизменяемы
Кортежи, как и строки, являются неизменяемыми, то есть значение элемента
кортежа невозможно изменить после его создания.
ПРИМЕЧАНИЕ
Хотя кортежи являются неизменяемыми, в некоторых ситуациях значения
в кортеже все же могут изменяться.
Эти странности и аномалии подробно рассматриваются в видеокурсе
«Immutability in Python» (https://realpython.com/courses/immutability-python/)
на сайте Real Python.
При попытке изменить в кортеже значение с заданным индексом Python выдает ошибку TypeError:
192 Глава 9 Кортежи, списки и словари
>>> values[0] = 2
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
values[0] = 2
TypeError: 'tuple' object does not support item assignment
Кортежи итерируемы
Кортежи, как и строки, итерируемы, то есть их содержимое можно перебирать
в цикле:
>>> vowels = ("a", "e", "i", "o", "u")
>>> for vowel in vowels:
... print(vowel.upper())
...
A
E
I
O
U
Цикл for в этом примере работает точно так же, как и циклы for, которые мы
использовали в главе 6 для перебора в числовых диапазонах range().
На первой итерации цикла из кортежа vowels извлекается значение "a". Оно
преобразуется в букву верхнего регистра строковым методом .upper(), описанным в главе 4, а затем выводится вызовом print().
На следующем шаге цикл извлекает значение "e", преобразует его к верхнему
регистру и выводит. То же самое происходит со значениями "i", "o" и "u".
Итак, вы научились создавать кортежи и выполнять с ними некоторые базовые
операции. Рассмотрим несколько стандартных сценариев использования кортежей.
Упаковка и распаковка кортежей
Существует третий, менее распространенный способ создания кортежей. Можно
ввести список значений, разделенных запятыми, без круглых скобок:
>>> coordinates = 4.21, 9.29
>>> type(coordinates)
<class 'tuple'>
Все выглядит так, словно одной переменной coordinates присваиваются два
значения. В каком-то смысле так и есть, хотя в результате два значения упаковываются в один кортеж. Чтобы убедиться в том, что coordinates действительно
является кортежем, можно воспользоваться функцией type().
9.1. Кортежи как неизменяемые последовательности 193
Если значения можно упаковать в кортеж, то вполне логично, что кортеж также
можно распаковать:
>>> x, y = coordinates
>>> x
4.21
>>> y
9.29
Значения, содержащиеся в одном кортеже coordinates, распаковываются в две
переменные — x и y.
Объединение упаковки с распаковкой кортежа позволяет выполнить несколько
присваиваний в одной строке:
>>> name, age, occupation = "David", 34, "programmer"
>>> name
'David'
>>> age
34
>>> occupation
'programmer'
Этот фрагмент работает, потому что сначала в правой части команды присваивания значения "David", 34 и "programmer" упаковываются в кортеж. Затем
значения распаковываются в три переменные, name, age и occupation, в указанном порядке.
ПРИМЕЧАНИЕ
Хотя присваивание значений нескольким переменным в одной строке может
уменьшить число строк в программе, так делать не рекомендуется.
Если значения присваиваются более чем двум или трем переменным, вам
будет труднее определить, какое значение связывается с той или иной переменной.
Учтите, что количество имен переменных в левой части выражения присваивания должно соответствовать количеству значений в кортеже из правой части.
В противном случае Python выдает ошибку ValueError:
>>> a, b, c, d = 1, 2, 3
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
a, b, c, d = 1, 2, 3
ValueError: not enough values to unpack (expected 4, got 3)
194 Глава 9 Кортежи, списки и словари
Сообщение об ошибке указывает, что кортеж в правой части содержит слишком
мало значений для распаковки по четырем переменным в левой части.
Python также выдает ошибку ValueError, если количество значений в кортеже
превышает количество имен переменных:
>>> a, b, c = 1, 2, 3, 4
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
a, b, c = 1, 2, 3, 4
ValueError: too many values to unpack (expected 3)
Теперь сообщение об ошибке указывает, что кортеж содержит слишком много
значений для распаковки по трем переменным.
Проверка существования значений
Чтобы проверить, содержится ли значение в кортеже, используйте ключевое
слово in:
>>> vowels = ("a", "e", "i", "o", "u")
>>> "o" in vowels
True
>>> "x" in vowels
False
Если значение в левой части in присутствует в кортеже из правой части, то
результат равен True. В противном случае результат равен False.
Возвращение нескольких значений из функции
Кортежи часто используются для возвращения нескольких значений из одной
функции:
>>> def adder_subtractor(num1, num2):
... return (num1 + num2, num1 - num2)
...
>>> adder_subtractor(3, 2)
(5, 1)
У функции adder_subtractor() два параметра — num1 и num2. Она возвращает
кортеж, где первый элемент содержит сумму двух чисел, а второй содержит их
разность.
Строки и кортежи — всего лишь два примера встроенных типов последовательностей Python. И строки, и кортежи являются неизменяемыми, они
9.2. Списки: изменяемые последовательности 195
итерируемы (поддерживают перебор), их можно индексировать и создавать
из них срезы.
В следующем разделе я расскажу о третьем типе последовательностей, у которого есть существенное отличие от строк и кортежей: он является изменяемым.
Упражнения
1. Создайте литерал кортежа с именем cardinal_numbers, содержащий
строки "first", "second" и "third" в указанном порядке.
2. Используя индексирование и print(), выведите строку с индексом 1 из
cardinal_numbers.
3. В одной строке кода распакуйте значения из cardinal_numbers в три новые строки с именами position1, position2 и position3. Затем выведите
каждое значение в отдельной строке.
4. Используя функцию tuple() и строковый литерал, создайте кортеж
с именем my_name, содержащий буквы вашего имени.
5. Проверьте, входит ли символ "x" в my_name , при помощи ключевого
слова in.
6. Используя синтаксис сегментов, создайте новый кортеж, содержащий
все буквы my_name, кроме первой.
9.2. СПИСКИ: ИЗМЕНЯЕМЫЕ
ПОСЛЕДОВАТЕЛЬНОСТИ
Структура данных list (список) — еще один тип последовательностей в Python.
Как строки и кортежи, списки состоят из элементов, индексируемых целыми
числами (начиная с 0).
На первый взгляд списки своим внешним видом и поведением сильно напоминают кортежи. Списки можно индексировать, создавать из них срезы, проверять существование элемента при помощи in, а также перебирать элементы
в цикле for.
Однако, в отличие от кортежей, списки являются изменяемыми; это означает,
что вы можете изменить значение с заданным индексом даже после того, как
список будет создан.
В этом разделе я расскажу, как создавать списки. Затем мы сравним списки
с кортежами.
196 Глава 9 Кортежи, списки и словари
Создание списков
Литерал списка очень похож на литерал кортежа, но вместо круглых скобок он
заключается в квадратные скобки []:
>>> colors = ["red", "yellow", "green", "blue"]
>>> type(colors)
<class 'list'>
При проверке списка Python выводит его в виде литерала:
>>> colors
['red', 'yellow', 'green', 'blue']
Значения в списках, как и значения в кортежах, не обязаны иметь один тип.
Литерал ["one", 2, 3.0] вполне допустим.
Кроме литералов, можно воспользоваться встроенной функцией list() для
создания нового объекта списка на основе любой другой последовательности.
Например, list() можно передать кортеж (1, 2, 3) для создания списка [1,
2, 3]:
>>> list((1, 2, 3))
[1, 2, 3]
Список даже можно создать на основе строки:
>>> list("Python")
['P', 'y', 't', 'h', 'o', 'n']
Каждая буква строки становится элементом списка.
Существует еще один способ создания списка на основе строки. Если имеется
строка с перечнем элементов, разделенных запятыми, воспользуйтесь строковым методом .split():
>>> groceries = "eggs, milk, cheese"
>>> grocery_list = groceries.split(", ")
>>> grocery_list
['eggs', 'milk', 'cheese']
Строковый аргумент, передаваемый .split(), называется разделителем. Изменяя разделитель, можно разбивать строку на списки разными способами:
>>> # Разбиение по точке с запятой
>>> "a;b;c".split(";")
9.2. Списки: изменяемые последовательности 197
['a', 'b', 'c']
>>> # Разбиение по пробелам
>>> "The quick brown fox".split(" ")
['The', 'quick', 'brown', 'fox']
>>> # Разбиение по нескольким символам
>>> "abbaabba".split("ba")
['ab', 'ab', '']
В последнем примере строка разбивается по вхождениям подстроки "ba", которая встречается сначала в позиции с индексом 2, а затем с индексом 6. Так как
разделитель состоит из двух символов, элементами списка становятся только
символы с индексами 0, 1, 4 и 5.
.split() всегда возвращает список, длина которого на 1 превышает количество
разделителей в строке. Разделитель "ba" встречается в "abbaabba" дважды, так
что список, возвращаемый split(), состоит из трех элементов.
Обратите внимание: последним элементом списка является пустая строка. Это
происходит из-за того, что за последним "ba" никакие другие символы не следуют. Если разделитель вообще не входит в строку, .split() возвращает список,
единственным элементом которого является исходная строка:
>>> "abbaabba".split("c")
['abbaabba']
Итак, существуют три способа создания списков.
1. Литерал списка.
2. Встроенная функция list().
3. Строковый метод .split().
Списки поддерживают те же операции, что и кортежи.
Основные операции списков
Операции индексирования и сегментации работают со списками так же, как
и с кортежами.
К элементам списков можно обращаться по индексам:
>>> numbers = [1, 2, 3, 4]
>>> numbers[1]
2
198 Глава 9 Кортежи, списки и словари
Также можно создать новый список на основе существующего, используя синтаксис сегментации:
>>> numbers[1:3]
[2, 3]
Для проверки существования элементов списков используется оператор in:
>>> # Проверка существования элемента
>>> "Bob" in numbers
False
Списки итерируемы; это означает, что их содержимое можно перебрать в цик­
ле for:
>>> # Выводит только четные числа в списке
>>> for number in numbers:
...
if number % 2 == 0:
...
print(number)
...
2
4
Главное отличие списка от кортежа заключается в том, что элементы списка
могут изменяться, тогда как элементы кортежа изменяться не могут.
Изменение элементов списка
Список можно рассматривать как последовательность пронумерованных ячеек.
Каждая ячейка содержит значение, и каждая ячейка в любой момент времени
должна быть заполнена. Однако значение в заданной ячейке всегда можно заменить новым.
Возможность замены значений в списке другими значениями называется изменяемостью. Списки являются изменяемыми. Элементы кортежей не могут
заменяться новыми значениями, поэтому кортежи называются неизменяемыми.
Чтобы заменить одно значение в списке другим, присвойте новое значение
в позиции, заданной индексом:
>>> colors = ["red", "yellow", "green", "blue"]
>>> colors[0] = "burgundy"
Значение с индексом 0 изменяется с "red" на "burgundy":
>>> colors
['burgundy', 'yellow', 'green', 'blue']
9.2. Списки: изменяемые последовательности 199
Вы можете изменить сразу несколько значений в списке, используя срезы:
>>> colors[1:3] = ["orange", "magenta"]
>>> colors
['burgundy', 'orange', 'magenta', 'blue']
colors[1:3] выбирает ячейки с индексами 1 и 2. Значениям в этих позициях
присваиваются строки "orange" и "magenta" соответственно.
Список, присваиваемый срезу, не обязан иметь такую же длину, как и срез. Например, можно присвоить список из трех элементов срезу из двух элементов:
>>> colors = ["red", "yellow", "green", "blue"]
>>> colors[1:3] = ["orange", "magenta", "aqua"]
>>> colors
['red', 'orange', 'magenta', 'aqua', 'blue']
Значения "orange" и "magenta" заменяют исходные значения "yellow" и "green"
в списке colors с индексами 1 и 2. Затем в позиции с индексом 4 создается
новая ячейка, которой присваивается значение "blue". Наконец, в позиции
с индексом 3 присваивается строка "aqua".
Если длина списка, присвоенного срезу, меньше длины среза, то общая длина
исходного списка уменьшается:
>>> colors
['red', 'orange', 'magenta', 'aqua', 'blue']
>>> colors[1:4] = ["yellow", "green"]
>>> colors
['red', 'yellow', 'green', 'blue']
Значения "yellow" и "green" заменяют значения "orange" и "magenta" в списке
colors с индексами 1 и 2. Затем значение "blue" заполняет позицию с индексом 3, а индекс 4 полностью удаляется из colors. Этот пример показывает, как
изменять списки путем индексации и срезов. Кроме того, для изменения списка
можно использовать методы списков.
Методы списков для добавления
и удаления элементов
Конечно же, вы можете добавлять и удалять элементы с использованием индексации и срезов, но методы списков предлагают более естественный и удобо­
читаемый механизм изменения.
Мы рассмотрим несколько методов списков для выполнения разных операций,
начиная со вставки одного значения в позицию с заданным индексом.
200 Глава 9 Кортежи, списки и словари
list.insert()
Метод list.insert() используется для вставки нового значения в список. Он
получает два параметра — индекс i и значение x — и вставляет значение x в позицию с индексом i:
>>> colors = ["red", "yellow", "green", "blue"]
>>> # Вставляет "orange" во вторую позицию
>>> colors.insert(1, "orange")
>>> colors
['red', 'orange', 'yellow', 'green', 'blue']
В этом примере стоит обратить внимание на пару важных моментов.
Первый применим ко всем методам. Чтобы использовать методы, вы сначала
указываете имя списка, с которым хотите выполнить операцию, затем точку
и имя метода списка.
Таким образом, чтобы вызвать insert() для списка colors, используйте запись
colors.insert(). Такой синтаксис уже знаком вам по строковым и числовым
методам.
Также обратите внимание на то, что при вставке значения "orange" в позицию
с индексом 1 значение "yellow" и все следующие значения сдвигаются вправо.
Если значение параметра индекса .insert() превышает наибольший индекс
списка, значение вставляется в конец списка:
>>> colors.insert(10, "violet")
>>> colors
['red', 'orange', 'yellow', 'green', 'blue', 'violet']
Здесь значение "violet" вставляется в позицию с индексом 5 несмотря на то,
что метод .insert() был вызван с индексом 10.
Также методу .insert() могут передаваться отрицательные индексы:
>>> colors.insert(-1, "indigo")
>>> colors
['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']
Фрагмент вставляет строку "indigo" в позицию с индексом -1, что соответствует последнему элементу списка. Значение "violet" сдвигается вправо на
одну позицию.
Если вы можете вставить значение по заданному индексу, вполне логично, что
также существует возможность удаления элемента с заданным индексом.
9.2. Списки: изменяемые последовательности 201
ВАЖНО!
При вставке значения в список методом .insert() результат не нужно присваивать исходному списку.
Например, следующий код фактически стирает содержимое списка colors:
>>> colors = colors.insert(-1, "indigo")
>>> print(colors)
None
Метод .insert() изменяет colors на месте. Это справедливо для всех методов
списков, которые не возвращают значения.
list.pop()
Метод list.pop() получает один параметр — индекс i — и удаляет из списка значение в позиции с заданным индексом. Удаляемое значение возвращается методом:
>>> color = colors.pop(3)
>>> color
'green'
>>> colors
['red', 'orange', 'yellow', 'blue', 'indigo', 'violet']
Значение "green" с индексом 3 удаляется и присваивается переменной color.
При проверке переменной colors можно убедиться в том, что строка "green"
действительно была удалена.
В отличие от .insert(), Python выдает ошибку IndexError при передаче .pop()
аргумента, превышающего последний индекс:
>>> colors.pop(10)
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
colors.pop(10)
IndexError: pop index out of range
Отрицательные индексы также работают с .pop():
>>> colors.pop(-1)
'violet'
>>> colors
['red', 'orange', 'yellow', 'blue', 'indigo']
Если при вызове .pop() значение не передается, то метод удаляет из списка
последний элемент:
202 Глава 9 Кортежи, списки и словари
>>> colors.pop()
'indigo'
>>> colors
['red', 'orange', 'yellow', 'blue']
Удаление последнего элемента вызовом .pop() без указания индекса обычно
считается более питоническим подходом.
list.append()
Метод list.append() используется для добавления нового элемента в конец
списка:
>>> colors.append("indigo")
>>> colors
['red', 'orange', 'yellow', 'blue', 'indigo']
Вызов .append() увеличивает длину списка на 1 и вставляет значение "indigo"
в последнюю позицию. Следует помнить, что .append() изменяет список «на
месте», как и .insert().
Использование .append() эквивалентно вставке элемента с индексом, большим
или равным длине списка. Приведенный выше пример можно записать и таким
образом:
>>> colors.insert(len(colors), "indigo")
Вызов .append() короче и содержательнее такого использования .insert().
Обычно считается, что этот способ добавления элемента в конец списка — более
питонический.
list.extend()
Метод list.extend() используется для добавления нескольких новых элементов в конец списка:
>>> colors.extend(["violet", "ultraviolet"])
>>> colors
['red', 'orange', 'yellow', 'blue', 'indigo', 'violet', 'ultraviolet']
Метод .extend() получает один параметр, который должен быть итерируемым
типом. Элементы этого объекта присоединяются в конец списка в том же порядке, в котором они указываются в аргументе, передаваемом .extend().
Как и .insert() и .append(), .extend() изменяет список «на месте».
9.2. Списки: изменяемые последовательности 203
Как правило, передаваемый .extend() аргумент содержит другой список, но
это также может быть кортеж. Например, приведенный выше пример можно
записать в следующем виде:
>>> colors.extend(("violet", "ultraviolet"))
Четыре метода списков, о которых мы рассказали в этом разделе, используются
чаще других. В следующей таблице приведено краткое описание этих методов.
МЕТОД СПИСКА
ОПИСАНИЕ
.insert(i, x)
Вставляет значение x в позицию с индексом i
.append(x)
Вставляет значение x в конец списка
.extend(iterable)
Вставляет все значения из iterable в конец списка с сохранением порядка
.pop(i)
Удаляет и возвращает элемент с индексом i
Кроме методов списков Python также содержит несколько полезных встроенных
функций для работы со списками чисел.
Списки чисел
Одна из самых распространенных операций, используемых со списками чисел, —
суммирование всех значений. Для этой цели можно воспользоваться циклом for:
>>> nums = [1, 2, 3, 4, 5]
>>> total = 0
>>> for number in nums:
...
total = total + number
...
>>> total
15
Сначала переменная total инициируется значением 0. Затем программа в цикле перебирает все числа в nums и прибавляет их к total. В итоге мы получаем
значение 15.
Хотя решение с циклом вполне прямолинейно, в Python существует другой,
куда более компактный вариант:
>>> sum([1, 2, 3, 4, 5])
15
Встроенная функция sum() получает список в аргументе и возвращает сумму
всех значений в списке.
204 Глава 9 Кортежи, списки и словари
Если список, переданный sum(), содержит значения, которые не являются
числовыми, выдается ошибка TypeError:
>>> sum([1, 2, 3, "four", 5])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Кроме sum(), существуют две другие полезные встроенные функции для работы
со списками: min() и max(). Эти функции возвращают наименьшее и наибольшее
значение в списке соответственно:
>>> min([1, 2, 3, 4, 5 ])
1
>>> max([1, 2, 3, 4, 5])
5
Обратите внимание: функции sum(), min() и max() также работают с кортежами:
>>> sum((1, 2, 3, 4, 5))
15
>>> min((1, 2, 3, 4, 5))
1
>>> max((1, 2, 3, 4, 5))
5
Тот факт, что функции sum(), min() и max() встроены в Python, подсказывает,
что они используются достаточно часто. Скорее всего, вы не раз примените их
в своих программах!
Генераторы списков
Для создания списка на основе существующей последовательности с поддержкой перебора также можно воспользоваться генератором списка:
>>> numbers = (1, 2, 3, 4, 5)
>>> squares = [num**2 for num in numbers]
>>> squares
[1, 4, 9, 16, 25]
Генератор списка является сокращенной формой записи для цикла for. В приведенном примере литерал кортежа, содержащий пять чисел, создается и присваивается переменной numbers. Во второй строке генератор списка перебирает
каждое число в numbers, возводит его в квадрат и включает в новый список
с именем squares.
9.2. Списки: изменяемые последовательности 205
Чтобы создать список squares с традиционным циклом for, вам придется создать
пустой список, перебрать числа из numbers и присоединить квадрат каждого
элемента к списку:
>>> squares = []
>>> for num in numbers:
...
squares.append(num**2)
...
>>> squares
[1, 4, 9, 16, 25]
Генераторы списков часто применяются для преобразования значений в списке
к другому типу.
Допустим, вы хотите преобразовать список строк, содержащих числа с плавающей точкой, в список объектов float. Задача решается следующим генератором
списка:
>>> str_numbers = ["1.5", "2.3", "5.25"]
>>> float_numbers = [float(value) for value in str_numbers]
>>> float_numbers
[1.5, 2.3, 5.25]
Генераторы списков поддерживаются не только в Python, но они принадлежат
к числу его самых популярных возможностей. Если вы видите, что вам приходится создавать пустой список, перебирать некоторую последовательность
и присоединять новые элементы к списку, то, скорее всего, этот код можно заменить генератором списка.
Упражнения
1. Создайте список food, содержащий два элемента: "rice" и "beans".
2. Присоедините к food строку "broccoli" методом .append().
3. Добавьте в food строки "bread" и "pizza" при помощи метода .extend().
4. Выведите первые два элемента списка food, используя функцию print()
и синтаксис сегментов.
5. Выведите последний элемент food, используя функцию print() и синтаксис сегментов.
6. Создайте список breakfast из строки "eggs, fruit, orangejuice" при
помощи строкового метода .split().
7. Убедитесь в том, что breakfast содержит три элемента, при помощи
функции len().
206 Глава 9 Кортежи, списки и словари
8. Создайте генератором списка новый список lengths, содержащий длины
всех строк из списка breakfast.
9.3. ВЛОЖЕНИЕ, КОПИРОВАНИЕ И СОРТИРОВКА
КОРТЕЖЕЙ И СПИСКОВ
Итак, вы узнали, что собой представляют кортежи и списки, научились создавать
их и выполнять базовые операции. Рассмотрим еще три концепции:
1) вложение;
2) копирование;
3) сортировка.
Вложенные списки и кортежи
Списки и кортежи могут содержать значения произвольных типов. Это означает, что списки и кортежи могут содержать списки и кортежи как значения.
Вложенный список или кортеж представляет собой список или кортеж, который
содержится как значение в другом списке или кортеже.
Например, следующий список содержит два значения, каждое из которых само
по себе является списком:
>>> two_by_two = [[1, 2], [3, 4]]
>>> # two_by_two имеет длину 2
>>> len(two_by_two)
2
>>> # Оба элемента two_by_two являются списками
>>> two_by_two[0]
[1, 2]
>>> two_by_two[1]
[3, 4]
Так как two_by_two[1] возвращает список [3, 4], для обращения к элементу
вложенного списка можно воспользоваться записью с двумя индексами:
>>> two_by_two[1][0]
3
Сначала Python вычисляет two_by_two[1] и возвращает [3, 4]. Затем Python
вычисляет [3, 4][0] и возвращает первый элемент 3.
9.3. Вложение, копирование и сортировка кортежей и списков 207
Очень упрощенно список списков или кортеж кортежей можно представить
себе как таблицу со строками и столбцами.
Список two_by_two содержит две строки: [1, 2] и [3, 4]. Столбцы состоят из
соответствующих элементов каждой строки. Таким образом, первый столбец
содержит элементы 1 и 3, а второй — элементы 2 и 4.
Впрочем, аналогия с таблицей — не более чем неформальное представление
списка списков. Например, Python не требует, чтобы все списки в списке списков имели одинаковую длину; в этом случае аналогия с таблицей не годится.
Копирование списка
Иногда требуется скопировать один список в другой. Однако вы не сможете
просто присвоить один объект списка другому объекту списка, потому что
получите следующий (возможно, неожиданный) результат:
>>> animals = ["lion", "tiger", "frumious Bandersnatch"]
>>> large_cats = animals
>>> large_cats.append("Tigger")
>>> animals
['lion', 'tiger', 'frumious Bandersnatch', 'Tigger']
В этом примере список, хранящийся в переменной animals, присваивается переменной large_cats, после чего в список large_cats добавляется новая строка
"Tigger". Но при выводе содержимого animals мы видим, что исходный список
тоже изменился.
Такое поведение относится к особенностям объектно-ориентированного программирования, но оно было реализовано намеренно. После выполнения
команды large_cats = animals переменные large_cats и animals относятся
к одному и тому же объекту.
Имя переменной в действительности представляет собой ссылку на область
памяти компьютера. Вместо того чтобы скопировать все содержимое объекта
списка и создать новый список, команда large_cats = animals присваивает
large_cats ту область памяти, на которую ссылается animals. В результате обе
переменные ссылаются на один и тот же объект в памяти — и любые изменения,
внесенные в одну переменную, влияют на другую.
Чтобы получить независимую копию списка animals, можно воспользоваться
срезом; эта запись возвращает новый список, содержащий те же значения:
>>> animals = ["lion", "tiger", "frumious Bandersnatch"]
>>> large_cats = animals[:]
208 Глава 9 Кортежи, списки и словари
>>> large_cats.append("leopard")
>>> large_cats
['lion', 'tiger', 'frumious Bandersnatch', 'leopard']
>>> animals
["lion", "tiger", "frumious Bandersnatch"]
Так как в срезе [:] индексы не указаны, возвращаются все элементы списка
от начала до конца. Список large_cats теперь содержит те же элементы, что
и animals, и они следуют в том же порядке, но к нему можно присоединять
элементы вызовом .append() без изменения списка, связанного с animals.
Если вы захотите создать копию списка списков, можно воспользоваться
­записью [:], как было показано выше:
>>> matrix1 = [[1, 2], [3, 4]]
>>> matrix2 = matrix1[:]
>>> matrix2[0] = [5, 6]
>>> matrix2
[[5, 6], [3, 4]]
>>> matrix1
[[1, 2], [3, 4]]
Посмотрим, что происходит при изменении первого элемента второго списка
в matrix2:
>>> matrix2[1][0] = 1
>>> matrix2
[[5, 6], [1, 4]]
>>> matrix1
[[1, 2], [1, 4]]
Обратите внимание: второй список в matrix1 тоже изменился!
Это происходит из-за того, что список на самом деле содержит не сами объекты, а ссылки на эти объекты в памяти. Срез [:] возвращает новый список,
содержащий те же ссылки, что и исходный список. На жаргоне программистов
этот способ копирования списка называется поверхностным копированием.
Чтобы скопировать список и все его элементы, необходимо создать глубокую
копию. Она является действительно независимой копией объекта. Для создания глубокой копии списка можно воспользоваться функцией deepcopy() из
модуля Python copy:
>>> import copy
>>> matrix3 = copy.deepcopy(matrix1)
>>> matrix3[1][0] = 3
>>> matrix3
9.3. Вложение, копирование и сортировка кортежей и списков 209
[[5, 6], [3, 4]]
>>> matrix1
[[5, 6], [1, 4]]
matrix3 создается как глубокая копия matrix1. При изменении элемента matrix3
соответствующий элемент matrix1 не изменяется.
ПРИМЕЧАНИЕ
За дополнительной информацией о поверхностном и глубоком копировании обратитесь к статье «Shallow vs Deep Copying of Python Objects» (https://
realpython.com/copying-python-objects/) на сайте Real Python.
Сортировка списков
У списков есть метод .sort(), который сортирует элементы по возрастанию.
По умолчанию список сортируется в алфавитном или числовом порядке в зависимости от типа элементов в списке:
>>> # Списки строк сортируются по алфавиту
>>> colors = ["red", "yellow", "green", "blue"]
>>> colors.sort()
>>> colors
['blue', 'green', 'red', 'yellow']
>>> # Списки чисел сортируются по порядку
>>> numbers = [1, 10, 5, 3]
>>> numbers.sort()
>>> numbers
[1, 3, 5, 10]
Обратите внимание: .sort() сортирует список «на месте», так что присваивать
результат вызова переменной необязательно.
.sort() также поддерживает необязательный параметр key, который может использоваться для настройки сортировки списка. Параметр key получает функ-
цию, а список сортируется на основании возвращаемого значения этой функции.
Например, чтобы отсортировать список строк по длине, можно передать в key
функцию len:
>>> colors = ["red", "yellow", "green", "blue"]
>>> colors.sort(key=len)
>>> colors
['red', 'blue', 'green', 'yellow']
210 Глава 9 Кортежи, списки и словари
Вызывать функцию, передаваемую в параметре key, не нужно. Просто укажите
имя функции без круглых скобок. Например, в предыдущем примере key передается имя len, а не len().
ВАЖНО!
Функция, передаваемая key, должна получать только один аргумент.
Также key можно передавать функции, определяемые пользователем. В следующем примере функция get_second_element() используется для сортировки
списка кортежей по их вторым элементам:
>>> def get_second_element(item):
...
return item[1]
...
>>> items = [(4, 1), (1, 2), (-9, 0)]
>>> items.sort(key=get_second_element)
>>> items
[(-9, 0), (4, 1), (1, 2)]
Помните, что функция, передаваемая в параметре key, должна получать только
один аргумент.
Упражнения
1. Создайте кортеж data, содержащий два значения. Первым значением
должен быть кортеж (1, 2), а вторым — кортеж (3, 4).
2. Напишите цикл for, который перебирает data и выводит сумму каждого
вложенного кортежа. Результат должен выглядеть примерно так:
Row 1 sum: 3
Row 2 sum: 7
3. Создайте список [4, 3, 2, 1] и присвойте его переменной numbers.
4. Создайте копию списка numbers с использованием записи [:].
5. Отсортируйте список numbers в числовом порядке методом .sort().
9.4. ЗАДАЧА: СПИСОК СПИСКОВ
Напишите программу, в которой создается следующий список списков:
universities = [
['California Institute of Technology', 2175, 37704],
9.4. Задача: список списков 211
]
['Harvard', 19627, 39849],
['Massachusetts Institute of Technology', 10566, 40732],
['Princeton', 7802, 37000],
['Rice', 5879, 35551],
['Stanford', 19535, 40569],
['Yale', 11701, 40500]
Определите функцию enrollment_stats(), получающую один параметр. Этим
параметром должен быть список списков, в котором каждый вложенный список
содержит три элемента:
1) название университета;
2) общее количество зачисленных студентов;
3) ежегодная плата за обучение.
Функция enrollment_stats() должна возвращать два списка. Первый содержит все данные о зачисленных зарегистрированных студентах, а второй — все
данные о плате за обучение.
Затем определите две функции — mean() и median(), которые получают один
списковый аргумент и возвращают среднее значение и медиану по каждому
списку соответственно.
Используя universities, enrollment_stats(), mean() и median(), вычислите
общее количество студентов, общую плату за обучение, среднее и медианное
количество студентов, а также среднюю и медианную плату за обучение.
Наконец, выведите все значения и отформатируйте вывод, чтобы он выглядел
так:
******************************
Total students: 77,285
Total tuition: $ 271,905
Student mean: 11,040.71
Student median: 10,566
Tuition mean: $ 38,843.57
Tuition median: $ 39,849
******************************
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
212 Глава 9 Кортежи, списки и словари
9.5. ЗАДАЧА: ПРИСТУП ВДОХНОВЕНИЯ
Сейчас вы напишете программу, которая генерирует стихи.
Создайте пять списков для разных типов слов:
1. Существительные: ["fossil", "horse", "aardvark", "judge", "chef",
"mango", "extrovert", "gorilla"]
2. Глаголы: ["kicks", "jingles", "bounces", "slurps", "meows", "explodes",
"curdles"]
3. Прилагательные: ["furry", "balding", "incredulous", "fragrant",
"exuberant", "glistening"]
4. Предлоги: ["against", "after", "into", "beneath", "upon", "for", "in",
"like", "over", "within"]
5. Наречия ["curiously", "extravagantly", "tantalizingly", "furiously",
"sensuously"]
Случайным образом выберите следующее количество элементов из каждого
списка:
zz
три существительных;
zz
три глагола;
zz
три прилагательных;
zz
два предлога;
zz
одно наречие.
Для этого можно воспользоваться функцией choice() из модуля random. Она
получает список и возвращает случайно выбранный элемент списка.
Пример использования random.choice() для получения случайного элемента
из списка ["a", "b", "c"]:
import random
random_element = random.choice(["a", "b", "c"])
Используя случайно выбранные слова, сгенерируйте и выведите стихотворение, структура которого аналогична стилю Клиффорда Пиковера (https://
en.wikipedia.org/wiki/Clifford_A._Pickover):
9.6. Храните отношения в словарях 213
{A/An} {прил1} {сущ1}
{A/An} {прил1} {сущ1} {гл1} {предл1} the {прил2} {сущ2}
{нареч1}, the {сущ1} {гл2}
the {сущ2} {гл3} {предл2} a {прил3} {сущ3}
Пример стихотворения, которое могла бы сгенерировать ваша программа:
A furry horse
A furry horse curdles within the fragrant mango
extravagantly, the horse slurps
the mango meows beneath a balding extrovert
При каждом запуске программы должно генерироваться новое стихотворение.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
9.6. ХРАНИТЕ ОТНОШЕНИЯ В СЛОВАРЯХ
Одной из самых полезных структур данных Python является словарь.
В этом разделе вы узнаете, что такое словарь, чем словари отличаются от списков
и кортежей и как создавать и использовать словари в вашем коде.
Что такое словарь?
Обычно словарем называется текст, содержащий определения слов. Каждая
­запись в словаре состоит из двух частей: определяемого слова и его определения.
Словари Python, как и списки и кортежи, предназначены для хранения коллекций объектов. Но вместо того, чтобы хранить объекты в последовательности,
словари хранят информацию в виде пар данных «ключ — значение». Другими
словами, каждый объект в словаре состоит из двух частей: ключа и значения.
Ключ в паре «ключ — значение» представляет собой уникальное имя, по которому можно идентифицировать значение. Если провести параллель с обычным
словарем, ключ можно сравнить с определяемым словом, а значение — с его
определением.
Например, словарь можно использовать для хранения названий штатов и их
столиц.
214 Глава 9 Кортежи, списки и словари
КЛЮЧ
ЗНАЧЕНИЕ
“California”
“Sacramento”
“New York”
“Albany”
“Texas”
“Austin”
В этой таблице ключами словаря являются названия штатов, а значениями —
названия их столиц.
Обычный словарь отличается от словаря Python тем, что в Python отношение
между ключом и его значением полностью произвольно. С любым ключом
можно связать любое значение.
Например, следующая таблица пар «ключ — значение» вполне допустима:
КЛЮЧ
ЗНАЧЕНИЕ
1
“Sunday”
“red”
12:45pm
17
True
Ключи в этой таблице логически не связаны со значениями. Единственная связь заключается в том, что с каждым ключом связано то или иное
значение.
В этом смысле словарь Python больше похож на отображение, чем на обычный
словарь. Термином «отображение» в математике называется отношение (соответствие) между двумя множествами.
Представление словарей как отображений особенно полезно. С этой точки
зрения, обычный словарь представляет собой отображение, связывающее слова
с их определениями.
Словарь Python представляет собой структуру данных, которая связывает множество ключей с множеством значений. С каждым ключом связывается одно
значение, которое определяет отношения между двумя множествами.
Создание словарей
Следующий фрагмент создает литерал словаря, содержащий названия штатов
и их столиц:
9.6. Храните отношения в словарях 215
>>> capitals = {
"California": "Sacramento",
"New York": "Albany",
"Texas": "Austin",
}
Обратите внимание: каждый ключ отделяется от своего значения двоеточием,
пары «ключ — значение» разделяются запятыми, а весь словарь заключается
в фигурные скобки.
Словарь можно также создать из последовательности кортежей при помощи
встроенной функции dict():
>>> key_value_pairs = (
... ("California", "Sacramento"),
... ("New York", "Albany"),
... ("Texas", "Austin"),
)
>>> capitals = dict(key_value_pairs)
При проверке переменной, содержащей словарь, ее содержимое выводится
в форме литерала словаря независимо от того, как она была создана:
>>> capitals
{'California': 'Sacramento', 'New York': 'Albany', 'Texas': 'Austin'}
ПРИМЕЧАНИЕ
Если вы воспроизводите наши примеры в версии Python до 3.6, вы заметите,
что порядок вывода словарей в интерактивном окне отличается от приведенного в примерах.
До выхода Python 3.6 порядок пар «ключ — значение» в словаре Python был
случайным. В более поздних версиях этот порядок гарантированно совпадает
с порядком их вставки.
Пустой словарь создается в форме литерала или вызовом dict():
>>> {}
{}
>>> dict()
{}
Итак, словарь создан. Посмотрим, как обратиться к хранящимся в нем значениям.
216 Глава 9 Кортежи, списки и словари
Обращение к значениям словаря
Чтобы обратиться к значению в словаре, укажите ключ в квадратных скобках ([]) после словаря или имени переменной, который был присвоен словарь:
>>> capitals["Texas"]
'Austin'
Запись с квадратными скобами, используемая для обращения к значению в словаре, похожа на ту, которая использовалась для получения значений из строк,
списков и кортежей. Тем не менее словарь — это структура данных, принципиально отличающаяся от структуры типа последовательность (списка или кортежа).
Чтобы увидеть различия, сделаем шаг назад и заметим, что словарь capitals
можно было бы определить в виде списка:
>>> capitals_list = ["Sacramento", "Albany", "Austin"]
Индексная запись позволяет получить названия столиц всех трех штатов из
словаря capitals:
>>> capitals_list[0] # Столица Калифорнии
'Sacramento'
>>> capitals_list[2] # Столица Техаса
'Austin'
Важная особенность словарей заключается в том, что они могут наделять контекстом содержащиеся в них значения. Смысл выражения capitals["Texas"]
более понятен, чем смысл capitals_list[2], и вам не придется запоминать
порядок следования значений в длинном списке или кортеже.
Концепция упорядочения составляет главное различие между обращением
к элементам из последовательности и элементам из словаря.
Для обращения к значениям в последовательности используются индексы —
целые числа, отражающие порядок элементов в последовательности.
Обращение к элементам словаря производится по ключу. Ключи не определяют
порядок элементов словаря. Они только предоставляют условную метку, которая
может использоваться для обращения к значению.
Добавление и удаление значений в словарях
Словари, как и списки, являются изменяемыми структурами данных. Это
­означает, что элементы можно добавлять и удалять из словаря.
9.6. Храните отношения в словарях 217
Добавим столицу Колорадо в словарь capitals:
>>> capitals["Colorado"] = "Denver"
При вводе "Colorado" в качестве ключа мы применим квадратные скобки, как
при получении значения. Затем оператор присваивания = мы используем для
присваивания значения "Denver" по новому ключу.
При проверке capitals мы видим, что в словаре появился новый ключ "Colorado"
со значением "Denver":
>>> capitals
{'California': 'Sacramento', 'New York': 'Albany', 'Texas': 'Austin',
'Colorado': 'Denver'}
Каждому ключу в словаре может быть присвоено только одно значение.
Если ключу присваивается новое значение, то Python заменяет им старое
значение:
>>> capitals["Texas"] = "Houston"
>>> capitals
{'California': 'Sacramento', 'New York': 'Albany', 'Texas': 'Houston',
'Colorado': 'Denver'}
Чтобы удалить элемент из словаря, используйте ключевое слово del с ключом,
связанным с удаляемым значением:
>>> del capitals["Texas"]
>>> capitals
{'California': 'Sacramento', 'New York': 'Albany',
'Colorado': 'Denver'}
Проверка существования ключа
При попытке обратиться к словарю по несуществующему ключу Python выдает
ошибку KeyError:
>>> capitals["Arizona"]
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
capitals["Arizona"]
KeyError: 'Arizona'
KeyError — самая распространенная ошибка при работе со словарями. Каждый
раз, когда вы ее встречаете, это означает, что программа попыталась обратиться
к значению по несуществующему ключу.
218 Глава 9 Кортежи, списки и словари
Чтобы проверить, существует ли ключ в словаре, воспользуйтесь ключевым
словом in:
>>> "Arizona" in capitals
False
>>> "California" in capitals
True
Ключевое слово in позволяет убедиться в том, что ключ существует, прежде
чем вы будете пытаться что-то сделать со значением:
>>> if "Arizona" in capitals:
...
# Вывести только в том случае, если ключ "Arizona" существует
...
print(f"The capital of Arizona is {capitals['Arizona']}.")
Важно помнить, что in проверяет существование ключей, а не значений:
>>> "Sacramento" in capitals
False
Хотя "Sacramento" является значением для существующего ключа "California"
в capitals, проверка его существования возвращает False.
Перебор содержимого словаря
Словари, как списки и кортежи, итерируемы, то есть поддерживают перебор.
Но он несколько отличается от перебора списка или кортежа. Когда вы перебираете словарь в цикле for, перебор ведется по ключам словаря:
>>> for key in capitals:
...
print(key)
...
California
New York
Colorado
Таким образом, если вы хотите перебрать словарь capitals и вывести "The
capital of X is Y", где X — название штата, а Y — его столица, это можно сделать так:
>>> for state in capitals:
print(f"The capital of {state} is {capitals[state]}")
The capital of California is Sacramento
The capital of New York is Albany
The capital of Colorado is Denver
9.6. Храните отношения в словарях 219
Впрочем, существует и более компактная запись, использующая метод словаря
.items(); она возвращает объект, похожий на список и содержащий кортежи пар
«ключ — значение». Например, capitals.items() возвращает список кортежей
штатов и их столиц:
>>> capitals.items()
dict_items([('California', 'Sacramento'), ('New York', 'Albany'),
('Colorado', 'Denver')])
Объект, возвращаемый .items(), в действительности списком не является.
Он относится к специальному типу dict_items:
>>> type(capitals.items())
<class 'dict_items'>
Не беспокойтесь о том, что собой представляет тип dict_items. Обычно вам
не придется работать с ним напрямую. Важно знать, что метод .items() может
использоваться для перебора ключей словаря одновременно со значениями.
Перепишем предыдущий цикл с .items():
>>> for state, capital in capitals.items():
...
print(f"The capital of {state} is {capital}")
The capital of California is Sacramento
The capital of New York is Albany
The capital of Colorado is Denver
При переборе capitals.items() каждая итерация цикла создает кортеж, содержащий название штата и его столицы. Присваивание этого кортежа state,
capital обеспечивает распаковку компонентов в переменные state и capital.
Ключи словаря и неизменяемость
В словаре capitals, с которым мы работали в этом разделе, каждый ключ был
строкой. Тем не менее ключи словаря вовсе не обязаны относиться к одному типу.
Например, можно добавить в capitals ключ, который является целым числом:
>>> capitals[50] = "Honolulu"
>>> capitals
{'California': 'Sacramento', 'New York': 'Albany',
'Colorado': 'Denver', 50: 'Honolulu'}
Существует только одно ограничение на то, какие значения являются валидными ключами словаря. Допустимы только неизменяемые типы. В частности,
это означает, что список не может быть ключом словаря.
220 Глава 9 Кортежи, списки и словари
Подумайте: что должно произойти, если список используется в качестве ключа
словаря, а потом в коде этот список изменится? Должен ли новый список быть
связан с тем же значением, что и старый список в словаре? Или значение, связанное со старым ключом, должно быть полностью удалено из словаря? Python
не строит предположения, он выдает исключение:
>>> capitals[[1, 2, 3]] = "Bad"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Может показаться несправедливым, что одни типы могут быть ключами,
а другие — нет, но в языке программирования очень важно четко определенное
поведение. Программа не должна угадывать, что имел в виду программист!
Для справки ниже мы перечислили все типы данных, упоминавшиеся ранее,
которые являются валидными для ключей словарей.
ВАЛИДНЫЕ ТИПЫ КЛЮЧЕЙ СЛОВАРЕЙ
Целые числа
Числа с плавающей точкой
Строки
Логические
Кортежи
В отличие от ключей, значения словарей могут относиться к любому типу
Python, в том числе быть другими словарями.
Вложенные словари
По аналогии со списками внутри других списков и кортежами внутри других
кортежей допустимы и вложенные словари. Изменим словарь capitals, чтобы
продемонстрировать эту возможность:
>>> states = {
...
"California": {
...
"capital": "Sacramento",
...
"flower": "California Poppy"
...
},
...
"New York": {
...
"capital": "Albany",
...
"flower": "Rose"
9.6. Храните отношения в словарях 221
...
...
...
...
...
... }
},
"Texas": {
"capital": "Austin",
"flower": "Bluebonnet"
},
Здесь мы не связывали названия штатов со столицами, мы создали словарь,
который связывает название штата со словарем, содержащим название столицы
и цветок — символ штата. Значением для каждого ключа является словарь:
>>> states["Texas"]
{'capital': 'Austin', 'flower': 'Bluebonnet'}
Чтобы узнать цветок, который является символом штата Техас, сначала получите значение с ключом "Texas", а затем значение с ключом "flower":
>>> states["Texas"]["flower"]
'Bluebonnet'
Вложенные словари встречаются достаточно часто. Они особенно полезны
при работе с данными, передаваемыми по сети. Вложенные словари также
хорошо подходят для моделирования структурированных данных (например,
электронных таблиц или реляционных баз данных).
Упражнения
1. Создайте пустой словарь с именем captains.
2. Используя синтаксис с квадратными скобками, включите следующие
данные в словарь поочередно:
ƒƒ 'Enterprise': 'Picard'
ƒƒ 'Voyager': 'Janeway'
ƒƒ 'Defiant': 'Sisko'
3. Напишите две команды, которые проверяют, существуют ли в словаре
ключи "Enterprise" и "Discovery". Если ключи не существуют, свяжите
с ними значения "unknown".
4. Напишите цикл for для вывода названия корабля и имени капитана,
содержащихся в словаре. Результат должен выглядеть примерно так:
The Enterprise is captained by Picard.
5. Удалите "Discovery" из словаря.
222 Глава 9 Кортежи, списки и словари
6. Дополнительно: создайте тот же словарь с использованием dict() и передайте исходные значения сразу при создании словаря.
9.7. ЗАДАЧА: ЦИКЛ ПО СТОЛИЦАМ
Вернемся к столицам штатов и применим полученные знания о словарях и цикле while!
Сначала заполните словарь названиями остальных штатов и их столиц и сохраните данные в файле capitals.py:
capitals_dict = {
'Alabama': 'Montgomery',
'Alaska': 'Juneau',
'Arizona': 'Phoenix',
'Arkansas': 'Little Rock',
'California': 'Sacramento',
'Colorado': 'Denver',
'Connecticut': 'Hartford',
'Delaware': 'Dover',
'Florida': 'Tallahassee',
'Georgia': 'Atlanta',
}
Затем выберите из словаря случайное название штата и присвойте название
штата и его столицы двум переменным. Первой строкой кода своей программы
импортируйте модуль random.
Затем выведите название штата и предложите пользователю указать столицу.
Если пользователь задал неправильный ответ, повторяйте вопрос, пока пользователь не ответит правильно или не введет команду "exit" для завершения.
Если пользователь ответил правильно, выведите сообщение "Correct" и завершите программу. Но если пользователь завершает программу без указания
правильного ответа, выведите правильный ответ и слово "Goodbye".
ПРИМЕЧАНИЕ
Проследите за тем, чтобы пользователь не пострадал из-за регистра символов.
Другими словами, ответ «Denver» считается идентичным «denver». Сделайте
то же самое для команды выхода — «EXIT» и «Exit» должны работать точно так
же, как и «exit».
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
9.9. Задача: коты в шляпах 223
9.8. КАК ВЫБРАТЬ СТРУКТУРУ ДАННЫХ
Итак, мы рассказали вам о трех структурах данных, реализованных в Python:
списках, кортежах и словарях.
Возникает вопрос: как понять, когда использовать ту или иную структуру
данных? Отличный вопрос, который возникает у многих новичков в Python.
Тип структуры данных зависит от решаемой задачи, и не существует никаких
однозначных правил, которые регламентировали бы выбор. Всегда необходимо
хорошенько обдумать задачу и сознательно выбрать структуру, которая лучше
всего подойдет для конкретного случая.
К счастью, все-таки есть некоторые рекомендации, которые помогут вам:
zz
Используйте список, если выполняются следующие условия:
ƒƒ у данных имеется естественный порядок;
ƒƒ данные будут обновляться или изменяться во время выполнения;
ƒƒ структура данных будет использоваться в основном для перебора.
zz
Используйте кортеж, если выполняются следующие условия:
ƒƒ у данных имеется естественный порядок;
ƒƒ данные не будут обновляться или изменяться во время выполнения;
ƒƒ структура данных будет использоваться в основном для перебора.
zz
Используйте словарь, если выполняются следующие условия:
ƒƒ данные не упорядочены или их порядок роли не играет;
ƒƒ данные будут обновляться или изменяться во время выполнения;
ƒƒ структура данных будет использоваться в основном для выборки
значений.
9.9. ЗАДАЧА: КОТЫ В ШЛЯПАХ
У вас сто котов. Однажды вы решаете рассадить всех своих котов в большой
круг. Изначально ни один из них не носит шляпы. Вы сто раз обходите круг,
всегда начиная с первого кота (кот № 1). Каждый раз, когда вы останавливаетесь рядом с котом, вы проверяете, есть ли на нем шляпа. Если шляпы нет, то
вы надеваете шляпу на кота, а если есть — снимаете ее.
1. На первом круге вы останавливаетесь у каждого кота и надеваете на него
шляпу.
224 Глава 9 Кортежи, списки и словари
2. На втором круге вы останавливаетесь у каждого второго кота (№ 2, № 4,
№ 6, № 8 и т. д.).
3. На третьем круге вы останавливаетесь у каждого третьего кота (№ 3,
№ 6, № 9, № 12 и т. д.).
4. Процесс продолжается до тех пор, пока вы не обойдете круг сто раз. При
последнем обходе вы останавливаетесь только у кота № 100.
Напишите программу, которая выводит, на каких котах будут шляпы после
всех обходов.
ПРИМЕЧАНИЕ
Эта задача не очень простая, но и не настолько сложная, как может показаться
на первый взгляд. Подобная задача часто встречается на собеседованиях при
приеме на работу, так как она показывает вашу сообразительность.
Сохраняйте спокойствие. Начните с диаграммы и псевдокода. Найдите закономерность, а потом переходите к программированию!
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
9.10. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе я рассказал о трех структурах данных: списках, кортежах и словарях.
Списки являются изменяемыми последовательностями объектов. Для взаимо­
действия со списками используются различные методы, включая .append()
и .extend(). Сортировка списков осуществляется методом .sort(). Для обращения к отдельным элементам списков используется запись, сходная с аналогичной записью строк. К спискам можно применять срезы.
Кортежи, как и списки, являются последовательностями объектов. Главное
отличие списков от кортежей заключается в том, что кортежи неизменяемы.
Созданный кортеж модифицировать невозможно. К элементам кортежей, как
и к элементам списков, можно обращаться по индексам и применять срезы.
Словари хранят данные в виде пар «ключ — значение». Они не являются
последовательностями, а значит, к элементам словаря нельзя обратиться по
порядковому номеру. Для обращения к элементу указывают ключ. Словари
прекрасно подходят для хранения отношений и для быстрого доступа к данным.
Словари, как и списки, являются изменяемыми.
9.10. Итоги и дополнительные ресурсы 225
Списки, кортежи и словари являются итерируемыми, то есть их элементы
можно перебирать в цикле. Мы рассмотрели примеры циклического перебора
всех трех структур в циклах for.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-tuples-lists-dicts
Дополнительные ресурсы
Дополнительную информацию о списках, кортежах и словарях вы найдете на
ресурсах:
zz
«Lists and Tuples in Python» (https://realpython.com/python-lists-tuples/)
zz
«Dictionaries in Python» (https://realpython.com/python-dicts/)
ГЛАВА 10
Объектно-ориентированное
программирование (ООП)
Объектно-ориентированное программирование (ООП) представляет собой
методологию структурирования программы за счет упаковки взаимосвязанных
свойств и поведения в отдельные объекты.
Представьте программу как своего рода сборочную линию: на каждом этапе
сборки отдельный узел системы (объект) обрабатывает материал, а в конечном
итоге из сырья получается конечный продукт.
Объект содержит данные (аналог сырья или предварительно обработанных
материалов на каждом этапе сборочной линии) и задает поведение (действие,
выполняемое каждым компонентом сборочной линии).
В этой главе вы научитесь:
zz
создавать класс — это аналог чертежа для создания объектов;
zz
использовать классы для создания новых объектов;
zz
моделировать системы с наследованием классов.
Итак, за дело!
10.1. ОПРЕДЕЛЕНИЕ КЛАССА
Примитивные структуры данных — такие, как числа, строки, списки, — создавались для представления простой информации: стоимости килограмма яблок,
названия стихотворения, ваши любимые цвета... А если надо представить что-то
более сложное?
Допустим, вы хотите создать базу данных сотрудников в организации. Требуется
хранить о каждом из них основную информацию: имя, возраст, должность и год
поступления на работу.
10.1. Определение класса 227
Одно из возможных решений — представить информацию о каждом сотруднике
в виде списка:
kirk = ["James Kirk", 34, "Captain", 2265]
spock = ["Spock", 35, "Science Officer", 2254]
mccoy = ["Leonard McCoy", "Chief Medical Officer", 2266]
У такого подхода масса недостатков.
Во-первых, усложняется сопровождение файлов с большим количеством кода.
Если вы обратитесь к kirk[0] спустя какое-то время после объявления списка
kirk, будете ли вы помнить, что элемент с индексом 0 содержит имя работника?
Во-вторых, возможно возникновение ошибки, если для разных работников
списки содержат разное количество элементов. В списке mccoy из приведенного выше примера возраст не указан, поэтому mccoy[1] вместо возраста вернет
строку с должностью "Chief Medical Officer".
Если вы хотите сделать такой код более управляемым и удобным в сопровождении, используйте классы — это отличный выход.
Классы и экземпляры
Классы применяются для создания пользовательских структур данных. Классы определяют функции, называемые методами; в них задаются поведение
и действия, которые объект, созданный на основе класса, сможет выполнять
со своими данными.
В этой главе мы создадим класс Dog для хранения информации о свойствах
и поведении собак.
Класс представляет собой «чертеж», то есть прототип для определения объектов.
Он не содержит никаких данных. Класс Dog указывает, что кличка и возраст
необходимы для определения собаки, но он не содержит клички и возраста
никакой конкретной собаки.
Если класс можно сравнить с чертежом, то экземпляр представляет собой объект, построенный на основе класса; он содержит реальные данные. Экземпляр
класса Dog уже не является чертежом. Он представляет конкретную собаку —
например, это может быть Майлз четырех лет от роду.
Иначе говоря, класс можно сравнить с бланком или анкетой, а экземпляр — это
анкета, заполненная информацией. Подобно тому как один вид анкеты разные
люди могут заполнить данными, уникальными для каждого из них, на основе
одного класса можно создать множество экземпляров.
228 Глава 10 Объектно-ориентированное программирование (ООП)
Как определить класс
Все определения классов начинаются с ключевого слова class, за которым
следует имя класса и двоеточие. Любой код с отступом, расположенный под
определением класса, считается частью тела класса.
Пример класса Dog:
class Dog:
pass
Тело класса Dog состоит из одной команды: ключевого слова pass. Оно часто
используется в качестве заполнителя, обозначающего, где в будущем появится
код. Это позволит запустить код, при этом Python не будет выдавать сообщения об ошибках.
ПРИМЕЧАНИЕ
В Python имена классов принято записывать по ВотТакойСхеме, то есть слова
сшиваются без пробелов и нижнего подчеркивания, через использование
заглавных букв. Например, имя класса для конкретной породы собак может
быть записано в виде JackRussellTerrier.
Класс Dog пока не особо интересен. Давайте немного улучшим его, добавив
свойства, которые должны быть общими для всех объектов Dog. Таких свойств
может быть достаточно много: кличка, возраст, цвет, порода и т. д. Для простоты
мы ограничимся кличкой и возрастом.
Свойства, общие для всех объектов Dog , должны определяться в методе
.__init__(). При создании нового объекта Dog метод .__init__() задает исходное состояние объекта, присваивая значения свойствам объекта. Иначе
говоря, метод .__init__() инициализирует каждый экземпляр класса.
Методу .__init__() может передаваться любое количество параметров, но
первым параметром всегда является переменная с именем self. При создании
нового экземпляра класса экземпляр автоматически передается в параметре self
при вызове .__init__(), чтобы для объекта можно было задать новые атрибуты.
Обновим класс Dog и добавим метод .__init__(), создающий атрибуты .name
и .age:
class Dog:
def __init__(self, name, age):
10.1. Определение класса 229
self.name = name
self.age = age
Обратите внимание: сигнатура метода .__init__() имеет отступ из четырех
пробелов, а тело метода — из восьми пробелов. Эти отступы очень важны. Они
сообщают Python, что метод .__init__() принадлежит классу Dog.
В теле .__init__() содержатся две команды, в которых используется переменная self.
1. self.name = name создает атрибут с именем name и присваивает ему значение параметра name.
2. self.age = age создает атрибут с именем age и присваивает ему значение
параметра age.
Атрибуты, созданные в .__init__(), называются атрибутами экземпляров.
Значение атрибута экземпляра относится к конкретному экземпляру класса.
Все объекты Dog обладают атрибутами name и age, но значения name и age зависят
от конкретного экземпляра Dog.
С другой стороны, атрибуты класса имеют одно и то же значение для всех
экземпляров класса. Атрибут класса можно определить, присваивая значение
переменной name за пределами .__init__().
Например, следующий класс Dog содержит атрибут класса с именем species
и значением "Canis familiaris":
class Dog:
# Атрибут класса
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
Атрибуты класса определяются непосредственно под первой строкой имени
класса с отступом в четыре пробела. Им всегда должно присваиваться исходное
значение. При создании экземпляра класса атрибуты класса формируются автоматически, и им присваиваются исходные значения. Используйте атрибуты
класса для определения свойств, которые должны иметь одно и то же значение
для всех экземпляров класса. Используйте атрибуты экземпляров для свойств,
которые отличаются в разных экземплярах.
Итак, класс Dog готов. Переходим к созданию экземпляров!
230 Глава 10 Объектно-ориентированное программирование (ООП)
10.2. СОЗДАНИЕ ЭКЗЕМПЛЯРОВ
(ИНСТАНЦИРОВАНИЕ)
Откройте интерактивное окно в IDLE и введите следующий фрагмент:
>>> class Dog:
...
pass
...
Тем самым вы создаете новый класс Dog , не содержащий ни атрибутов, ни
методов.
Создание нового объекта класса называется инстанцированием.
Чтобы создать новый объект Dog, введите имя класса, за которым следует пара
круглых скобок:
>>> Dog()
<__main__.Dog object at 0x106702d30>
Теперь новый объект Dog существует по адресу 0x106702d30. Загадочная строка из букв и цифр — адрес памяти, указывающий, где объект Dog хранится
в памяти компьютера. Учтите, что адрес, который вы увидите в своей системе,
будет другим.
Теперь создайте второй объект Dog:
>>> Dog()
<__main__.Dog object at 0x0004ccc90>
Новый экземпляр Dog располагается по другому адресу памяти. Дело в том, что
это совершенно новый экземпляр, который никак не связан с первым объектом
Dog, созданным ранее.
Можно посмотреть на это иначе. Введите следующий фрагмент:
>>> a = Dog()
>>> b = Dog()
>>> a == b
False
В этом коде создаются два объекта Dog, которые присваиваются переменным a
и b. При сравнении a и b оператором == будет получен результат False. Хотя
как a, так и b являются экземплярами класса Dog, они представляют два разных
объекта в памяти.
10.2. Создание экземпляров (инстанцирование) 231
Атрибуты класса и экземпляра
Теперь создайте новый класс Dog с атрибутом класса .species и двумя атрибутами экземпляров .name и .age:
>>> class Dog:
...
species = "Canis familiaris"
...
def __init__(self, name, age):
...
self.name = name
...
self.age = age
...
>>>
Чтобы инстанцировать объект класса Dog, необходимо задать значения для name
и age. Если этого не сделать, Python выдаст ошибку TypeError:
>>> Dog()
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
Dog()
TypeError: __init__() missing 2 required positional
arguments: 'name' and 'age'
Чтобы передать аргументы для параметров name и age, укажите соответствующие
значения в круглых скобках после имени класса:
>>> buddy = Dog("Buddy", 9)
>>> miles = Dog("Miles", 4)
Этот фрагмент создает два экземпляра Dog — один представляет девятилетнюю
собаку по кличке Buddy, а другой — четырехлетнюю собаку по кличке Miles.
Метод .__init__() класса Dog получает три параметра, тогда почему в этом
примере ему передаются только два аргумента?
При инстанцировании объекта Dog Python создает новый экземпляр и передает
его в первом параметре .__init__()., задавая self, и вам придется думать только
о параметрах name и age.
После того как экземпляры Dog будут созданы, вы можете обращаться к их
атрибутам экземпляров с использованием точечной нотации:
>>> buddy.name
'Buddy'
>>> buddy.age
9
>>> miles.name
232 Глава 10 Объектно-ориентированное программирование (ООП)
'Miles'
>>> miles.age
4
Аналогичным образом можно обращаться к атрибутам класса:
>>> buddy.species
'Canis familiaris'
Одно из самых больших преимуществ использования классов для организации данных заключается в том, что экземпляры гарантированно содержат все
заданные атрибуты. Все экземпляры Dog содержат атрибуты .species, .name
и .age, и вы можете обращаться к ним с полной уверенностью в том, что они
всегда возвращают какое-либо значение.
Хотя атрибуты заведомо существуют, их значения можно менять динамически:
>>> buddy.age = 10
>>> buddy.age
10
>>> miles.species = "Felis silvestris"
>>> miles.species
'Felis silvestris'
В этом примере вы меняете значение атрибута .age в объекте buddy на 10. Затем атрибут .species объекта miles изменяется на "Felis silvestris" — одну
из разновидностей кошек. Конечно, собака получается странная, но сам код на
языке Python абсолютно допустим!
Главный вывод заключается в том, что пользовательские объекты по умолчанию изменяемы. Вспомните: объект называется изменяемым, если он может
изменяться динамически во время выполнения. Например, списки и словари
являются изменяемыми, а строки и кортежи неизменяемы.
Методы экземпляров
Методами экземпляров называют функции, которые определяются внутри
класса и могут вызываться только из экземпляра этого класса. Как и в случае
с .__init__(), первым параметром метода экземпляра всегда является self.
Откройте новое окно редактора в IDLE и задайте следующий класс Dog:
class Dog:
species = "Canis familiaris"
10.2. Создание экземпляров (инстанцирование) 233
def __init__(self, name, age):
self.name = name
self.age = age
# Метод экземпляра
def description(self):
return f"{self.name} is {self.age} years old"
# Другой метод экземпляра
def speak(self, sound):
return f"{self.name} says {sound}"
Этот класс Dog содержит два метода экземпляра.
1. Метод .description() возвращает строку с кличкой и возрастом собаки.
2. Метод .speak() получает один параметр sound и возвращает строку
с кличкой собаки и транскрипцией звуков, которые она издает.
Сохраните измененный класс Dog в файле dog.py и запустите программу клавишей F5. Затем откройте интерактивное окно и введите следующие команды,
чтобы посмотреть на методы экземпляров в действии:
>>> miles = Dog("Miles", 4)
>>> miles.description()
'Miles is 4 years old'
>>> miles.speak("Woof Woof")
'Miles says Woof Woof'
>>> miles.speak("Bow Wow")
'Miles says Bow Wow'
В этом классе Dog метод .description() возвращает строку, содержащую информацию об экземпляре Dog из переменной miles. При написании собственных
классов желательно создать метод, который возвращает строку с полезной
информацией об экземпляре класса. Тем не менее метод .description() — не
самый питонический способ.
При создании объекта типа list можно воспользоваться функцией print() для
вывода строки, которая выглядит как список:
>>> names = ["David", "Dan", "Joanna", "Fletcher"]
>>> print(names)
['David', 'Dan', 'Joanna', 'Fletcher']
234 Глава 10 Объектно-ориентированное программирование (ООП)
А теперь посмотрим, что происходит при выводе объекта miles функцией
print():
>>> print(miles)
<__main__.Dog object at 0x00aeff70>
При вызове print(miles) вы получаете загадочное сообщение, из которого можно узнать, что объект Dog находится в памяти по адресу 0x00aeff70. Пользы от
такого сообщения немного. Чтобы изменить выводимую информацию, следует
определить специальный метод экземпляра с именем .__str__().
В окне редактора измените имя метода .description() класса Dog на .__str__():
class Dog:
# Остальные части класса Dog остаются без изменений
# Замените .description() на __str__()
def __str__(self):
return f"{self.name} is {self.age} years old"
Сохраните файл и нажмите F5. Теперь при вызове print(miles) вы получите
намного более понятный вывод:
>>> miles = Dog("Miles", 4)
>>> print(miles)
'Miles is 4 years old'
Такие методы, как .__init__() и .__str__(), называются dunder-методами
(dunder — сокращение от double underscores, то есть двойное подчеркивание). Существует много dunder-методов, которые могут использоваться для
настройки поведения классов в Python. И хотя эта тема слишком сложна для
книги начального уровня, понимание dunder-методов очень важно для освоения
ООП на языке Python.
В следующем разделе вы увидите, как создавать классы на основе других классов.
Но сначала проверьте, насколько вы усвоили материал, выполнив несколько
упражнений.
Упражнения
1. Измените класс Dog и включите в него третий атрибут экземпляра с именем coat_color, в котором будет храниться цвет шерсти собаки в виде
строки. Сохраните новый класс в файле и протестируйте его, добавив
следующий фрагмент в конец программы:
10.3. Наследование от других классов 235
philo = Dog("Philo", 5, "brown")
print(f"{philo.name}'s coat is {philo.coat_color}.")
Программа должна выводить следующий результат:
Philo's coat is brown.
2. Создайте класс Car с двумя атрибутами экземпляров: .color для хранения
цвета машины в виде строки и .mileage для хранения пробега в милях
в виде целого числа. Создайте два объекта Car — синюю машину с пробегом 20 000 и красную с пробегом 30 000. Выведите данные на каждую
машину. Результат должен выглядеть так:
The blue car has 20,000 miles.
The red car has 30,000 miles.
3. Измените класс Car и добавьте в него метод экземпляра .drive(), который
получает числовой аргумент и прибавляет его к атрибуту .mileage. Убедитесь в том, что ваше решение работает; создайте экземпляр с нулевым
пробегом, вызовите .drive(100) и выведите атрибут .mileage, чтобы
убедиться в том, что он принял значение 100.
10.3. НАСЛЕДОВАНИЕ ОТ ДРУГИХ КЛАССОВ
Наследованием называется процесс, в котором один класс получает атрибуты
и методы другого класса. Создаваемый класс называется производным, или
дочерним, классом, а классы, от которых наследуют производные классы, называются родительскими.
Дочерние классы могут переопределять или расширять атрибуты и методы
родительских классов. Другими словами, они наследуют все атрибуты и методы родительских классов, но также могут задавать свои уникальные атрибуты
и методы.
Хотя аналогия не идеальна, но наследование объектов можно рассматривать
как некое подобие наследования генетического. Возможно, вы унаследовали
цвет волос от своей матери. Это атрибут, с которым вы родились. Допустим,
вы решили окрасить волосы в фиолетовый цвет. Вы успешно переопределили
атрибут цвета волос, унаследованный от матери (предполагается, что ее волосы
не были фиолетовыми).
Также вы в каком-то смысле наследуете от родителей язык общения. Если ваши
родители говорят на английском, то и вы будете говорить на английском. Теперь представьте, что вы решили выучить второй язык — допустим, немецкий.
236 Глава 10 Объектно-ориентированное программирование (ООП)
В таком случае набор атрибутов расширяется, потому что вы добавили атрибут,
которого не было у ваших родителей.
Пример с собачьей площадкой
Вообразите, что вы находитесь на собачьей площадке. На ней много собак разных пород, и все они заняты своими собачьими делами.
Вы хотите смоделировать площадку при помощи классов Python. Класс Dog,
написанный в предыдущем разделе, позволяет различать собак по кличке и возрасту, но не по породе.
Мы можем изменить класс Dog в окне редактора и добавить в него атрибут .breed:
class Dog:
species = "Canis familiaris"
def __init__(self, name, age, breed):
self.name = name
self.age = age
self.breed = breed
Методы экземпляров, определенные ранее, здесь не указаны, потому что они
для данного обсуждения не важны.
Нажмите F5, чтобы сохранить файл. Теперь вы можете смоделировать собачью
площадку, задав параметры нескольких разных собак в интерактивном окне:
>>> miles = Dog("Miles", 4, "Jack Russell Terrier")
>>> buddy = Dog("Buddy", 9, "Dachshund")
>>> jack = Dog("Jack", 3, "Bulldog")
>>> jim = Dog("Jim", 5, "Bulldog")
Разные породы собак имеют разные отличительные особенности. Например,
у бульдогов лай низкий, а у такс — высокий и пронзительный.
Если использовать только класс Dog, можно передавать методу .speak строку
с транскрипцией лая каждый раз, когда этот метод вызывается для экземпляра Dog:
>>> buddy.speak("Yap")
'Buddy says Yap'
>>> jim.speak("Woof")
'Jim says Woof'
>>> jack.speak("Woof")
'Jack says Woof'
10.3. Наследование от других классов 237
Впрочем, решение с передачей строки при каждом вызове .speak() однообразно и неудобно. Более того, строка, представляющая звук, издаваемый каждым
экземпляром Dog, должна определяться его атрибутом .breed, а нам приходится
вручную передавать правильную строку .speak() при каждом вызове.
Чтобы с классом Dog было удобнее работать, мы создадим отдельный дочерний
класс для каждой породы собак. Это позволит расширить функциональность,
наследуемую каждым производным классом, включая определение аргумента
по умолчанию для .speak().
Родительские классы и дочерние классы
Создадим дочерний класс для каждой из трех пород, упоминавшихся ранее: джек-рассел-терьер (Jack Russell Terrier), такса (Dachshund) и бульдог
(Bulldog).
Для удобства приведу полное определение класса Dog:
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} is {self.age} years old"
def speak(self, sound):
return f"{self.name} says {sound}"
Чтобы создать дочерний класс, создайте новый класс с соответствующим именем
и укажите имя родительского класса в круглых скобках. Следующий фрагмент
создает три новых класса, производных от Dog:
class JackRussellTerrier(Dog):
pass
class Dachshund(Dog):
pass
class Bulldog(Dog):
pass
После того как дочерние классы будут определены, можно создать экземпляры
собак конкретных пород:
238 Глава 10 Объектно-ориентированное программирование (ООП)
>>> miles = JackRussellTerrier("Miles", 4)
>>> buddy = Dachshund("Buddy", 9)
>>> jack = Bulldog("Jack", 3)
>>> jim = Bulldog("Jim", 5)
Экземпляры дочерних классов наследуют все атрибуты и методы родительского
класса:
>>> miles.species
'Canis familiaris'
>>> buddy.name
'Buddy'
>>> print(jack)
Jack is 3 years old
>>> jim.speak("Woof")
'Jim says Woof'
Чтобы увидеть, к какому классу принадлежит объект, воспользуйтесь встроенной функцией type():
>>> type(miles)
<class '__main__.JackRussellTerrier'>
А если вы захотите узнать, является ли miles также экземпляром класса Dog?
В этом вам поможет встроенная функция isinstance():
>>> isinstance(miles, Dog)
True
Обратите внимание: функция isinstance() получает два аргумента, объект
и класс. В данном случае isinstance() проверяет, является ли miles экземпляром класса Dog, и возвращает True.
Все объекты, miles, buddy, jack и jim, являются экземплярами Dog, но miles
не является экземпляром Bulldog, а jack не является экземпляром Dachshund:
>>> isinstance(miles, Bulldog)
False
>>> isinstance(jack, Dachshund)
False
В более общем понимании все объекты, созданные на основе дочерних классов,
являются экземплярами родительского класса, хотя при этом не являются
экземплярами других дочерних классов.
10.3. Наследование от других классов 239
Итак, мы создали дочерние классы для нескольких пород собак. Теперь назначим
каждой породе транскрипцию звука, который она издает.
Расширение функциональности
родительского класса
Так как разные породы собак лают по-разному, мы хотим предоставить значение
по умолчанию для аргумента sound в соответствующем методе .speak(). Для
этого необходимо переопределить .speak() в определении класса для каждой
породы.
Чтобы переопределить метод, заданный в родительском классе, вы определяете одноименный метод в дочернем классе. Вот как это делается в классе
JackRussellTerrier:
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return f"{self.name} says {sound}"
Теперь метод .speak() определен в классе JackRussellTerrier с аргументом
по умолчанию для sound, которому присвоено значение "Arf".
Обновите файл dog.py новой версией класса JackRussellTerrier и нажмите F5,
чтобы сохранить и запустить файл. Теперь можно вызвать метод .speak() для
экземпляра JackRussellTerrier без передачи аргумента для sound:
>>> miles = JackRussellTerrier("Miles", 4)
>>> miles.speak()
'Miles says Arf'
В некоторых ситуациях одна и та же собака издает разные звуки, так что, если
Майлз рассердится и зарычит, вы все еще сможете вызвать .speak() с другим
звуком:
>>> miles.speak("Grrr")
'Miles says Grrr'
Имея дело с наследованием классов, важно помнить, что изменения в родительском классе автоматически распространяются на дочерние классы — при
условии, что изменяемый атрибут или метод не был переопределен в дочернем
классе.
Например, измените в окне редактора строку, возвращаемую методом .speak()
из класса Dog:
240 Глава 10 Объектно-ориентированное программирование (ООП)
class Dog:
# Другие атрибуты и методы остаются неизменными
# Изменяется строка, возвращаемая .speak()
def speak(self, sound):
return f"{self.name} barks: {sound}"
Сохраните файл и нажмите F5. Теперь при создании нового экземпляра Bulldog
с именем jim метод jim.speak() вернет новую строку:
>>> jim = Bulldog("Jim", 5)
>>> jim.speak("Woof")
'Jim barks: Woof'
Однако при вызове .speak() для экземпляра JackRussellTerrier новый формат
вывода не используется:
>>> miles = JackRussellTerrier("Miles", 4)
>>> miles.speak()
'Miles says Arf'
Иногда полное переопределение метода из родительского класса оправданно.
Но в данном случае мы не хотим, чтобы в классе JackRussellTerrier терялись
изменения, внесенные в форматирование выходной строки Dog.speak().
Как и прежде, задача решается определением метода .speak() в дочернем классе
JackRussellTerrier. Но вместо того, чтобы явно определять выходную строку,
мы вызовем метод .speak() класса Dog внутри метода .speak() дочернего класса
с теми же аргументами, которые передавались JackRussellTerrier.speak().
Для обращения к родительскому классу из метода дочернего класса используется вызов super():
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return super().speak(sound)
Когда вы вызываете super().speak(sound) внутри JackRussellTerrier, Python
ищет в родительском классе Dog метод .speak() и вызывает его с переменной
sound.
Обновите файл dog.py обновленной версией класса JackRussellTerrier. Сохраните файл и нажмите F5, чтобы протестировать его в интерактивном окне:
>>> miles = JackRussellTerrier("Miles", 4)
>>> miles.speak()
'Miles barks: Arf'
10.3. Наследование от других классов 241
Теперь при вызове miles.speak() вывод будет отражать новое форматирование
в классе.
ВАЖНО!
В этом примере иерархия классов очень проста: у класса JackRussellTerrier
всего один родительский класс Dog. В реальных примерах иерархии классов
могут быть достаточно сложными.
Вызов .super() не ограничивается простым поиском метода или атрибута
в родительском классе. Он обходит всю иерархию классов в поисках подходящего метода или атрибута. При малейшей невнимательности super() может
привести к удивительным результатам.
В следующем разделе мы объединим все, что вы узнали о классах, для создания
модели фермы. Но прежде чем браться за задание, проверьте, насколько вы поняли материал, выполнив несколько упражнений.
Упражнения
1. Создайте класс GoldenRetriever, наследующий от класса Dog. Задайте
аргументу sound метода GoldenRetriever.speak() значение по умолчанию "Bark". Используйте следующий код для родительского класса Dog:
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"{self.name} is {self.age} years old"
def speak(self, sound):
return f"{self.name} says {sound}"
2. Напишите класс Rectangle, представляющий прямоугольник. Экземпляры класса должны создаваться с двумя атрибутами: .length и .width.
Добавьте в класс метод .area(), который возвращает площадь прямо­
угольника (length * width).
Затем напишите класс Square, представляющий квадрат. Этот класс должен наследовать от класса Rectangle и создаваться с одним атрибутом
.side_length. Протестируйте класс Square: создайте экземпляр Square
с атрибутом .side_length, равным 4. Вызов .area() должен возвращать 16.
242 Глава 10 Объектно-ориентированное программирование (ООП)
Задайте свойству .width вашего экземпляра Square значение 5. Затем
снова вызовите .area(). Метод должен вернуть значение 20.
Этот пример показывает, что наследование классов не всегда наилучшим
образом моделирует отношения между подмножествами. В математике
любой квадрат является прямоугольником, но в компьютерных программах это не всегда так.
Хорошо продумывайте варианты поведения, чтобы они выполняли
именно то, что вы задумали, и используйте иерархии классов с осторожностью.
10.4. ЗАДАЧА: МОДЕЛЬ ФЕРМЫ
В этой задаче вы создадите упрощенную модель фермы. Не упускайте из виду,
что правильных ответов может быть несколько.
В этой задаче на первый план выходит не столько синтаксис классов Python,
сколько проектирование программных структур вообще, а это в высшей степени
субъективно. Задачу мы намеренно сформулировали открытой, чтобы вы подумали над тем, как организовать ваш код по классам.
Прежде чем начинать программирование, возьмите бумагу и ручку и набросайте модель своей фермы, включая классы, атрибуты и методы. Подумайте над
наследованием. Как предотвратить дублирование кода? Не жалейте времени
и проработайте столько вариантов модели, сколько посчитаете нужным.
Требования задачи открыты для интерпретации, но постарайтесь придерживаться следующих рекомендаций.
1. В программе должно быть по крайней мере четыре класса: родительский
класс Animal и не менее трех производных классов животных, наследующих от Animal.
2. Каждый класс должен содержать несколько атрибутов и по крайней
мере один метод, моделирующий поведение, присущее конкретному
животному или всем животным, — они должны ходить, бегать, есть,
спать и т. д.
3. Не усложняйте. Используйте наследование. Предусмотрите возможность
вывода подробной информации о животных и их поведении.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
10.5. Итоги и дополнительные ресурсы 243
10.5. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы узнали об объектно-ориентированном программировании
(ООП) — парадигме программирования, используемой во многих современных
языках, включая Java, C#, C++ и Python.
Вы узнали, как определить класс — своего рода чертеж для строительства
объектов, а также создавать экземпляры на основе класса. Также вы узнали
об атрибутах, соответствующих свойствам объекта, и методах, определяющих
особенности поведения и действия объекта.
Наконец, вы узнали, как работает механизм наследования, создающий производ­
ные классы на основе родительского класса. Далее я показал, как обращаться
к методу родительского класса при помощи вызова super() и как проверить,
наследует ли объект от другого класса, при помощи isinstance().
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-oop
Дополнительные ресурсы
В этой главе вы познакомились с основами ООП, но вам предстоит еще очень
много узнать по этой теме! За дополнительной информацией обращайтесь
к следующим ресурсам:
zz
официальная документация Python (https://docs.python.org/3/tutorial/
classes.html)
zz
статьи об ООП на сайте Real Python (https://realpython.com/search?q=oop)
ГЛАВА 11
Модули и пакеты
По мере накопления опыта вы начнете браться за программирование больших
проектов, и в какой-то момент вам станет ясно, что хранить весь код проекта
в одном файле очень неудобно.
Но есть выход: использовать не один файл, а распределить код по нескольким
файлам — модулям. Далее вы в любой момент можете собрать отдельные
модули вместе и построить из них большое приложение, как строят дом из
кирпичей.
В этой главе вы научитесь:
zz
создавать собственный модуль;
zz
использовать модуль в других файлах командой import;
zz
объединять несколько модулей в пакет.
Итак, за дело!
11.1. РАБОТА С МОДУЛЯМИ
Модуль — это файл, содержащий код Python, который может повторно использоваться в других файлах с кодом Python.
Собственно говоря, каждый файл с кодом Python, который вы создали при изу­
чении этой книги, был модулем, но вы пока не знаете, как использовать код из
одного модуля в другом модуле.
Разбиение программы на модули имеет четыре важных достоинства.
1. Простота: модуль решает одну задачу.
2. Удобство сопровождения: маленькие файлы лучше больших.
11.1. Работа с модулями 245
3. Удобство повторного использования: модули сокращают дублирование
кода.
4. Определение областей видимости: модули имеют собственные пространства имен.
Этот раздел посвящен модулям. Вы научитесь создавать их в IDLE, импортировать один модуль в другой, а также узнаете, как модули создают пространства
имен.
Создание модулей
Запустите IDLE и откройте новое окно редактора командой FileNew File или
нажатием клавиш Ctrl+N. В окне редактора определите функцию add(), которая
возвращает сумму двух своих параметров:
# adder.py
def add(x, y):
return x + y
Выберите команду FileSave или нажмите клавиши Ctrl+S, чтобы сохранить
файл с именем adder.py в новом каталоге myproject/. Файл adder.py — это модуль
Python! Он не является законченной программой, но от модулей этого и не
требуется.
Теперь откройте другое окно редактора клавишами Ctrl+N и введите следующий
код:
# main.py
value = add(2, 2)
print(value)
Сохраните файл с именем main.py в только что созданной папке myproject/. Нажмите F5, чтобы выполнить модуль.
При выполнении модуля в интерактивном окне IDLE выводится ошибка
NameError:
Traceback (most recent call last):
File "//Documents/myproject/main.py", line 1, in <module>
value = add(2, 2)
NameError: name 'add' is not defined
Появление ошибки NameError неудивительно, потому что функция add() определяется в файле adder.py, а не main.py. Чтобы использовать add() в main.py,
необходимо сначала импортировать модуль adder.
246 Глава 11 Модули и пакеты
Импортирование одного модуля в другой
В окне редактора добавьте следующую строку в начало файла main.py:
# main.py
import adder # <-- Добавьте эту строку
# Дальнейший код остается без изменений
value = add(2, 2)
print(value)
Когда вы импортируете (командой import) один модуль в другой, содержимое
импортированного модуля становится доступно в другом модуле. Модуль
с коман­дой import называется вызывающим модулем. В данном примере
adder.py — импортируемый модуль, а main.py — вызывающий модуль.
Нажмите Ctrl+S, чтобы сохранить main.py, а затем запустите модуль клавишей
F5. И на этот раз появляется ошибка NameError! Дело в том, что обращение
к add() возможно только из пространства имен adder.
Пространство имен представляет собой набор имен — переменных, функций
и классов. Каждый модуль Python имеет собственное пространство имен.
Чтобы обратиться к переменным, функциям и классам модуля из этого же
модуля, достаточно указать их имя. Именно так мы поступали в примерах
до настоящего момента. Но для импортированных модулей этот способ не
годится.
Чтобы обратиться из вызывающего модуля к имени, содержащемся в импортированном модуле, укажите имя импортированного модуля, точку и нужное имя:
<модуль>.<имя>
Например, чтобы вызвать add() в модуле adder, необходимо использовать
­запись adder.add().
ВАЖНО!
Имя, используемое для импортирования модуля, совпадает с именем файла
модуля.
По этой причине имена файлов модулей должны быть допустимыми идентификаторами Python. Это означает, что они могут содержать только буквы
верхнего и нижнего регистра, цифры и символы подчеркивания и не могут
начинаться с цифры.
11.1. Работа с модулями 247
Обновите код main.py:
# main.py
import adder
value = adder.add(2, 2) # <-- Измените эту строку
print(value)
Сохраните файл и запустите модуль. В интерактивном окне выводится значение 4.
При выполнении команды import <модуль> в начале файла импортируется все
пространство имен модуля. Все новые переменные и функции, добавленные
в adder.py, становятся доступными в main.py, дополнительно ничего импортировать не придется.
Откройте окно редактора для adder.py и добавьте следующую функцию перед
add():
# adder.py
# Этот код остается без изменений
def add(x, y):
return x + y
def double(x): # <-- Добавьте эту функцию
return x + x
Сохраните файл. Откройте окно редактора для main.py и добавьте следующий код:
# main.py
import adder
value = adder.add(2, 2)
double_value = adder.double(value) # <-- Добавьте эту строку
print(double_value) # <-- Измените эту строку
Сохраните и запустите main.py. При выполнении модуля в интерактивном окне
выводится значение 8. Так как double() уже существует в пространстве имен
adder, ошибка NameError не выдается.
Разновидности команды import
Команда import весьма гибкая. Существуют две ее разновидности, о которых
необходимо знать.
1. import <модуль> as <другое_имя>
2. from <модуль> import <имя>
Рассмотрим каждую из этих разновидностей более подробно.
248 Глава 11 Модули и пакеты
import <модуль> as <другое_имя>
Ключевое слово as позволяет изменить имя импортирования:
import <модуль> as <другое_имя>
Когда вы импортируете модуль этим способом, для обращения к пространству
имен модуля используется <другое_имя> вместо имени <модуль>.
Например, модифицируйте команду import в main.py следующим образом:
import adder as a # <-- Измените эту строку
# Последующий код остается без изменений
value = adder.add(2, 2)
double_value = adder.double(value)
print(double_value)
Сохраните файл и нажмите F5. Python выдает ошибку NameError:
Traceback (most recent call last):
File "//Mac/Home/Documents/myproject/main.py", line 3, in <module>
value = adder.add(2, 2)
NameError: name 'adder' is not defined
Имя adder теперь не распознается, потому что модуль был импортирован
с именем a вместо adder.
Чтобы программа main.py снова заработала, необходимо заменить adder.add()
и adder.double() на a.add() и a.double():
import adder as a
value = a.add(2, 2) # <-- Измените эту строку
double_value = a.double(value) # <-- И эту строку тоже!
print(double_value)
Сохраните файл и запустите модуль. Ошибка NameError не возникает, а в интерактивном окне выводится значение 8.
from <модуль> import <имя>
Вместо импортирования всего пространства имен можно импортировать только
конкретное имя из модуля. Для этого команда import заменяется следующей
командой:
from <модуль> import <имя>
11.1. Работа с модулями 249
Например, замените в main.py команду import следующей:
# main.py
from adder import add # <-- Измените эту строку
value = adder.add(2, 2)
double_value = adder.double(2, 2)
print(double_value)
Сохраните файл и нажмите F5. Происходит исключение NameError:
Traceback (most recent call last):
File "//Documents/myproject/main.py", line 3, in <module>
value = adder.add(2, 2)
NameError: name 'adder' is not defined
Трассировка сообщает, что имя adder не определено. Из adder.py импортируется только имя add, которое помещается в локальное пространство имен модуля main.py. Это означает, что add() можно использовать без полной записи
adder.add().
Замените adder.add() и adder.double() в main.py на add() и double():
# main.py
from adder import add
value = add(2, 2) # <-- Измените эту строку
double_value = double(value) # <-- И эту строку тоже!
print(double_value)
Сохраните файл и запустите модуль. Как вы думаете, что произойдет?
Возникает еще одна ошибка NameError:
Traceback (most recent call last):
File "//Documents/myproject/main.py", line 4, in <module>
double_value = double(value)
NameError: name 'double' is not defined
На этот раз NameError сообщает, что имя double не определено; это доказывает,
что из модуля adder было импортировано только имя add.
Чтобы импортировать имя double, включите его в команду import в main.py:
# main.py
from adder import add, double # <-- Измените эту строку
# Дальнейший код остается без изменений
value = add(2, 2)
double_value = double(value)
print(double_value)
250 Глава 11 Модули и пакеты
Сохраните и запустите модуль. Теперь он выполняется без ошибки NameError.
В интерактивном окне выводится значение 8.
Сводка команд import
В следующей таблице суммируется то, что вы узнали об импортировании
модулей.
КОМАНДА IMPORT
РЕЗУЛЬТАТ
import <модуль>
Импортирует все пространство имен модуля <модуль>
под именем <модуль>. Для обращения к именам из вызывающего модуля используется запись <модуль>.<имя>
import <модуль> as
<другое_имя>
Импортирует все пространство имен модуля <модуль>
под именем <другое_имя>. Для обращения к именам
из вызывающего модуля используется запись <другое_имя>.<имя>
from <модуль> import
<имя1>, <имя2>…
Импортирует из модуля <модуль> только имена <имя1>,
<имя2> и т. д. Имена добавляются в локальное пространство имен вызывающего модуля, и к ним можно
обращаться напрямую
Разделение пространств имен — одно из главных преимуществ разбиения кода
на модули. Давайте разберемся, чем же так важны пространства имен и почему
вам стоит о них знать.
Для чего нужны пространства имен
Допустим, каждому человеку на нашей планете присвоен идентификатор. Чтобы
людей можно было отличать друг от друга, все идентификаторы должны быть
уникальными. Да, идентификаторов понадобится довольно много!
Мир разделен на страны, поэтому мы можем сгруппировать людей по стране
рождения. Если присвоить каждой стране уникальный код, его можно присоединить к идентификатору. Например, тому, кто родился в Соединенных
Штатах, может быть присвоен идентификатор US-357, а тому, кто родился
в Великобритании, — идентификатор GB-246.
В такой схеме двум людям из разных стран могут быть присвоены одинаковые
идентификаторы. Их удастся различить, потому что в начале идентификаторов
указаны разные коды страны. У всех людей из одной страны идентификаторы
должны быть уникальными, но глобально уникальные идентификаторы уже
не нужны.
11.1. Работа с модулями 251
Коды стран в этом сценарии можно сравнить с пространствами имен, и они
демонстрируют три основные причины их использования:
1. Они обеспечивают группировку имен в логических контейнерах.
2. Они предотвращают конфликты между совпадающими именами.
3. Они предоставляют контекст для имен.
Пространства имен в коде обладают теми же преимуществами.
Мы уже рассказали о трех разных способах импортирования одного модуля
в другой. Если вы будете помнить о преимуществах пространства имен, это
поможет вам определить, какая команда импортирования окажется наиболее
подходящей в каждом конкретном случае. В общем случае предпочтительнее
всего команда import <модуль>, потому что она полностью отделяет пространство имен импортируемого модуля от пространства имен вызывающего модуля.
Более того, для обращения к именам из импортируемого модуля в вызывающем
модуле используется формат <модуль>.<имя>, по которому сразу видно, из
какого модуля происходит имя.
Формат import <модуль> as <другое_имя> обычно используется в двух ситуациях.
1. Имя модуля слишком длинное, и вы хотите импортировать его сокращенную версию.
2. Имя модуля конфликтует с существующим именем в вызывающем
модуле.
Команда import <модуль> as <другое_имя> также отделяет пространство имен
импортируемого модуля от пространства имен вызывающего модуля. С другой
стороны, имя, которое вы присваиваете модулю, может не настолько легко
узнаваться, как исходное имя.
Импортирование конкретных имен из модуля обычно считается наименее желательным способом импортирования кода из модуля. Импортируемые имена
добавляются прямо в пространство имен вызывающего модуля, в результате
чего полностью теряется контекст вызываемого модуля.
Иногда модули содержат функцию или класс с таким же именем, как в другом
модуле. Например, в стандартную библиотеку Python входит модуль datetime,
который содержит класс с именем datetime.
Допустим, в вашем коде используется команда import:
import datetime
252 Глава 11 Модули и пакеты
Команда импортирует модуль datetime в пространство имен вашего кода, поэтому для использования класса datetime из модуля datetime нужно применять
следующую запись:
datetime.datetime(2020, 2, 2)
Пока не отвлекайтесь на то, как работает класс datetime. В этом примере важно,
что вам приходится вводить datetime.datetime каждый раз, когда вы хотите
воспользоваться классом datetime, и такие громоздкие повторы утомительны.
Это превосходный пример того, когда уместно использовать одну из разновидностей команды import. Чтобы не утратить контекст модуля datetime, программисты на Python обычно импортируют модуль и переименовывают его в dt:
import datetime as dt
Чтобы использовать класс datetime, теперь достаточно ввести dt.datetime:
dt.datetime(2020, 2, 2)
Также класс datetime часто импортируется прямо в пространство имен вызывающего модуля:
from datetime import datetime
Это тоже неплохо, потому что контекст не теряется. В конце концов, имена
класса и модуля совпадают.
Если класс datetime импортируется напрямую, для обращения к нему вам не
придется использовать точечную нотацию:
datetime(2020, 2, 2)
Разные версии import позволяют сэкономить время, которое тратится на ввод
слишком длинных имен модулей в точечной нотации. Тем не менее злоупотребление различными формами import может привести к потере контекста, и код
станет менее понятным.
Всегда руководствуйтесь здравым смыслом при импортировании модулей,
чтобы по возможности сохранить контекст.
Упражнения
1. Создайте модуль greeter.py, содержащий единственную функцию greet().
Функция должна получать один строковый параметр name и выводить
11.2. Работа с пакетами 253
в интерактивном окне текст Hello {name}!, где {name} заменяется аргументом функции.
2. Создайте модуль main.py, который импортирует функцию greet() из
greeter.py и вызывает функцию с аргументом "Real Python".
11.2. РАБОТА С ПАКЕТАМИ
Модули позволяют разделить программу на отдельные файлы, которые можно
повторно использовать по мере надобности. Взаимосвязанный код объединяют
в один модуль и хранят отдельно.
Пакеты позволяют более широко использовать эту организационную структуру,
давая возможность группировать взаимосвязанные модули в одном пространстве имен.
Сейчас вы научитесь создавать собственные пакеты Python и импортировать
код из этих пакетов в другие модули.
Создание пакетов
Пакет — это папка, содержащая один или несколько модулей Python. В пакет
должен также входить специальный модуль с именем __init__.py. Следующий
пример пакета демонстрирует эту структуру.
Модуль __init__.py может вообще не содержать кода! Он только должен существовать, чтобы Python распознал папку mypackage/ как пакет Python.
Запустите на своем компьютере файловый менеджер (Проводник или другую
программу, к которой вы привыкли) и создайте где-нибудь новую папку с именем packages_example/. Внутри нее создайте другую папку с именем mypackage/.
Папка packages_example/ называется папкой проекта, или корневой папкой проекта, потому что она содержит все файлы или папки проекта packages_examples.
Папка mypackage/ вскоре станет пакетом Python. Сейчас она им не является,
потому что не содержит ни одного модуля.
254 Глава 11 Модули и пакеты
Откройте IDLE и создайте новое окно редактора нажатием клавиш Ctrl+N.
Включите в начало файла следующий комментарий:
# main.py
Теперь нажмите Ctrl+S и сохраните файл с именем main.py в созданной ранее
папке packages_-example/.
Откройте другое окно редактора клавишами Ctrl+N. Включите следующую
строку в начало файла:
# __init__.py
Сохраните файл с именем __init__.py во вложенной папке mypackage/ папки
packages_example.
Наконец, создайте еще два окна редактора. Сохраните эти файлы с именами
module1.py и module2.py соответственно в папке mypackage/ и вставьте в начало
каждого файла комментарий с именем файла. Когда вы все завершите, у вас
должны быть открыты пять окон IDLE: интерактивное окно и четыре окна
редактора.
Итак, структура пакета создана; теперь можно добавить код. Добавьте в файл
module1.py следующую функцию:
# module1.py
def greet(name):
print(f"Hello, {name}!")
Добавьте в файл module2.py следующую функцию:
# module2.py
def depart(name):
print(f"Goodbye, {name}!")
Обязательно сохраните оба файла, module1.py и module2.py! Теперь все готово
к тому, чтобы импортировать и использовать эти модули в модуле main.py.
Импортирование модулей из пакетов
Включите в файл main.py следующий фрагмент:
# main.py
import mypackage
mypackage.module1.greet("Pythonista")
mypackage.module2.depart("Pythonista")
11.2. Работа с пакетами 255
Сохраните main.py и нажмите F5, чтобы запустить модуль. В интерактивном
окне выдается ошибка AttributeError:
Traceback (most recent call last):
File "\MacHomeDocumentspackages_examplemain.py", line 5, in <module>
mypackage.module1.greet("Pythonista")
AttributeError: module 'mypackage' has no attribute 'module1'
Когда вы импортируете модуль mypackage, пространства имен module1 и module2
не импортируются автоматически — их тоже необходимо импортировать. Измените команду import в начале main.py:
# main.py
import mypackage.module1 # <-- Измените эту строку
# Дальнейший код остается без изменений
mypackage.module1.greet("Pythonista")
mypackage.module2.depart("Pythonista")
Теперь сохраните и запустите модуль main.py. В интерактивном окне должен
появиться следующий результат:
Hello, Pythonista!
Traceback (most recent call last):
File "\MacHomeDocumentspackages_examplemain.py", line 6, in <module>
mypackage.module2.depart("Pythonista")
AttributeError: module 'mypackage' has no attribute 'module2'
Функция mypackage.module1.greet() была вызвана, потому что в интерактивном
окне было выведено сообщение "Hello, Pythonista!".
Однако функция mypackage.module2.depart() вызвана не была. Эта строка
создала ошибку атрибутов, потому что на данный момент из mypackage был
импортирован только модуль module1.
Чтобы импортировать module2, добавьте следующую команду import в начало
файла main.py:
# main.py
import mypackage.module1
import mypackage.module2 # <-- Add this line
# Дальнейший код остается без изменений
mypackage.module1.greet("Pythonista")
mypackage.module2.depart("Pythonista")
Если теперь сохранить и выполнить main.py, будут вызваны обе функции,
greet() и depart():
256 Глава 11 Модули и пакеты
Hello, Pythonista!
Goodbye, Pythonista!
В общем случае при импортировании модулей из пакетов вводят полное имя
в следующем формате:
import <имя_пакета>.<имя_модуля>
Сначала указывают имя пакета, затем точку, а после нее имя импортируемого
модуля.
ВАЖНО!
Имена папок пакетов, как и имена файлов модулей, должны быть допустимыми идентификаторами Python. Они могут содержать только буквы верхнего
и нижнего регистра, цифры и символы подчеркивания и не могут начинаться
с цифры.
Как и в случае с модулями, существует несколько разновидностей команды
import, которые используют для импортирования пакетов.
Разновидности команды import для пакетов
Когда я рассказывал об импортировании имен из модулей, то показал вам три
разновидности команды import. Для импортирования модулей из пакетов их
четыре:
1. import <пакет>
2. import <пакет> as <другое_имя>
3. from <пакет> import <модуль>
4. from <пакет> import <модуль> as <другое_имя>
Они работают почти так же, как их аналоги. Например, вместо импортирования
mypackage.module1 и mypackage.module2 в отдельных строках можно импортировать оба модуля в одной строке. Внесите изменения в файл main.py:
# main.py
from mypackage import module1, module2
module1.greet("Pythonista")
module2.depart("Pythonista")
Если сохранить и запустить модуль, в интерактивном окне будут выведены те
же результаты.
11.2. Работа с пакетами 257
Имя импортируемого модуля можно изменить при помощи ключевого слова as:
# main.py
from mypackage import module1 as m1, module2 as m2
m1.greet("Pythonista")
m2.depart("Pythonista")
Также из модуля пакета допустимо импортировать отдельные имена. Например,
если вы внесете следующие изменения в файл main.py, результат, выводимый
при сохранении и запуске модуля, останется неизменным:
# main.py
from mypackage.module1 import greet
from mypackage.module2 import depart
greet("Pythonista")
depart("Pythonista")
Поскольку есть несколько способов импортирования пакета, то естественно
задаться вопросом: какой из них лучше?
Рекомендации по импортированию пакетов
Рекомендации по импортированию имен из модулей действуют и при импортировании модулей из пакетов. Старайтесь сделать импортирование как можно
более явным и однозначным, чтобы модули и имена, импортированные в вызывающий модуль, имели соответствующий контекст.
В общем случае наиболее однозначным является следующий формат:
import <пакет>.<модуль>
Для обращения к именам из модуля вводится команда вида:
<пакет>.<модуль>.<имя>
При таком подходе у вас не возникнут вопросы, когда вам встретятся имена из
импортируемых модулей. Но иногда имена пакетов и модулей оказываются
слишком длинными, и вам приходится снова и снова вводить конструкцию
<пакет>.<модуль> в своем коде.
Следующий формат позволяет пропустить имя пакета и импортировать в пространство имен вызывающего модуля только имя модуля:
from <пакет> import <модуль>
258 Глава 11 Модули и пакеты
Теперь для обращения к имени из модуля достаточно ввести <модуль>.<имя>.
Хотя такая конструкция уже не показывает, из какого пакета происходит имя,
контекст модуля остается очевидным.
А вот следующий формат обычно считается неоднозначным, и его стоит использовать только в том случае, если риск импортирования из модуля имени,
конфликтующего с именем в вызывающем модуле, полностью отсутствует:
from <пакет>.<модуль> import <имя>
Вы узнали об импортировании модулей из пакетов. Теперь давайте посмотрим,
как вкладывать пакеты в другие пакеты.
Импортирование модулей из подпакетов
Пакет — всего лишь обычная папка, содержащая один или несколько модулей
Python, одному из которых должно быть присвоено имя __init__.py. Таким
образом, следующая структура пакетов вполне допустима.
Пакет, вложенный в другой пакет, называется подпакетом. Например, папка
mysubpackage является подпакетом mypackage, потому что она содержит модуль
__init__.py, а также второй модуль с именем module3.py.
Запустите на своем компьютере файловый менеджер и создайте новую папку с именем mysubpackage/. Удостоверьтесь, что она находится внутри папки
mypackage/, созданной ранее.
Откройте в IDLE два новых окна редактора. Создайте файлы __init__.py
и module3.py, сохраните оба модуля в папке mysubpackage/. Включите в файл
module3.py следующий код:
# module3.py
people = ["John", "Paul", "George", "Ringo"]
Теперь откройте файл main.py из корневой папки проекта packages_examples/.
Удалите существующий код и замените его следующим:
11.2. Работа с пакетами 259
# main.py
from mypackage.module1 import greet
from mypackage.mysubpackage.module3 import people
for person in people:
greet(person)
Список people из модуля module3 в пакете mysubpackage импортируется через
имя модуля в точечной нотации mypackage.mysubpackage.module3.
Сохраните и запустите main.py. В интерактивном окне появится результат:
Hello, John!
Hello, Paul!
Hello, George!
Hello, Ringo!
Подпакеты удобны для организации кода очень больших пакетов. Они помогают
поддерживать в пакете четкую и упорядоченную структуру папок.
Тем не менее глубокое вложение подпакетов приводит к появлению длинных
имен в точечной нотации. Только представьте, сколько символов вам придется
ввести, чтобы импортировать модуль из подпакета, находящегося в подпакете
входящего в подпакет пакета.
Желательно ограничить подпакеты одним, максимум двумя уровнями вложенности.
Упражнения
1. В новой папке проекта с именем package_exercises/ создайте пакет helpers,
состоящий из трех модулей: __init__.py, string.py и math.py.
Добавьте в модуль string.py функцию с именем shout(), которая получает
один строковый параметр и возвращает новую строку с символами, преобразованными к верхнему регистру.
Добавьте в модуль math.py функцию с именем area(), которая получает
два параметра, length и width, и возвращает их произведение length *
width.
2. В корневой папке проекта создайте модуль main.py, который импортирует
функции shout() и area(). Используйте shout() и area() для вывода
следующего результата:
THE AREA OF A 5-BY-8 RECTANGLE IS 40
260 Глава 11 Модули и пакеты
11.3. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе я показал, как создавать модули и пакеты Python, а также импортировать объекты из других модулей. Вы узнали, что деление кода на модули
и пакеты обладает рядом достоинств.
zz
Маленькие файлы с кодом проще больших.
zz
Маленькие файлы с кодом создают меньше проблем при сопровождении.
zz
Модули можно повторно использовать в проекте.
zz
Модули группируют взаимосвязанные объекты в изолированных пространствах имен.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-modules-packages
Дополнительные ресурсы
За дополнительной информацией о модулях и пакетах обращайтесь к следующим ресурсам:
zz
«Python Modules and Packages» (https://realpython.com/courses/pythonmodules-packages/)
zz
«Absolute vs Relative Imports» (https://realpython.com/courses/absolutevs-relative-imports-python/)
ГЛАВА 12
Операции ввода и вывода
с файлами
До сих пор вы писали программы, получающие входные данные от одного из
двух источников: от пользователя или самой программы. Вывод ограничивался
отображением текста в интерактивном окне IDLE.
Эти методы ввода и вывода бесполезны во многих ситуациях — вот несколько
примеров:
zz
входные значения неизвестны во время написания программы;
zz
программе требуется больше данных, чем пользователь способен ввести
вручную;
zz
результаты после запуска программы необходимо передать другим разработчикам.
На помощь приходят файлы.
В этой главе вы научитесь:
zz
работать с путями и метаданными файлов;
zz
читать и записывать текстовые файлы;
zz
читать и записывать файлы в формате CSV;
zz
создавать, удалять, копировать и перемещать файлы и папки.
Итак, за дело!
12.1. ФАЙЛЫ И ФАЙЛОВАЯ СИСТЕМА
Скорее всего, у вас уже есть опыт работы с файлами. Впрочем, даже в этом
случае программисту необходимо знать о файлах то, чего не знают обычные
пользователи.
262 Глава 12 Операции ввода и вывода с файлами
Сейчас мы познакомим вас с концепциями, необходимыми для работы с файлами в Python.
Анатомия файла
Существует много типов файлов: текстовые, графические, аудиофайлы, файлы
в формате PDF и т. д. Впрочем, независимо от типа файл представляет собой
последовательность байтов, называемую содержимым файла.
Байт — целое число со значением в диапазоне от 0 до 255. Байты записываются
на физический носитель при сохранении файла. Когда вы обращаетесь к файлу
на своем компьютере, байты файла последовательно считываются с диска.
В файле нет ничего, что указывало бы, как интерпретировать его содержимое. Вы
как программист отвечаете за преобразование байтов в нужный формат, когда
ваша программа открывает файл. Может показаться, что это достаточно сложно,
но Python выполняет большую часть тяжелой работы за вас. Например, Python
может преобразовать числовые байты текстового файла в текстовые символы. Вам
не нужно знать, как выполняется это преобразование. В стандартной библиотеке
имеются инструменты для работы с разными типами файлов, включая графические и аудиофайлы. Чтобы обратиться к файлу на устройстве хранения данных,
необходимо знать, где хранится файл и как взаимодействовать с этим устройством.
Эта грандиозная работа выполняется файловой системой вашего компьютера.
Файловая система
Файловая система компьютера решает две задачи.
1. Она выдает абстрактное представление файлов, хранящихся на компьютере и любых устройствах, подключенных к нему.
2. Она взаимодействует с устройствами для управления записью и чтением
данных файлов.
Python взаимодействует с файловой системой на вашем компьютере. Возможности таких взаимодействий ограничиваются операциями, которые поддерживаются файловой системой.
ВАЖНО!
В разных операционных системах (ОС) используются разные файловые системы. Важно помнить об этом при написании кода, который будет выполняться
в различных ОС.
12.1. Файлы и файловая система 263
Файловая система управляет взаимодействиями между компьютером и физическим устройством хранения данных. И это очень хорошо. Это означает, что вам —
программисту Python — не придется беспокоиться о таких подробностях, как
обращение к физическому носителю или управление вращением жесткого диска.
Иерархия файловых систем
Файловые системы организуют файлы в иерархию каталогов, также называемых
папками. На верху иерархии находится корневой каталог. В нем хранятся все
остальные файлы и каталоги файловой системы.
ВАЖНО!
В Windows каждый диск имеет собственную иерархию файлов с корневым
каталогом, представленным именем диска.
В macOS и Linux каждый диск представляет собой подкаталог единого корневого каталога.
Каждый файл обладает именем, которое должно отличаться от имен всех остальных файлов в той же папке. Каталоги также могут содержать другие каталоги,
называемые подкаталогами, или вложенными папками.
Следующее дерево каталогов наглядно представляет иерархию файлов и папок
в некоторой файловой системе.
В этой файловой системе корневому каталогу присвоено имя root/. Он содержит два подкаталога с именами app/ и photos/. Подкаталог app/ содержит файл
program.py и файл data.txt. Каталог photos/ содержит два подкаталога с именами
cats/ и dogs/, каждый из которых содержит два графических файла.
264 Глава 12 Операции ввода и вывода с файлами
Полное имя файла
Чтобы отыскать файл в файловой системе, необходимо последовательно перечислить каталоги, начиная с корневого, и в конце цепочки указать имя файла.
Запись местонахождения файла в таком представлении называется путем
к файлу (также используется термин «полное имя»).
Например, путь к файлу jack_russel.gif в файловой системе из предыдущего
примера имеет вид root/photos/dogs/jack_russel.gif.
Способ записи пути к файлам зависит от операционной системы. Вот примеры
пути к файлам в Windows, macOS и Linux:
1. Windows: C:\Users\David\Documents\hello.txt
2. macOS: /Users/David/Documents/hello.txt
3. Ubuntu Linux: /home/David/Documents/hello.txt
Все три пути ведут к текстовому файлу hello.txt, который находится в подкаталоге Documents каталога пользователя с именем David. Как видите, пути к файлам
в разных операционных системах заметно отличаются.
В macOS и Ubuntu Linux операционная система использует виртуальную
файловую систему, в которой все файлы и каталоги на всех устройствах располагаются в одном корневом каталоге, который обычно обозначается косой
чертой /. Файлы и папки внешних устройств хранения данных обычно находятся
в подкаталоге с именем media/.
В системе Windows единого корневого каталога не существует. Каждое устройство (диск) обладает отдельной файловой системой с собственным корневым
каталогом, имя которого состоит из буквенного обозначения диска, за которым
следует двоеточие и обратная косая черта \.
Как правило, жесткому диску, на котором установлена операционная система,
назначается буква C, поэтому корневой каталог файловой системы для этого
диска имеет имя C:.
Другое принципиальное отличие файловых путей Windows, macOS и Ubuntu
заключается в том, что каталоги в путях Windows разделяются обратной косой
чертой \, тогда как разделителем каталогов в macOS и Ubuntu является обычная
косая черта /.
Когда вы пишете программы, которые должны выполняться в разных операционных системах, очень важно правильно указывать путь. В версиях Python
до 3.4 стандартная библиотека содержит модуль pathlib, упрощающий работу
с путем в разных операционных системах.
12.2. Работа с путями к файлам в Python 265
12.2. РАБОТА С ПУТЯМИ К ФАЙЛАМ В PYTHON
Модуль pathlib из стандартной библиотеки Python предоставляет основной
интерфейс для работы с путями к файлам. Прежде чем что-либо делать с модулем, его необходимо импортировать.
Откройте интерактивное окно в IDLE и импортируйте pathlib следующей
командой:
>>> import pathlib
Модуль pathlib содержит класс с именем Path, который используется для
представления пути к файлу.
Создание объекта Path
Есть несколько способов создания объектов Path.
1. На основе строки.
2. Методами класса Path.home() и Path.cwd().
3. Оператором /.
Проще всего создать объект Path на основе строки.
Создание объектов Path из строк
Например, следующий фрагмент создает объект Path, представляющий путь
к файлу macOS "/Users/David/Documents/hello.txt":
>>> path = pathlib.Path("/Users/David/Documents/hello.txt")
Однако с путем в Windows появляется проблема. В Windows каталоги разделяются обратной косой чертой \. Python интерпретирует обратную косую черту
как начало служебной последовательности (escape-последовательности), представляющей специальный символ в строке — например, символ новой строки \n.
При попытке создать объект Path для пути к файлу в Windows "C:\Users\David\
Desktop\hello.txt" выдается ошибка:
>>> path = pathlib.Path("C:\Users\David\Desktop\hello.txt")
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes
in position 2-3: truncated \UXXXXXXXX escape
Проблему можно решить двумя способами.
266 Глава 12 Операции ввода и вывода с файлами
Во-первых, в пути к файлам Windows можно использовать обычную косую
черту / вместо обратной \:
>>> path = pathlib.Path("C:/Users/David/Desktop/hello.txt")
Python нормально интерпретирует эту строку и правильно автоматически ­преобразует ее в путь при взаимодействии с операционной системой
Windows.
Во-вторых, строку можно преобразовать в необработанную (raw string), для
чего перед ней ставят префикс r:
>>> path = pathlib.Path(r"C:\Users\David\Desktop\hello.txt")
Тем самым вы приказываете Python игнорировать служебные последовательности и просто прочитать строку в том виде, в котором она записана.
Использование Path.home() и Path.cwd()
Кроме создания объекта Path на основе строки, можно воспользоваться классом
Path. Он содержит методы класса, возвращающие объекты Path для специальных каталогов. Два самых полезных метода класса — Path.home() и Path.cwd().
У каждой операционной системы имеется специальный каталог для хранения
данных текущего пользователя. Он называется домашним каталогом пользователя. Его местонахождение зависит от ОС:
zz
Windows: C:\Users\<имя_пользователя>
zz
macOS: /Users/<имя_пользователя>
zz
Ubuntu Linux: /home/<имя_пользователя>
Метод класса Path.home() создает объект Path, содержащий путь к домашнему
каталогу независимо от того, в какой ОС выполняется код:
>>> home = pathlib.Path.home()
Проверив переменную home в Windows, вы получите примерно такой результат:
>>> home
WindowsPath("C:/Users/David")
Создаваемый объект относится к классу WindowsPath, производному от Path.
В других операционных системах возвращается объект производного класса
с именем PosixPath.
12.2. Работа с путями к файлам в Python 267
Например, в macOS при проверке home будет выведен результат следующего
вида:
>>> home
PosixPath("/Users/David")
Далее в примерах вывода этого раздела будут отображаться объекты WindowsPath.
Тем не менее все примеры также работают с объектами PosixPath.
ПРИМЕЧАНИЕ
Объекты WindowsPath и PosixPath содержат одни и те же методы и атрибуты.
С точки зрения программирования две разновидности объектов Path ничем
не отличаются.
Метод класса Path.cwd() возвращает объект Path для текущего рабочего каталога (Current Working Directory, CWD). Это динамическая ссылка на каталог,
которая зависит от того, с каким каталогом в настоящий момент работает процесс на вашем компьютере. Она всегда представляет текущий каталог файловой
системы.
При работе в IDLE текущим рабочим каталогом обычно назначается домашний
каталог текущего пользователя:
>>> pathlib.Path.cwd()
WindowsPath("C:/Users/David/Documents")
Впрочем, это не всегда так. Более того, текущий рабочий каталог может изменяться на протяжении жизненного цикла программы.
Метод Path.cwd() удобен, но будьте внимательны при его использовании. Необходимо точно понимать, какой именно каталог считается текущим рабочим.
Использование оператора /
Если у вас уже имеется существующий объект Path, оператор / можно использовать для дополнения пути подкаталогами или именами файлов.
Например, следующий фрагмент создает объект Path, представляющий файл
с именем hello.txt в подкаталоге Desktop домашнего каталога текущего пользователя:
>>> home / "Desktop" / "hello.txt"
WindowsPath('C:/Users/David/Desktop/hello.txt')
268 Глава 12 Операции ввода и вывода с файлами
В левой части оператора / всегда должен находиться объект Path. В правой части
может быть строка, представляющая один файл либо каталог, или же строка,
представляющая путь либо другой объект Path.
Абсолютные и относительные пути
Путь, начинающийся с корневого каталога в файловой системе, называется
абсолютным. Не все пути являются абсолютными. Путь, который не является
абсолютным, называется относительным.
Пример объекта Path с относительным путем:
>>> path = pathlib.Path("Photos/image.jpg")
Обратите внимание: строка пути не начинается с C:\ или /. Чтобы проверить, является ли путь абсолютным, можно воспользоваться функцией .is_absolute():
>>> path.is_absolute()
False
Относительные пути имеют смысл только в том случае, если они рассматриваются в контексте другого каталога. Чаще всего они используются для описания
пути к файлу относительно текущего рабочего каталога или домашнего каталога
пользователя.
Относительный путь можно преобразовать в абсолютный оператором /:
>>> home = pathlib.Path.home()
>>> home / pathlib.Path("Photos/image.png")
WindowsPath('C:/Users/David/Photos/image.png')
Слева от косой черты / указывают абсолютный путь к каталогу, содержащему
относительный путь. Справа — относительный путь.
Впрочем, у вас не всегда достаточно информации для построения абсолютного
пути. В таких случаях применяйте метод Path.resolve().
Если метод .resolve() вызывается для существующего объекта Path, возвращается новый объект Path, представляющий абсолютный путь:
>>> relative_path = pathlib.Path("/Users/David")
>>> absolute_path = relative_path.resolve()
>>> absolute_path
WindowsPath('C:/Users/David')
Path.resolve() пытается проложить наиболее длинный абсолютный путь.
12.2. Работа с путями к файлам в Python 269
Иногда относительный путь оказывается неоднозначным. В таких случаях
.resolve() возвращает относительный путь. Другими словами, нет гарантий
того, что .resolve() вернет абсолютный путь.
После того как объект Path будет создан, вы можете узнать компоненты пути
к тому файлу, на который он ссылается.
Доступ к компонентам пути
Любой путь к файлу содержит список каталогов. Атрибут .parents объекта Path
возвращает итерируемый объект, содержащий список каталогов из этого пути:
>>> path = pathlib.Path.home() / "hello.txt"
>>> path
WindowsPath("C:/Users/David")
>>> list(path.parents)
[WindowsPath("C:/Users/David"), WindowsPath("C:/Users"),
WindowsPath("C:/")]
Учтите, что каталоги возвращаются в порядке, обратном порядку их следования
в пути к файлу. Иначе говоря, последний каталог в пути становится первым
в списке родительских каталогов.
Родительские каталоги можно перебрать в цикле for:
>>> for directory in path.parents:
...
print(directory)
...
C:\Users\David
C:\Users
C:\
Атрибут .parent возвращает имя первого родительского каталога в пути к файлу
в виде строки:
>>> path.parent
WindowsPath('C:/Users/David')
.parent — это сокращенная запись для .parents[0].
Если путь к файлу является абсолютным, вы можете обратиться к корневому
каталогу пути при помощи атрибута .anchor:
>>> path.anchor
'C:\'
Стоит заметить, что .anchor возвращает строку, а не другой объект Path.
270 Глава 12 Операции ввода и вывода с файлами
Для относительных путей .anchor возвращает пустую строку:
>>> path = pathlib.Path("hello.txt")
>>> path.anchor
''
Атрибут .name возвращает имя файла или каталога, на который указывает путь:
>>> home = pathlib.Path.home() # C:\Users\David
>>> home.name
'David'
>>> path = home / "hello.txt"
>>> path.name
'hello.txt'
Имена файлов состоят из двух частей. Часть слева от точки называется основой,
а часть справа — суффиксом или расширением файла.
Атрибуты .stem и .suffix возвращают строки, содержащие каждую из этих
частей имени:
>>> path.stem
'hello'
>>> path.suffix
'.txt'
Наверное, вас интересует, как сделать что-нибудь полезное с файлом hello.txt.
В следующем разделе я расскажу, как читать и записывать файлы. Но прежде
чем открыть файл для чтения, желательно проверить, существует ли этот файл.
Проверка существования пути к файлу
Объект Path для пути к файлу можно создать даже в том случае, если путь не
существует. Конечно, пути, которые не ведут к реальным файлам или каталогам,
особой пользы не принесут, если только вы не создадите их в какой-то момент.
У объектов Path имеется метод .exists(), который возвращает True или False
в зависимости от того, существует ли путь на компьютере, на котором выполняется программа.
Например, если в вашем домашнем каталоге отсутствует файл hello.txt, вызов
.exists() для объекта Path, представляющего этот файл, вернет False:
>>> path = pathlib.Path.home() / "hello.txt"
>>> path.exists()
False
12.2. Работа с путями к файлам в Python 271
Используя текстовый редактор или другой инструмент, создайте пустой
текстовый файл с именем hello.txt в своем домашнем каталоге. Затем снова
выполните приведенный выше пример и убедитесь в том, что path.exists()
возвращает True.
Также можно проверить, ведет ли путь к файлу или к каталогу. В первом случае
используется метод .is_file():
>>> path.is_file()
True
Если файл не существует, то .is_file() возвращает False.
Чтобы проверить, ведет ли путь к каталогу, используйте метод .is_dir():
>>> # "hello.txt" не является каталогом
>>> path.is_dir()
False
>>> # home является каталогом
>>> home.is_dir()
True
Работа с путями — неотъемлемая часть любого программного проекта, где
выполняется чтение или запись данных на жесткий диск или другой носитель
информации. Понимание различий между путями к файлам в разных операционных системах и умение работать с объектами pathlib.Path для любой
ОС — исключительно важный и полезный навык.
Упражнения
1. Создайте новый объект Path для файла с именем my_file.txt в папке
my_folder/, находящейся в домашнем каталоге вашего компьютера. Присвойте этот объект Path переменной с именем file_path.
2. Проверьте, существует ли файл/каталог для пути, присвоенного file_
path.
3. Выведите имя для пути, присвоенного file_path. Программа должна
вывести my_file.txt.
4. Выведите имя родительского каталога для пути, присвоенного file_path.
Программа должна вывести my_folder.
272 Глава 12 Операции ввода и вывода с файлами
12.3. ОСНОВНЫЕ ОПЕРАЦИИ
ФАЙЛОВОЙ СИСТЕМЫ
Теперь, когда вы умеете работать с путями к файлам при помощи модуля pathlib,
я расскажу о типичных операциях с файлами и об их выполнении в коде Python.
Создание каталогов и файлов
Чтобы создать новый каталог, воспользуйтесь методом Path.mkdir(). Введите
в интерактивном окне IDLE следующий фрагмент:
>>> from pathlib import Path
>>> new_dir = Path.home() / "new_directory"
>>> new_dir.mkdir()
После импортирования класса Path мы создаем новый путь для каталога с именем new_directory/ в своем домашнем каталоге и присваиваем этот путь переменной new_dir. Затем используем метод .mkdir()для создания нового каталога.
Теперь надо проверить, что новый каталог существует и действительно является
каталогом, а не файлом:
>>> new_dir.exists()
True
>>> new_dir.is_dir()
True
Если вы попытаетесь создать каталог, который уже существует, происходит
ошибка:
>>> new_dir.mkdir()
Traceback (most recent call last):
File "<pyshell#32>", line 1, in <module>
new_dir.mkdir()
File "C:\Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py", line 1266, in mkdir
self._accessor.mkdir(self, mode)
FileExistsError: [WinError 183] Cannot create a file when
that file already exists: 'C:\\Users\\David\\new_directory'
При вызове .mkdir() Python снова пытается создать папку new_directory/. Так
как каталог уже существует, попытка завершается неудачей и происходит исключение FileExistsError.
А что если вы хотите создать новый каталог только в том случае, если он еще не
существует, а заодно избежать ошибки FileExistsError, если каталог существует?
12.3. Основные операции файловой системы 273
В таком случае присвойте параметру exist_ok метода .mkdir() значение True:
>>> new_dir.mkdir(exist_ok=True)
Когда при выполнении .mkdir() параметр exist_ok равен True, то каталог
создается только в том случае, если он еще не существует. Если же каталог
существует, то ничего не происходит.
Присваивание exist_ok значения True при вызове .mkdir() эквивалентно
следующему коду:
>>> if not new_dir.exists():
...
new_dir.mkdir()
И хотя этот код нормально работает, решение с присваиванием параметру
exist_ok значения True короче без ущерба для удобочитаемости.
Теперь посмотрим, что произойдет при попытке создать подкаталог внутри
несуществующего каталога:
>>> nested_dir = new_dir / "folder_a" / "folder_b"
>>> nested_dir.mkdir()
Traceback (most recent call last):
File "<pyshell#38>", line 1, in <module>
nested_dir.mkdir()
File "C:\Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py", line 1266, in mkdir
self._accessor.mkdir(self, mode)
FileNotFoundError: [WinError 3] The system cannot findthe path
specified: 'C:\\Users\\David\\new_directory\\folder_a\\folder_b'
Проблема в том, что родительский каталог folder_a/ не существует. Как правило,
для создания каталога все родительские папки целевого каталога (в данном
случае folder_b/) уже должны существовать.
Чтобы сформировать все родительские папки, необходимые для создания целевого каталога, присвойте необязательному параметру parents метода .mkdir()
значение True:
>>> nested_dir.mkdir(parents=True)
В этом случае .mkdir() создает родительский каталог folder_a/, что необходимо
для создания целевого каталога folder_b/.
Объединяя все сказанное, мы получаем следующую типичную схему создания
каталогов:
path.mkdir(parents=True, exist_ok=True)
274 Глава 12 Операции ввода и вывода с файлами
Когда параметрам parents и exist_ok присвоено значение True, весь путь будет
создан в случае необходимости, а если путь уже существует, то исключение не
выдается.
Эта схема полезна, но такой подход не универсален. Например, если пользователь введет несуществующий путь, лучше перехватить исключение и предложить проверить введенный путь. Возможно, пользователь просто сделал
опечатку в имени существующего каталога!
Теперь посмотрим, как создавать файлы. Создайте новый объект Path с именем
file_path для пути new_directory/file1.txt:
>>> file_path = new_dir / «file1.txt»
В каталоге new_directory/ файл с именем file1.txt отсутствует, так что путь еще
не существует:
>>> file_path.exists()
False
Файл можно создать методом Path.touch():
>>> file_path.touch()
Метод создает новый файл с именем file1.txt в папке new_directory/. Он еще не
содержит никаких данных:
>>> file_path.exists()
True
>>> file_path.is_file()
True
В отличие от .mkdir(), метод .touch() не выдает исключение, если создаваемый
путь уже существует:
>>> # При повторном вызове .touch() исключение не выдается
>>> file_path.touch()
Файл, созданный вызовом .touch(), не содержит никаких данных. Я покажу,
как записывать данные в файлы в разделе 12.5 «Чтение и запись файлов».
Создать файл в несуществующем каталоге невозможно:
>>> file_path = new_dir / "folder_c" / "file2.txt"
>>> file_path.touch()
Traceback (most recent call last):
12.3. Основные операции файловой системы 275
File "<pyshell#47>", line 1, in <module>
file_path.touch()
File "C:\Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py", line 1256, in touch
fd = self._raw_open(flags, mode)
File "C:\Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py", line 1063, in _raw_open
return self._accessor.open(self, flags, mode)
FileNotFoundError: [Errno 2] No such file or directory:
'C:\\Users\\David\\new_directory\\folder_c\\file2.txt'
Ошибка FileNotFoundError возникает из-за того, что в папке new_directory/ нет
вложенной папки folder_c/.
В отличие от .mkdir(), метод .touch() не имеет параметра parents, позволяющего автоматически создавать родительские каталоги. Это означает, что все
необходимые каталоги должны быть созданы до вызова .touch().
Например, можно воспользоваться атрибутом .parent для получения пути
к родительской папке для файла file2.txt, а затем вызвать .mkdir() для создания
каталога:
>>> file_path.parent.mkdir()
Так как .parent возвращает объект Path, можно выполнить сцепленный вызов
.mkdir() для выполнения всей операции в одной строке кода.
После создания каталога folder_c/ файл будет успешно создан:
>>> file_path.touch()
Итак, вы знаете, как создавать файлы и каталоги, и мы можем перейти к чтению
содержимого каталога.
Перебор содержимого каталога
Модуль pathlib позволяет перебрать содержимое каталога. Например, такая
необходимость может возникнуть для обработки всех файлов в каталоге.
Под термином «обработка» в данном случае понимается чтение файла и извлечение некоторых данных, сжатие файлов в каталоге или какая-нибудь другая
операция.
А пока сосредоточимся на том, как получить содержимое заданного каталога.
Далее в этой главе я расскажу, как прочитать данные из файлов.
276 Глава 12 Операции ввода и вывода с файлами
Все элементы каталога являются либо файлами, либо подкаталогами. Метод
Path.iterdir() возвращает итератор для объектов Path, представляющих все
элементы в каталоге.
Чтобы использовать .iterdir(), сначала необходимо получить объект Path,
представляющий каталог. Используем папку new_directory/, которую мы ранее
создали в домашнем каталоге и присвоили переменной new_dir:
>>> for path in new_dir.iterdir():
...
print(path)
...
C:\Users\David\new_directory\file1.txt
C:\Users\David\new_directory\folder_a
C:\Users\David\new_directory\folder_c
В данный момент папка new_directory/ содержит три элемента:
1) файл с именем file1.txt;
2) каталог с именем folder_c/;
3) каталог с именем folder_а/.
Метод .iterdir() возвращает итерируемый объект, который можно преобразовать в список:
>>> list(new_dir.iterdir())
[WindowsPath('C:/Users/David/new_directory/file1.txt'),
WindowsPath('C:/Users/David/new_directory/folder_a'),
WindowsPath('C:/Users/David/new_directory/folder_c')]
Преобразовывать этот объект в список требуется достаточно редко. Как правило,
вызов .iterdir() используется в цикле for, как было сделано в первом примере.
Учтите, что .iterdir() возвращает только элементы, непосредственно содержащиеся в папке new_directory/. Иначе говоря, вы не увидите путь к файлу,
хранящемуся в каталоге folder_c/.
Способ перебора содержимого каталога и всех его подкаталогов существует,
но легко сделать это с .iterdir() не удастся. Вскоре мы доберемся до решения
этой задачи, но сначала я покажу, как искать файлы в каталогах.
Поиск файлов в каталоге
Иногда требуется перебрать только файлы определенного типа или файлы
с определенными правилами выбора имен. Вы можете вызвать метод Path.glob()
12.3. Основные операции файловой системы 277
для пути, представляющего каталог, чтобы получить итерируемый объект для
содержимого каталога, удовлетворяющего некоторому критерию.
Может показаться странным, что метод для поиска файлов называется .glob(),
но здесь есть исторические причины. В ранних версиях операционной системы
Unix программа с именем glob использовалась для расширения шаблонов путей
к файлам до полных путей.
Метод .glob() делает нечто похожее. Методу передается строка, содержащая
шаблон с подстановочным символом, а .glob() возвращает список путей, соответствующих этому шаблону.
Подстановочный символ — специальный символ, который используется
в шаблонах. При создании конкретного пути к файлу он заменяется другими
символами. Например, в шаблоне "*.txt" звездочка * является подстановочным
символом, который может быть заменен любым количеством других символов.
Шаблон "*.txt" совпадает с любым путем к файлу, который завершается расширением .txt. Другими словами, если при замене * в шаблоне всеми символами
в пути к файлу, кроме четырех последних, будет получен исходный путь, то он
совпадет с шаблоном "*.txt".
Рассмотрим пример использования папки new_directory/, ранее присвоенной
переменной new_dir:
>>> for path in new_dir.glob("*.txt"):
...
print(path)
...
C:\Users\David\new_directory\file1.txt
Метод .glob(), как и .iterdir(), возвращает итерируемый объект, содержащий
список путей, но в этом случае возвращаются только пути, соответствующие
шаблону "*.txt".
Следует заметить, что метод .glob() возвращает только пути файлов, содержащиеся в папке, для которой он вызывается.
Возвращаемое значение .glob() можно преобразовать в список:
>>> list(new_dir.glob("*.txt"))
[WindowsPath('C:/Users/David/new_directory/file1.txt')]
Чаще всего .glob() используется в циклах for.
В следующей таблице описаны наиболее популярные подстановочные символы.
278 Глава 12 Операции ввода и вывода с файлами
ПОДСТАНОВОЧНЫЙ СИМВОЛ
ОПИСАНИЕ
ПРИМЕР
СОВПАДАЕТ
НЕ СОВПАДАЕТ
*
Любое количество
символов
"*b*"
b, ab, bc, abc
a, c, ac
?
Один символ
"?bc"
abc, bbc, cbc
bc, aabc,
abcd
[abc]
Один из символов,
перечисленных
в квадратных скобках
[CB]at
Cat, Bat
at, cat, bat
Примеры использования каждого подстановочного символа я покажу позднее.
А сейчас мы создадим еще несколько файлов в папке new_directory/, чтобы у нас
было больше возможностей для экспериментов.
Введите следующий код:
>>> paths = [
...
new_dir / "program1.py",
...
new_dir / "program2.py",
...
new_dir / "folder_a" / "program3.py",
...
new_dir / "folder_a" / "folder_b" / "image1.jpg",
...
new_dir / "folder_a" / "folder_b" / "image2.png",
... ]
>>> for path in paths:
...
path.touch()
...
>>>
После выполнения этого фрагмента структура папки new_directory/ будет такой,
как показано ниже.
12.3. Основные операции файловой системы 279
Итак, у нас появилась более интересная структура, с которой можно работать.
Давайте посмотрим, как .glob() работает с каждым из подстановочных символов.
Подстановочный символ *
Подстановочный символ * заменяет любое количество символов в шаблоне.
Например, шаблон "*.py" соответствует всем путям, заканчивающимся суффиксом .py:
>>> list(new_dir.glob("*.py"))
[WindowsPath('C:/Users/David/new_directory/program1.py'),
WindowsPath('C:/Users/David/new_directory/program2.py')]
Символ * может многократно использоваться в одном шаблоне:
>>> list(new_dir.glob("*1*"))
[WindowsPath('C:/Users/David/new_directory/file1.txt'),
WindowsPath('C:/Users/David/new_directory/program1.py')]
Шаблон "*1*" соответствует любому пути, содержащему цифру 1, до и после
которой может следовать любое количество символов. В папке new_directory/
цифра 1 встречается только в file1.txt и program1.py.
Если опустить первое вхождение * в шаблоне "*1*" (получим шаблон "1*"),
то совпадений не будет:
>>> list(new_dir.glob("1*"))
[]
Шаблон "1*" совпадает с путями к файлам, которые начинаются с цифры 1, за
которыми следует любое количество символов. В папке new_directory/ нет ни
одного файла, путь к которому соответствует этому шаблону, поэтому вызов
.glob() ничего не возвращает.
Подстановочный символ ?
Подстановочный символ ? заменяет один символ в шаблоне. Например, шаблон
"program?.py" соответствует любому пути, начинающемуся со слова program,
за которым следует один символ и суффикс .py:
>>> list(new_dir.glob("program?.py"))
[WindowsPath('C:/Users/David/new_directory/program1.py'),
WindowsPath('C:/Users/David/new_directory/program2.py')]
280 Глава 12 Операции ввода и вывода с файлами
Символ ? может многократно использоваться в одном и том же шаблоне:
>>> list(new_dir.glob("?older_?"))
[WindowsPath('C:/Users/David/new_directory/folder_a'),
WindowsPath('C:/Users/David/new_directory/folder_c')]
Шаблон "?older_?" соответствует путям, начинающимся с любой буквы, за которой следует older_ и еще один символ. В папке new_directory/ такими путями
являются каталоги folder_a/ и folder_b/.
Подстановочные символы * и ? можно объединять в одном шаблоне:
>>> list(new_dir.glob("*1.??"))
[WindowsPath('C:/Users/David/new_directory/program1.py')]
Шаблон "*1.??" соответствует любому пути, который состоит из цифры 1,
за которой следует точка и еще два символа. В каталоге new_directory/ этому
шаблону соответствует только program1.py. Обратите внимание: file1.txt этому
шаблону не соответствует, потому что за точкой следуют три символа.
Подстановочный символ []
Подстановочный символ [] отчасти напоминает ?, потому что он заменяет
только один символ. Отличие в том, что вместо одного произвольного символа, как в случае с ?, [] должен совпадать только с одним символом из набора,
заключенного в квадратные скобки.
Например, шаблон "program[13].py" соответствует любому пути, содержащему слово program, за которым следует цифра 1 или 3, а потом расширение .py.
В новой папке new_directory/folder этому шаблону соответствует только файл
program1.py:
>>> list(new_dir.glob("program[13].py"))
[WindowsPath('C:/Users/David/new_directory/program1.py')]
Как и в случае с другими подстановочными символами, вы можете многократно
использовать [] в одном шаблоне, а также комбинировать его с другими.
Рекурсивный поиск совпадений
и подстановочный символ **
Как вы уже видели, .iterdir() и .glob() имеют одно серьезное ограничение:
они возвращают пути только для той папки, для которой они были вызваны.
12.3. Основные операции файловой системы 281
Например, new_dir.glob("*.txt") возвращает только путь file1.txt из new_
directory/. Путь file2.txt из подкаталога folder_c/ не возвращается, хотя он и совпадает с шаблоном "*.txt".
Существует специальный подстановочный символ **, с помощью которого
можно применять шаблон рекурсивно. Для этого в начале шаблона надо поставить префикс "**/". Тем самым вы приказываете .glob() искать соответствия
шаблону как в текущем каталоге, так и во всех его подкаталогах.
Например, шаблон "**/*.txt" соответствует как file1.txt, так и folder_c/file2.txt:
>>> list(new_dir.glob("**/*.txt"))
[WindowsPath('C:/Users/David/new_directory/file1.txt'),
WindowsPath('C:/Users/David/new_directory/folder_c/file2.txt')]
Аналогичным образом шаблон "**/*.py" соответствует любым файлам .py
в каталоге new_directory/ и всех его подкаталогах:
>>> list(new_dir.glob("**/*.py"))
[WindowsPath('C:/Users/David/new_directory/program1.py'),
WindowsPath('C:/Users/David/new_directory/program2.py'),
WindowsPath('C:/Users/David/new_directory/folder_a/program3.py')]
Также существует сокращенный метод рекурсивного поиска с именем .rglob().
При использовании этого метода передается шаблон без префикса "**/":
>>> list(new_dir.rglob("*.py"))
[WindowsPath('C:/Users/David/new_directory/program1.py'),
WindowsPath('C:/Users/David/new_directory/program2.py'),
WindowsPath('C:/Users/David/new_directory/folder_a/program3.py')]
Буква r в .rglob() означает recursive, то есть рекурсивный. Некоторые люди
предпочитают использовать этот метод вместо того, чтобы включать в шаблоны
префикс "**/", потому что эта запись немного короче. Обе версии абсолютно
допустимы. В этой книге мы будем использовать .rglob(), а не префикс.
Перемещение и удаление файлов и папок
Иногда требуется переместить файл или каталог в новое место либо полностью
удалить. Это можно сделать средствами модуля pathlib, но учтите, что иногда
такой способ приводит к потере данных, поэтому такие операции следует выполнять с исключительной осторожностью.
Для перемещения файла или каталога используется метод .replace().
282 Глава 12 Операции ввода и вывода с файлами
Например, следующий фрагмент перемещает файл file1.txt из папки new_
directory/ во вложенную папку folder_a/:
>>> source = new_dir / "file1.txt"
>>> destination = new_dir / "folder_a" / "file1.txt"
>>> source.replace(destination)
WindowsPath('C:/Users/David/new_directory/folder_a/file1.txt')
Здесь метод .replace() вызывается для исходного пути. Путь назначения передается .replace() в единственном аргументе. Обратите внимание: .replace()
возвращает путь к новому местонахождению файла.
ВАЖНО!
Если путь назначения уже существует, то вызов .replace() перезаписывает
его исходным файлом без выдачи каких-либо исключений. Если вы будете
действовать неосторожно, это может привести к потере данных.
Возможно, стоит сначала проверить, не существует ли уже файл с таким путем
назначения, и перемещать исходный файл только в том случае, если файла
с путем назначения нет:
if not destination.exists():
source.replace(destination)
Метод .replace() также может использоваться для перемещения или переименования целых каталогов. Например, следующий фрагмент переименовывает
подкаталог folder_c каталога new_directory/ в folder_d/:
>>> source = new_dir / "folder_c"
>>> destination = new_dir / "folder_d"
>>> source.replace(destination)
WindowsPath('C:/Users/David/new_directory/folder_d')
Как и прежде, если приемная папка уже существует, она полностью заменяется
исходной папкой, что может привести к значительной потере данных.
Для удаления файла используется метод .unlink():
>>> file_path = new_dir / "program1.py"
>>> file_path.unlink()
Команда удаляет файл program1.py в папке new_directory/. Чтобы убедиться
в этом, вызовите метод .exists():
>>> file_path.exists()
False
12.3. Основные операции файловой системы 283
Также можно воспользоваться методом .iterdir():
>>> list(new_dir.iterdir())
[WindowsPath('C:/Users/David/new_directory/folder_a'),
WindowsPath('C:/Users/David/new_directory/folder_d'),
WindowsPath('C:/Users/David/new_directory/program2.py')]
Если путь, для которого вызывается .unlink(), не существует, выдается исключение FileNotFoundError:
>>> file_path.unlink()
Traceback (most recent call last):
File "<pyshell#94>", line 1, in <module>
file_path.unlink()
File "C:\Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py", line 1303, in unlink
self._accessor.unlink(self)
FileNotFoundError: [WinError 2] The system cannot find the file
specified: 'C:\\Users\\David\\new_directory\\program1.py'
Если вы хотите проигнорировать исключение, присвойте необязательному
параметру missing_ok значение True:
>>> file_path.unlink(missing_ok=True)
В данном случае ничего не происходит, потому что файл, представляемый
file_path, не существует.
ВАЖНО!
Удаленный файл пропадает навсегда. Прежде чем удалять его, убедитесь, что
вы действительно этого хотите!
Используйте .unlink() только с путями, представляющими файлы. Чтобы удалить каталог, используйте метод .rmdir(). Помните, что каталог должен быть
пустым. В противном случае операция выдает исключение OSError:
>>> folder_d = new_dir / "folder_d"
>>> folder_d.rmdir()
Traceback (most recent call last):
File "<pyshell#97>", line 1, in <module>
folder_d.rmdir()
File "C:\Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py", line 1314, in rmdir
self._accessor.rmdir(self)
OSError: [WinError 145] The directory is not empty:
'C:\\Users\\David\\new_directory\\folder_d'
284 Глава 12 Операции ввода и вывода с файлами
В папке folder_d/ находится только один файл file2.txt. Чтобы удалить folder_d/,
сначала необходимо удалить все хранящиеся в ней файлы:
>>> for path in folder_d.iterdir():
...
path.unlink()
...
>>> folder_d.rmdir()
Теперь папка folder_d/ удалена:
>>> folder_d.exists()
False
Если вам потребуется удалить весь каталог, даже если он не пуст, pathlib вам
не поможет. Однако встроенный модуль shutil включает функцию rmtree(),
которая применяется для удаления каталогов, содержащих файлы.
Пример использования rmtree() для удаления folder_a/:
>>> import shutil
>>> folder_a = new_dir / "folder_a"
>>> shutil.rmtree(folder_a)
Вспомните, что folder_a/ содержит подкаталог folder_b/, в котором хранятся два
файла с именами image1.jpg и image2.png.
При вызове rmtree() с передачей объекта folder_a удаляется папка folder_a/
вместе со всем содержимым:
>>> # The folder_a/ directory no longer exists
>>> folder_a.exists()
False
>>> # Searching for `image*.*` files returns nothing
>>> list(new_dir.rglob("image*.*"))
[]
Раздел получился достаточно объемным. Вы узнали, как выполняются некоторые стандартные операции файловых систем:
zz
создание файлов и каталогов;
zz
перебор содержимого каталогов;
zz
поиск файлов и папок по шаблону;
zz
перемещение и удаление файлов и папок.
Все эти операции требуются достаточно часто. Однако очень важно помнить,
что ваши программы — всего лишь «гости» на компьютерах других людей.
12.4. Задача: перемещение всех графических файлов в новый каталог 285
Неосторожность может привести к непреднамеренному повреждению чужой
информации, потере важных документов и других данных.
Всегда будьте внимательны при работе с файловыми системами. Если у вас
возникнут сомнения, перед выполнением операции удостоверьтесь, что путь
существует, и всегда предлагайте пользователю подтвердить предстоящую
операцию!
Упражнения
1. Создайте в домашней папке новый каталог с именем my_folder/.
2. Создайте в my_folder/ три файла:
ƒƒ file1.txt
ƒƒ file2.txt
ƒƒ image1.png
3. Переместите файл image1.png в новый каталог images/, находящийся
в каталоге my_folder/.
4. Удалите файл file1.txt.
5. Удалите каталог my_folder/.
12.4. ЗАДАЧА: ПЕРЕМЕЩЕНИЕ ВСЕХ
ГРАФИЧЕСКИХ ФАЙЛОВ В НОВЫЙ КАТАЛОГ
В папке practice_files располагается вложенная папка с именем documents/. Она
содержит несколько файлов и вложенных папок. Некоторые файлы хранят
графические изображения — они имеют расширения .png, .gif или .jpg.
Создайте в папке practice_files подпапку с именем images/, переместите в нее все
графические файлы. Когда это будет сделано, в новой папке должны находиться
четыре файла:
zz
image1.png
zz
image2.gif
zz
image3.png
zz
image4.jpg
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
286 Глава 12 Операции ввода и вывода с файлами
12.5. ЧТЕНИЕ И ЗАПИСЬ ФАЙЛОВ
В современном мире мы имеем дело с файлами на каждом шагу. Они являются основным средством хранения и передачи данных в цифровом формате. Скорее всего,
только за сегодняшний день вы уже открывали десятки, если не сотни файлов.
В этом разделе я расскажу, как читать и записывать файлы в Python.
Что такое файл?
Файл представляет собой последовательность байтов, а байт содержит число от
0 до 255. Другими словами, файл является последовательностью целых чисел.
Чтобы интерпретировать содержимое файла, хранящиеся в нем байты необходимо декодировать.
В стандартную библиотеку Python включены модули для работы с текстом,
данными в формате CSV и аудиофайлами. Также доступны многочисленные
сторонние пакеты для работы с файлами других типов.
В главе 13 «Установка пакетов с помощью pip» вы узнаете, как устанавливать
сторонние пакеты. Глава 14 «Создание и изменение файлов PDF» посвящена
работе с файлами в формате PDF.
А сейчас вы узнаете, как работать с обычными текстовыми файлами.
Текстовые файлы
Текстовые файлы содержат только текст. Пожалуй, с этой разновидностью
файлов работать проще всего. Тем не менее есть пара особенностей, которые
могут создать проблемы при работе с текстовыми файлами:
1) кодировка символов;
2) завершители строк.
Прежде чем переходить к чтению и записи текстовых файлов, я расскажу, как
эффективно преодолевать эти проблемы.
Кодировка символов
Текстовые файлы хранятся на диске в виде последовательности байтов. Каждый
байт (или группа байтов в некоторых случаях) представляет отдельный символ в файле. При записи текстовых файлов символы, вводимые с клавиатуры,
12.5. Чтение и запись файлов 287
преобразуются в байты; процесс преобразования называется кодированием.
При чтении текстового файла байты декодируются обратно в текст.
Целое число, с которым связывается символ, определяется кодировкой символов файла. Существует много кодировок символов. На практике чаще всего
используются следующие четыре кодировки:
1. ASCII
2. UTF-8
3. UTF-16
4. UTF-32
В некоторых кодировках — например, в ASCII и UTF-8 — символы кодируются
одинаково. Например, цифры и буквы латинского алфавита одинаково кодируются в ASCII и UTF-8.
Отличие между ASCII и UTF-8 заключается в том, что UTF-8 позволяет кодировать больше символов. ASCII не позволяет кодировать такие символы, как
ñ или ü, а в UTF-8 это возможно. Это означает, что текст в кодировке ASCII
можно декодировать в UTF-8, но декодирование текста в кодировке UTF-8
в ASCII возможно не всегда.
ВАЖНО!
Если для кодирования и декодирования текста используются разные кодировки, могут возникнуть серьезные проблемы.
Например, текст, закодированный в UTF-8 и декодированный в UTF-16, может
быть интерпретирован как текст на совершенно другом языке, а вовсе не на
том, который предполагался изначально!
Более подробно о кодировке символов рассказано в статье «Unicode &
Character Encodings in Python: A Painless Guide» (https://realpython.com/pythonencodings-guide/) на сайте Real Python.
Знать, какая кодировка используется в файле, важно, но это не всегда очевидно.
На современных компьютерах с системой Windows текстовые файлы обычно
кодируются в UTF-16 или UTF-8. В macOS и Ubuntu Linux по умолчанию
обычно используется UTF-8.
Далее в этом разделе мы будем работать с файлами, для которых используется
кодировка UTF-8. Если при этом у вас возникнут проблемы, возможно, вам
следует изменить кодировку примеров.
288 Глава 12 Операции ввода и вывода с файлами
Завершители строк
Каждая строка текстового файла завершается одним или двумя символами,
которые интерпретируются как метка конца строки. Эти символы обычно не
отображаются в текстовом редакторе, но существуют в данных файла в виде
байтов.
Два символа, используемых для представления конца строки, — возврата
курсора и переноса строки. В строках Python это escape-последовательности
\r и \n соответственно.
В Windows завершители строк по умолчанию представляются комбинацией
этих двух символов. В macOS и большинстве дистрибутивов Linux завершители
строк представляются единственным символом переноса строки.
При чтении файлов Windows в macOS или Linux между строками текста иногда выводятся лишние пустые строки. Это происходит из-за того, что символ
возврата курсора также является завершителем строки в macOS и Linux.
Допустим, следующий текстовый файл был создан в Windows:
Pug\r\n
Jack Russell Terrier\r\n
English Springer Spaniel\r\n
German Shepherd\r\n
В macOS или Ubuntu этот файл интерпретируется с двойными интервалами
между строками:
Pug\r
\n
Jack Russell Terrier\r
\n
English Springer Spaniel\r
\n
German Shepherd\r
\n
На практике использование разных завершителей строк в разных операционных системах обычно не создает проблем. Python автоматически преобразует
завершители строк, и вам не придется беспокоиться о них.
Объекты файлов в Python
Файлы в Python представляются файловыми объектами, которые являются
экземплярами классов, предназначенных для работы с разными типами файлов.
12.5. Чтение и запись файлов 289
В Python существуют следующие типы файловых объектов.
1. Объекты текстовых файлов предназначены для работы с текстовыми
файлами.
2. Объекты двоичных файлов предназначены для непосредственной работы
с байтами, содержащимися в файлах.
Объекты текстовых файлов выполняют кодирование и декодирование байтов.
От вас требуется только указать, какая кодировка символов должна использоваться. Объекты двоичных файлов не выполняют никакого кодирования или
декодирования.
Файловые объекты в Python создают двумя способами:
1) методом Path.open();
2) встроенной функцией open().
Рассмотрим обе возможности.
Метод Path.open()
Для использования метода Path.open() необходимо сначала получить объект
Path. Выполните в интерактивном окне IDLE следующий фрагмент:
>>> from pathlib import Path
>>> path = Path.home() / "hello.txt"
>>> path.touch()
>>> file = path.open(mode="r", encoding="utf-8")
Сначала мы создаем объект Path для файла hello.txt и присваиваем его переменной path. Затем метод path.touch() создает файл в домашнем каталоге.
Наконец, .open() возвращает новый файловый объект, представляющий файл
hello.txt, и присваивает его переменной file.
При открытии файла используются два параметра, задаваемых ключевыми
словами.
1. Параметр mode определяет, в каком режиме должен быть открыт файл.
Аргумент "r" открывает файл в режиме чтения.
2. Параметр encoding определяет кодировку символов, используемую для
декодирования файла. Аргумент "utf-8" задает кодировку символов
UTF-8.
290 Глава 12 Операции ввода и вывода с файлами
Вы можете проверить переменную file и убедиться в том, что ей присвоен
объект текстового файла:
>>> file
<_io.TextIOWrapper name='C:\Users\David\hello.txt' mode='r'
encoding='utf-8'>
Объекты текстовых файлов являются экземплярами класса TextIOWrapper. Вам
никогда не придется создавать экземпляры этого класса напрямую, потому что
их можно создавать методом Path.open().
Существует несколько режимов открытия файлов. Они описаны в следующей
таблице.
РЕЖИМ
ОПИСАНИЕ
"r"
Создает объект текстового файла для чтения и выдает ошибку, если
файл не удалось открыть
"w"
Создает объект текстового файла для записи и перезаписывает все
существующие данные в файле
"a"
Создает объект текстового файла для присоединения данных в конец файла
"rb"
Создает объект двоичного файла для чтения и выдает ошибку, если
файл не удалось открыть
"wb"
Создает объект двоичного файла для записи и перезаписывает все
существующие данные в файле
"ab"
Создает объект двоичного файла для присоединения данных в конец файла
В следующей таблице приведены строки для самых распространенных кодировок символов.
СТРОКА
КОДИРОВКА
"ascii"
ASCII
"utf-8"
UTF-8
"utf-16"
UTF-16
"utf-32"
UTF-32
Когда вы создаете объект файла вызовом .open(), Python хранит ссылку на
файл, пока вы не прикажете Python закрыть файл или программа не завершится.
12.5. Чтение и запись файлов 291
ВАЖНО!
Всегда явно приказывайте Python закрыть файл.
Не закрывать за собой файлы — все равно что не убирать за собой грязь. Когда
программа завершает работу, в системе не должно оставаться лишнего мусора.
Чтобы закрыть файл, вызовите метод .close() для объекта файла:
>>> file.close()
Если у вас имеется существующий объект Path, для открытия файлов рекомендуется использовать Path.open(). Тем не менее для открытия файлов можно
воспользоваться и встроенной функцией open().
Встроенная функция open()
Встроенная функция open() работает почти так же, как метод Path.open(),
только в первом параметре передается строка с путем к открываемому файлу.
Создайте новую переменную file_path и присвойте ей строку с путем к файлу
hello.txt, который был создан ранее:
>>> file_path = "C:/Users/David/hello.txt"
Не забудьте скорректировать путь для вашего компьютера.
Затем создайте новый объект файла встроенной функцией open() и присвойте
его переменной file:
>>> file = open(file_path, mode="r", encoding="utf-8")
Первым параметром open() должна быть строка пути. Режимы mode и encoding
аналогичны одноименным параметрам метода Path.open(). В данном примере
mode присваивается "r" (режим чтения), а encoding присваивается "utf-8".
Файловый объект, возвращаемый вызовом open() (как и файловый объект,
возвращаемый Path.open()), является экземпляром TextIOWrapper:
>>> file
<_io.TextIOWrapper name='C:/Users/David/hello.txt' mode='r'
encoding='utf-8'>
Чтобы закрыть файл, вызовите метод .close() для файлового объекта:
>>> file.close()
292 Глава 12 Операции ввода и вывода с файлами
Чтобы открыть файлы на основе существующего объекта pathlib.Path, в большинстве случаев используется метод Path.open(). Тем не менее, если вся функциональность модуля pathlib вам не нужна, функция open() отлично подойдет
для быстрого создания файлового объекта.
Команда with
Когда вы открываете файл, ваша программа получает доступ к данным, внешним
по отношению к самой программе. Операционная система должна управлять
связью вашей программы с физическим файлом. Когда вы вызываете метод
файлового объекта .close(), операционная система разрывает эту связь.
Если ваша программа аварийно завершится после того, как файл будет открыт,
но до его закрытия, то системные ресурсы, задействованные в связи с файлом,
так и останутся зарезервированными, пока операционная система не поймет,
что они более не используются. Чтобы гарантировать, что ресурсы файловой
системы будут освобождены даже в случае сбоя программы, файл можно открыть командой with. Схема использования команды with выглядит так:
with path.open(mode="r", encoding="utf-8") as file:
# Работа с файлом
Команда with состоит из двух частей: заголовка и тела. Заголовок всегда начинается с ключевого слова with и завершается двоеточием. Возвращаемое значение
path.open() присваивается имени переменной после ключевого слова as.
После заголовка команды with следует блок кода с отступом. Когда управление
выходит за пределы блока с отступом, объект файла, присвоенный file, будет
автоматически закрыт, даже если во время выполнения кода внутри блока произойдет исключение.
Команды with также могут использоваться со встроенной функцией open():
with open(file_path, mode="r", encoding="utf-8") as file:
# Работа с файлом
На самом деле нет никаких причин не открывать файлы командой with. Этот
синтаксис работы с файлами считается питоническим. Далее в книге мы будем
использовать эту схему при каждом открытии файла.
Чтение данных из файла
Запустите текстовый редактор, откройте файл hello.txt в ранее созданном домашнем каталоге и введите текст Hello, World. Сохраните файл.
12.5. Чтение и запись файлов 293
В интерактивном окне IDLE введите следующий фрагмент:
>>> path = Path.home() / "hello.txt"
>>> with path.open(mode="r", encoding="utf-8") as file:
...
text = file.read()
...
>>>
Файловый объект, созданный path.open(), присваивается переменной file.
Внутри блока with метод файлового объекта.read() читает текст из файла
и присваивает результат переменной text.
Значение, возвращаемое .read(), представляет собой строковый объект со
значением "Hello, World":
>>> type(text)
<class 'str'>
>>> text
'Hello, World'
Метод .read() читает текст из файла и возвращает его в виде строки (string).
Если файл содержит несколько строк (lines)1 текста, то они разделяются символом новой строки \n. Снова откройте файл hello.txt в текстовом редакторе
и разместите текст "Hello again"во второй строке файла. Сохраните файл.
В интерактивном окне IDLE снова прочитайте текст из файла:
>>> with path.open(mode="r", encoding="utf-8") as file:
...
text = file.read()
...
>>> text
'Hello, World\nHello again'
Между строками выводится символ \n.
Вместо чтения всего содержимого файла за один раз также можно читать файл
по строкам текста:
>>> with path.open(mode="r", encoding="utf-8") as file:
...
for line in file.readlines():
...
print(line)
...
Hello, World
Hello again
1
Английские слова string и line переводятся одинаково — «строка», поэтому может
возникнуть путаница. Здесь string означает тип «строка» в Python, а line — просто
строку текста в файле. — Примеч. ред.
294 Глава 12 Операции ввода и вывода с файлами
Метод .readlines() возвращает итерируемый набор текстовых строк файла. При
каждой итерации цикла for возвращается и выводится очередная строка файла.
Обратите внимание на дополнительную пустую строку между двумя строками
текста. Ее появление не связано с завершителями строк в файле. Дело в том,
что print() автоматически вставляет символ новой строки в конец каждой
выводимой строки.
Чтобы вывести две строки файла без лишней пустой строки, передайте функции
print() в необязательном параметре end пустую строку:
>>> with path.open(mode="r", encoding="utf-8") as file:
...
for line in file.readlines():
...
print(line, end="")
...
Hello, World
Hello again
Часто бывает удобнее использовать .readlines() вместо .read() . Например, каждая строка в файле может представлять одну запись. При помощи
.readlines() можно перебрать строки и обработать их так, как требуется.
Если вы попытаетесь прочитать данные из несуществующего файла, то и функция
Path.open(), и встроенная функция open() выдают ошибку FileNotFoundError:
>>> path = Path.home() / "new_file.txt"
>>> with path.open(mode="r", encoding="utf-8") as file:
...
text = file.read()
...
Traceback (most recent call last):
File "<pyshell#197>", line 1, in <module>
with path.open(mode="r", encoding="utf-8") as file:
File "C:\Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py", line 1200, in open
return io.open(self, mode, buffering, encoding, errors, newline,
File "C:Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py", line 1054, in _opener
return self._accessor.open(self, flags, mode)
FileNotFoundError: [Errno 2] No such file or directory:
'C:\\Users\\David\\new_file.txt'
А теперь посмотрим, как записать данные в файл.
Запись данных в файл
Чтобы записать данные в обычный текстовый файл, следует передать соответствующую строку методу .write() файлового объекта. Файловый объект
12.5. Чтение и запись файлов 295
должен быть открыт в режиме записи, для чего в параметре mode передается
значение "w".
Например, следующий фрагмент записывает текст "Hi there!" в файл hello.txt,
хранящийся в домашнем каталоге:
>>> with path.open(mode="w", encoding="utf-8") as file:
...
file.write("Hi there!")
...
9
>>>
Обратите внимание: после выполнения блока with выводится целое число 9. Это
происходит из-за того, что метод .write() возвращает количество записанных
символов. Строка "Hi there!" состоит из девяти символов, поэтому .write()
возвращает 9.
Когда текст "Hi there!" записывается в файл hello.txt, все существующее содержимое перезаписывается. Все выглядит так, как если бы вы удалили старый
файл hello.txt и создали новый.
ВАЖНО!
Когда вы передаете параметр mode="w" при вызове .open(), содержимое
исходного файла перезаписывается. Это приводит к потере всех исходных
данных в файле!
Чтобы убедиться в том, что файл содержит только текст "Hi there!", прочитаем
и выведем содержимое файла:
>>> with path.open(mode="r", encoding="utf-8") as file:
...
text = file.read()
...
>>> print(text)
Hi there!
Чтобы данные присоединялись к концу файла, следует открыть файл в режиме
присоединения:
>>> with path.open(mode="a", encoding="utf-8") as file:
...
file.write("\nHello")
...
6
Когда файл открывается в режиме присоединения, новые данные записываются
в конец файла, а старые данные остаются на месте. В начало строки включен
296 Глава 12 Операции ввода и вывода с файлами
символ новой строки, чтобы слово "Hello" выводилось на новой строке в конце
файла.
Без начального символа новой строки слово "Hello" окажется в одной строке
с текстом в конце файла.
Чтобы убедиться в том, что слово "Hello" записано во второй строке, достаточно
открыть файл и прочитать данные:
>>> with path.open(mode="r", encoding="utf-8") as file:
...
text = file.read()
...
>>> print(text)
Hi there!
Hello
Метод .writelines() позволяет записать в файл сразу несколько строк. Прежде
всего, создайте список строк:
>>> lines_of_text = [
...
"Hello from Line 1\n",
...
"Hello from Line 2\n",
...
"Hello from Line 3 \n",
... ]
Затем откройте файл в режиме записи и вызовите метод .writelines(), чтобы
записать каждую строку из списка в файл:
>>> with path.open(mode="w", encoding="utf-8") as file:
...
file.writelines(lines_of_text)
...
>>>
Каждая строка из lines_of_text записывается в файл. Обратите внимание
на то, что каждая строка завершается символом новой строки \n. Это объясняется тем, что .writelines() не выводит каждый элемент списка в новой
строке.
Если вы откроете несуществующий путь в режиме записи, Python создаст файл
при условии, что все родительские папки в пути существуют:
>>> path = Path.home() / "new_file.txt"
>>> with path.open(mode="w", encoding="utf-8") as file:
... file.write("Hello!")
...
6
12.5. Чтение и запись файлов 297
Так как каталог Path.home() существует, новый файл new_file.txt создается
автоматически. Тем не менее, если хотя бы один из родительских каталогов не
существует, вызов .open() выдаст ошибку FileNotFoundError:
>>> path = Path.home() / "new_folder" / "new_file.txt"
>>> with path.open(mode="w", encoding="utf-8") as file:
...
file.write("Hello!")
...
Traceback (most recent call last):
File "<pyshell#172>", line 1, in <module>
with path.open(mode="w", encoding="utf-8") as file:
File "C:\Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py", line 1200, in open
return io.open(self, mode, buffering, encoding, errors, newline,
File "C:\Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py", line 1054, in _opener
return self._accessor.open(self, flags, mode)
FileNotFoundError: [Errno 2] No such file or directory:
'C:\\Users\\David\\new_folder\\new_file.txt'
Если вы хотите записать данные в путь, но не уверены в том, что его родительские папки существуют, вызовите метод .mkdir() с параметром parents=True,
прежде чем открывать файл в режиме записи:
>>> path.parent.mkdir(parents=True)
>>> with path.open(mode="w", encoding="utf-8") as file:
...
file.write("Hello!")
...
6
Из этого раздела вы узнали много нового. Вы узнали, что все файлы являются
последовательностями байтов — целых чисел со значениями от 0 до 255.
Вы изучили кодировки символов, используемые для преобразования между
байтами и текстом, а также освоили отличия завершителей строк в разных
операционных системах. Наконец, вы научились читать и записывать текстовые
файлы с использованием метода Path.open() и встроенной функции open().
Упражнения
1. Запишите следующий текст в файл starships.txt, хранящийся в вашем
домашнем каталоге:
Discovery
Enterprise
Defiant
Voyager
Каждое слово должно располагаться в отдельной строке.
298 Глава 12 Операции ввода и вывода с файлами
2. Прочитайте файл starhips.txt, созданный в упражнении 1, и выведите
каждую строку текста в файле. В выводе не должно быть лишних пустых
строк между словами.
3. Прочитайте файл starhips.txt и выведите слова, начинающиеся с буквы D.
12.6. ЧТЕНИЕ И ЗАПИСЬ ДАННЫХ CSV
Допустим, в вашем доме установлен датчик, который измеряет температуру
каждые четыре часа. Таким образом, за день регистрируются шесть показаний
температуры.
Каждое показание сохраняется в списке
>>> temperature_readings = [68, 65, 68, 70, 74, 72]
Ежедневно датчик генерирует новый список чисел. Чтобы сохранить эти значения в файле, можно записывать значения за день в новой строке текстового
файла, разделяя их запятыми:
>>> from pathlib import Path
>>> file_path = Path.home() / "temperatures.csv"
>>> with file_path.open(mode="a", encoding="utf-8") as file:
...
file.write(str(temperature_readings[0]))
...
for temp in temperature_readings[1:]:
...
file.write(f",{temp}")
...
2
3
3
3
3
3
Фрагмент создает в домашнем каталоге файл с именем temperatures.csv и открывает его в режиме присоединения. В новой строке от конца файла первое
значение списка temperature_readings записывается в файл. Затем в той же
строке записываются все остальные значения в списке, перед каждым из которых вставляется запятая.
В итоге в файл будет записана строка текста "68,65,68,70,74,72". Чтобы убедиться в этом, прочитайте текст из файла:
>>> with file_path.open(mode="r", encoding="utf-8") as file:
...
text = file.read()
...
12.6. Чтение и запись данных CSV 299
>>> text
'68,65,68,70,74,72'
Такой формат называется CSV (Comma-Separated Values — значения, разделенные запятыми). Файл temperatures.csv называется файлом CSV.
Файлы CSV хорошо подходят для хранения записей последовательных данных,
потому что каждая строка значений CSV может быть прочитана и представлена
в виде списка:
>>> temperatures = text.split(",")
>>> temperatures
['68', '65', '68', '70', '74', '72']
В разделе 9.2 «Списки: изменяемые последовательности» я показывал, как
создать список на основе строки строковым методом .split(). В приведенном
выше примере новый список создается на основе текста, прочитанного из файла
temperatures.csv.
Значения в списке temperatures являются строками, а не целыми числами (в отличие от значений, изначально записанных в файл). Дело в том, что значения
всегда читаются из текстовых файлов в строковом формате.
Строки можно преобразовать в целые числа при помощи генератора списка:
>>> int_temperatures = [int(temp) for temp in temperatures]
>>> int_temperatures
[68, 65, 68, 70, 74, 72]
Мы успешно восстановили список, который был записан в файл temperatures.csv!
Эти примеры показывают, что CSV — обычные текстовые файлы. Используя
инструменты из раздела 12.5 «Чтение и запись файлов», можно сохранить серии
значений в строках файла CSV, а затем прочитать их из файла и восстановить
данные.
Чтение и запись файлов CSV выполняются настолько часто, что в стандартную
библиотеку Python был включен модуль csv, упрощающий работу с файлами
CSV. В следующих разделах я покажу, как использовать модуль csv для чтения
и записи файлов CSV.
Модуль csv
Модуль csv применяется для чтения и записи файлов CSV. В этом разделе мы
переработаем предыдущий пример с помощью модуля csv, чтобы показать, какие
операции вы можете выполнять с использованием этого модуля.
300 Глава 12 Операции ввода и вывода с файлами
Для начала импортируйте модуль csv в интерактивном окне IDLE:
>>> import csv
Создадим новый файл CSV с показателями температуры за несколько дней.
Запись файлов CSV с использованием csv.writer
Создайте список списков, содержащий значения температуры за три дня:
>>> daily_temperatures = [
... [68, 65, 68, 70, 74, 72],
... [67, 67, 70, 72, 72, 70],
... [68, 70, 74, 76, 74, 73],
... ]
Теперь откройте файл temperatures.csv в режиме записи:
>>> file_path = Path.home() / "temperatures.csv"
>>> file = file_path.open(mode="w", encoding="utf-8", newline="")
Вместо того чтобы использовать команду with, мы создаем файловый объект
и присваиваем его переменной file, чтобы вы могли проанализировать каждый
шаг в процессе записи данных.
ВАЖНО!
Обратите внимание, что в приведенном выше примере параметру newline
метода .open() присвоено значение "".
Это связано с тем, что модуль csv выполняет собственные преобразования
новых строк. Если не указать newline="" при открытии файла, то некоторые
системы (например, Windows) интерпретируют символы новой строки некорректно и вставляют вторую новую строку после каждой строки в файле.
Теперь создайте новый объект записи CSV, для чего следует передать файловый
объект file методу csv.writer():
>>> writer = csv.writer(file)
Метод csv.writer() возвращает объект записи CSV с методами для записи
данных в файл CSV.
Например, при помощи метода writer.writerow() можно записать список
в новую строку файла CSV:
12.6. Чтение и запись данных CSV 301
>>> for temp_list in daily_temperatures:
...
writer.writerow(temp_list)
...
19
19
19
Как и метод .write() объекта файла, .writerow() возвращает количество символов, записанных в файл. Каждый список из daily_temperatures преобразуется
в строку, содержащую значения температуры, разделенные запятыми, и каждая
из этих строк состоит из 19 символов.
Теперь закройте файл:
>>> file.close()
Открыв файл temperatures.csv в текстовом редакторе, вы увидите в нем следующий текст:
68,65,68,70,74,72
67,67,70,72,72,70
68,70,74,76,74,73
В приведенных примерах команда with не использовалась для записи в файл,
чтобы вы могли проанализировать каждую операцию в интерактивном окне
IDLE. На практике гораздо лучше использовать with.
Вот как выглядит код с использованием команды with:
with file_path.open(mode="w", encoding="utf-8", newline="") as file:
writer = csv.writer(file)
for temp_list in daily_temperatures:
writer.writerow(temp_list)
Главное преимущество использования csv.writer для записи в файл CSV заключается в том, что вам не нужно беспокоиться о преобразовании значений
в строки, прежде чем записывать их в файл. Объект csv.writer делает это за
вас, в результате чего код получится более компактным и чистым.
Метод .writerow() записывает одну строку данных в файл CSV, но можно записать и сразу несколько строк при помощи .writerows(). Код сокращается еще
значительнее, если ваши данные уже находятся в списке списков:
with file_path.open(mode="w", encoding="utf-8", newline="") as file:
writer = csv.writer(file)
writer.writerows(daily_temperatures)
302 Глава 12 Операции ввода и вывода с файлами
Давайте прочитаем данные из temperatures.csv, чтобы восстановить список спис­
ков daily_temperatures, использованный для создания файла.
Чтение файлов CSV с использованием csv.reader
Для чтения файла CSV средствами модуля csv используется класс csv.reader.
Объекты csv.reader, как и csv.writer, создаются на основе объекта файла:
>>> file = file_path.open(mode="r", encoding="utf-8", newline="")
>>> reader = csv.reader(file)
Вызов csv.reader() возвращает объект чтения CSV, который может использоваться для перебора строк в файле CSV:
>>> for row in reader:
... print(row)
...
['68', '65', '68', '70', '74', '72']
['67', '67', '70', '72', '72', '70']
['68', '70', '74', '76', '74', '73']
>>> file.close()
Каждая строка файла CSV возвращается в виде списка строк. Чтобы восстановить список списков daily_temperatures, необходимо преобразовать каждый
список строк в список целых чисел при помощи генератора списка.
Ниже приведен пример, в котором мы открываем файл CSV командой with,
читаем каждую строку в файле CSV, преобразуем список строк в список целых
чисел, а затем сохраняем каждый список целых чисел в списке списков с именем
daily_temperatures:
>>> # Создание пустого списка
>>> daily_temperatures = []
>>> with file_path.open(mode="r", encoding="utf-8", newline="") as file:
...
reader = csv.reader(file)
...
for row in reader:
...
# Преобразование строки в список целых чисел
...
int_row = [int(value) for value in row]
...
# Присоединение списка целых чисел к списку daily_temperatures
...
daily_temperatures.append(int_row)
...
>>> daily_temperatures
[[68, 65, 68, 70, 74, 72], [67, 67, 70, 72, 72, 70],
[68, 70, 74, 76, 74, 73]]
Работать с файлами CSV средствами модуля csv намного удобнее, чем применять стандартные средства чтения и записи простых текстовых файлов.
12.6. Чтение и запись данных CSV 303
Впрочем, иногда файл CSV является более сложным, чем просто файл со
строками однотипных значений. Каждая строка может представлять запись
с разными полями, а первой строкой в файле может быть строка заголовка
с именами полей.
Чтение и запись файлов CSV с заголовками
Пример файла CSV с несколькими типами данных и со строкой заголовка:
name,department,salary
Lee,Operations,75000.00
Jane,Engineering,85000.00
Diego,Sales,80000.00
Первая строка файла содержит имена полей. Каждая последующая строка содержит запись со значениями для каждого поля.
Для чтения файлов CSV с такой структурой, как показано выше, можно использовать csv.reader(), но вам придется отдельно обрабатывать строку заголовка,
а каждая строка возвращается в виде обычного списка без имен полей.
Гораздо удобнее возвращать каждую строку в виде словаря, ключами которого
являются имена полей, а значениями — значения полей в строке. Именно так
работают объекты csv.DictReader.
В текстовом редакторе создайте в своем домашнем каталоге новый файл CSV
с именем employees.csv и сохраните в нем приведенный выше текст CSV. В интерактивном окне IDLE откройте файл employees.csv и создайте новый объект
csv.DictReader:
>>> file_path = Path.home() / "employees.csv"
>>> file = file_path.open(mode="r", encoding="utf-8", newline="")
>>> reader = csv.DictReader(file)
При создании объекта DictReader предполагается, что первая строка файла CSV
содержит имена полей. Эти значения сохраняются в списке и присваиваются
атрибуту .fieldnames экземпляра DictReader:
>>> reader.fieldnames
['name', 'department', 'salary']
Как и объекты csv.reader, объекты DictReader поддерживают перебор:
>>> for row in reader:
...
print(row)
304 Глава 12 Операции ввода и вывода с файлами
...
{'name': 'Lee', 'department': 'Operations', 'salary': '75000.000'}
{'name': 'Jane', 'department': 'Engineering', 'salary': '85000.00'}
{'name': 'Diego', 'department': 'Sales', 'salary': '80000.00'}
>>> file.close()
Вместо того чтобы возвращать каждую строку в виде списка, объекты DictReader
возвращают каждую строку виде словаря. Ключами словаря являются имена
полей, а значениями — значения полей из каждой строки в файле CSV.
Обратите внимание: поле salary читается как строка. Так как файлы CSV являются обычными текстовыми файлами, значения всегда читаются как строки, но
их можно преобразовать в другие нужные вам типы данных. Например, можно
обработать каждую строку функцией, преобразующей ключи к правильным
типам данных:
>>> def process_row(row):
...
row["salary"] = float(row["salary"])
...
return row
...
>>> with file_path.open(mode="r", encoding="utf-8", newline="") as file:
...
reader = csv.DictReader(file)
...
for row in reader:
...
print(process_row(row))
...
{'name': 'Lee', 'department': 'Operations', 'salary': 75000.0}
{'name': 'Jane', 'department': 'Engineering', 'salary': 85000.0}
{'name': 'Diego', 'department': 'Sales', 'salary': 80000.0}
Функция process_row() получает словарь, прочитанный из файла CSV, и возвращает новый словарь с ключом "salary", преобразованным в число с плавающей точкой.
Файлы CSV с заголовками можно создавать с использованием класса
csv.DictWri­t er , который записывает словари с совместно используемыми
ключами в строки файла CSV.
Следующий список словарей представляет собой небольшую базу данных
с именами пользователей и их возрастом:
>>> people = [
...
{"name": "Veronica", "age": 29},
...
{"name": "Audrey", "age": 32},
...
{"name": "Sam", "age": 24},
... ]
12.6. Чтение и запись данных CSV 305
Чтобы сохранить данные в списке people в файле CSV, откройте новый файл
с именем people.csv в режиме записи и создайте новый объект csv.DictWriter
для файлового объекта:
>>> file_path = Path.home() / "people.csv"
>>> file = file_path.open(mode="w", encoding="utf-8", newline="")
>>> writer = csv.DictWriter(file, fieldnames=["name", "age"])
Когда вы создаете экземпляр newDictWriter, в первом параметре передается
файловый объект для записи данных CSV. Параметр fieldnames, который является обязательным, содержит список строк с именами полей.
ПРИМЕЧАНИЕ
В приведенном примере в параметре fieldnames передается списковый литерал [«name», «age»].
Также можно присвоить fieldnames значение people[0].keys(), так как people[0] —
словарь, ключами которого являются имена полей. Это полезно, если имена
полей неизвестны или же их столько, что списковый литерал становится неудобным.
Как и объекты csv.writer, объекты DictWriter содержат методы .writerow()
и .writerows() для записи строк данных в файл. Объекты DictWriter также
содержат третий метод .writeheader(), который записывает строку заголовка
в файл CSV:
>>> writer.writeheader()
10
Метод .writeheader() возвращает количество символов, записанных
в файл, — в данном случае 10. Записывать строку заголовка необязательно, но это рекомендуется делать, потому что она помогает определить, что
представляют собой данные, содержащиеся в файле CSV. Кроме того, они
упрощают чтение строк из файлов CSV в формате словаря с использованием
класса DictReader.
При наличии записанного заголовка можно записать данные списка people
в файл CSV методом .writerows():
>>> writer.writerows(people)
>>> file.close()
306 Глава 12 Операции ввода и вывода с файлами
В вашем домашнем каталоге появился файл с именем people.csv, содержащий
следующие данные:
name,age
Veronica,29
Audrey,32
Sam,24
Файлы CSV предоставляют гибкие и удобные средства хранения данных.
Их часто применяют в сфере бизнеса, и умение работать с ними — безусловно,
ценный навык!
Упражнения
1. Напишите программу, которая записывает следующий список списков
в файл numbers.csv в вашем домашнем каталоге:
numbers = [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
]
2. Напишите программу, которая читает числа в файле numbers.csv из
упражнения 1 в список списков целых чисел с именем numbers. Выведите
прочитанный список списков. Результат должен выглядеть так:
[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]]
3. Напишите программу, которая записывает следующий список словарей
в файл favorite_colors.csv в вашем домашнем каталоге:
favorite_colors = [
{"name": "Joe", "favorite_color": "blue"},
{"name": "Anne", "favorite_color": "green"},
{"name": "Bailey", "favorite_color": "red"},
]
4. Выходной файл CSV должен иметь следующий формат:
name,favorite color
Joe,blue
Anne,green
Bailey,red
5. Напишите программу, которая читает данные из файла favorite_colors.csv
(см. упражнение 3) в список словарей favorite_colors. Выведите список
словарей. Результат должен выглядеть примерно так:
12.8. Итоги и дополнительные ресурсы 307
[{"name": "Joe", "favorite_color": "blue"},
{"name": "Anne", "favorite_color": "green"},
{"name": "Bailey", "favorite_color": "red"}]
12.7. ЗАДАЧА: СОЗДАНИЕ СПИСКА РЕКОРДОВ
В папке practice_files находится файл CSV scores.csv с данными об игроках и их
показателях. Первые несколько строк файла выглядят так:
name, score
LLCoolDave,23
LLCoolDave,27
red,12
LLCoolDave,26
tom123,26
Напишите программу, которая читает данные из файла CSV и создает новый
файл high_scores.csv, в котором каждая строка содержит имя игрока и его наиболее высокий результат.
Выходной файл CSV должен выглядеть примерно так:
name,high_score
LLCoolDave,27
red,12
tom123,26
O_O,22
Misha46,25
Empiro,23
MaxxT,25
L33tH4x,42
johnsmith,30
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
12.8. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе я рассказал о файловой системе и о путях к файлам, а также показал, как работать с ними при помощи модуля pathlib стандартной библиотеки
Python. Вы научились создавать новые объекты Path, обращаться к компонентам
путей, а также создавать, перемещать и удалять файлы и папки.
Также вы узнали, как читать и записывать обычные текстовые файлы методом
Path.open() и встроенной функцией open() и как работать с файлами CSV
308 Глава 12 Операции ввода и вывода с файлами
(значения, разделенные запятыми) с использованием модуля csv из стандартной библиотеки Python.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-files
Дополнительные ресурсы
За дополнительной информацией о модулях и пакетах обращайтесь к следующим ресурсам:
zz
«Reading and Writing Files in Python» (https://realpython.com/read-writefiles-python/)
zz
«Working With Files in Python» (https://realpython.com/working-with-filesin-python/)
ГЛАВА 13
Установка пакетов
с помощью pip
До сих пор мы работали в рамках стандартной библиотеки Python. Изучая
остальной материал нашего учебного курса, вы будете работать с различными
пакетами, не включенными в Python по умолчанию.
Для многих языков программирования существует менеджер пакетов, автоматизирующий процесс установки, обновления и удаления сторонних пакетов.
И Python — не исключение.
Менеджер пакетов, ставший де-факто стандартным для Python, называется pip.
Традиционно его приходилось загружать и устанавливать отдельно от Python.
Начиная с версии 3.4 он включен в большинство дистрибутивов языка.
В этой главе вы узнаете:
zz
как установить и управлять сторонними пакетами в pip;
zz
каковы преимущества и риски использования сторонних пакетов.
Итак, за дело!
13.1. УСТАНОВКА СТОРОННИХ ПАКЕТОВ
С ПОМОЩЬЮ PIP
Менеджер пакетов Python — pip — используется для установки и управления
сторонними пакетами. Эта программа существует отдельно от Python, хотя
весьма вероятно, что она была установлена на вашем компьютере, когда вы
загружали и устанавливали Python.
pip работает через командную строку. Это означает, что программа должна за-
пускаться из командной строки или программы-терминала. Способ открытия
терминала зависит от операционной системы.
310 Глава 13 Установка пакетов с помощью pip
Windows
Нажмите клавишу Windows, затем введите cmd и нажмите Enter, чтобы открыть
приложение Командная строка (Command Prompt). На экране появляется окно,
которое выглядит примерно так, как показано ниже:
Также можно воспользоваться приложением PowerShell: нажмите клавишу
Windows, введите powershell и нажмите Enter, появится окно PowerShell.
13.1. Установка сторонних пакетов с помощью pip 311
macOS
Нажмите Cmd+пробел, чтобы открыть окно поиска Spotlight. Введите команду
terminal и нажмите Enter, чтобы запустить приложение Terminal. Открывшееся
окно выглядит так:
Ubuntu Linux
Щелкните на кнопке Show Applications в нижней части панели инструментов
и проведите поиск по строке terminal. Щелкните на значке приложения Terminal,
чтобы открыть терминал. Открывшееся окно терминала выглядит примерно так:
312 Глава 13 Установка пакетов с помощью pip
Проверка установки pip
Открыв окно терминала, убедимся в том, что программа pip установлена на
вашем компьютере. Конкретный способ проверки зависит от операционной
системы.
В Windows введите следующую команду для проверки pip:
$ python -m pip --version
В macOS и Linux наличие установленной копии pip проверяется следующей
командой:
$ python3 -m pip --version
Если программа pip установлена, то в окне терминала должна появиться информация, которая выглядит примерно так:
pip 20.2.3 from c:\users\David\appdata\local\programs\python\
python\lib\site-packages\pip (python 3.9)
Из вывода следует, что в настоящее время в системе установлена версия
pip 20.2.3 и она связана с установкой Python 3.9.
ВАЖНО!
Если вы не получили никакого результата при проверке pip или было выведено сообщение об ошибке, попробуйте выполнить команду python3.9
-m pip --version. Возможно, все последующие команды python3 придется
заменить на python3.9.
Обновление pip до последней версии
Прежде чем двигаться дальше, убедитесь в том, что у вас установлена последняя
версия pip. Чтобы обновить pip, введите следующую команду в окне терминала
и нажмите Enter:
$ python3 -m pip install --upgrade pip
Если доступна более новая версия pip, она будет загружена и установлена автоматически. В противном случае появится сообщение о том, что в системе уже
установлена самая последняя версия: «Requirement already satisfied» — или
что-нибудь в этом роде.
13.1. Установка сторонних пакетов с помощью pip 313
ВАЖНО!
В этом разделе все приводимые команды начинаются с python3 -m pip. Это
важно для пользователей macOS и Linux, потому что команда python -m pip
может установить команды для неправильной версии Python.
Но если вы являетесь пользователем Windows, вам придется использовать
python -m pip, потому что команда python3 -m pip не будет работать на вашем
компьютере.
Итак, программа pip обновлена до последней версии — давайте посмотрим, что
она может делать!
Вывод списка всех установленных пакетов
Вы можете воспользоваться pip для вывода списка установленных пакетов.
Посмотрим, какие пакеты доступны в настоящий момент. Введите следующую
команду в окне терминала:
$ python3 -m pip list
Если никакие пакеты еще не установлены (например, если вы начали этот учебный курс с установки Python 3.9), pip выведет примерно такую информацию:
Package
Version
---------- ------pip
19.3.1
setuptools 41.2.0
Как видите, список небольшой. В нем указана сама программа pip, потому что
pip также является пакетом. Возможно, также в списке будет присутствовать
setuptools — пакет, используемый pip для настройки и установки других
пакетов.
Когда вы устанавливаете пакет с использованием pip, он появится в списке.
Вы всегда можете воспользоваться командой pip list для просмотра списка
пакетов (и версий каждого пакета), установленных в системе в настоящее время.
Установка пакета
Сейчас мы покажем, как установить ваш первый пакет Python. Для этого
упражнения мы возьмем requests, один из самых популярных пакетов Python
за всю историю языка.
314 Глава 13 Установка пакетов с помощью pip
Введите в терминале следующую команду:
$ python3 -m pip install requests
Пока pip устанавливает пакет requests, выводится информация о ходе установки:
Collecting requests
Downloading https://.../requests-2.22.0-py2.py3-none-any.whl (57kB)
|................................| 61kB 2.0MB/s
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1
Downloading https://...urllib3-1.25.7-py2.py3-none-any.whl (125kB)
|................................| 133kB 3.3MB/s
Collecting certifi>=2017.4.17
Downloading https://...certifi-2019.11.28.py3-none-any.whl (156kB)
|................................| 163kB ...
Collecting chardet<3.1.0,>=3.0.2
Downloading https://...chardet-3.0.4-py2.py3-none-any.whl (133kB)
|................................| 143kB 6.8MB/s
Collecting idna<2.9,>=2.5
Downloading https://...idna-2.8-py2.py3-none-any.whl (58kB)
|................................| 61kB 3.8MB/s
Installing collected packages: urllib3, certifi, chardet, idna,
requests
Successfully installed certifi-2019.11.28 chardet-3.0.4 idna-2.8
requests-2.22.0 urllib3-1.25.7
ПРИМЕЧАНИЕ
Форматирование было слегка изменено, чтобы вывод нормально помещался
на странице. Результат, который вы увидите на своем компьютере, может несколько отличаться от приведенного.
Обратите внимание: сначала pip сообщает о загрузке requests. Вы видите URLадрес, с которого pip устанавливает пакет, а также индикатор, демонстрирующий
ход загрузки.
После этого мы замечаем, что pip устанавливает еще четыре пакета: chardet,
certifi, idna и urllib3. Они называются зависимостями пакета requests. Это
означает, что установка этих пакетов необходима для правильной работы requests.
После того как pip завершит установку requests и его зависимостей, снова
выполните команду pip list в окне терминала. На этот раз список будет выглядеть так:
$ python3 -m pip list
Package
Version
13.1. Установка сторонних пакетов с помощью pip 315
---------- ---------certifi
2019.11.28
chardet
3.0.4
idna
2.8
pip
19.3.1
requests
2.22.0
setuptools 41.2.0
urllib3
1.25.7
Как видите, в системе установлена версия 2.22.0 пакета requests, а также зависимости chardet, certifi, idna и urllib3.
По умолчанию pip устанавливает последнюю версию пакета. Вы также можете
управлять тем, какая версия пакета будет установлена, при помощи необязательных спецификаторов версии.
Установка конкретных версий пакетов
Существует несколько способов, как установить именно ту версию, которую
вы хотите. Например:
1) последнюю версию с номером, большим заданного номера;
2) последнюю версию с номером, меньшим заданного номера;
3) последнюю версию с конкретным номером.
Чтобы установить последнюю версию requests с номером версии 2 и выше,
выполните следующую команду:
$ python3 -m pip install requests>=2.0
Обратите внимание на выражение >=2.0 после имени пакета requests. Оно приказывает pip установить версию requests, номер которой больше или равен 2.0.
Выражение >= называется спецификатором версии; оно указывает, какая
версия пакета должна быть установлена. Есть несколько разных спецификаторов, которые вы можете использовать в командах. Чаще всего встречаются
следующие спецификаторы.
СПЕЦИФИКАТОР ВЕРСИИ
ОПИСАНИЕ
<=, >=
Меньше или больше спецификатора (включительно)
<, >
Меньше или больше спецификатора (не включая)
==
В точности равно спецификатору
316 Глава 13 Установка пакетов с помощью pip
Рассмотрим несколько примеров.
Чтобы установить последнюю версию, номер которой меньше либо равен заданной, используйте спецификатор версии <=:
$ python3 -m pip install requests<=3.0
Команда установит последнюю версию requests, номер которой меньше либо
равен 3.0.
Спецификаторы версии <= и >= инклюзивны, то есть они включают номер, следующий за спецификатором. Также существуют эксклюзивные спецификаторы,
не включающие номер: спецификаторы < и >.
Следующая команда устанавливает последнюю версию requests, номер которой
строго меньше 3.0:
$ python3 -m pip install requests<3.0
Спецификаторы можно объединять, чтобы программа pip всегда устанавливала версию в заданном диапазоне номеров. Например, следующая команда
устанавливает последнюю версию requests из серии 1.0:
$ python3 -m pip install requests>=1.0,<2.0
Такие команды стоит использовать в том случае, если ваш проект был совместим
только с версией 1.0 пакета и вы хотите установить последние обновления для
этой серии.
Наконец, зависимости можно привязать к конкретной версии спецификатором
версии ==:
$ python3 -m pip install requests==2.22.0
Эта команда устанавливает фиксированную версию 2.22.0 пакета request.
Вывод подробной информации о пакетах
Теперь, когда пакет requests установлен, вы можете воспользоваться pip для
просмотра подробной информации о пакете:
$ python3 -m pip show requests
Name: requests
Version: 2.22.0
Summary: Python HTTP for Humans.
Home-page: https://requests.readthedocs.io
13.1. Установка сторонних пакетов с помощью pip 317
Author: Kenneth Reitz
Author-email: me@kennethreitz.org
License: Apache 2.0
Location: c:\users\David\...\python\python\lib\site-packages
Requires: chardet, idna, certifi, urllib3
Required-by:
Команда python3 -m pip show выводит информацию об установленном пакете,
включая имя автора, адрес электронной почты и домашнюю страницу в интернете, на которой вы сможете больше узнать о том, что делает пакет.
Пакет requests используется для выдачи запросов HTTP из программы Python.
Этот пакет, чрезвычайно полезный во многих областях, стал зависимостью для
большого числа других пакетов Python.
Удаление пакета
Если пакет можно установить при помощи pip, то вполне логично, что его также
можно удалить. Давайте удалим пакет requests.
Чтобы удалить его, введите следующую команду в окне терминала:
$ python3 -m pip uninstall requests
ВАЖНО!
Если у вас уже имеются проекты, использующие requests или одну из его
зависимостей, возможно, вам не стоит выполнять команды, о которых мы
расскажем далее в этом разделе.
Немедленно появится следующее приглашение:
Uninstalling requests-2.22.0:
Would remove:
c:\users\damos\...\requests-2.22.0.dist-info\*
c:\users\damos\a...\requests\*
Proceed (y/n)?
Прежде чем удалять что-нибудь с вашего компьютера, pip сначала спрашивает
у вас разрешение. Как предусмотрительно!
Введите y и нажмите Enter, чтобы продолжить. Появится сообщение, которое
подтверждает, что пакет requests был удален:
Successfully uninstalled requests-2.22.0
318 Глава 13 Установка пакетов с помощью pip
Снова просмотрите список пакетов:
$ python3 -m pip list
Package
Version
---------- --------certifi
2018.4.16
chardet
3.0.4
idna
2.7
pip
10.0.1
setuptools 39.0.1
urllib3
1.23
Обратите внимание: pip удаляет requests, но не трогает его зависимости! Такое
поведение реализовано сознательно, а не по ошибке.
Представьте, что вы установили в своей системе несколько пакетов, часть из
которых имеет общие зависимости. Если бы программа pip удаляла пакеты
вместе с зависимостями, то другие пакеты, для которых необходимы эти зависимости, стали бы неработоспособными!
А пока удалите оставшиеся пакеты командой pip uninstall. Все четыре пакета
можно удалить одной командой:
$ python3 -m pip uninstall certifi chardet idna urllib3
Когда это будет сделано, снова выполните команду pip list и убедитесь, что
пакеты были удалены. Команда должна вывести тот же список пакетов, который
вы видели в самом начале работы.
Package
Version
---------- ------pip
10.0.1
setuptools 39.0.1
Экосистема сторонних пакетов — одна из самых сильных сторон Python. Эти
пакеты позволяют программистам Python работать чрезвычайно эффективно
и создавать полнофункциональные программные продукты намного быстрее,
чем это можно сделать на таком языке, как, допустим, C++. Тем не менее использование сторонних пакетов имеет некоторые особенности, к которым следует относиться с осторожностью. Об этом я расскажу в следующем разделе.
13.2. ПОДВОДНЫЕ КАМНИ СТОРОННИХ ПАКЕТОВ
Элегантность сторонних пакетов заключается в том, что они позволяют легко
добавить в проект новую функциональность, и вам не приходится кодировать
13.2. Подводные камни сторонних пакетов 319
все с нуля. Тем самым достигается значительное повышение производительности.
Однако чем больше мощность, тем выше ответственность. Как только вы
включаете в свой проект чей-то пакет, вы оказываете огромное доверие тем,
кто занимался его разработкой и сопровождением.
Используя пакет, который разрабатывал кто-то другой, вы отчасти теряете
контроль над своим проектом. Разработчики, занимающиеся сопровождением
пакета, могут выпустить новую версию с изменениями, несовместимыми с той
версией, которая используется в вашем проекте.
По умолчанию pip устанавливает новейшую версию пакета. Если вы передадите
свой код другим разработчикам, которые установят обновленную версию используемого в вашей программе пакета, возможно, они не смогут запустить ваш код.
Это создает значительную проблему как для вас, так и для конечного пользователя. К счастью, Python предоставляет решение для этой достаточно частой
проблемы: виртуальные среды.
Виртуальная среда создает изолированную и, что самое важное, — воспроизводимую среду, которая может использоваться для разработки проекта. Среда
содержит конкретную версию Python, а также конкретные версии зависимостей
вашего проекта.
Когда вы передаете свой код кому-то другому, у получателя остается возможность воспроизвести среду и быть уверенным в том, что ему удастся выполнить
код без ошибок.
Виртуальные среды — достаточно сложная тема, рассказ о которой выходит за
рамки нашей книги. Чтобы больше узнать о виртуальных средах и о том, как их
использовать, обращайтесь к учебному курсу «Managing Python Dependencies
With Pip and Virtual Environments» (https://realpython.com/products/managingpython-dependencies/) на сайте Real Python. В этом курсе вы узнаете, как:
zz
устанавливать сторонние пакеты Python с использованием менеджера
пакетов pip в Windows, macOS и Linux на более высоком уровне, чем
описано здесь, а также как их использовать и управлять ими;
zz
изолировать зависимости проектов в виртуальных средах для предотвращения конфликтов версий в ваших проектах Python;
zz
применять полный процесс поиска и идентификации качественных
сторонних пакетов, состоящий из семи этапов, в ваших проектах Python
(и для обоснования ваших решений у коллег и руководства);
320 Глава 13 Установка пакетов с помощью pip
zz
создавать воспроизводимые среды разработки и развертывания приложений с использованием менеджера пакетов pip и файлов требований.
13.3. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы научились устанавливать сторонние пакеты с помощью pip —
менеджера пакетов языка Python. Вы узнали несколько полезных команд pip,
включая pip install, pip list, pip show и pip uninstall.
Также вы узнали о некоторых проблемах, которые иногда возникают при использовании сторонних пакетов. Не каждый пакет, который может быть загружен в pip, подойдет для вашего проекта. Так как код установленного пакета вы
не контролируете, вам приходится верить, что пакет безопасен и будет хорошо
работать у пользователей вашей программы.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-installing-packages
Дополнительные ресурсы
За дополнительной информацией об управлении сторонними пакетами обращайтесь к следующим ресурсам:
zz
«Managing Python Dependencies» (https://realpython.com/products/
managing-python-dependencies/)
zz
«Python Virtual Environments: A Primer» (https://realpython.com/pythonvirtual-environments-a-primer/)
ГЛАВА 14
Создание и изменение
файлов PDF
PDF (Portable Document Format) — один из самых известных форматов для
распространения документов в интернете. Файлы PDF могут содержать текст,
графику, таблицы, формы, мультимедийный контент (например, видео или
анимации) — и все это в одном файле.
Разнообразие типов контента усложняет работу с PDF. Столько разных видов
данных, которые необходимо декодировать при открытии файла PDF! К счастью, в экосистеме Python существуют отличные пакеты для чтения, обработки
и создания файлов PDF.
В этой главе вы научитесь:
zz
читать текст из файла PDF;
zz
разбивать файл PDF на несколько файлов;
zz
объединять несколько файлов PDF;
zz
поворачивать и обрезать страницы в файлах PDF;
zz
шифровать и дешифровать файлы PDF с паролем;
zz
создавать файл PDF с нуля.
Итак, за дело!
14.1. ИЗВЛЕЧЕНИЕ ТЕКСТА ИЗ ФАЙЛА PDF
В этом разделе вы научитесь читать файлы PDF и извлекать из них текст при
помощи пакета PyPDF2 . Но прежде чем пользоваться пакетом, необходимо
установить его с помощью pip:
$ python3 -m pip install PyPDF2
322 Глава 14 Создание и изменение файлов PDF
Проверьте установку, выполнив следующую команду в терминале:
$ python3 -m pip show PyPDF2
Name: PyPDF2
Version: 1.26.0
Summary: PDF toolkit
Home-page: http://mstamy2.github.com/PyPDF2
Author: Mathieu Fenniak
Author-email: biziqe@mathieu.fenniak.net
License: UNKNOWN
Location: c:\users\david\python\lib\site-packages
Requires:
Required-by:
Обратите внимание на информацию о версии. На момент написания книги
новейшей версией PyPDF2 была версия 1.26.0. Если у вас открыта среда IDLE,
ее необходимо перезапустить, чтобы использовать пакет PyPDF2.
Открытие файла PDF
Начнем с открытия файла PDF и чтения информации об этом файле. В примерах
мы используем файл Pride_and_Prejudice.pdf, хранящийся в папке practice_files
главы 14.
ПРИМЕЧАНИЕ
Решения упражнений и необходимые файлы можно загрузить со следующей
страницы, если это еще не было сделано ранее:
https://github.com/realpython/python-basics-exercises
Откройте интерактивное окно в IDLE и импортируйте класс PdfFileReader из
пакета PyPDF2:
>>> from PyPDF2 import PdfFileReader
Для создания нового экземпляра класса PdfFileReader вам понадобится путь
к файлу PDF, который вы собираетесь открыть. Получим его средствами модуля pathlib:
>>> from pathlib import Path
>>> pdf_path = (
...
Path.home() /
...
"python-basics-exercises" /
...
"ch14-interact-with-pdf-files" /
...
"practice_files" /
14.1. Извлечение текста из файла PDF 323
...
... )
"Pride_and_Prejudice.pdf"
Переменная pdf_path теперь содержит путь к PDF-версии романа Джейн Остин
«Pride and Prejudice» («Гордость и предубеждение»).
ПРИМЕЧАНИЕ
Возможно, вам придется изменить значение pdf_path, чтобы оно соответствовало местонахождению папки python-basics-exercises/ на вашем компьютере.
Теперь создайте экземпляр PdfFileReader:
>>> pdf = PdfFileReader(str(pdf_path))
Объект pdf_path преобразуется в строку, потому что класс PdfFileReader не
умеет читать данные из объекта pathlib.Path.
Как мы говорили в главе 12 «Операции ввода и вывода с файлами», все открытые файлы следует закрывать до завершения программы. Объект PdfFileReader
делает это за вас, так что вам не придется беспокоиться об открытии или закрытии файлов PDF!
Когда экземпляр PdfFileReader будет создан, вы можете использовать его для
сбора информации о файле PDF. Например, .getNumPages() возвращает количество страниц, содержащихся в файле:
>>> pdf.getNumPages()
234
Возможно, вы заметили, что имя .getNumPages() записано в смешанном регистре, а не в нижнем с подчеркиваниями, как рекомендовалось в PEP 8. Помните: в PEP 8 содержатся рекомендации, а не правила. С точки зрения Python
смешанный регистр абсолютно законен.
ПРИМЕЧАНИЕ
Пакет PyPDF2 происходит от пакета pyPdf. Он был написан в 2005 году, всего
через 4 года после публикации PEP 8.
В это время многие программисты переходили на Python с языков, в которых
смешанный регистр был более распространенным.
324 Глава 14 Создание и изменение файлов PDF
Также к информации документа можно обратиться через атрибут .documentInfo:
>>> pdf.documentInfo
{'/Title': 'Pride and Prejudice, by Jane Austen', '/Author': 'Chuck',
'/Creator': 'Microsoft® Office Word 2007',
'/CreationDate': 'D:20110812174208', '/ModDate': 'D:20110812174208',
'/Producer': 'Microsoft® Office Word 2007'}
Объект, возвращаемый .documentInfo, выглядит как словарь, но в действительности им не является. К каждому элементу в .documentInfo можно обращаться
как к атрибуту.
Например, для получения названия используется атрибут .title:
>>> pdf.documentInfo.title
'Pride and Prejudice, by Jane Austen'
Объект .documentInfo содержит метаданные PDF, указанные при создании PDF.
Класс PdfFileReader предоставляет все методы и атрибуты, необходимые для
обращения к данным в файлах PDF. Давайте посмотрим, что можно сделать
с файлом PDF и как это делается.
Извлечение текста из страницы
Страницы PDF представлены в PyPDF2 классом PageObject . Экземпляры
PageObject используются для взаимодействия со страницами в файлах PDF.
Создавать экземпляры PageObject напрямую не нужно. Вместо этого к ним
можно обращаться при помощи метода .getPage() объекта PdfFileReader.
Извлечение текста из одной страницы PDF выполняется в два этапа.
1. Получение PageObject вызовом PdfFileReader.getPage().
2. Извлечение текста в виде строки методом .extractText() экземпляра
PageObject.
Файл Pride_and_Prejudice.pdf состоит из 234 страниц. Каждой странице присвоен индекс от 0 до 233. Чтобы получить объект PageObject, представляющий
конкретную страницу, передайте индекс страницы при вызове PdfFileRea­­
der.getPage():
>>> first_page = pdf.getPage(0)
.getPage() returns a PageObject:
>>> type(first_page)
<class 'PyPDF2.pdf.PageObject'>
14.1. Извлечение текста из файла PDF 325
Текст страницы извлекается методом PageObject.extractText():
>>> first_page.extractText()
'\n \nThe Project Gutenberg EBook of Pride and Prejudice, by Jane
Austen\n \n\nThis eBook is for the use of anyone anywhere at no cost
and with\n \nalmost no restrictions whatsoever. You may copy it,
give it away or\n \nre\n-\nuse it under the terms of the Project
Gutenberg License included\n \nwith this eBook or online at
www.gutenberg.org\n \n \n \nTitle: Pride and Prejudice\n \n
\nAuthor: Jane Austen\n \n \nRelease Date: August 26, 2008
[EBook #1342]\n\n[Last updated: August 11, 2011]\n \n \nLanguage:
Eng\nlish\n \n \nCharacter set encoding: ASCII\n \n \n***
START OF THIS PROJECT GUTENBERG EBOOK PRIDE AND PREJUDICE ***\n \n
\n \n \n \nProduced by Anonymous Volunteers, and David Widger\n
\n \n \n \n \n \n \nPRIDE AND PREJUDICE \n \n \nBy Jane
Austen \n \n\n \n \nContents\n \n'
Результат был дополнительно отформатирован, чтобы он лучше выглядел на
странице. Вывод, который вы получите на своем компьютере, может быть отформатирован иначе.
ПРИМЕЧАНИЕ
В зависимости от способа кодирования файла PDF текст, прочитанный из PDF,
может содержать странные символы или же в нем могут пропасть разрывы
строк. Это оборотная сторона чтения текста из PDF. В реальных приложениях
текст, прочитанный из PDF, иногда приходится чистить вручную.
Каждый объект PdfFileReader содержит атрибут .pages, который может использоваться для последовательного перебора всех страниц файла PDF. Например,
следующий цикл for выводит содержимое всех страниц PDF с текстом романа
«Pride and Prejudice»:
>>> for page in pdf.pages:
...
print(page.extractText())
...
Объединим все, что вы узнали, и напишем программу, которая извлекает весь
текст из файла Pride_and_Prejudice.pdf и сохраняет его в файле .txt.
Все вместе
Откройте в IDLE новое окно редактора и введите следующий код:
from pathlib import Path
from PyPDF2 import PdfFileReader
326 Глава 14 Создание и изменение файлов PDF
# Замените следующий путь правильным путем для вашего компьютера.
pdf_path = (
Path.home() /
"python-basics-exercises" /
"ch14-interact-with-pdf-files" /
"practice-files" /
"Pride_and_Prejudice.pdf"
)
# 1
pdf_reader = PdfFileReader(str(pdf_path))
output_file_path = Path.home() / "Pride_and_Prejudice.txt"
# 2
with output_file_path.open(mode="w") as output_file:
# 3
title = pdf_reader.documentInfo.title
num_pages = pdf_reader.getNumPages()
output_file.write(f"{title}\nNumber of pages: {num_pages}\n\n")
# 4
for page in pdf_reader.pages:
text = page.extractText()
output_file.write(text)
Разберем этот код поэтапно.
1. Сначала новый экземпляр PdfFileReader присваивается переменной
pdf_reader. Также создается новый объект Path для файла Pride_and_
Prejudice.txt в вашем домашнем каталоге; он присваивается переменной
output_file_path.
2. Затем output_file_path открывается в режиме записи, а объект файла,
возвращенный вызовом .open(), присваивается переменной output_
file. Команда with, о которой вы узнали в главе 12 «Операции ввода
и вывода с файлами», обеспечивает закрытие файла при выходе из
блока with.
3. Далее в блоке with название PDF и количество страниц записываются
в текстовый файл вызовом output_file.write().
4. Наконец, программа в цикле for перебирает все страницы PDF. На каждом
шаге цикла следующий объект PageObject присваивается переменной
page. Текст каждой страницы извлекается вызовом page.extractText()
и записывается в output_file.
Сохраните и запустите программу. Она создает в вашем домашнем каталоге
новый файл с именем Pride_and_Prejudice.txt и полным текстом документа
Pride_and_Prejudice.pdf. Откройте его и убедитесь сами!
14.2. Извлечение страниц из файлов PDF 327
Упражнения
1. В папке practice_files главы 14 присутствует файл PDF с именем zen.pdf.
Создайте экземпляр PdfFileReader для этого файла PDF.
2. Используя экземпляр PdfFileReader из упражнения 1, выведите общее
количество страниц в файле PDF.
3. Выведите текст первой страницы файла PDF из упражнения 1.
14.2. ИЗВЛЕЧЕНИЕ СТРАНИЦ ИЗ ФАЙЛОВ PDF
В предыдущем разделе я показал, как извлечь весь текст из файла PDF и сохранить его в файле .txt. Теперь вы узнаете, как извлечь страницу или диапазон
страниц из существующего файла PDF и сохранить их в новом файле PDF.
Для создания нового файла PDF можно воспользоваться объектом PdfFileWriter.
Познакомимся поближе с этим классом и узнаем, что необходимо для создания
PDF с использованием PyPDF2.
Использование класса PdfFileWriter
Класс PdfFileWriter создает новые файлы PDF. В интерактивном окне IDLE
импортируйте класс PdfFileWriter и создайте новый экземпляр с именем
pdf_writer:
>>> from PyPDF2 import PdfFileWriter
>>> pdf_writer = PdfFileWriter()
Объект PdfFileWriter можно сравнить с пустым файлом PDF. Прежде чем сохранить страницы в файле, необходимо их включить в файл. Добавим в pdf_writer
пустую страницу:
>>> page = pdf_writer.addBlankPage(width=72, height=72)
Обязательные параметры width и height определяют размеры страницы в единицах, которые называются пунктами. Один пункт равен 1/72 дюйма, так что
приведенный выше код добавляет в pdf_writer пустую квадратную страницу
с размером стороны 1 дюйм.
Метод .addBlankPage() возвращает новый экземпляр PageObject, который
представляет страницу, добавленную в PdfFileWriter:
>>> type(page)
<class 'PyPDF2.pdf.PageObject'>
328 Глава 14 Создание и изменение файлов PDF
В этом примере экземпляр PageObject, возвращенный .addBlankPage(), присваивается переменной page, но на практике этого делать обычно не нужно.
Иначе говоря, как правило, при вызове .addBlankPage() возвращаемое значение
ничему не присваивается:
>>> pdf_writer.addBlankPage(width=72, height=72)
Чтобы записать содержимое pdf_writer в файл PDF, передайте объект двоичного
файла в режиме записи при вызове pdf_writer.write():
>>> from pathlib import Path
>>> with Path("blank.pdf").open(mode="wb") as output_file:
...
pdf_writer.write(output_file)
...
>>>
Фрагмент создает в текущем рабочем каталоге новый файл с именем blank.pdf.
Открыв этот файл в программе просмотра PDF (например, в Adobe Acrobat),
вы увидите документ с одной пустой квадратной страницей с размером стороны
1 дюйм.
ВАЖНО!
Обратите внимание: при сохранении файла PDF файловый объект передается
методу .write() объекта PdfFileWriter, а не методу .write() файлового объекта.
В частности, следующий код работать не будет:
>>> with Path("blank.pdf").open(mode="wb") as output_file:
...
output_file.write(pdf_writer)
Многим программистам-новичкам этот подход кажется противоестественным.
Будьте внимательны и не совершайте этой ошибки!
Объекты PdfFileWriter могут выполнять запись в новые файлы PDF, но не
позволяют создать с нуля никакой новый контент, кроме пустых страниц.
На первый взгляд это серьезная проблема, но во многих ситуациях создавать
новый контент не нужно. Часто вы работаете со страницами, извлеченными из
файлов PDF, открытых экземпляром PdfFileReader.
ПРИМЕЧАНИЕ
О том, как создать файл PDF, рассказано в разделе 14.8 «Создание файла
PDF с нуля».
14.2. Извлечение страниц из файлов PDF 329
В приведенном выше примере создание нового файла PDF средствами PyPDF2
состояло из трех этапов:
1. Создание экземпляра PdfFileWriter.
2. Добавление одной или нескольких страниц в экземпляр PdfFileWriter.
3. Запись данных в файл вызовом PdfFileWriter.write().
Эта схема будет часто повторяться, когда мы будем изучать различные способы
добавления страниц в экземпляр PdfFileWriter.
Извлечение одной страницы из файла PDF
Вернемся к файлу PDF с книгой «Pride and Prejudice» из предыдущего раздела.
Мы откроем файл PDF, извлечем первую страницу и создадим новый файл PDF,
содержащий только одну извлеченную страницу.
Откройте интерактивное окно в IDLE и импортируйте PdfFileReader
и PdfFileWriter из модуля PyPDF2, а также класс Path из модуля pathlib:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
Затем откройте файл Pride_and_Prejudice.pdf экземпляром PdfFileReader:
>>> # Приведите путь в соответствие с вашим компьютером
>>> pdf_path = (
...
Path.home() /
...
"python-basics-exercises" /
...
"ch14-interact-with-pdf-files" /
...
"practice_files" /
...
"Pride_and_Prejudice.pdf"
... )
>>> input_pdf = PdfFileReader(str(pdf_path))
Передайте индекс 0 методу .getPage(), чтобы получить объект PageObject,
представляющий первую страницу PDF:
>>> first_page = input_pdf.getPage(0)
Теперь создайте новый экземпляр PdfFileWriter и добавьте в него первую
страницу методом .addPage():
>>> pdf_writer = PdfFileWriter()
>>> pdf_writer.addPage(first_page)
Метод .addPage() добавляет страницу в набор страниц объекта pdf_writer,
как и в случае с .addBlankPage(). Отличие в том, что этому методу необходим
существующий объект PageObject.
330 Глава 14 Создание и изменение файлов PDF
Теперь запишите содержимое pdf_writer в новый файл:
>>> with Path("first_page.pdf").open(mode="wb") as output_file:
...
pdf_writer.write(output_file)
...
>>>
В вашем текущем рабочем каталоге появился новый файл PDF с именем
first_page.pdf, который содержит самую первую страницу из файла PDF «Pride
and Prejudice». Неплохо!
Извлечение нескольких страниц из файла PDF
Извлечем из файла Pride_and_Prejudice.pdf первую главу и сохраним ее в новом
файле PDF.
Если вы откроете Pride_and_Prejudice.pdf в программе просмотра PDF, то увидите, что первая глава занимает вторую, третью и четвертую страницы PDF.
Так как индексирование страниц начинается с 0, потребуется извлечь страницы
с индексами 1, 2 и 3.
В подготовительной фазе следует импортировать необходимые классы и открыть файл PDF:
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
>>> from pathlib import Path
>>> pdf_path = (
...
Path.home() /
...
"python-basics-exercises" /
...
"ch14-interact-with-pdf-files" /
...
"practice_files" /
...
"Pride_and_Prejudice.pdf"
... )
>>> input_pdf = PdfFileReader(str(pdf_path))
Наша задача — извлечь страницы с индексами 1, 2 и 3, добавить их в новый
экземпляр PdfFileWriter, а затем записать в новый файл PDF.
Одно из возможных решений — перебор по диапазону страниц от 1 до 3, извлечение страницы на каждом шаге цикла и добавление ее в экземпляр PdfFileWriter:
>>> pdf_writer = PdfFileWriter()
>>> for n in range(1, 4):
...
page = input_pdf.getPage(n)
...
pdf_writer.addPage(page)
...
>>>
14.2. Извлечение страниц из файлов PDF 331
Цикл перебирает числа 1, 2 и 3, потому что range(1, 4) не включает последнее
число (4). На каждом шаге цикла страница с текущим индексом извлекается методом .getPage(), после чего она добавляется в pdf_writer методом .addPage().
Теперь pdf_writer содержит три страницы, в чем можно убедиться при помощи
.getNumPages():
>>> pdf_writer.getNumPages()
3
Наконец, извлеченные страницы записываются в новый файл PDF:
>>> with Path("chapter1.pdf").open(mode="wb") as output_file:
...
pdf_writer.write(output_file)
...
>>>
Теперь вы можете открыть файл chapter1.pdf в текущем рабочем каталоге и убедиться в том, что он содержит первую главу книги «Pride and Preju­
dice».
Другой способ извлечения нескольких страниц из файла PDF основан на использовании того факта, что PdfFileReader.pages поддерживает нотацию срезов.
Переработаем предыдущий пример, чтобы в нем использовался атрибут .pages
вместо перебора по объекту range.
Начнем с инициализации нового объекта PdfFileWriter:
>>> pdf_writer = PdfFileWriter()
Переберем срез .pages по индексам от 1 до 4:
>>> for page in input_pdf.pages[1:4]:
...
pdf_writer.addPage(page)
...
>>>
Напомню, что значения в срезе лежат в диапазоне от элемента с первым индексом в срезе до элемента со вторым индексом в срезе (не включая его). Таким
образом, .pages[1:4] возвращает итерируемый объект, содержащий страницы
с индексами 1, 2 и 3.
Наконец, содержимое pdf_writer записывается в выходной файл:
>>> with Path("chapter1_slice.pdf").open(mode="wb") as output_file:
...
pdf_writer.write(output_file)
...
>>>
332 Глава 14 Создание и изменение файлов PDF
Теперь откройте файл chapter1_slice.pdf из текущего рабочего каталога и сравните его с файлом chapter1.pdf, созданным перебором по объекту range. Они
содержат одинаковые страницы!
В некоторых ситуациях требуется извлечь из файла PDF все страницы. Для
этого можно воспользоваться способами, продемонстрированными выше, но
PyPDF2 предоставляет упрощенное решение.
У экземпляров PdfFileWriter имеется метод .appendPagesFromReader(), которым можно воспользоваться для присоединения страниц из экземпляра
PdfFileReader.
Чтобы использовать .appendPagesFromReader() , передайте экземпляр
PdfFileReader в параметре reader метода. Например, следующий код копирует каждую страницу из файла PDF с книгой «Pride and Prejudice» в экземпляр
PdfFileWriter:
>>> pdf_writer = PdfFileWriter()
>>> pdf_writer.appendPagesFromReader(pdf_reader)
Экземпляр pdf_writer содержит каждую страницу из pdf_reader!
Упражнения
1. Извлеките последнюю страницу из файла Pride_and_Prejudice.pdf и сохраните ее в файле last_page.pdf, находящемся в домашнем каталоге.
2. Извлеките из файла Pride_and_Prejudice.pdf все страницы с четными
индексами (не номерами страниц!) и сохраните их в новом файле every_
other_page.pdf в домашнем каталоге.
3. Разбейте файл Pride_and_Prejudice.pdf на два новых файла PDF. Первый должен содержать первые 150 страниц, а второй — все оставшиеся
страницы. Сохраните оба файла в домашнем каталоге с именами part_1.
pdf и part_2.pdf.
14.3. ЗАДАЧА: КЛАСС PDFFILESPLITTER
Создайте класс с именем PdfFileSplitter, который читает данные файла
PDF из существующего экземпляра PdfFileReader и разбивает их на два
новых объекта PDF. При создании экземпляра класса должна передаваться
строка пути.
14.4. Конкатенация и слияние файлов PDF 333
Например, следующая команда создает экземпляр PdfFileSplitter на основе
файла PDF mydoc.pdf в текущем рабочем каталоге:
pdf_splitter = PdfFileSplitter("mydoc.pdf")
Класс PdfFileSplitter должен содержать два метода.
1. Метод .split() получает один параметр breakpoint, в котором передается
целое число — номер страницы, которая становится последней в первой
части разбиваемого файла PDF.
2. Метод .write() получает один параметр filename, в котором передается
строка пути.
После вызова .split() класс PdfFileSplitter должен содержать атрибут
.writer1, которому присваивается экземпляр PdfFileWriter со всеми страницами исходного файла PDF до страницы breakpoint (не включая ее). Также
должен присутствовать атрибут .writer2, которому присваивается экземпляр
PdfFileWriter с остальными страницами исходного файла PDF.
При вызове .write() записываются два файла PDF — первый с именем filename
+ "_1.pdf", а второй с именем filename + "_2.pdf".
В следующем примере файл mydoc.pdf разделяется на две части, первая из которых
заканчивается страницей 4, а результаты разбиения записываются в два файла
с именами mydoc_split_1.pdf и mydoc_split_2.pdf:
pdf_splitter.split(breakpoint=4)
pdf_splitter.write("mydoc_split")
Чтобы проверить, как работает созданный класс, разбейте файл Pride_and_
Prejudice.pdf из папки practice_files главы 14 на две части, первая из которых
закончится страницей 150.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
14.4. КОНКАТЕНАЦИЯ И СЛИЯНИЕ
ФАЙЛОВ PDF
При работе с файлами PDF часто выполняются две типичные операции: конкатенация и слияние нескольких файлов PDF в один.
334 Глава 14 Создание и изменение файлов PDF
Конкатенация двух и более файлов PDF означает, что файлы объединяются
последовательно друг за другом, образуя один документ. Например, компания
в конце месяца может провести конкатенацию нескольких ежедневных отчетов
в один ежемесячный отчет.
Слияние двух объектов PDF также объединяет их в один файл. Но эта операция
не присоединяет второй файл PDF к концу первого файла. Слиянием можно
вставить файл после заданной страницы первого файла PDF. Затем все страницы
первого файла PDF, следующие после точки вставки, сдвигаются в позицию
после вставленного файла PDF.
Главное отличие между этими двумя операциями заключается в том, что
PdfFileWriter может только присоединять страницы в конец списка страниц
(конкатенация), тогда как PdfFileMerger поддерживает вставку в произвольной
позиции (слияние).
Создайте свой первый экземпляр PdfFileMerger. В интерактивном окне IDLE
введите следующий код, чтобы импортировать класс PdfFileMerger и создать
новый экземпляр:
>>> from PyPDF2 import PdfFileMerger
>>> pdf_merger = PdfFileMerger()
Только что созданные объекты PdfFileMerger пусты. Прежде чем вы сможете
с ними что-то сделать, в них необходимо добавить страницы.
Добавить страницы в объект pdf_merger можно двумя способами. Выбор зависит
от того, что именно нужно сделать.
zz .append() выполняет конкатенацию, то есть присоединяет все страницы
существующего документа PDF в конец набора страниц, содержащихся
в PdfFileMerger.
zz .merge() вставляет все страницы существующего документа PDF после
заданной страницы PdfFileMerger.
В этом разделе будут рассмотрены оба способа. Начнем с .append().
Конкатенация файлов PDF вызовом .append()
В папке practice_files главы 14 находится подкаталог expense_reports, в котором
содержатся три отчета о расходах сотрудника компании.
Вы хотите выполнить конкатенацию трех файлов PDF и передать их работо­
дателю в одном файле PDF, чтобы сотрудник получил компенсацию за затраты,
связанные с работой.
14.4. Конкатенация и слияние файлов PDF 335
Для начала можно воспользоваться модулем pathlib и получить список объектов Path для всех трех отчетов в папке expense_reports/:
>>> from pathlib import Path
>>> reports_dir = (
...
Path.home() /
...
"python-basics-exercises" /
...
"ch14-interact-with-pdf-files" /
...
"practice_files" /
...
"expense_reports"
... )
После импортирования класса Path необходимо построить путь к каталогу
expense_reports/. Будьте внимательны: возможно, вам придется изменить путь
в приведенном выше коде и привести его в соответствие с путем на вашем
компьютере.
Когда у вас появится путь к каталогу expense_reports/, присвоенный переменной
reports_dir, вы можете использовать .glob() для получения итерируемого
набора путей к каталогам файлов PDF.
Просмотрим содержимое каталога:
>>> for path in reports_dir.glob("*.pdf"):
...
print(path.name)
...
Expense report 1.pdf
Expense report 3.pdf
Expense report 2.pdf
В выводе приведены имена трех файлов, но они не упорядочены. Более того,
порядок файлов, которые вы получите на своем компьютере, может отличаться
от показанного.
В общем случае порядок путей, возвращаемых .glob(), не гарантирован, поэтому
вам придется упорядочить их самостоятельно. Для этого можно создать список,
содержащий три пути к файлам, а затем вызвать .sort() для этого списка:
>>> expense_reports = list(reports_dir.glob("*.pdf"))
>>> expense_reports.sort()
Напомню, что .sort() сортирует список на месте, так что присваивать возвращаемое значение переменной необязательно. После вызова .list() список
expense_reports будет отсортирован по алфавиту.
Чтобы убедиться в том, что сортировка сработала, снова переберите expense_
reports и выведите имена файлов:
336 Глава 14 Создание и изменение файлов PDF
>>> for path in expense_reports:
...
print(path.name)
...
Expense report 1.pdf
Expense report 2.pdf
Expense report 3.pdf
Так гораздо лучше!
Теперь можно выполнить конкатенацию трех файлов PDF. Для этого будет использован метод PdfFileMerger.append(), которому передается один строковый
аргумент, представляющий путь к файлу PDF. При вызове .append() все страницы из файла PDF присоединяются к набору страниц в объекте PdfFileMerger.
Посмотрим, как это делается на практике. Сначала импортируйте класс
PdfFileMerger и создайте новый экземпляр:
>>> from PyPDF2 import PdfFileMerger
>>> pdf_merger = PdfFileMerger()
Затем переберите пути из отсортированного списка expense_reports и присоедините их к pdf_merger:
>>> for path in expense_reports:
...
pdf_merger.append(str(path))
...
>>>
Обратите внимание: каждый объект Path в expense_reports/ преобразуется
в строку вызовом str() перед тем, как он передается в pdf_merger.append().
После того как все файлы из каталога expense_reports/ будут объединены в объекте pdf_merger, остается записать все данные в выходной файл PDF. Экземпляры PdfFileMerger содержат метод .write(), который работает аналогично
PdfFileWriter.write().
Откройте новый двоичный файл в режиме чтения, после чего передайте файловый объект методу pdf_merge.write():
>>> with Path("expense_reports.pdf").open(mode="wb") as output_file:
...
pdf_merger.write(output_file)
...
>>>
Теперь в вашем текущем рабочем каталоге появился файл PDF с именем
expense_reports.pdf. Откройте его в программе просмотра PDF — и вы увидите,
что все три отчета объединены в одном файле.
14.4. Конкатенация и слияние файлов PDF 337
Слияние файлов PDF методом .merge()
Для слияния двух и более файлов PDF используется метод PdfFileMer­
ger.merge(). Он аналогичен .append(), не считая того, что вы должны указать,
где в выходном файле PDF должен быть вставлен контент из объединяемых
файлов.
Рассмотрим пример из практики. Компания Goggle, Inc. подготовила квартальный отчет, но забыла включить в него оглавление. Программист заметил
ошибку и быстро создал PDF с отсутствующим оглавлением. Теперь необходимо
объединить этот файл PDF с исходным отчетом.
Оба файла PDF — отчет и оглавление — находятся в подкаталоге quarterly_report/
папки practice_files главы 14. Отчет хранится в файле report.pdf, а оглавление —
в файле toc.pdf.
В интерактивном окне IDLE импортируйте класс PdfFileMerger и создайте
объекты Path для файлов report.pdf и toc.pdf:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileMerger
>>> report_dir = (
...
Path.home() /
...
"python-basics-exercises" /
...
"ch14-interact-with-pdf-files" /
...
"practice_files" /
...
"quarterly_report"
... )
>>> report_path = report_dir / "report.pdf"
>>> toc_path = report_dir / "toc.pdf"
Прежде всего следует присоединить файл PDF с отчетом к новому экземпляру
PdfFileMerger методом .append():
>>> pdf_merger = PdfFileMerger()
>>> pdf_merger.append(str(report_path))
После того как в pdf_merger появились страницы, можно провести слияние
файла PDF с оглавлением в нужной позиции. Открыв файл report.pdf в программе просмотра PDF, вы увидите, что первая страница отчета — титульная.
Вторая содержит введение, а остальные — разные разделы отчета.
Мы хотим, чтобы оглавление было вставлено после титульной страницы, но
непосредственно перед введением. Так как индексы страниц PDF в PyPDF2 начинаются с 0, оглавление необходимо вставить после страницы с индексом 0,
но перед страницей с индексом 1.
338 Глава 14 Создание и изменение файлов PDF
Для этого следует вызвать pdf_merger.merge() с двумя аргументами.
1. Первый аргумент — целое число 1, обозначающее индекс страницы для
вставки оглавления.
2. Второй — строка пути к файлу PDF, содержащему оглавление.
Вот как это выглядит:
>>> pdf_merger.merge(1, str(toc_path))
Все страницы из файла PDF с оглавлением будут вставлены перед страницей
с индексом 1. Так как файл PDF с оглавлением состоит только из одной страницы, он вставляется в позицию с индексом 1. Страница, которой изначально
был присвоен индекс 1, сдвигается в позицию с индексом 2. Страница, которой
изначально был присвоен индекс 2, сдвигается в позицию с индексом 3, и т. д.
Теперь запишите файл PDF, полученный в результате слияния, в выходной файл:
>>> with Path("full_report.pdf").open(mode="wb") as output_file:
...
pdf_merger.write(output_file)
...
>>>
В текущем рабочем каталоге есть файл full_report.pdf. Откройте его в программе просмотра PDF и убедитесь, что оглавление было вставлено в правильной
позиции.
Конкатенация и слияние PDF — чрезвычайно распространенные операции.
Хотя примеры в этом разделе выглядят несколько искусственно, только представьте, насколько полезной окажется программа в случае слияния тысяч PDF
или при автоматизации рутинных задач, ручное выполнение которых заняло
бы слишком много времени.
Упражнения
1. Папка practice_files главы 14 содержит три файла PDF с именами merge1.pdf,
merge2.pdf и merge3.pdf. Используя экземпляр PdfFileMerger, выполните
слияние двух файлов merge1.pdf и merge2.pdf методом .append(). Сохраните результат в файле с именем concatenated.pdf в вашем домашнем
каталоге.
2. Создайте новый экземпляр PdfFileMerger и используйте .merge() для
слияния файла merge3.pdf и файла concatenated.pdf из упражнения 1,
вставив merge3.pdf между двумя страницами concatenated.pdf. Сохраните
новый файл в домашнем каталоге с именем merged.pdf.
14.5. Поворот и обрезка страниц PDF 339
Результатом должен стать файл PDF из трех страниц. На первой странице
должно выводиться число 1, на второй — 2 и на третьей — 3.
14.5. ПОВОРОТ И ОБРЕЗКА СТРАНИЦ PDF
Вы уже узнали, как извлекать текст и страницы из файлов PDF и как выполнить конкатенацию и слияние двух и более файлов. Это чрезвычайно распространенные операции с PDF, но PyPDF2 обладает множеством других полезных
возможностей.
Сейчас мы расскажем, как поворачивать и обрезать страницы в файлах PDF.
Поворот страниц
Начнем с вращения страниц. В этом примере мы воспользуемся файлом ugly.pdf
из папки practice_files главы 14.
Файл ugly.pdf содержит текст сказки Ганса Христиана Андерсена «Ugly
Duckling» («Гадкий утенок»), но каждая нечетная страница повернута на 90°
против часовой стрелки.
Исправим этот недостаток. В новом интерактивном окне IDLE импортируйте
классы PdfFileReader и PdfFileWriter из PyPDF2, а также класс Path из модуля
pathlib:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
Создайте объект Path для файла ugly.pdf:
>>> pdf_path = (
...
Path.home() /
...
"python-basics-exercises" /
...
"ch14-interact-with-pdf-files" /
...
"practice_files" /
...
"ugly.pdf"
... )
Теперь создайте экземпляры PdfFileReader и PdfFileWriter:
>>> pdf_reader = PdfFileReader(str(pdf_path))
>>> pdf_writer = PdfFileWriter()
Наша цель — использовать pdf_writer для создания нового файла PDF, в котором все страницы имеют правильную ориентацию. Четные страницы в PDF
340 Глава 14 Создание и изменение файлов PDF
ориентированы правильно, но нечетные страницы повернуты на 90° против
часовой стрелки.
Для решения проблемы мы воспользуемся методом PageObject.rota­
teClockwise(). Метод получает целочисленный аргумент (в градусах) и поворачивает страницу по часовой стрелке на заданный угол. Например,
.rotateClockwise(90) поворачивает страницу PDF по часовой стрелке на 90°.
ПРИМЕЧАНИЕ
Кроме .rotateClockwise() класс PageObject также содержит файл .rotateCoun­
terClockwise() для поворота страниц против часовой стрелки.
Поворот страниц в PDF можно осуществить несколькими способами. Мы
рассмотрим два; оба основаны на методе .rotateClockwise(), но по-разному
определяют, какие страницы будут повернуты. Первый способ перебирает
индексы страниц из файла PDF и проверяет, соответствует ли каждый индекс
странице, которую необходимо повернуть. Если проверка дает положительный
результат, вызовите .rotateClockwise() для поворота страницы, а затем добавьте
страницу в pdf_writer.
Реализация выглядит примерно так:
>>> for n in range(pdf_reader.getNumPages()):
...
page = pdf_reader.getPage(n)
...
if n % 2 == 0:
...
page.rotateClockwise(90)
...
pdf_writer.addPage(page)
...
>>>
ПРИМЕЧАНИЕ
При выполнении приведенного выше цикла в интерактивном окне IDLE вы
увидите довольно обширный вывод. Дело в том, что .rotateClockwise() возвращает экземпляр PageObject.
Пока не обращайте внимания на этот вывод. При выполнении программ из
окна редактора IDLE вы его не увидите.
Поворачиваются страницы с четным индексом. На первый взгляд это кажется странным, потому что неправильно повернуты нечетные страницы в PDF.
Однако номера страниц в PDF начинаются с 1, а индексы страниц начинаются
с 0. Это означает, что страницы с нечетными номерами имеют четные индексы.
14.5. Поворот и обрезка страниц PDF 341
Если у вас голова идет кругом, не огорчайтесь! На подобных нюансах спотыкаются даже профессиональные программисты, имеющие многолетний опыт
решения подобных задач.
После поворота всех страниц в файле PDF можно записать содержимое pdf_
writer в новый файл и убедиться в том, что все работает:
>>> with Path("ugly_rotated.pdf").open(mode="wb") as output_file:
...
pdf_writer.write(output_file)
...
>>>
В вашем текущем рабочем каталоге должен появиться файл с именем ugly_
rotated.pdf с правильно повернутыми страницами из файла ugly.pdf.
Недостаток только что продемонстрированного решения с поворотом страниц
из файла ugly.pdf заключается в том, что вы должны заранее знать, какие страницы нужно повернуть. На практике не всегда возможно просмотреть весь PDF,
отмечая, какие из страниц ориентированы неправильно.
На самом деле определить, какие страницы необходимо повернуть, можно на
месте… Вернее, почти всегда можно.
Посмотрим, как это делается, начиная с создания нового экземпляра
­ dfFi­leReader:
P
>>> pdf_reader = PdfFileReader(str(pdf_path))
Это необходимо, потому что вы изменили страницы в старом экземпляре
PdfFileReader, повернув их.
Таким образом, создавая новый экземпляр, вы начинаете все с чистого листа.
Экземпляры PageObject поддерживают словарь значений, содержащих информацию о странице:
>>> pdf_reader.getPage(0)
{'/Contents': [IndirectObject(11, 0), IndirectObject(12, 0),
IndirectObject(13, 0), IndirectObject(14, 0), IndirectObject(15, 0),
IndirectObject(16, 0), IndirectObject(17, 0), IndirectObject(18, 0)],
'/Rotate': -90, '/Resources': {'/ColorSpace': {'/CS1':
IndirectObject(19, 0), '/CS0': IndirectObject(19, 0)}, '/XObject':
{'/Im0': IndirectObject(21, 0)}, '/Font': {'/TT1':
IndirectObject(23, 0), '/TT0': IndirectObject(25, 0)}, '/ExtGState':
{'/GS0': IndirectObject(27, 0)}}, '/CropBox': [0, 0, 612, 792],
'/Parent': IndirectObject(1, 0), '/MediaBox': [0, 0, 612, 792],
'/Type': '/Page', '/StructParents': 0}
342 Глава 14 Создание и изменение файлов PDF
Ничего себе! Однако среди всей тарабарщины в четвертой строке вывода мы
видим ключ с именем /Rotate. С этим ключом связано значение -90.
Для обращения к ключу через объект PageObject используется синтаксис индексирования, как и для объектов Python dict:
>>> page = pdf_reader.getPage(0)
>>> page["/Rotate"]
-90
Проверив ключ /Rotate для второй страницы в pdf_reader, мы видим, что с ним
связано значение 0:
>>> page = pdf_reader.getPage(1)
>>> page["/Rotate"]
0
Это означает, что странице с индексом 0 назначен поворот на –90°. Иначе говоря,
она поворачивается против часовой стрелки на 90°. У страницы с индексом 1
угол поворота равен 0, то есть она вообще не поворачивается.
Если повернуть первую страницу вызовом .rotateClockwise(), то значение
/Rotate изменится с -90 на 0:
>>> page = pdf_reader.getPage(0)
>>> page["/Rotate"]
-90
>>> page.rotateClockwise(90)
>>> page["/Rotate"]
0
Теперь вы знаете, как проверить ключ /Rotate, и сможете использовать его для
поворота страниц в файле ugly.pdf.
Прежде всего необходимо заново инициализировать объекты pdf_reader
и pdf_writer, чтобы начать с чистого листа:
>>> pdf_reader = PdfFileReader(str(pdf_path))
>>> pdf_writer = PdfFileWriter()
Напишите цикл, который перебирает страницы из итерируемого объекта
pdf_reader.pages, проверяет значение /Rotate и поворачивает страницу, если
это значение равно -90:
>>> for page in pdf_reader.pages:
...
if page["/Rotate"] == -90:
...
page.rotateClockwise(90)
14.5. Поворот и обрезка страниц PDF 343
...
...
>>>
pdf_writer.addPage(page)
Этот цикл не просто короче цикла из первого решения — для него не нужно
знать заранее, какие страницы требуется повернуть. Такой цикл можно использовать для поворота страниц в любом файле PDF, причем вам даже не придется
открывать этот файл и заглядывать в него.
Чтобы завершить решение, запишите содержимое pdf_writer в новый файл:
>>> with Path("ugly_rotated2.pdf").open(mode="wb") as output_file:
...
pdf_writer.write(output_file)
...
>>>
Теперь вы можете открыть файл ugly_rotated2.pdf в текущем рабочем каталоге
и сравнить его с файлом ugly_rotated.pdf, сгенерированным ранее. Два файла
должны выглядеть одинаково.
ВАЖНО!
Предупреждение по поводу ключа /Rotate: его существование для страницы
не гарантировано.
Если ключ /Rotate не существует, обычно это означает, что страница не была
повернута. Тем не менее это предположение не стопроцентно.
Если PageObject не содержит ключа/Rotate, то при попытке обращения по
этому ключу произойдет исключение KeyError. Для его перехвата можно воспользоваться блоком try … except.
Значение /Rotate не всегда соответствует вашим ожиданиям. Например, если
вы отсканируете бумажный документ, в котором страница была повернута на
90° против часовой стрелки, будет казаться, что содержимое файла PDF повернуто. При этом ключ /Rotate может содержать значение 0.
Это одна из многих странностей, которые усложняют работу с файлами PDF.
Иногда достаточно просто открыть PDF в программе просмотра PDF и вручную
выяснить, что здесь происходит.
Обрезка страниц
Другая распространенная операция с файлами PDF — обрезка страниц. Например, это нужно для разбиения одной страницы на несколько частей или для
извлечения небольшого фрагмента страницы (подписи, иллюстрации и т. д.).
344 Глава 14 Создание и изменение файлов PDF
В папке practice_files главы 14 хранится файл с именем half_and_half.pdf. В нем
содержится часть текста сказки Ганса Христиана Андерсена «The Little Mermaid»
(«Русалочка»).
Каждая страница файла PDF состоит из двух столбцов. Разобьем каждую страницу на две, чтобы на ней был опубликован только один столбец.
Начнем с импортирования классов PdfFileReader и PdfFileWriter из модуля
PyPDF2, а также класса Path из модуля pathlib:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
Теперь создайте объект Path для файла half_and_half.pdf:
>>> pdf_path = (
...
Path.home() /
...
"python-basics-exercises" /
...
"ch14-interact-with-pdf-files" /
...
"practice_files" /
...
"half_and_half.pdf"
... )
Затем создайте новый объект PdfFileReader и получите первую страницу PDF:
>>> pdf_reader = PdfFileReader(str(pdf_path))
>>> first_page = pdf_reader.getPage(0)
Чтобы выполнить обрезку страницы, давайте поближе познакомимся со структурой страниц. Экземпляры PageObject (такие, как first_page) имеют атрибут
.mediaBox , который представляет прямоугольную область, определяющую
границы страницы.
Атрибут .mediaBox можно исследовать в интерактивном окне IDLE, прежде
чем использовать его для обрезки страницы:
>>> first_page.mediaBox
RectangleObject([0, 0, 792, 612])
Атрибут .mediaBox возвращает объект RectangleObject. Он определяется в пакете PyPDF2 и представляет прямоугольную область страницы.
Список [0, 0, 792, 612] в выходных данных задает прямоугольную область.
Первые два числа показывают координаты x и y левого нижнего угла прямоугольника. Третье и четвертое числа представляют ширину и высоту прямо­
14.5. Поворот и обрезка страниц PDF 345
угольника соответственно. Все значения задаются в пунктах, один пункт равен
1/72 дюйма.
RectangleObject([0, 0, 792, 612]) представляет прямоугольную область с левым
нижним углом в начале координат, шириной 792 пункта (11 дюймов) и высотой
612 пунктов, или 8,5 дюйма. Это размеры стандартного листа формата Letter
в горизонтальной ориентации, который используется в файле PDF с текстом
«Русалочки». Для страницы PDF формата Letter в вертикальной ориентации
будет возвращен объект RectangleObject([0, 0, 612, 792]).
Объект RectangleObject содержит четыре атрибута, которые возвращают
координаты углов прямоугольника: .lowerLeft , .lowerRight , .upperLeft
и .upperRight. Как и значения width и height, эти координаты указываются
в пунктах.
Четыре свойства определяют координаты каждого угла RectangleObject:
>>> first_page.mediaBox.lowerLeft
(0, 0)
>>> first_page.mediaBox.lowerRight
(792, 0)
>>> first_page.mediaBox.upperLeft
(0, 612)
>>> first_page.mediaBox.upperRight
(792, 612)
Каждое свойство возвращает кортеж с координатами заданного угла. К отдельным координатам можно обращаться по индексам, как и к элементам любого
кортежа Python:
>>> first_page.mediaBox.upperRight[0]
792
>>> first_page.mediaBox.upperRight[1]
612
Чтобы изменить координаты mediaBox, присвойте новый кортеж одному из его
свойств:
>>> first_page.mediaBox.upperLeft = (0, 480)
>>> first_page.mediaBox.upperLeft
(0, 480)
При изменении координат .upperLeft атрибут .upperRight автоматически изменяется для сохранения прямоугольной формы:
>>> first_page.mediaBox.upperRight
(792, 480)
346 Глава 14 Создание и изменение файлов PDF
Изменяя координаты RectangleObject, возвращаемые .mediaBox, вы фактически
обрезаете страницу. Объект first_page теперь содержит только информацию,
содержащуюся в границах нового объекта RectangleObject.
Запишите обрезанную страницу в новый файл PDF:
>>> pdf_writer = PdfFileWriter()
>>> pdf_writer.addPage(first_page)
>>> with Path("cropped_page.pdf").open(mode="wb") as output_file:
...
pdf_writer.write(output_file)
...
>>>
Открыв обрезанный файл cropped_page.pdf в текущем рабочем каталоге, вы
увидите, что верхняя часть страницы удалена. Как обрезать страницу так,
чтобы оставался видимым только текст в левой части страницы? Необходимо
уменьшить горизонтальный размер страницы вдвое. Для этого достаточно
изменить координату .upperRight объекта .mediaBox. Давайте посмотрим,
как это делается.
Прежде всего необходимо создать новые объекты PdfFileReader и PdfFileWriter,
потому что вы только что изменили первую страницу в pdf_reader, а затем добавили ее в pdf_writer:
>>> pdf_reader = PdfFileReader(str(pdf_path))
>>> pdf_writer = PdfFileWriter()
Теперь получим первую страницу PDF:
>>> first_page = pdf_reader.getPage(0)
На этот раз будем работать с копией первой страницы, чтобы только что извлеченная страница не изменялась. Для этого можно импортировать модуль
copy из стандартной библиотеки Python и создать копию страницы вызовом
deepcopy():
>>> import copy
>>> left_side = copy.deepcopy(first_page)
Теперь скорректируем left_side без изменения каких-либо свойств first_page.
Это позволяет нам позднее использовать first_page для извлечения текста
правой части страницы.
Пришло время немного посчитать. Мы уже выяснили, что правый верхний
угол .mediaBox необходимо переместить в центр верхнего края страницы. Для
14.5. Поворот и обрезка страниц PDF 347
этого мы создали новый кортеж, первый компонент которого равен половине
исходного значения, и присвоили его свойству .upperRight.
Начнем с получения текущих координат правого верхнего угла .mediaBox:
>>> current_coords = left_side.mediaBox.upperRight
Затем создадим новый кортеж, первая координата в котором равна половине
значения текущей координаты, а вторая равна исходной:
>>> new_coords = (current_coords[0] / 2, current_coords[1])
Наконец, кортеж с новыми координатами присваивается атрибуту .upperRight:
>>> left_side.mediaBox.upperRight = new_coords
Исходная страница была успешно обрезана — теперь она содержит только текст
в левой части! Перейдем к определению правого края страницы.
Сначала получим новую копию first_page:
>>> right_side = copy.deepcopy(first_page)
На этот раз вместо угла .upperRight перемещаем угол .upperLeft:
>>> right_side.mediaBox.upperLeft = new_coords
Левый верхний угол перемещаем в ту же точку, в которую был перемещен
правый верхний угол при извлечении левой части страницы. Таким образом,
right_side.mediaBox теперь представляет прямоугольник, левый верхний угол
которого находится в середине верхнего края страницы, а правый верхний угол
совпадает с правым верхним углом страницы.
Наконец, добавим страницы left_side и right_side в pdf_writer и запишем
их в новый файл PDF:
>>> pdf_writer.addPage(left_side)
>>> pdf_writer.addPage(right_side)
>>> with Path("cropped_pages.pdf").open(mode="wb") as output_file:
...
pdf_writer.write(output_file)
...
>>>
Откройте файл cropped_pages.pdf в программе просмотра PDF. Вы увидите
файл с двумя страницами: первая содержит текст из левой половины исходной
первой страницы, а вторая — текст из правой половины.
348 Глава 14 Создание и изменение файлов PDF
Упражнение
1. В папке practice_files главы 14 находится файл PDF с именем split_and_
rotate.pdf. Создайте в своем домашнем каталоге новый файл rotated.pdf,
который содержит страницы split_and_rotate.pdf, повернутые на 90°
против часовой стрелки.
2. Используя файл rotated.pdf, созданный в упражнении 1, разбейте каждую
страницу PDF по вертикали по центру страницы. Создайте в домашнем
каталоге новый файл PDF split.pdf со всеми страницами, полученными
в результате разбиения. Файл split.pdf должен содержать четыре страницы
с номерами 1, 2, 3 и 4 в указанном порядке.
14.6. ШИФРОВАНИЕ И ДЕШИФРОВАНИЕ
ФАЙЛОВ PDF
Иногда файлы PDF защищаются паролями. Пакет PyPDF2 позволяет работать
с зашифрованными файлами PDF, а также добавлять парольную защиту к существующим файлам PDF.
Шифрование файлов PDF
Для добавления парольной защиты к файлам PDF используется метод
.encrypt() экземпляра PdfFileWriter(). Он получает два основных параметра.
1. user_pwd задает пароль пользователя, позволяющий открыть и прочитать
файл PDF.
2. owner_pwd задает пароль владельца, позволяющий открыть файл PDF без
каких-либо ограничений, включая редактирование.
Воспользуемся .encrypt() для добавления пароля к файлу PDF. Сначала откроем файл newsletter.pdf из каталога practice_files главы 14:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
>>> pdf_path = (
...
Path.home() /
...
"python-basics-exercises" /
...
"ch14-interact-with-pdf-files" /
...
"practice_files" /
...
"newsletter.pdf"
... )
>>> pdf_reader = PdfFileReader(str(pdf_path))
14.6. Шифрование и дешифрование файлов PDF 349
Создадим новый экземпляр PdfFileWriter и добавим в него страницы из
pdf_reader:
>>> pdf_writer = PdfFileWriter()
>>> pdf_writer.appendPagesFromReader(pdf_reader)
Затем добавим пароль "SuperSecret" вызовом pdf_writer.encrypt():
>>> pdf_writer.encrypt(user_pwd="SuperSecret")
Если задается только пароль пользователя user_pwd, аргументу owner_pwd по
умолчанию присваивается та же строка. Таким образом, приведенная выше
строка кода задает пароли как пользователя, так и владельца.
Наконец, запишем зашифрованные данные PDF в выходной файл newsletter_
protected.pdf в вашем домашнем каталоге:
>>> output_path = Path.home() / "newsletter_protected.pdf"
>>> with output_path.open(mode="wb") as output_file:
...
pdf_writer.write(output_file)
Когда вы откроете файл в программе просмотра PDF, вам будет предложено
ввести пароль. Введите строку "SuperSecret", чтобы открыть PDF.
Если вам понадобится назначить файлу PDF отдельный пароль владельца,
передайте вторую строку в параметре owner_pwd:
>>> user_pwd = "SuperSecret"
>>> owner_pwd = "ReallySuperSecret"
>>> pdf_writer.encrypt(user_pwd=user_pwd, owner_pwd=owner_pwd)
В этом примере используется пароль пользователя "SuperSecret" и пароль
владельца "ReallySuperSecret".
Если файл PDF зашифрован паролем, то при попытке открыть его вы должны
ввести пароль для просмотра его содержимого. Защита распространяется и на
чтение из PDF в программе Python. А теперь посмотрим, как дешифровать PDF
средствами модуля PyPDF2.
Дешифрование файлов PDF
Чтобы дешифровать зашифрованный файл PDF, воспользуйтесь методом
.decrypt() экземпляра PdfFileReader.
Метод .decrypt() получает один параметр password, в котором передается пароль для дешифрования. Уровень доступа, который вы получаете при открытии
PDF, зависит от аргумента, передаваемого в параметре password.
350 Глава 14 Создание и изменение файлов PDF
Сейчас мы откроем зашифрованный файл newsletter_protected.pdf, созданный
в предыдущем разделе, и воспользуемся средствами PyPDF2 для его дешифрования.
Сначала создайте новый экземпляр PdfFileReader с путем к защищенному
файлу PDF:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
>>> pdf_path = Path.home() / "newsletter_protected.pdf"
>>> pdf_reader = PdfFileReader(str(pdf_path))
Прежде чем дешифровать PDF, проверьте, что произойдет при попытке получить первую страницу:
>>> pdf_reader.getPage(0)
Traceback (most recent call last):
File "c:\realpython\venv\lib\site-packages\PyPDF2\pdf.py",
line 1617, in getObject
raise utils.PdfReadError("file has not been decrypted")
PyPDF2.utils.PdfReadError: file has not been decrypted
Выданное исключение PdfReadError сообщает, что файл PDF не был дешифрован.
ПРИМЕЧАНИЕ
Приведенная выше трассировка была сокращена, чтобы показать самое
главное. Вывод, который вы получите на своем компьютере, будет намного
длиннее.
Чтобы дешифровать файл, создайте новый экземпляр PdfFileReader:
>>> pdf_reader = PdfFileReader(str(pdf_path))
Затем дешифруйте файл:
>>> pdf_reader.decrypt(password="SuperSecret")
1
Метод .decrypt() возвращает целое число — признак успеха дешифрования:
zz 0 — пароль указан неверно;
zz 1 — правильный пароль пользователя;
zz 2 — правильный пароль владельца.
14.7. Задача: восстановление порядка страниц 351
После того как файл был дешифрован, вы можете обратиться к содержимому
файла PDF:
>>> pdf_reader.getPage(0)
{'/Contents': IndirectObject(7, 0), '/CropBox': [0, 0, 612, 792],
'/MediaBox': [0, 0, 612, 792], '/Parent': IndirectObject(1, 0),
'/Resources': IndirectObject(8, 0), '/Rotate': 0, '/Type': '/Page'}
Итак, вы научились извлекать текст, а также обрезать и поворачивать страницы
файлов так, как считаете нужным.
Упражнения
1. Папка practice_files главы 14 содержит файл PDF с именем top_secret.pdf.
Зашифруйте файл с паролем пользователя Unguessable, используя метод
PdfFileWriter.encrypt().
Сохраните зашифрованный файл с именем top_secret_encrypted.pdf
в своем домашнем каталоге.
2. Откройте файл top_secret_encrуpted.pdf, созданный в упражнении 1,
­расшифруйте его и выведите текст, содержащийся на первой странице
PDF.
14.7. ЗАДАЧА: ВОССТАНОВЛЕНИЕ
ПОРЯДКА СТРАНИЦ
В папке practice_files главы 14 содержится файл PDF с именем scrambled.pdf,
состоящий из семи страниц. На каждой странице находится число от 1 до 7, но
порядок чисел нарушен.
Кроме того, некоторые страницы повернуты на 90, 180 или 270 градусов по
часовой стрелке или против нее.
Напишите программу, которая восстанавливает файл PDF. Для этого она сортирует страницы по номеру в тексте и при необходимости поворачивает их
в вертикальное положение.
ПРИМЕЧАНИЕ
Предполагается, что каждый объект PageObject, прочитанный из scrambled.pdf,
содержит ключ «/Rotate».
352 Глава 14 Создание и изменение файлов PDF
Сохраните восстановленный файл PDF в файле с именем unscrambled.pdf в домашнем каталоге.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
14.8. СОЗДАНИЕ ФАЙЛА PDF С НУЛЯ
Пакет PyPDF2 отлично подходит для чтения и изменения существующих файлов
PDF, но у него есть принципиальное ограничение: он не может использоваться
для создания новых файлов PDF. В этом разделе мы воспользуемся инструментарием ReportLab Toolkit для генерирования файлов PDF с нуля.
ReportLab представляет собой полнофункциональное решение для создания
PDF. Существует платная коммерческая версия, но также доступна версия
с открытым исходным кодом и ограниченной функциональностью.
Установка reportlab
Прежде всего следует установить reportlab при помощи pip:
$ python3 -m pip install reportlab
Проверьте установку командой pip show:
$ python3 -m pip show reportlab
Name: reportlab
Version: 3.5.34
Summary: The Reportlab Toolkit
Home-page: http://www.reportlab.com/
Author: Andy Robinson, Robin Becker, the ReportLab team
and the community
Author-email: reportlab-users@lists2.reportlab.com
License: BSD license (see license.txt for details),
Copyright (c) 2000-2018, ReportLab Inc.
Location: c:\realpython\venv\lib\site-packages
Requires: pillow
Required-by:
На момент написания книги последней версией reportlab была версия 3.5.34.
Если у вас открыта среда IDLE, ее необходимо перезапустить, прежде чем вы
сможете использовать пакет reportlab.
14.8. Создание файла PDF с нуля 353
Использование класса Canvas
Основной интерфейс для создания PDF в reportlab предоставляет класс Canvas
из модуля reportlab.pdfgen.canvas.
Откройте новое интерактивное окно в IDLE и введите следующую команду,
чтобы импортировать класс Canvas:
>>> from reportlab.pdfgen.canvas import Canvas
При создании нового экземпляра Canvas передается строка с именем создаваемого файла PDF.
Создайте новый экземпляр Canvas для файла hello.pdf:
>>> canvas = Canvas("hello.pdf")
Теперь у вас имеется экземпляр Canvas, который был присвоен переменной
canvas; он связан с файлом с именем hello.pdf в текущем рабочем каталоге.
Впрочем, файл hello.pdf еще не существует.
Добавим в PDF текст. Эта задача решается методом .drawString():
>>> canvas.drawString(72, 72, "Hello, World")
Первые два аргумента, передаваемые .drawString(), определяют позицию вывода текста на объекте canvas. Первый аргумент задает расстояние от левого
края, а второй — расстояние от нижнего края.
Значения, передаваемые .drawString(), задаются в пунктах. Так как один пункт
равен 1/72 дюйма, .drawString(72, 72, "Hello, World") выводит строку "Hello,
World" на расстоянии в один дюйм от левого и от нижнего края страницы.
Для сохранения данных PDF в файле используется метод .save():
>>> canvas.save()
В текущем рабочем каталоге появился файл PDF с именем hello.pdf. Откройте
его в программе просмотра PDF — в нижней части страницы появится текст
Hello, World!
В только что созданном файле PDF стоит обратить внимание на два обстоятельства.
354 Глава 14 Создание и изменение файлов PDF
1. По умолчанию используется размер страницы A4, отличный от стандартного для США размера Letter.
2. По умолчанию используется шрифт Helvetica, 12 пунктов.
Ваши возможности не ограничены этими параметрами.
Настройка размера страницы
При создании объекта Canvas можно изменить размер страницы необязательным
параметром pagesize. Этот параметр получает кортеж значений с плавающей
точкой, задающих ширину и высоту страницы в пунктах.
Например, чтобы задать для страницы стандартный размер 8,5 × 11 дюймов,
создайте следующий объект Canvas:
canvas = Canvas("hello.pdf", pagesize=(612.0, 792.0))
Кортеж (612.0, 792.0) представляет лист размера Letter, потому что 8,5 × 72
равно 612, а 11 × 72 равно 792.
Если математические вычисления для преобразования пунктов в дюймы или
сантиметры нагоняют на вас тоску, используйте модуль reportlab.lib.units для
упрощения преобразований. Модуль .units содержит несколько вспомогательных объектов (таких, как inch и cm), которые помогут вам с преобразованиями.
Импортируйте объекты inch и cm из модуля reportlab.lib.units:
>>> from reportlab.lib.units import inch, cm
Теперь проверьте значения обоих объектов:
>>> cm
28.346456692913385
>>> inch
72.0
И cm, и inch являются значениями с плавающей точкой. Они представляют
количество пунктов в каждой единице измерения. Сантиметр (cm) составляет
28.346456692913385 пунктов, а дюйм (inch) — 72.0 пункта.
Чтобы использовать единицы, умножьте название единицы на количество
единиц, которые нужно преобразовать в пункты. Например, в следующей команде inch используется для назначения странице размера 8,5 дюйма в ширину
и 11 дюймов в высоту:
>>> canvas = Canvas("hello.pdf", pagesize=(8.5 * inch, 11 * inch))
14.8. Создание файла PDF с нуля 355
Передавая кортеж pagesize, можно создать страницу любого размера. Впрочем,
в пакете reportlab определены стандартные встроенные размеры страниц,
с которыми проще работать.
Размеры страниц определяются в модуле reportlab.lib.pagesizes. Например,
чтобы выбрать размер страницы Letter, импортируйте объект LETTER из модуля
pagesize и передайте его в параметре pagesize при создании экземпляра Canvas:
>>> from reportlab.lib.pagesizes import LETTER
>>> canvas = Canvas("hello.pdf", pagesize=LETTER)
Проверив объект LETTER, вы увидите, что он содержит кортеж чисел с плавающей точкой:
>>> LETTER
(612.0, 792.0)
Модуль reportlab.lib.pagesize определяет много стандартных размеров
страниц. В таблице перечислены некоторые варианты с указанием размеров.
РАЗМЕР СТРАНИЦЫ
РАЗМЕРЫ
A4
210 мм × 297 мм
LETTER
8,5 дюймов × 11 дюймов
LEGAL
8,5 дюймов × 14 дюймов
TABLOID
11 дюймов × 17 дюймов
Кроме этих размеров модуль содержит определения всех стандартных размеров
листов ISO 216 (https://ru.wikipedia.org/wiki/ISO_216).
Настройка свойств шрифта
Также при выводе текста на объект Canvas можно изменить шрифт, его размер
и цвет.
Для изменения шрифта и его размера используется метод .setFont(). Сначала
создайте новый экземпляр Canvas с именем файла font-example.pdf и размером
листа Letter:
>>> canvas = Canvas("font-example.pdf", pagesize=LETTER)
Затем выберите шрифт Times New Roman, 18 пунктов:
>>> canvas.setFont("Times-Roman", 18)
356 Глава 14 Создание и изменение файлов PDF
Наконец, выведите строку "Times New Roman (18 pt)" на объект Canvas и сохраните его:
>>> canvas.drawString(1 * inch, 10 * inch, "Times New Roman (18 pt)")
>>> canvas.save()
С этими настройками текст будет выводиться на расстоянии 1 дюйм от левого
края страницы и 10 дюймов от нижнего края. Откройте файл font-example.pdf
в текущем рабочем каталоге и удостоверьтесь в этом!
По умолчанию доступны три шрифта:
1. "Courier"
2. "Helvetica"
3. "Times-Roman"
Каждый шрифт существует в полужирном и курсивном начертании. Список
всех начертаний шрифтов, доступных в reportlab:
zz "Courier"
zz "Courier-Bold
zz "Courier-BoldOblique"
zz "Courier-Oblique"
zz "Helvetica"
zz "Helvetica-Bold"
zz "Helvetica-BoldOblique"
zz "Helvetica-Oblique"
zz "Times-Bold"
zz "Times-BoldItalic
zz "Times-Italic"
zz "Times-Roman"
Также можно задать цвет шрифта методом .setFillColor(). В следующем
примере мы создадим файл PDF с именем font-colors.pdf, содержащий текст
синего цвета:
from reportlab.lib.colors import blue
from reportlab.lib.pagesizes import LETTER
from reportlab.lib.units import inch
14.9. Итоги и дополнительные ресурсы 357
from reportlab.pdfgen.canvas import Canvas
canvas = Canvas("font-colors.pdf", pagesize=LETTER)
# Назначить шрифт Times New Roman, 12 пунктов
canvas.setFont("Times-Roman", 12)
# Вывести текст синего цвета на расстоянии 1 дюйма от левого края
# и 10 дюймов от нижнего края
canvas.setFillColor("blue")
canvas.drawString(1*inch, 10*inch, "Blue text")
# Сохранить файл PDF
canvas.save()
blue — объект, импортированный из модуля reportlab.lib.colors module. Этот
модуль содержит несколько распространенных цветов. Полный список цветов
хранится в исходном коде reportlab.
Примерами этого раздела мы хотели показать, как работает объект Canvas.
Но это знакомство — поверхностное. С модулем reportlab вы можете создавать
таблицы, формы и даже высококачественную графику с нуля!
В руководстве пользователя ReportLab вы найдете многочисленные примеры
построения документов PDF. Руководство может стать отличной отправной
точкой, если вы захотите больше узнать о создании PDF в Python.
14.9. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы научились создавать и изменять файлы PDF средствами пакетов
PyPDF2 и reportlab.
Вы узнали, как средствами пакета PyPDF2:
zz
читать файлы PDF и извлекать из них текст при помощи класса
PdfFileReader;
zz
выполнять конкатенацию и слияние файлов PDF при помощи класса
PdfFileMerger;
zz
поворачивать и обрезать страницы файлов PDF;
zz
шифровать и дешифровать файлы PDF с паролем.
Также в этой главе я кратко познакомил вас с тем, как создавать файлы PDF
с нуля средствами пакета reportlab. Вы научились:
358 Глава 14 Создание и изменение файлов PDF
zz
пользоваться классом Canvas;
zz
записывать текст в Canvas вызовом .drawString();
zz
задавать гарнитуру и размер шрифта методом .setFont();
zz
изменять цвет шрифта вызовом .setFillColor().
Пакет reportlab — мощный инструмент для создания файлов PDF, и вы получили лишь первое представление о его возможностях.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-pdf
Дополнительные ресурсы
За дополнительной информацией о работе с файлами PDF в Python обращайтесь к следующим ресурсам:
zz
«How toWork With a PDF in Python» (https://realpython.com/pdf-python/)
zz
«ReportLab PDF Library User Guidе» (https://www.reportlab.com/docs/
reportlab-userguide.pdf)
ГЛАВА 15
Базы данных
В главе 12 мы показали, как читать и сохранять данные из файлов средствами
Python. Также для хранения информации часто используются базы данных (БД).
База данных представляет собой структурированную систему хранения данных.
Иногда она состоит из нескольких файлов CSV, упорядоченных в каталогах,
а иногда оказывается гораздо сложнее. Система управления базами данных
(СУБД) — программа, управляющая доступом к БД и взаимодействием с ней.
В комплект поставки Python включена упрощенная система управления базами данных SQLite. Она идеально подходит для изучения азов работы с базами
данных.
В этой главе вы научитесь:
zz
создавать базу данных SQLite;
zz
сохранять и читать данные из базы SQLite;
zz
использовать пакеты для работы с другими базами данных.
ВАЖНО!
SQLite использует язык SQL (Structured Query Language — язык структурированных запросов) для взаимодействия со своей базой данных. Некоторый опыт
работы с SQL пригодится вам при изучении материала этой главы.
Итак, за дело!
15.1. ЗНАКОМСТВО С SQLITE
Существует много разных ядер баз данных SQL, и некоторые из них лучше
подходят для каких-то конкретных целей, чем другие. Одно из самых простых
и облегченных ядер баз данных SQL — SQLite — входит в стандартную установку Python, а значит, уже работает на вашем компьютере.
360 Глава 15 Базы данных
В этом разделе вы научитесь пользоваться пакетом sqlite3 для создания новых
баз данных SQLite, а также для хранения и чтения данных.
Основы SQLite
Вот основные этапы работы с SQLite.
1. Импортирование пакета sqlite3.
2. Подключение к существующей БД или создание новой.
3. Выполнение команд SQL.
4. Закрытие подключения к БД.
Начнем знакомство с ними в интерактивном окне IDLE. Откройте IDLE и введите следующие команды:
>>> import sqlite3
>>> connection = sqlite3.connect("test_database.db")
Функция sqlite3.connect() используется для подключения или создания
новой базы данных.
При выполнении команды .connect("test_database.db") Python ищет существующую БД с именем "test_database.db". Если БД с таким именем не
найдена, в текущем рабочем каталоге создается новая.
Чтобы создать БД в другом каталоге, необходимо указать полный путь в аргументе .connect().
ПРИМЕЧАНИЕ
Также возможно создать базу данных в памяти, передав .connect() строку
":memory:":
connection = sqlite3.connect(":memory:")
Этот способ хорошо подходит для хранения данных, которые должны существовать только во время работы программы.
Аргумент .connect() возвращает объект sqlite3.Connection. В этом можно
убедиться при помощи type():
>>> type(connection)
<class 'sqlite3.Connection'>
15.1. Знакомство с SQLite 361
Объект Connection осуществляет соединение между программой и базой данных. Он содержит набор атрибутов и методов, которые могут использоваться
для взаимодействия с БД.
Для хранения данных понадобится объект Cursor, который можно получить
вызовом connection.cursor():
>>> cursor = connection.cursor()
>>> type(cursor)
<class 'sqlite3.Cursor'>
Объект sqlite3.Cursor становится «окном» для взаимодействия с базой данных. При помощи Cursor можно создавать таблицы базы данных, выполнять
команды SQL и получать результаты запроса.
ПРИМЕЧАНИЕ
В терминологии баз данных курсором называется объект, предназначенный
для выборки результатов запроса к базе данных — по одной строке данных
за раз.
Воспользуемся функцией SQLite datetime() для получения значения текущего
местного времени:
>>> query = "SELECT datetime('now', 'localtime');"
>>> results = cursor.execute(query)
>>> results
<sqlite3.Cursor object at 0x000001A27EB85E30>
"SELECT datetime('now', 'localtime');" — команда SQL, возвращающая дату
и время в настоящий момент. Текст запроса присваивается переменной query
и передается cursor.execute(). Команда применяет запрос к базе данных и возвращает объект Cursor, который присваивается переменной results.
Возможно, вас интересует, где увидеть время, возвращенное datetime(). Чтобы
получить результаты запроса, используйте метод results.fetchone(), который
возвращает кортеж с первой строкой результатов:
>>> row = results.fetchone()
>>> row
('2018-11-20 23:07:21',)
Так как .fetchone() возвращает кортеж, необходимо обратиться к первому
элементу для получения строки с информацией о дате и времени:
362 Глава 15 Базы данных
>>> time = row[0]
>>> time
'2018-11-20 23:09:45'
Наконец, вызовите connection.close() для закрытия подключения к базе
данных:
>>> connection.close()
Важно всегда закрывать подключение к БД после завершения работы с ней,
чтобы системные ресурсы не оставались занятыми после того, как ваша программа прекратит работу.
Использование with для управления
подключением к базе данных
Вспомните, о чем мы говорили в главе 12: команда with может использоваться
с open() для открытия файла и его автоматического закрытия после выполнения блока with. Та же схема используется с подключениями баз данных SQLite,
и этот способ открытия подключений считается предпочтительным.
Пример использования datetime() из предыдущего примера с командой with
для управления подключением к БД:
>>> with sqlite3.connect("test_database.db") as connection:
...
cursor = connection.cursor()
...
query = "SELECT datetime('now', 'localtime');"
...
results = cursor.execute(query)
...
row = results.fetchone()
...
time = row[0]
...
>>> time
'2018-11-20 23:14:37'
В этом примере объект Connection, возвращенный sqlite3.connect(), присваивается переменной connection в команде with.
Код в блоке with создает новый объект Cursor методом connection.cursor(), а затем получает текущее время методами .execute() и .fetchone() объекта Cursor.
Управление подключениями к базе данных с помощью команды with обладает
множеством преимуществ. Полученный код часто оказывается более чистым
и компактным, чем код без использования with. Более того, как будет показано
в следующем примере, любые изменения, вносимые в базу данных, автоматически сохраняются.
15.1. Знакомство с SQLite 363
Работа с таблицами базы данных
Обычно создавать целую базу данных только для получения текущего времени
не стоит. Базы данных, как правило, используются для сохранения и чтения
информации. Чтобы сохранить информацию в базе, следует создать таблицу
и записать в нее набор значений.
Создадим таблицу People с тремя столбцами: FirstName, LastName и Age. Запрос
SQL для создания этой таблицы выглядит так:
CREATE TABLE People(FirstName TEXT, LastName TEXT, Age INT);
Обратите внимание: после FirstName и LastName следует слово TEXT, а после Age
следует слово INT. Оно сообщает SQLite, что значения в столбцах FirstName
и LastName являются текстовыми, тогда как значения в столбце Age являются
целыми числами.
После того как таблица будет создана, ее можно заполнить данными командой INSERT INTO SQL. Следующий запрос вставляет значения Ron, Obvious и 42
в столбцы FirstName, LastName и Age соответственно:
INSERT INTO People VALUES('Ron', 'Obvious', 42);
Обратите внимание: строки 'Ron' и 'Obvious' заключены в одинарные кавычки.
При этом они остаются валидными строками в Python, но, что важнее, в SQLite
валидны только строки в одинарных кавычках.
ВАЖНО!
Когда вы записываете запросы в SQL в виде строк на языке Python, проследите,
чтобы они заключались в двойные кавычки. Это позволит вам использовать
одинарные кавычки внутри них как ограничители строк в SQLite.
SQLite — не единственная СУБД SQL, где действует соглашение об одинарных
кавычках. Постоянно помните об этом, работая с базами данных SQL.
А теперь посмотрим, как выполнить эти команды и сохранить изменения в базе
данных. Сначала это будет сделано без команды with.
В новом окне редактора введите следующую программу:
import sqlite3
create_table = """
CREATE TABLE People(
364 Глава 15 Базы данных
FirstName TEXT,
LastName TEXT,
Age INT
);"""
insert_values = """
INSERT INTO People VALUES(
'Ron',
'Obvious',
42
);"""
connection = sqlite3.connect("test_database.db")
cursor = connection.cursor()
cursor.execute(crate_table)
cursor.execute(insert_values)
connection.commit()
connection.close()
Сначала создаются две строки с командами SQL, которые создают таблицу
People и вставляют в нее данные. Эти строки присваиваются переменным
create_table и insert_values.
Обе команды записываются в синтаксисе с утроенными кавычками, чтобы мы
могли отформатировать код. SQL игнорирует отступы, что позволяет использовать пробелы в строке для улучшения удобочитаемости кода Python.
Затем мы создаем объект Connection вызовом sqlite3.connect() и присваиваем его переменной connection. Также можно создать объект Cursor вызовом
connection.cursor() и использовать его для выполнения двух команд SQL.
Наконец, метод connection.commit() сохраняет информацию в базе данных.
Этот метод сохраняет внесенные изменения. Если не выполнить connection.
commit(), то таблица People создана не будет.
Сохраните файл и нажмите F5, чтобы запустить программу. База данных test_
database.db содержит таблицу People с одной строкой данных. В этом можно
убедиться в интерактивном окне:
>>> connection = sqlite3.connect("test_database.db")
>>> cursor = connection.cursor()
>>> query = "SELECT * FROM People;"
>>> results = cursor.execute(query)
>>> results.fetchone()
('Ron', 'Obvious', 42)
А теперь перепишем программу с использованием команды with для управления
подключением к базе данных.
15.1. Знакомство с SQLite 365
Прежде чем что-либо делать, необходимо удалить таблицу People, чтобы со­
здать ее заново. Введите следующий код в интерактивном окне, чтобы удалить
таблицу People из базы данных:
>>> cursor.execute("DROP TABLE People;")
<sqlite3.Cursor object at 0x000001F739DB6650>
>>> connection.commit()
>>> connection.close()
Вернитесь к окну редактора и измените программу следующим образом:
import sqlite3
create_table = """
CREATE TABLE People(
FirstName TEXT,
LastName TEXT,
Age INT
);"""
insert_values = """
INSERT INTO People VALUES(
'Ron',
'Obvious',
42
);"""
with sqlite3.connect("test_database.db") as connection:
cursor = connection.cursor()
cursor.execute(create_table)
cursor.execute(insert_values)
Ни вызов connection.close(), ни вызов connection.commit() не обязательны.
Любые изменения, вносимые в базу данных, будут автоматически сохранены
при завершении выполнения блока with. Это еще одно преимущество использования команды with для управления подключением к БД.
Выполнение нескольких команд SQL
Сценарий SQL представляет собой набор разделенных точкой с запятой команд
SQL, которые могут выполняться одновременно. Объекты Cursor содержат
метод .executescript() для выполнения сценариев SQL.
Следующая программа выполняет сценарий SQL, который создает таблицу
People и вставляет в нее несколько значений:
import sqlite3
sql = """
DROP TABLE IF EXISTS People;
366 Глава 15 Базы данных
CREATE TABLE People(
FirstName TEXT,
LastName TEXT,
Age INT
);
INSERT INTO People VALUES(
'Ron',
'Obvious',
'42'
);"""
with sqlite3.connect("test_database.db") as connection:
cursor = connection.cursor()
cursor.executescript(sql)
Также возможно выполнить несколько сходных команд, вызвав метод
.executemany() и передав кортеж кортежей, в котором каждый внутренний
кортеж предоставляет информацию для одной команды.
Например, если у вас имеется большой набор записей о людях, которые нужно
вставить в таблицу People, вы можете сохранить эту информацию в следующем
кортеже кортежей:
people_values = (
("Ron", "Obvious", 42),
("Luigi", "Vercotti", 43),
("Arthur", "Belling", 28)
)
После этого всю информацию можно вставить всего одной строкой кода:
cursor.executemany("INSERT INTO People VALUES(?, ?, ?)", people_values)
Вопросительные знаки обозначают место для подстановки элементов кортежей,
содержащихся в people_values. Это называется параметризованной командой.
Каждый знак ? представляет параметр, который заменяется значением из
people_values при выполнении метода. Параметры заменяются по порядку.
Иначе говоря, первый знак ? заменяется первым значением в people_values,
второй знак ? заменяется вторым значением и т. д.
Проблемы безопасности с параметризованными
командами
По соображениям безопасности — особенно при взаимодействиях с таблицами SQL, основанными на данных, введенных пользователем, — всегда следует
применять параметризованные команды SQL. Дело в том, что пользователь
теоретически может ввести данные, которые выглядят как код SQL и вызывают
15.1. Знакомство с SQLite 367
неожиданное поведение команд SQL. Это называется атакой внедрения SQL,
причем, возможно, у пользователя нет вредоносных намерений и это происходит абсолютно случайно.
Допустим, вы хотите вставить запись в таблицу People на основании информации, введенной пользователем. Первая попытка может выглядеть примерно так:
import sqlite3
# Получить данные людей от пользователя
first_name = input("Enter your first name: ")
last_name = input("Enter your last name: ")
age = int(input("Enter your age: "))
# Выполнить команды вставки для введенных данных
query = (
"INSERT INTO People Values"
f"('{first_name}', '{last_name}', {age});"
)
with sqlite3.connect("test_database.db") as connection:
cursor = connection.cursor()
cursor.execute(query)
А если имя пользователя содержит апостроф? Попробуйте добавить в таблицу
имя Flannery O'Connor — и вы увидите, что программа перестает работать. Дело
в том, что апостроф — то же самое, что одинарная кавычка, и для программы
все выглядит так, словно код SQL завершается раньше, чем вы предполагали.
В данном случае код только порождает ошибку, что уже достаточно плохо. Однако в некоторых случаях некорректный ввод может привести к повреждению
всей таблицы. Многие другие трудно прогнозируемые случаи могут нарушить
структуру таблицы SQL и даже удалить части базы данных. Чтобы этого не
произошло, всегда используйте параметризованные команды.
В следующем коде параметризованная команда используется для безопасной
вставки пользовательского ввода в базу данных:
import sqlite3
first_name = input("Enter your first name: ")
last_name = input("Enter your last name: ")
age = int(input("Enter your age: "))
data = (first_name, last_name, age)
with sqlite3.connect("test_database.db") as connection:
cursor = connection.cursor()
cursor.execute("INSERT INTO People VALUES(?, ?, ?);", data)
368 Глава 15 Базы данных
Параметризация также пригодится для обновления строки в базе данных
коман­дой SQL UPDATE:
cursor.execute(
"UPDATE People SET Age=? WHERE FirstName=? AND LastName=?;",
(45, 'Luigi', 'Vercotti')
)
Этот код обновляет значение столбца Age значением 45 для строки, в которой
поле FirstName содержит 'Luigi', а поле LastName содержит 'Vercotti'.
Чтение данных
Вставка и обновление информации в базе данных вряд ли принесут пользу, если
вам не удается прочитать информацию из этой базы данных.
Для чтения информации из базы данных можно воспользоваться методами
курсора .fetchone() и .fetchall(). Метод .fetchone() возвращает одну строку
данных из результатов запроса, тогда как .fetchall() читает сразу все результаты запроса.
Следующая программа демонстрирует использование .fetchall():
import sqlite3
values = (
("Ron", "Obvious", 42),
("Luigi", "Vercotti", 43),
("Arthur", "Belling", 28),
)
with sqlite3.connect("test_database.db") as connection:
cursor = connection.cursor()
cursor.execute("DROP TABLE IF EXISTS People")
cursor.execute("""
CREATE TABLE People(
FirstName TEXT,
LastName TEXT,
Age INT
);"""
)
cursor.executemany("INSERT INTO People VALUES(?, ?, ?);", values)
# Выбрать все имена и фамилии людей, возраст которых
# превышает 30 лет
cursor.execute(
"SELECT FirstName, LastName FROM People WHERE Age > 30;"
)
for row in cursor.fetchall():
print(row)
15.2. Библиотеки для работы с другими базами данных SQL 369
В этой программе мы сначала удаляем таблицу People, чтобы уничтожить изменения, внесенные в предыдущих примерах этого раздела. Затем мы заново
создаем таблицу People и вставляем в нее несколько значений. Далее вызовом
.execute() выполняется команда SELECT, которая возвращает имена и фамилии
всех людей, возраст которых превышает 30.
Наконец, .fetchall() возвращает результаты запроса в виде списка кортежей,
в котором каждый кортеж содержит одну строку данных из результатов запроса.
Если ввести программу в новом окне редактора, а затем сохранить и запустить
файл, в интерактивном окне появится следующий вывод:
('Ron', 'Obvious')
('Luigi', 'Vercotti')
Действительно, это единственные люди в базе данных, чей возраст более 30 лет.
Упражнения
1. Создайте новую базу данных, содержащую таблицу Roster. Таблица состоит из трех полей: Name, Species и Age. Столбцы Name и Species должны
быть текстовыми, а столбец Age должен быть целочисленным полем.
2. Заполните созданную таблицу следующими значениями:
NAME
SPECIES
AGE
Benjamin Sisko
Human
40
Jadzia Dax
Trill
300
Kira Nerys
Bajoran
29
3. Обновите поле Name записи Jadzia Dax, чтобы оно содержало значение
Ezri Dax.
4. Выведите значения Name и Age для всех строк данных, у которых поле
Species содержит значение Bajoran.
15.2. БИБЛИОТЕКИ ДЛЯ РАБОТЫ С ДРУГИМИ
БАЗАМИ ДАННЫХ SQL
Если у вас имеется другая база данных SQL, с которой вы бы хотели работать
из кода Python, синтаксис в основном будет идентичен тому, с которым мы вас
только что познакомили для SQLite. Тем не менее вам придется установить
370 Глава 15 Базы данных
дополнительные пакеты для взаимодействия с БД, потому что SQLite — единственное встроенное решение.
Существует много разновидностей SQL с соответствующими пакетами Python.
Несколько наиболее распространенных и надежных альтернатив SQLite с открытым исходным кодом:
zz pyodbc для работы с базами данных ODBC (Open Database Connectivity),
такими как Microsoft SQL Server;
zz psycopg2 для работы с базами данных PostgreSQL;
zz PyMySQL для работы с базами данных MySQL.
Одно из отличий SQLite от других ядер баз данных (не считая синтаксиса кода
SQL, который незначительно различается в разных «диалектах» SQL) заключается в том, что многие ядра баз данных требуют имени пользователя и пароля
для подключения. За информацией о синтаксисе подключения к базе данных
обращайтесь к документации конкретного пакета.
Пакет SQLAlchemy — еще один популярный инструмент для работы с базами
данных. SQLAlchemy является системой объектно-реляционного отображения
(Object-Relational Mapper — ORM); такие системы используют объектно-ориентированную парадигму для построения запросов к базам данных. Их можно
настроить для подключения к разным базам данных. Объектно-ориентированный подход позволяет создавать запросы без написания низкоуровневых
команд SQL.
15.3. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе я показал, как взаимодействовать с базами данных ядра SQLite,
которое входит в поставку Python. SQLite — компактная и облегченная система
управления базами данных SQL, которая предназначена для хранения и выборки данных в программах Python. Для взаимодействия с SQLite в Python
необходимо импортировать модуль sqlite3.
Чтобы работать с базой данных SQLite, следует сначала подключиться к существующей БД или создать новую БД функцией sqlite3.connect(), которая возвращает объект Connection. После этого можно использовать метод connection.
cursor() для получения нового объекта Cursor.
Объекты Cursor предназначены для выполнения команд SQL и получения
результатов запросов. Например, cursor.execute() и cursor.executescript()
15.3. Итоги и дополнительные ресурсы 371
используются для выполнения запросов SQL. Для получения результатов запросов можно применять методы cursor.fetchone() и cursor.fetchall().
Кроме того, мы рассказали о нескольких сторонних пакетах для подключения
к другим базам данных SQL, включая пакет psycopg2 для подключения к базам данных PostgreSQL и pyodbc для Microsoft SQL Server. Вы также узнали
о библиотеке SQLAlchemy, предоставляющей стандартный интерфейс для подключения к различным базам данных SQL.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-databases
Дополнительные ресурсы
За дополнительной информацией о работе с базами данных обращайтесь к следующим ресурсам:
zz
«Introduction to Python SQL Libraries» (https://realpython.com/pythonsql-libraries/)
zz
«Preventing SQL Injection Attacks With Python» (https://realpython.com/
prevent-python-sql-injection/)
ГЛАВА 16
Веб-программирование
Пожалуй, интернет стал самым большим источником информации (и дезинформации) на планете.
Многие дисциплины: теория обработки данных, бизнес-аналитика, расследовательская журналистика — могут извлечь колоссальную пользу из сбора
и анализа данных с веб-сайтов.
Веб-скрапингом (web scraping) называется процесс сбора и обработки низко­
уровневых данных с веб-сайтов. В сообществе Python были разработаны достаточно мощные средства извлечения веб-данных.
В этой главе вы научитесь:
zz
парсить данные с веб-сайтов с помощью строковых методов и регулярных
выражений;
zz
парсить данные с веб-сайтов с помощью парсера HTML;
zz
взаимодействовать с формами и другими компонентами веб-сайтов.
ВАЖНО!
При чтении этой главы желательно иметь некоторый опыт работы с HTML.
Итак, за дело!
16.1. СКРАПИНГ И ПАРСИНГ ТЕКСТА
С ВЕБ-САЙТОВ
Автоматизированный сбор данных с веб-сайтов называется веб-скрапингом.
Некоторые веб-сайты явно запрещают пользователям извлекать данные с применением автоматизированных средств вроде тех, которые мы будем создавать
в этой главе. На то есть две возможные причины:
16.1. Скрапинг и парсинг текста с веб-сайтов 373
1. У сайта есть веские основания для защиты данных. Например, Google
Maps не позволяет слишком быстро запрашивать слишком много результатов.
2. Лавина повторных запросов к веб-серверу может забивать полосу пропускания, замедляя работу сайта для других пользователей, возможно —
с созданием избыточной нагрузки на сервер, так что сайт полностью
перестает реагировать на запросы.
ВАЖНО!
Прежде чем заниматься скрапингом, всегда проверяйте правила и условия использования веб-сайта, чтобы узнать, не нарушает ли этих правил обращение
к сайту с помощью автоматизированных инструментов. С юридической точки
зрения веб-скрапинг не имеет однозначной трактовки. Но, пожалуйста, учтите,
что описанные ниже методы могут оказаться незаконными применительно
к веб-сайтам, запрещающим скрапинг.
Попробуем извлечь HTML-код с одной веб-страницы. Мы будем использовать
страницу на сайте Real Python, созданную специально для этой главы.
Ваша первая программа для веб-скрапинга
В стандартную библиотеку Python входит пакет urllib, содержащий средства для работы с URL-адресами. В частности, urllib.requestmodule содержит функцию urlopen() , которая служит для открытия URL-адреса
в про­грамме.
В интерактивном окне IDLE введите следующую команду, чтобы импортировать urlopen():
>>> from urllib.request import urlopen
Веб-страница, которую мы будем открывать, доступна по URL-адресу:
>>> url = "http://olympus.realpython.org/profiles/aphrodite"
Чтобы открыть веб-страницу, передайте url функции urlopen():
>>> page = urlopen(url)
urlopen() возвращает объект HTTPResponse:
>>> page
<http.client.HTTPResponse object at 0x105fef820>
374 Глава 16 Веб-программирование
Чтобы извлечь HTML-код страницы, сначала вызовите метод .read() объекта
HTTPResponse, который возвращает последовательность байтов. Затем вызовите
метод .decode() для декодирования байтов в строку в кодировке UTF-8:
>>> html_bytes = page.read()
>>> html = html_bytes.decode("utf-8")
Далее выведите HTML-код, чтобы просмотреть содержимое веб-страницы:
>>> print(html)
<html>
<head>
<title>Profile: Aphrodite</title>
</head>
<body bgcolor="yellow">
<center>
<br><br>
<img src="/static/aphrodite.gif" />
<h2>Name: Aphrodite</h2>
<br><br>
Favorite animal: Dove
<br><br>
Favorite color: Red
<br><br>
Hometown: Mount Olympus
</center>
</body>
</html>
После получения HTML-кода в текстовом виде вы сможете извлечь из него
информацию несколькими способами.
Извлечение текста из HTML строковыми методами
Один из способов получения информации из HTML-кода веб-страницы основан
на использовании строковых методов. Например, можно при помощи метода
.find() провести поиск тегов <title> по тексту HTML и извлечь заголовок
веб-страницы.
Извлечем заголовок веб-страницы, запрошенной в предыдущем примере.
Если вам известны индексы первого символа заголовка и первого символа
закрывающего тега </title>, вы можете воспользоваться срезом строки для
извлечения заголовка.
Так как .find() возвращает индекс первого вхождения подстроки, вы можете
получить индекс открывающего тега <title>, передавая строку "<title>" при
вызове .find():
16.1. Скрапинг и парсинг текста с веб-сайтов 375
>>> title_index = html.find("<title>")
>>> title_index
14
Впрочем, нам нужен не индекс тега <title>, а индекс самого заголовка. Чтобы
получить индекс первой буквы в заголовке, следует прибавить длину строки
"<title>" к title_index:
>>> start_index = title_index + len("<title>")
>>> start_index
21
Теперь получим индекс закрывающего тега </title>, передавая строку "</
title>" методу .find():
>>> end_index = html.find("</title>")
>>> end_index
39
Наконец, текст заголовка извлекается с помощью среза строки html:
>>> title = html[start_index:end_index]
>>> title
'Profile: Aphrodite'
Реальная разметка HTML может быть куда более сложной и куда менее предсказуемой, чем разметка страницы профиля Aphrodite. Другая страница профиля
с менее тривиальной разметкой HTML доступна для ваших экспериментов по
адресу http://olympus.realpython.org/profiles/poseidon.
Попробуйте извлечь заголовок по этому новому URL-адресу таким же способом,
как в предыдущем примере:
>>> url = "http://olympus.realpython.org/profiles/poseidon"
>>> page = urlopen(url)
>>> html = page.read().decode("utf-8")
>>> start_index = html.find("<title>") + len("<title>")
>>> end_index = html.find("</title>")
>>> title = html[start_index:end_index]
>>> title
'\n<head>\n<title >Profile: Poseidon'
Сюрприз! С заголовком выдается некоторое количество HTML-кода. Почему?
HTML-разметка страницы /profiles/poseidon похожа на /profiles/aphrodite,
но есть небольшое различие. В открывающем теге <title> перед закрывающей
угловой скобкой > стоит дополнительный пробел, в результате чего тег отображается в виде <title >.
376 Глава 16 Веб-программирование
html.find("<title>") возвращает -1, потому что точная подстрока "<title>"
не существует. Когда –1 прибавляется к результату len("<title>"), который
равен 7, переменной start_index присваивается значение 6.
Символом с индексом 6 строки html является символ новой строки \n, непосредственно предшествующей открывающей угловой скобке < тега <head>. Это
означает, что выражение html[start_index:end_index] вернет всю разметку
HTML от символа новой строки до позиции, предшествующей тегу </title>.
При обработке HTML могут возникать бесчисленные и весьма непредсказуемые
проблемы такого рода. Нам понадобится более надежный механизм извлечения
текста из HTML.
Знакомство с регулярными выражениями
Регулярные выражения — шаблоны, которые применяются для поиска текста
в строках. Поддержка регулярных выражений в Python осуществляется через
модуль re стандартной библиотеки.
ПРИМЕЧАНИЕ
Регулярные выражения поддерживаются не только в Python. Это общая концепция программирования, которая может использоваться в любом языке.
Чтобы работать с регулярными выражениями, прежде всего необходимо импортировать модуль re:
import re
В регулярных выражениях для обозначения элементов шаблона используются
специальные символы, называемые метасимволами. Например, звездочка *
обозначает нуль или более вхождений символа, находящегося непосредственно
перед звездочкой.
В следующем примере функция findall() используется для поиска в строке
любого текста, соответствующего заданному регулярному выражению:
>>> re.findall("ab*c", "ac")
['ac']
В первом аргументе re.findall() передается регулярное выражение, для которого ищется совпадение, а во втором — проверяемая строка. В приведенном
примере ищется совпадение для шаблона "ab*c" в строке "ac".
16.1. Скрапинг и парсинг текста с веб-сайтов 377
Регулярное выражение "ab*c" совпадает с любой частью строки, которая начинается с "a" и завершается "c", а между ними содержится нуль или несколько
вхождений "b". re.findall() возвращает список всех совпадений. Строка "ac"
совпадает с этим шаблоном, поэтому она возвращается в списке.
Несколько примеров применения того же шаблона к разным строкам:
>>> re.findall("ab*c", "abcd")
['abc']
>>> re.findall("ab*c", "acc")
['ac']
>>> re.findall("ab*c", "abcac")
['abc', 'ac']
>>> re.findall("ab*c", "abdc")
[]
Обратите внимание: если ни одного совпадения не найдено, то findall() возвращает пустой список.
Поиск по шаблону производится с учетом регистра. Если вы хотите искать
совпадение независимо от регистра, передайте третий аргумент со значением
re.IGNORECASE:
>>> re.findall("ab*c", "ABC")
[]
>>> re.findall("ab*c", "ABC", re.IGNORECASE)
['ABC']
Точка обозначает один произвольный символ в регулярном выражении. Например, поиск всех строк, которые содержат буквы "a" и "c", разделенные одним
символом, выполняется так:
>>> re.findall("a.c", "abc")
['abc']
>>> re.findall("a.c", "abbc")
[]
>>> re.findall("a.c", "ac")
[]
>>> re.findall("a.c", "acc")
['acc']
Шаблон .* в регулярном выражении обозначает произвольный символ, повторенный любое количество раз. Например, "a.*c" можно использовать для поиска
378 Глава 16 Веб-программирование
любой подстроки, начинающейся с "a" и заканчивающейся "c", независимо от
того, какая буква — или какие буквы — находятся между ними:
>>> re.findall("a.*c", "abc")
['abc']
>>> re.findall("a.*c", "abbc")
['abbc']
>>> re.findall("a.*c", "ac")
['ac']
>>> re.findall("a.*c", "acc")
['acc']
Обычно для поиска совпадения конкретного шаблона в строке используется
функция re.search(). Она несколько сложнее re.findall(), потому что возвращает объект MatchObject, в котором хранятся разные группы данных. Дело
в том, что совпадения могут быть найдены внутри других совпадений, и re.
search() возвращает все возможные результаты.
Подробности строения MatchObject сейчас несущественны. Пока вам достаточно знать, что вызов .group() для MatchObject возвращает первый результат
с наибольшим охватом; в большинстве случаев это именно то, что вам нужно:
>>> match_results = re.search("ab*c", "ABC", re.IGNORECASE)
>>> match_results.group()
'ABC'
В модуле re реализована еще одна функция, которая пригодится для парсинга
текста. Функция re.sub() (сокращение от substitute — подстановка) позволяет
заменить текст в строке, совпадающий с регулярным выражением, новым текстом. Своим поведением она напоминает строковый метод .replace(), о котором
шла речь в главе 4. В аргументах re.sub() передается регулярное выражение,
затем текст замены и исходная строка. Пример:
>>> string = "Everything is <replaced> if it's in <tags>."
>>> string = re.sub("<.*>", "ELEPHANTS", string)
>>> string
'Everything is ELEPHANTS.'
Вероятно, не совсем то, что вы ожидали?
re.sub() использует регулярное выражение "<.*>" для поиска и замены всего
текста от первого символа < до последнего символа >, то есть всего текста от начала <replaced> до конца <tags>. Это объясняется тем, что регулярные выражения
16.1. Скрапинг и парсинг текста с веб-сайтов 379
Python работают по максимальному принципу — при использовании таких
символов, как *, они пытаются найти самое длинное возможное совпадение.
Также можно воспользоваться минимальным шаблоном *?. Он работает точно
так же, как *, не считая того, что он находит самую короткую из возможных
строк текста:
>>> string = "Everything is <replaced> if it's in <tags>."
>>> string = re.sub("<.*?>", "ELEPHANTS", string)
>>> string
"Everything is ELEPHANTS if it's in ELEPHANTS."
Извлечение текста из HTML с использованием
регулярных выражений
Вооружившись этой информацией, попробуем извлечь заголовок из разметки
http://olympus.realpython.org/profiles/dionysus, которая включает неаккуратно
записанную строку HTML:
<TITLE >Profile: Dionysus</title / >
У метода .find() здесь возникли бы сложности, но при умном использовании
регулярных выражений можно обработать такую разметку быстро и эффективно:
import re
from urllib.request import urlopen
url = "http://olympus.realpython.org/profiles/dionysus"
page = urlopen(url)
html = page.read().decode("utf-8")
pattern = "<title.*?>.*?</title.*?>"
match_results = re.search(pattern, html, re.IGNORECASE)
title = match_results.group()
title = re.sub("<.*?>", "", title) # Удалить теги HTML
print(title)
Давайте детальнее рассмотрим первое регулярное выражение в строке шаблона
и разобьем его на три части.
1. <title.*?> совпадает с открывающим тегом <TITLE > в html. Часть <title
совпадает с <TITLE, потому что re.search() вызывается со значением
re.IGNORECASE, а *?> совпадает со всем текстом после <TITLE вплоть до
первого вхождения >.
2. .*? минимально совпадает со всем текстом после открывающего тега
<TITLE >, останавливаясь на первом совпадении </title.*?>.
380 Глава 16 Веб-программирование
3. </title.*?> отличается от первого шаблона только использованием
символа /, так что он совпадает с закрывающим тегом </title / > в html.
Второе регулярное выражение, строка "<.*?>", также использует минимальное
выражение .*? для поиска всех тегов HTML в строке заголовка. Заменяя любые
совпадения пустой строкой "", re.sub() удаляет теги и возвращает только текст.
При правильном использовании регулярные выражения становятся очень
мощным инструментом. Та информация, с которой мы познакомили вас сейчас,
едва ли дает даже первое представление об их возможностях. За дополнительными сведениями о регулярных выражениях и об их использовании обращайтесь к курсу из двух частей «Regular Expressions: Regexes in Python» (https://
realpython.com/regex-python/) на сайте Real Python.
ПРИМЕЧАНИЕ
Веб-скрапинг может оказаться утомительной и монотонной работой. Каждый
сайт не похож на другие, и разметка HTML часто неидеальна. Более того, сайты
изменяются со временем. Если программа веб-скрапинга работает сегодня,
это еще не значит, что она будет работать через год — или даже через неделю,
если на то пошло!
Упражнения
1. Напишите программу, которая извлекает весь код HTML веб-страницы
по адресу http://olympus.realpython.org/profiles/dionysus.
2. Используйте строковый метод .find() для вывода текста, следующего за
Name: и Favorite Color: (не включая начальные пробелы или завершающие
теги HTML, которые могут присутствовать в той же строке).
3. Повторите предыдущее упражнение с регулярными выражениями. В конце
каждого шаблона следует поставить знак < (начало тега HTML) или символ
новой строки, а из полученного текста надо убрать все лишние пробелы
и символы новой строки при помощи строкового метода .strip().
16.2. ИСПОЛЬЗОВАНИЕ ПАРСЕРА HTML
ДЛЯ ИЗВЛЕЧЕНИЯ ВЕБ-ДАННЫХ
Хотя регулярные выражения отлично подходят для поиска по шаблону, иногда
проще использовать парсер HTML, специально разработанный для разбора
16.2. Использование парсера HTML для извлечения веб-данных 381
страниц HTML. Для этой цели было написано много инструментов Python, но
начать стоит с библиотеки Beautiful Soup.
Установка библиотеки Beautiful Soup
Чтобы установить Beautiful Soup, выполните следующую команду в своем
терминале:
$ python3 -m pip install beautifulsoup4
Выполните команду pip show, чтобы просмотреть подробную информацию
о только что установленном пакете:
$ python3 -m pip show beautifulsoup4
Name: beautifulsoup4
Version: 4.9.1
Summary: Screen-scraping library
Home-page: http://www.crummy.com/software/BeautifulSoup/bs4/
Author: Leonard Richardson
Author-email: leonardr@segfault.org
License: MIT
Location: c:\realpython\venv\lib\site-packages
Requires:
Required-by:
В частности, обратите внимание, что последней версией на момент написания
книги была версия 4.9.1.
Создание объекта BeautifulSoup
Введите следующую программу в новом окне редактора:
from bs4 import BeautifulSoup
from urllib.request import urlopen
url = "http://olympus.realpython.org/profiles/dionysus"
page = urlopen(url)
html = page.read().decode("utf-8")
soup = BeautifulSoup(html, "html.parser")
Программа выполняет три операции.
1. Открывает URL http://olympus.realpython.org/profiles/dionysus при помощи функции urlopen() из модуля urllib.request.
2. Читает HTML-код страницы в виде строки и присваивает ее переменной
html.
3. Создает объект BeautifulSoup и присваивает его переменной soup.
382 Глава 16 Веб-программирование
Объект BeautifulSoup, присвоенный переменной soup, создается с двумя аргументами. В первом аргументе передается разбираемая разметка HTML, а второй,
строка "html.parser", сообщает объекту, какой парсер должен использоваться
во внутренней реализации. "html.parser" — это встроенный парсер HTML
языка Python.
Использование объекта BeautifulSoup
Сохраните и запустите приведенную выше программу. Когда она завершит
работу, вы сможете использовать переменную soup в интерактивном окне для
парсинга содержимого html.
Например, объекты BeautifulSoup содержат метод .get_text(), который мы
используем для извлечения всего текста из документа и автоматического удаления всех тегов HTML.
Введите следующий код в интерактивном окне IDLE:
>>> print(soup.get_text())
Profile: Dionysus
Name: Dionysus
Hometown: Mount Olympus
Favorite animal: Leopard
Favorite Color: Wine
В выводе много пустых строк! Они появились из-за символов новой строки
в тексте документа HTML. При необходимости вы можете удалить их строковым методом .replace().
Часто из документа HTML требуется получить только конкретный текст.
Использование Beautiful Soup для извлечения текста и последующий вызов
строкового метода .find() иногда оказываются проще работы с регулярными
выражениями.
Тем не менее иногда сами теги HTML являются элементами, указывающими
на данные, которые требуется извлечь. Допустим, вы хотите получить URLадреса всех изображений на странице. Эти ссылки содержатся в атрибуте src
тегов HTML <img>.
16.2. Использование парсера HTML для извлечения веб-данных 383
В таком случае можно воспользоваться функцией find_all(), возвращающей
список всех экземпляров этого конкретного тега:
>>> soup.find_all("img")
[<img src="/static/dionysus.jpg"/>, <img src="/static/grapes.png"/>]
Функция возвращает список всех тегов <img> в HTML-документе. Может показаться, что объекты в списке являются строками, представляющими теги, но
в действительности это экземпляры класса Tag из библиотеки Beautiful Soup.
Объекты Tag предлагают простой интерфейс для работы с содержащейся в них
информацией.
Чтобы немного исследовать этот способ, для начала распакуем объекты Tag из
списка:
>>> image1, image2 = soup.find_all("img")
Каждый объект Tag содержит свойство .name, которое возвращает строку с типом тега HTML:
>>> image1.name
'img'
Чтобы обратиться к атрибутам HTML объекта Tag, укажите имя атрибута в квадратных скобках (так, как если бы атрибуты были ключами словаря).
Например, тег <img src="/static/dionysus.jpg"/> содержит единственный атрибут src со значением "/static/dionysus.jpg". А тег ссылки <a href="https://
realpython.com" target="_blank"> содержит два атрибута, href и target.
Чтобы получить источники изображений на странице профиля Dionysus, вы
обращаетесь к атрибуту src в синтаксисе словарей, упоминавшемся выше:
>>> image1["src"]
'/static/dionysus.jpg'
>>> image2["src"]
'/static/grapes.png'
К некоторым тегам в документах HTML можно обращаться по именам свойств
объектов Tag. Например, для получения тега <title> в документе можно воспользоваться свойством .title:
>>> soup.title
<title>Profile: Dionysus</title>
384 Глава 16 Веб-программирование
Обратившись к исходному коду профиля Dionysus (перейдите по адресу http://
olympus.realpython.org/profiles/dionysus, щелкните правой кнопкой мыши на
странице и выберите команду просмотра исходного кода страницы), вы увидите,
что тег <title> записан в документе в следующем виде:
<title >Profile: Dionysus</title/>
Beautiful Soup автоматически чистит теги, удаляя лишние пробелы в открывающем теге и лишнюю косую черту / в закрывающем теге.
Также можно получить только строку, заключенную между тегами заголовка,
из свойства .string объекта Tag:
>>> soup.title.string
'Profile: Dionysus'
Одна из самых полезных возможностей Beautiful Soup — поиск конкретных
разновидностей тегов, атрибуты которых соответствуют определенным значениям. Например, если вы хотите найти все теги <img>, у которых атрибут src
равен значению /static/dionysus.jpg, передайте .find_all() дополнительный
аргумент:
>>> soup.find_all("img", src="/static/dionysus.jpg")
[<img src="/static/dionysus.jpg"/>]
Пример выглядит искусственно, и, возможно, полезность этого приема сразу
не очевидна. Если вы потратите некоторое время, чтобы помониторить вебсайты и их исходный код, вы заметите, что многие из них имеют чрезвычайно
сложную структуру HTML.
При веб-скрапинге нас часто интересуют конкретные фрагменты страницы.
Потратив немного времени на просмотр документа HTML, вы сможете идентифицировать теги с уникальными атрибутами, которые годятся для извлечения
нужных данных.
И тогда вместо того, чтобы полагаться на сложные регулярные выражения или
проводить поиск по документу с .find(), вы сможете напрямую обращаться
к конкретному интересующему вас тегу и извлекать нужные данные.
В некоторых случаях Beautiful Soup не предоставляет нужной функциональности. Библиотекой lxml (https://lxml.de) труднее пользоваться на первых порах, но она обладает большей гибкостью при разборе документов HTML, чем
Beautiful Soup. Возможно, вам стоит ознакомиться с ее возможностями, когда
вы будете уверенно чувствовать себя с Beautiful Soup.
16.3. Работа с HTML-формами 385
ПРИМЕЧАНИЕ
Парсеры HTML — такие, как Beautiful Soup, — сэкономят вам немало времени
и усилий при поиске конкретных данных на веб-страницах. Тем не менее иногда HTML-код написан настолько плохо, что даже сложный парсер (например,
Beautiful Soup) не может правильно интерпретировать теги HTML.
В таких случаях обычно приходится извлекать необходимую информацию
самостоятельно (то есть с использованием .find() и регулярных выражений).
Упражнения
1. Напишите программу, которая извлекает весь HTML-код веб-страницы
http://olympus.realpython.org/profiles.
2. Используя Beautiful Soup, выделите список всех ссылок на странице —
проведите поиск тегов HTML с именем a и получите значение атрибута
href для каждого тега.
3. Извлеките HTML-код каждой страницы в списке — добавьте полный
путь к файлу и выведите текст (без тегов HTML) на каждой странице,
используя метод .get_text() из библиотеки Beautiful Soup.
16.3. РАБОТА С HTML-ФОРМАМИ
Модуль urllib, с которым вы работали ранее в этой главе, хорош для запроса содержимого веб-страницы. Однако в некоторых случаях получение нужного контента требует взаимодействия с веб-страницей. Например, пользователь должен
отправить форму или щелкнуть на кнопке, чтобы отобразить скрытое содержимое.
В стандартной библиотеке Python нет встроенных средств для интерактивной
работы с веб-страницами, но в PyPI доступны многочисленные сторонние пакеты. К их числу принадлежит пакет MechanicalSoup (https://github.com/hickford/
MechanicalSoup) — популярный и достаточно простой в использовании.
В сущности, MechanicalSoup устанавливает консольный браузер, то есть браузер
без графического интерфейса. Работой браузера можно управлять на программном уровне из программы Python.
Установка MechanicalSoup
MechanicalSoup можно установить при помощи pip в терминале:
$ python3 -m pip install MechanicalSoup
386 Глава 16 Веб-программирование
Команда pip show выводит более подробную информацию о пакете:
$ python3 -m pip show mechanicalsoup
Name: MechanicalSoup
Version: 0.12.0
Summary: A Python library for automating interaction with websites
Home-page: https://mechanicalsoup.readthedocs.io/
Author: UNKNOWN
Author-email: UNKNOWN
License: MIT
Location: c:\realpython\venv\lib\site-packages
Requires: requests, beautifulsoup4, six, lxml
Required-by:
В частности, обратите внимание, что на момент написания книги последняя
версия — 0.12.0. Чтобы библиотека MechanicalSoup была загружена и опознана
после установки, необходимо закрыть и перезапустить сеанс IDLE.
Создание объекта Browser
Введите следующий фрагмент в интерактивном окне IDLE:
>>> import mechanicalsoup
>>> browser = mechanicalsoup.Browser()
Объекты Browser представляют консольный браузер. Их используют для запроса страницы из интернета, для чего URL-адрес передается методу .get():
>>> url = "http://olympus.realpython.org/login"
>>> page = browser.get(url)
Переменной page присваивается объект Response, в котором хранится ответ,
полученный от браузера на запрос URL-адреса:
>>> page
<Response [200]>
Число 200 представляет код состояния, возвращенный запросом. Код состояния
200 означает, что запрос был успешным. Неуспешный запрос может вернуть
код состояния 404, если URL не существует, или код состояния 500, если при
обработке запроса произошла ошибка на сервере.
MechanicalSoup использует Beautiful Soup для парсинга HTML-кода из запроса. Переменная page содержит атрибут .soup , представляющий объект
BeautifulSoup:
16.3. Работа с HTML-формами 387
>>> type(page.soup)
<class 'bs4.BeautifulSoup'>
Чтобы просмотреть HTML-код, проверьте атрибут .soup:
>>> page.soup
<html>
<head>
<title>Log In</title>
</head>
<body bgcolor="yellow">
<center>
<br/><br/>
<h2>Please log in to access Mount Olympus:</h2>
<br/><br/>
<form action="/login" method="post" name="login">
Username: <input name="user" type="text"/><br/>
Password: <input name="pwd" type="password"/><br/><br/>
<input type="submit" value="Submit"/>
</form>
</center>
</body>
</html>
Обратите внимание: страница содержит тег <form> с элементами <input> для
ввода имени пользователя и пароля.
Отправка данных формы
с помощью MechanicalSoup
Откройте страницу http://olympus.realpython.org/login в браузере и просмотрите
ее, прежде чем двигаться дальше. Попробуйте ввести случайное имя пользователя и пароль. Если данные были указаны неправильно, в нижней части страницы
выводится сообщение «Wrong username or password!».
Но если задать правильные данные (имя пользователя zeus и пароль
ThunderDude), то вы будете перенаправлены на страницу /profiles.
Следующий пример показывает, как использовать MechanicalSoup для заполнения и отправки этой формы средствами Python.
Важной частью HTML-кода является форма ввода регистрационных данных — то есть все, что находится внутри тегов <form>. Тег <form> на странице
содержит атрибут name, которому присвоено значение login. Форма содержит
два элемента <input> с именами user и pwd. Третий элемент <input> определяет
кнопку отправки данных Submit.
388 Глава 16 Веб-программирование
Итак, вы знаете структуру формы авторизации, а также данные, которые в нее
следует ввести. Теперь рассмотрим программу, которая заполняет форму и отправляет ее.
Введите в новом окне редактора следующий код:
import mechanicalsoup
# 1
browser = mechanicalsoup.Browser()
url = "http://olympus.realpython.org/login"
login_page = browser.get(url)
login_html = login_page.soup
# 2
form = login_html.select("form")[0]
form.select("input")[0]["value"] = "zeus"
form.select("input")[1]["value"] = "ThunderDude"
# 3
profiles_page = browser.submit(form, login_page.url)
Сохраните файл и нажмите F5, чтобы запустить его. Чтобы убедиться в том, что
данные были успешно приняты, введите следующую команду в интерактивном
окне:
>>> profiles_page.url
'http://olympus.realpython.org/profiles'
Разобьем приведенный выше пример на несколько этапов.
1. Создается экземпляр Browser, который далее используется для запроса
страницы http://olympus.realpython.org/login. HTML-код страницы присваивается переменной login_html с использованием свойства .soup.
2. login_html.select("form") возвращает список всех элементов <form> на
странице. Так как страница содержит только один элемент <form>, к форме
можно обратиться через элемент списка с индексом 0. Следующие две
строки выбирают поля для имени пользователя и пароля, а также задают
им значения "zeus" и "ThunderDude" соответственно.
3. Форма отправляется вызовом browser.submit(). Методу передаются два
аргумента: объект формы и URL-адрес страницы login_page, к которому
вы обращаетесь через login_page.url.
В интерактивном окне убедитесь, что при отправке данных успешно происходит
перенаправление на страницу /profiles. Если что-то пошло не так, то значение
profiles_page.url останется прежним — "http://olympus.realpython.org/login".
16.3. Работа с HTML-формами 389
ПРИМЕЧАНИЕ
Хакеры могут использовать подобные автоматизированные программы для
подбора паролей методом полного перебора (брутфорса, brute force): программа быстро проверяет разные комбинации имени и пароля, пока не найдет
работающий вариант.
Имейте в виду, что это считается серьезным правонарушением — обнаружив,
что от вас поступает слишком много запросов, почти все современные сайты
блокируют вам доступ и сообщают ваш IP-адрес надзорным органам. Даже
не пытайтесь!
Итак, значение переменной profiles_page задано. Посмотрим, как на программном уровне получить URL-адрес каждой ссылки на странице /profiles.
Для этого снова используем метод .select(), но на этот раз передадим строку
"a" для выбора всех якорных элементов <a> на странице:
>>> links = profiles_page.soup.select("a")
Далее можно перебрать все ссылки и вывести их атрибут href:
>>> for link in links:
...
address = link["href"]
...
text = link.text
...
print(f"{text}: {address}")
...
Aphrodite: /profiles/aphrodite
Poseidon: /profiles/poseidon
Dionysus: /profiles/dionysus
URL-адреса, содержащиеся в атрибутах href, являются относительными; от
них не будет пользы, если вы захотите перейти к ним позднее при помощи
MechanicalSoup.
Если вам известен полный URL-адрес, то из него можно выделить часть,
необходимую для построения абсолютного адреса. В нашем случае базовый URL-адрес — http://olympus.realpython.org. После этого можно выполнить ­конкатенацию базового URL-адреса с относительным URL из атрибута
href:
>>> base_url = "http://olympus.realpython.org"
>>> for link in links:
...
address = base_url + link["href"]
...
text = link.text
...
print(f"{text}: {address}")
390 Глава 16 Веб-программирование
...
Aphrodite: http://olympus.realpython.org/profiles/aphrodite
Poseidon: http://olympus.realpython.org/profiles/poseidon
Dionysus: http://olympus.realpython.org/profiles/dionysus
Используя только .get(), .select() и .submit(), можно сделать много полезного. Но библиотека MechanicalSoup способна на гораздо большее. За дополнительной информацией о MechanicalSoup обращайтесь к официальной
документации (https://mechanicalsoup.readthedocs.io/en/stable/).
Упражнения
1. Используйте MechanicalSoup для предоставления правильного имени
пользователя ("zeus") и пароля ("ThunderDude") форме авторизации,
находящейся по адресу http://olympus.realpython.org/login.
2. Выведите заголовок текущей страницы, чтобы убедиться, что вы были
перенаправлены на страницу /profiles.
3. Используйте MechanicalSoup для возвращения к странице входа (вернитесь к предыдущей странице).
4. Введите в форме авторизации неправильное имя пользователя и пароль,
а затем найдите в HTML-коде возвращенной веб-страницы текст «Wrong
username or password!», чтобы убедиться в том, что попытка входа завершилась неудачей.
16.4. ВЗАИМОДЕЙСТВИЕ С ВЕБ-САЙТАМИ
В РЕАЛЬНОМ ВРЕМЕНИ
Иногда возникает необходимость получить данные в реальном времени с сайта,
где информация постоянно обновляется.
В те времена, когда вы еще не начинали изучать программирование на языке
Python, вам пришлось бы сидеть перед браузером и постоянно обновлять страницу, чтобы узнать, не появился ли обновленный контент. Но теперь процесс
можно автоматизировать методом .get() объекта MechanicalSoup Browser.
Откройте браузер и перейдите на http://olympus.realpython.org/dice. Страница
моделирует бросок шестигранного кубика, результат обновляется при каждом
обновлении браузера. Мы напишем программу, которая непрерывно занимается
скрапингом страницы, чтобы извлечь новый результат.
16.4. Взаимодействие с веб-сайтами в реальном времени 391
Прежде всего необходимо определить, какой элемент страницы содержит
результат броска. Щелкните правой кнопкой мыши в любой точке страницы
и выберите команду просмотра исходного кода страницы. Где-то в середине
HTML-кода располагается тег <h2>, который выглядит так:
<h2 id="result">4</h2>
У вас текст тега <h2> может быть другим, и все же именно этот элемент страницы
понадобится для извлечения результата.
ПРИМЕЧАНИЕ
В этом примере можно легко убедиться, что на странице существует всего
один элемент с id="result". Хотя предполагается, что атрибут id уникален, на
практике всегда следует проверять, что интересующий вас элемент идентифицируется однозначно.
Для начала напишем простую программу, которая открывает страницу /dice,
извлекает результат и выводит его на консоль:
import mechanicalsoup
browser = mechanicalsoup.Browser()
page = browser.get("http://olympus.realpython.org/dice")
tag = page.soup.select("#result")[0]
result = tag.text
print(f"The result of your dice roll is: {result}")
В этом примере метод .select() объекта BeautifulSoup используется для поиска
элемента с id=result. Строка "#result", передаваемая .select(), использует
селектор CSS ID для обозначения того, что result является значением id.
Чтобы периодически получать новый результат, необходимо создать цикл,
загружающий страницу при каждой итерации. Таким образом, фрагмент под
строкой browser = mechanicalsoup.Browser() в приведенном выше коде должен
перейти в тело цикла.
Наш пример будет получать результаты четырех бросков с десятисекундными
интервалами. Для этого последняя строка кода должна дать команду Python
приостановить выполнение на 10 секунд. Задача решается функцией sleep()
из модуля Python time. Она получает один аргумент, представляющий продолжительность паузы в секундах.
392 Глава 16 Веб-программирование
Следующий пример показывает, как работает sleep():
import time
print("I'm about to wait for five seconds...")
time.sleep(5)
print("Done waiting!")
При выполнении этого кода сообщение "Done waiting!" появится только через
пять секунд после выполнения первой функции print().
В примере с броском кубика необходимо передать sleep() число 10. Обновленная версия программы:
import time
import mechanicalsoup
browser = mechanicalsoup.Browser()
for i in range(4):
page = browser.get("http://olympus.realpython.org/dice")
tag = page.soup.select("#result")[0]
result = tag.text
print(f"The result of your dice roll is: {result}")
time.sleep(10)
При запуске программы на консоли немедленно появляется первый результат.
Через 10 секунд выводится второй результат, потом третий и, наконец, четвертый. Что произойдет после вывода четвертого результата?
Программа продолжит работать еще 10 секунд до того, как завершить работу!
Конечно, так и должно быть — ведь вы сами ей это приказали! Тем не менее лишняя пауза только расходует время. Чтобы этого не происходило, воспользуйтесь
командой if для выполнения time.sleep() только для первых трех запросов:
import time
import mechanicalsoup
browser = mechanicalsoup.Browser()
for i in range(4):
page = browser.get("http://olympus.realpython.org/dice")
tag = page.soup.select("#result")[0]
result = tag.text
print(f"The result of your dice roll is: {result}")
# Подождать 10 секунд, если это не последний запрос
if i < 3:
time.sleep(10)
16.5. Итоги и дополнительные ресурсы 393
Применяя подобные приемы, можно извлекать информацию с веб-сайтов, периодически обновляющих свои данные. Тем не менее следует помнить, что запрос
страницы несколько раз подряд за короткий промежуток времени может рассматриваться как подозрительное и даже злонамеренное использование сайта.
ВАЖНО!
Многие сайты публикуют документ с условиями использования (Terms of Use).
Часто ссылка на него размещается в подвале главной страницы сайта.
Всегда читайте этот документ, прежде чем пытаться извлекать данные. Если вы
не нашли условий использования, попробуйте связаться с владельцем сайта
и узнать, нет ли у него правил, касающихся частых запросов.
Несоблюдение условий использования может привести к блокировке вашего
IP-адреса, так что будьте внимательны и уважайте права других!
Чрезмерное количество запросов иногда вызывает даже сбой сервера. Понятно,
почему так много сайтов обращает внимание на массовые запросы к своему
серверу! Всегда проверяйте условия использования и уважительно относитесь
к ним, если решите бомбардировать сайт запросами.
Упражнение
Повторите пример из этого раздела с извлечением результата броска, но также
включите текущее время его получения с веб-страницы. Это время можно получить из части строки внутри тега <p>, следующего после результата броска
в HTML-коде страницы.
16.5. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
Хотя данные с веб-серверов можно парсить средствами стандартной библиотеки
Python, в PyPI существует множество инструментов, упрощающих этот процесс.
В этой главе я показал, как:
zz
запрашивать веб-страницы при помощи встроенного модуля Python
urllib;
zz
парсить HTML средствами Beautiful Soup;
zz
взаимодействовать с веб-формами с использованием MechanicalSoup;
zz
многократно запрашивать данные с сайта для проверки обновлений.
394 Глава 16 Веб-программирование
Писать программы автоматизированного извлечения веб-данных интересно,
и в интернете хватает разных любопытных проектов.
Но помните: не все хотят, чтобы вы извлекали данные с их веб-серверов. Всегда
проверяйте условия использования (Terms of Use) сайта, прежде чем браться
за такую задачу, и сознательно выбирайте частоту веб-запросов, чтобы не перегружать сервер лишним трафиком.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-web
Дополнительные ресурсы
За дополнительной информацией о взаимодействии с веб-сайтами средствами
Python обращайтесь к следующим ресурсам:
zz
«Beautiful Soup: Build a Web ScraperWith Python» (https://realpython.com/
beautiful-soup-web-scraper-python/)
zz
«API Integration in Python» (https://realpython.com/api-integration-inpython/)
ГЛАВА 17
Научные вычисления
и построение графиков
Python — один из ведущих языков программирования для научных вычислений
и теории обработки данных (data science).
Популярность Python в этой области частично обусловлена обилием сторонних
пакетов для обработки и визуализации данных, доступных в PyPI.
В экосистеме Python можно найти инструменты на любой случай — от работы
с большими массивами данных до их визуализации на графиках и диаграммах.
В этой главе вы научитесь:
zz
работать с массивами данных, используя NumPy;
zz
строить диаграммы и графики с помощью Matplotlib.
ПРИМЕЧАНИЕ
Предполагается, что у вас есть некоторый опыт работы с матрицами. Если вы
не знакомы с матрицами или не интересуетесь научными вычислениями, эту
главу можно смело пропустить.
Итак, за дело!
17.1. ИСПОЛЬЗОВАНИЕ NUMPY ДЛЯ МАТРИЧНЫХ
ВЫЧИСЛЕНИЙ
В этом разделе вы научитесь хранить и обрабатывать данные в матричном виде
при помощи пакета NumPy (http://www.numpy.org/). Но сначала выясним, для
чего нужен NumPy.
396 Глава 17 Научные вычисления и построение графиков
Если вы изучали курс линейной алгебры, то, возможно, вспомните, что матрица — это прямоугольная таблица, которая представляет собой совокупность
строк и столбцов, на пересечении которых находятся ее элементы — числа.
На «чистом» языке Python матрицу можно создать в виде списка списков:
>>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
На первый взгляд, такое решение неплохо работает. Вы можете обращаться к отдельным элементам матрицы по индексам. Например, обращение ко второму
элементу первой строки матрицы выглядит так:
>>> matrix[0][1]
2
Допустим, вы хотите умножить каждый элемент матрицы на 2. Для этого надо
написать вложенный цикл for, который перебирает все элементы одной строки
матрицы:
>>> for row in matrix:
...
for i in range(len(row)):
...
row[i] = row[i] * 2
...
>>> matrix
[[2, 4, 6], [8, 10, 12], [14, 16, 18]]
Вроде бы несложно, но суть в том, что на «чистом» языке Python вам придется
проделать значительную работу с нуля для реализации даже очень простых
операций линейной алгебры. Для работы с многомерными массивами NumPy
предоставляет практически всю необходимую функциональность. Такое решение получается более эффективным, чем реализация на «чистом» Python. Пакет
NumPy написан на языке C и использует сложные алгоритмы для эффективного
выполнения вычислений.
ПРИМЕЧАНИЕ
Область применения NumPy не ограничивается научными вычислениями.
Допустим, вы проектируете игру и вам нужны простые средства для манипуляций со строками и столбцами. Массивы NumPy идеально подходят для
хранения двумерных данных.
Установка NumPy
Работу с пакетом NumPy начнем с его установки при помощи pip:
$ python3 -m pip install numpy
17.1. Использование NumPy для матричных вычислений 397
После того как установка NumPy будет завершена, можно вывести подробную
информацию о пакете командой pip show:
$ python3 -m pip show numpy
Name: numpy
Version: 1.18.5
Summary: NumPy: array processing for numbers, strings,
records, and objects.
Home-page: http://www.numpy.org
Author: Travis E. Oliphant et al.
Author-email: None
License: BSD
Location: c:\realpython\venv\lib\site-packages
Requires:
Required-by:
В частности, из вывода можно узнать, что новейшей версией на момент написания книги была версия 1.18.5.
Создание массива NumPy
После того как пакет NumPy будет установлен, мы создадим в нем матрицу из
первого примера этого раздела. Матрицы в NumPy являются экземплярами
объекта ndarray (сокращение от n-dimensional array — n-мерный массив).
ПРИМЕЧАНИЕ
N-мерным массивом называется массив с n измерениями. Например, одномерный массив представляет собой список, а двумерный — матрицу. Массивы
могут иметь три, четыре и более измерений.
В этом разделе мы рассмотрим массивы с одним или двумя измерениями.
Для создания объекта ndarray можно воспользоваться псевдонимом array. Объекты array инициализируются списками списков, так что для создания матрицы
из первого примера в виде массива NumPy можно поступить так:
>>> import numpy as np
>>> matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> matrix
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
Обратите внимание: NumPy выводит матрицу в удобочитаемом формате. Это
происходит даже при выводе матрицы вызовом print():
398 Глава 17 Научные вычисления и построение графиков
>>> print(matrix)
[[1 2 3]
[4 5 6]
[7 8 9]]
К отдельным элементам массива можно обращаться так же, как и к элементам
списка списков:
>>> matrix[0][1]
2
Кроме того, можно обращаться к элементам всего с одной парой квадратных
скобок, с разделением индексов запятой:
>>> matrix[0, 1]
2
Чем же массивы NumPy принципиально отличаются от списков Python?
Прежде всего массивы NumPy могут хранить только объекты одного типа — например, числа в матрице из рассмотренного примера, тогда как списки Python
могут хранить объекты смешанных типов.
Посмотрим, что произойдет при попытке создания массива с разнотипными
элементами:
>>> np.array([[1, 2, 3], ["a", "b", "c"]])
array([['1', '2', '3'],
['a', 'b', 'c']], dtype='<U11')
NumPy не выдает ошибки. Вместо этого каждый элемент преобразуется в строку.
Выражение dtype='<U11' в этом выводе означает, что массив позволяет хранить
только строки в формате Юникод длиной не более 11 байт.
Автоматические преобразования типов данных иногда помогают, но они также
могут стать потенциальным источником проблем — типы данных преобразуются
не так, как вы ожидали.
Обычно рекомендуется выполнять любые преобразования типов до инициализации объекта array. Так вы можете быть уверены в том, что тип данных,
хранящихся в массиве, соответствует ожиданиям.
В NumPy каждое измерение массива называется осью (axis). Матрицы, о которых мы говорили ранее, имели две оси. Массивы с двумя осями называются
двумерными массивами.
17.1. Использование NumPy для матричных вычислений 399
Пример трехмерного массива:
>>> matrix = np.array([
...
[[1, 2, 3], [4, 5, 6]],
...
[[7, 8, 9], [10, 11, 12]],
...
[[13, 14, 15], [16, 17, 18]]
... ])
Чтобы обратиться к элементу этого массива, необходимо указать три индекса:
>>> matrix[0][1][2]
6
>>> matrix[0, 1, 2]
6
Если вы считаете, что создание такого трехмерного массива выглядит странно,
далее в этом разделе я покажу более правильный способ создания массивов
высокой размерности.
Операции с массивами
После того как объект array будет создан, вы сможете пустить в ход всю мощь
NumPy и выполнять с массивом различные операции.
Вспомните, как в предыдущем примере мы написали вложенный цикл for для
умножения каждого элемента матрицы на 2. В NumPy эта операция выполняется
простым умножением объекта array на 2:
>>> A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> 2 * A
array([[ 2, 4, 6],
[ 8, 10, 12],
[14, 16, 18]])
Операции между двумя матрицами выполняются поэлементно, то есть оператор
применяется к соответствующим элементам матрицы:
>>> B = np.array([[5, 4, 3], [7, 6, 5], [9, 8, 7]])
>>> C = B - A
>>> C
array([[ 4, 2, 0],
[ 3, 1, -1],
[ 2, 0, -2]])
Обратите внимание: C[0][0] равно B[0][0] - A[0][0]. То же относится ко всем
остальным парам индексов. Все базовые арифметические операторы — +, -, *,
/ — в отношении массивов выполняются поэлементно.
400 Глава 17 Научные вычисления и построение графиков
Например, при умножении двух массивов оператором * произведение двух
матриц не вычисляется:
>>> A = np.array([[1, 1, 1], [1, 1, 1], [1, 1, 1]])
>>> A * A
array([[1, 1, 1],
[1, 1, 1],
[1, 1, 1]])
Для вычисления настоящего произведения матриц (https://ru.wikipedia.org/
wiki/Умножение_матриц) используется оператор @:
>>> A @ A
array([[3, 3, 3],
[3, 3, 3],
[3, 3, 3]])
Оператор @ появился в Python 3.5, и, если вы используете более старую версию
Python, матрицы придется умножать другим способом. NumPy предоставляет
функцию matmul() для умножения двух матриц:
>>> np.matmul(matrix, matrix)
array([[3, 3, 3],
[3, 3, 3],
[3, 3, 3]])
Оператор @ использует функцию np.matmul() во внутренней реализации, поэтому принципиальных различий между этими двумя способами нет.
Ниже перечислены другие распространенные операции с массивами:
>>> matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> # Получение кортежа длин осей
>>> matrix.shape
(3, 3)
>>> # Получение массива диагональных элементов
>>> matrix.diagonal()
array([1, 5, 9])
>>> # Получение одномерного массива всех записей
>>> matrix.flatten()
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> # Транспонирование
>>> matrix.transpose()
array([[1, 4, 7],
17.1. Использование NumPy для матричных вычислений 401
[2, 5, 8],
[3, 6, 9]])
>>> # Вычисление минимума
>>> matrix.min()
1
>>> # Вычисление максимума
>>> matrix.max()
9
>>> # Вычисление среднего значения всех элементов
>>> matrix.mean()
5.0
>>> # Вычисление суммы всех элементов
>>> matrix.sum()
45
А теперь рассмотрим некоторые способы создания новых массивов на базе
старых.
Стыковка и изменение формы массивов
Если размеры осей совпадают, два массива можно состыковать по вертикали
методом np.vstack() или по горизонтали методом np.hstack():
>>> A = np.array([[1, 2, 3], [4, 5, 6]])
>>> B = np.array([[7, 8, 9], [10, 11, 12]])
>>> np.vstack([A, B])
array([[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12]])
>>> np.hstack([A, B])
array([[ 1, 2, 3, 7, 8, 9],
[ 4, 5, 6, 10, 11, 12]])
Также можно изменить форму массивов методом np.reshape():
>>> A.reshape(6, 1)
array([[1],
[2],
[3],
[4],
[5],
[6]])
402 Глава 17 Научные вычисления и построение графиков
Следует заметить, что .reshape() возвращает новый массив, а не изменяет исходный массив на месте.
Общий размер массива после изменения формы должен соответствовать размеру
исходного массива. Например, выполнить matrix.reshape(2, 5) невозможно:
>>> A.reshape(2, 5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: cannot reshape array of size 6 into shape (2, 5)
В этом случае мы пытаемся изменить форму массива с девятью элементами
и превратить его в массив с двумя столбцами и пятью строками. Такой массив
содержит десять элементов.
Метод np.reshape() особенно полезен в сочетании с np.arange() — эквивалентом функции Python range() в NumPy. Главное различие заключается в том,
что np.arange() возвращает объект array:
>>> nums = np.arange(1, 10)
>>> nums
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
Диапазон np.arange() начинается с первого аргумента и завершается непосредственно перед вторым аргументом. Таким образом, np.arange(1, 10) возвращает
массив, содержащий числа от 1 до 9.
Совместно np.arange() и np.reshape() предоставляют полезный способ со­
здания матрицы:
>>> matrix = nums.reshape(3, 3)
>>> matrix
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
Это даже можно сделать в одной строке, объединив вызовы np.arange()
и np.reshape() в цепочку:
>>> np.arange(1, 10).reshape(3, 3)
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
Такой способ создания матриц особенно полезен для создания массивов высокой размерности. Пример построения трехмерного массива с использованием
np.array() и np.reshape():
17.1. Использование NumPy для матричных вычислений 403
>>> np.arange(1, 13).reshape(3, 2, 2)
array([[[ 1, 2],
[ 3, 4]],
[[ 5, 6],
[ 7, 8]],
[[ 9, 10],
[11, 12]]])
Конечно, не каждый многомерный массив может быть построен из последовательного списка чисел. В таких случаях часто проще создать одномерный список
элементов, а затем привести массив к нужной форме вызовом np.reshape():
>>> arr = np.array([1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23])
>>> arr.reshape(3, 2, 2)
array([[[ 1, 3],
[ 5, 7]],
[[ 9, 11],
[13, 15]],
[[17, 19],
[21, 23]]])
В списке, передаваемом np.array() в приведенном примере, разность в любой
паре равна 2. Для упрощения создания массивов такого рода можно передать
np.arange() необязательный третий аргумент с именем stride:
>>> np.arange(1, 24, 2)
array([ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23])
Перепишем предыдущий пример так, чтобы np.arange() передавался аргумент
stride:
>>> np.arange(1, 24, 2).reshape(3, 2, 2)
array([[[ 1, 3],
[ 5, 7]],
[[ 9, 11],
[13, 15]],
[[17, 19],
[21, 23]]])
В этом разделе я познакомил вас с несколькими способами создания и обработки многомерных массивов с использованием структуры данных NumPy
array. Однако вы получили лишь очень поверхностное представление о том,
404 Глава 17 Научные вычисления и построение графиков
что можно сделать с NumPy! В конце главы вы найдете ссылки на материалы,
которые помогут вам углубить ваши знания о NumPy.
Упражнения
1. Используйте np.arange() и np.reshape() для создания массива NumPy
3 × 3 с именем A, содержащего числа от 3 до 11.
2. Выведите минимальное, максимальное и среднее значение по всем элементам A.
3. Возведите в квадрат каждый элемент A, используя оператор **. Сохраните
результаты в массиве с именем B.
4. Пристыкуйте A к B вызовом np.vstack(), после чего сохраните результаты
в массиве с именем C.
5. Используйте оператор @ для вычисления произведения матриц C и A.
6. Измените форму C до размеров 3 × 3 × 2.
17.2. ПОСТРОЕНИЕ ГРАФИКОВ
С ПОМОЩЬЮ MATPLOTLIB
В предыдущем разделе я показал, как работать с массивами данных средствами
NumPy. Хотя пакет NumPy удобен для работы с данными, большие массивы
чисел обычно плохо воспринимаются пользователями. Лучше наглядно представлять такие данные в виде графиков или диаграмм.
В этом разделе я кратко расскажу о Matplotlib — одном из популярных пакетов
для быстрого создания двумерных графиков.
ПРИМЕЧАНИЕ
Если вам доводилось создавать графики в MATLAB, вы обнаружите, что
Matplotlib очень на него похож.
Сходство между MATLAB и Matplotlib неслучайно. Интерфейс создания диаграмм MATLAB напрямую происходит от Matplotlib. Даже если вы еще не
пользовались Matplotlib, то, скорее всего, создание диаграмм в Matplotlib
покажется вам простым и понятным.
Итак, за дело!
17.2. Построение графиков с помощью Matplotlib 405
Установка Matplotlib
Установим Matplotlib из терминала следующей командой:
$ python3 -m pip install matplotlib
После этого вы можете просмотреть информацию о пакете командой pip show:
$ python3 -m pip show matplotlib
Name: matplotlib
Version: 3.2.1
Summary: Python plotting package
Home-page: http://matplotlib.org
Author: John D. Hunter, Michael Droettboom
Author-email: matplotlib-users@python.org
License: BSD
Location: c:\realpython\venv\lib\site-packages
Requires: python-dateutil, pytz, kiwisolver, numpy,
cycler, six, pyparsing
Required-by:
В частности, обратите внимание на то, что новейшей версией на момент написания книги была версия 3.2.1.
Построение простейших графиков
с использованием pyplot
Пакет Matplotlib предоставляет два разных способа создания графиков.
Первый и самый простой способ использует интерфейс pyplot. Именно этот
интерфейс сразу кажется пользователям MATLAB знакомым.
Второй способ построения графиков в Matplotlib основан на объектно-ориентированном API. Объектно-ориентированный подход предоставляет больше
возможностей для управления графиками, чем интерфейс pyplot. Тем не менее
его концепции обычно более абстрактны.
В этом разделе вы освоите интерфейс pyplot и научитесь строить эффектные
графики с минимальными затратами времени.
Начнем с создания простого графика. Введите следующий фрагмент в новом
окне редактора IDLE:
from matplotlib import pyplot as plt
plt.plot([1, 2, 3, 4, 5])
plt.show()
406 Глава 17 Научные вычисления и построение графиков
Сохраните программу и запустите ее клавишей F5. На экране появится новое
окно со следующим графиком:
Вызов plt.plot([1, 2, 3, 4, 5]) строит график с линией, проходящей через
точки (0, 1), (1, 2), (2, 3), (3, 4) и (4, 5).
Список [1, 2, 3, 4, 5], переданный plt.plot(), представляет координаты точек
на графике. Так как значения x не заданы, Matplotlib автоматически использует
индексы элементов списка — в данном случае 0, 1, 2, 3 и 4, так как в Python
нумерация индексов начинается с 0.
Функция plt.plot() строит график, но на экране при этом никакая информация не появляется. Чтобы отобразить график на экране, необходимо вызвать
plt.show().
Также можно задать значения x для точек вашего графика, для чего следует
передать plt.plot() два списка. Когда plt.plot() передаются два аргумента,
первый список задает значения x, а второй — значения y.
В окне редактора приведите программу к следующему виду:
from matplotlib import pyplot as plt
xs = [1, 2, 3, 4, 5]
17.2. Построение графиков с помощью Matplotlib 407
ys = [2, 4, 6, 8, 10]
plt.plot(xs, ys)
plt.show()
Сохраните обновленную программу и нажмите F5. На экране должен появиться
следующий график:
Обратите внимание: метки на осях теперь представляют новые координаты
точек графика.
Возможности plot() не ограничиваются рисованием линий. На приведенных выше графиках получилось, что все точки находятся на прямой линии.
По умолчанию при нанесении точек вызовом plot() каждая пара соседних
точек соединяется отрезком прямой.
Обновите значения x и y так, чтобы точки не располагались на прямой:
from matplotlib import pyplot as plt
xs = [1, 2, 3, 4, 5]
ys = [3, -1, 4, 0, 6]
plt.plot(xs, ys)
plt.show()
408 Глава 17 Научные вычисления и построение графиков
Сохраните файл и нажмите клавишу F5. На экране появится следующий график.
У plot() имеется необязательный параметр форматирования, который используется для задания цвета и стиля линий или точек.
Например, следующая программа передает в параметре форматирования строку
"g-o":
from matplotlib import pyplot as plt
plt.plot([2, 4, 6, 8, 10], "g-o")
plt.show()
Буква g в "g-o" задает зеленый цвет (green), знак - задает сплошную линию,
а буква o — пометку точек на линии кругами.
17.2. Построение графиков с помощью Matplotlib 409
Полный список всех комбинаций форматирования вы найдете в документации
Matplotlib (https://matplotlib.org/2.0.2/api/pyplot_api.html).
Рисование нескольких графиков в одном окне
Если вам нужно вывести несколько графиков в одном окне, вызовите plot()
несколько раз.
Например, следующая программа рисует два графика:
from matplotlib import pyplot as plt
plt.plot([1, 2, 3, 4, 5])
plt.plot([1, 2, 4, 8, 16])
plt.show()
Сохраните программу в новом окне редактора и нажмите F5, чтобы увидеть
результат.
Обратите внимание: графики выводятся разными цветами. Если вы хотите
управлять внешним видом каждого графика, передайте строки форматирования
plot() в дополнение к значениям x и y:
from matplotlib import pyplot as plt
plt.plot([1, 2, 3, 4, 5], "g-o")
plt.plot([1, 2, 4, 8, 16], "b-^")
plt.show()
410 Глава 17 Научные вычисления и построение графиков
Теперь графики выводятся синим и зеленым цветом с выделением точек.
А теперь рассмотрим некоторые возможности построения графиков по другим
типам источников данных.
Вывод на графике данных из массивов NumPy
До настоящего момента мы хранили данные для графика в классических спис­
ках Python. В реальном мире для хранения данных, вероятно, будет использоваться что-то вроде массива NumPy. К счастью, Matplotlib хорошо работает
с объектами array.
Например, вместо списка можно воспользоваться функцией NumPy arange(),
чтобы определить данные для графика и передать полученный объект array
при вызове plot():
from matplotlib import pyplot as plt
import numpy as np
array = np.arange(1, 6)
plt.plot(array)
plt.show()
17.2. Построение графиков с помощью Matplotlib 411
Этот фрагмент строит следующий график:
При передаче двумерного массива каждый столбец массива интерпретируется как значения оси ординат y. Например, следующий фрагмент кода рисует
четыре линии:
from matplotlib import pyplot as plt
import numpy as np
data = np.arange(1, 21).reshape(5, 4)
# data содержит следующий массив:
# array([[ 1, 2, 3, 4],
#
[ 5, 6, 7, 8],
#
[ 9, 10, 11, 12],
#
[13, 14, 15, 16],
#
[17, 18, 19, 20]])
plt.plot(data)
plt.show()
Вот результат:
Если же вы хотите нанести на график матрицу по строкам, то выводить следует
результат транспонирования массива:
from matplotlib import pyplot as plt
import numpy as np
data = np.arange(1, 21).reshape(5, 4)
plt.plot(data.transpose())
plt.show()
На графике, сгенерированном этим фрагментом кода, линии построены по
строкам матрицы (вместо столбцов).
17.2. Построение графиков с помощью Matplotlib 413
До настоящего момента на графиках, которые мы строили, не было никакой
информации о том, что же этот график показывает. Сейчас мы расскажем, как
отформатировать график и добавить текст.
Форматирование графиков
Попробуем показать на графике, сколько материала о языке Python было изу­
чено за первые 20 дней в результате чтения Real Python по сравнению с другим
веб-сайтом:
from matplotlib import pyplot as plt
import numpy as np
days = np.arange(0, 21)
other_site, real_python = days, days ** 2
plt.plot(days, other_site)
plt.plot(days, real_python)
plt.show()
График, выводимый этим кодом, выглядит так:
График далеко не идеален. На оси x отображаются половины дней, у графика
нет заголовка и названия осей.
Начнем с настройки оси x. Для определения местонахождения меток на осях
используется функция plt.xticks():
414 Глава 17 Научные вычисления и построение графиков
from matplotlib import pyplot as plt
import numpy as np
days = np.arange(0, 21)
other_site, real_python = days, days ** 2
plt.plot(days, other_site)
plt.plot(days, real_python)
plt.xticks([0, 5, 10, 15, 20])
plt.show()
Теперь на графике появляются метки дней: 0, 5, 10, 15 и 20.
Такой график лучше воспринимается, но и сейчас неясно, что же показывает
каждая ось. Для добавления названия осей используются функции plt.xlabel()
и plt.ylabel(). Обе функции имеют один параметр, в котором должна передаваться строка с названием оси.
Для добавления заголовка к графику используется функция plt.title(). Как
и plt.xlabel() и plt.ylabel(), функция plt.title() получает один строковый
аргумент с заголовком графика.
Обновите приведенный выше код, чтобы к оси x добавлялось название "Days of
Reading" (Время чтения в днях), к оси y — "Amount of Python Learned" (Объем
изученного Python), а сам график выводился под заголовком "Python Learned
Reading Real Python vs Other Site" (Python, изученный на Real Python vs на
других сайтах):
17.2. Построение графиков с помощью Matplotlib 415
from matplotlib import pyplot as plt
import numpy as np
days = np.arange(0, 21)
other_site, real_python = days, days ** 2
plt.plot(days, other_site)
plt.plot(days, real_python)
plt.xticks([0, 5, 10, 15, 20])
plt.xlabel("Days of Reading")
plt.ylabel("Amount of Python Learned")
plt.title("Python Learned Reading Real Python vs Other Site")
plt.show()
График с заголовком и названиями осей показан ниже.
Становится на что-то похоже!
Но проблемы все еще остались: непонятно, какой график относится к Real
Python, а какой — к другому веб-сайту. Чтобы уточнить, какая линия чему
соответствует, можно добавить условные обозначения вызовом plt.legend().
plt.legend() имеет один обязательный позиционный параметр. Функции пере-
дается список строк с названиями всех графиков на рисунке. Строки должны
следовать в том же порядке, в котором они добавлялись на рисунок.
416 Глава 17 Научные вычисления и построение графиков
Например, следующая обновленная версия добавляет условные обозначения,
которые поясняют, какой график представляет Real Python, а какой — «другой
сайт» («other site»).
Так как первым был добавлен график с данными другого сайта other_site,
первой строкой в списке, передаваемом plt.legend(), становится "Other Site":
from matplotlib import pyplot as plt
import numpy as np
days = np.arange(0, 21)
other_site, real_python = days, days ** 2
plt.plot(days, other_site)
plt.plot(days, real_python)
plt.xticks([0, 5, 10, 15, 20])
plt.xlabel("Days of Reading")
plt.ylabel("Amount of Python Learned")
plt.title("Python Learned Reading Real Python vs Other Site")
plt.legend(["Other Site", "Real Python"])
plt.show()
Вот как выглядит итоговый вариант:
plt.legend() также поддерживает ряд необязательных параметров, предназна-
ченных для настройки условных обозначений. За дополнительной информацией
17.2. Построение графиков с помощью Matplotlib 417
обращайтесь к описанию условных обозначений в документации Matplotlib
(https://matplotlib.org/users/legend_guide.html).
Другие разновидности графиков
До сих пор вы строили только линейные графики, но Matplotlib позволяет строить
многие другие разновидности, включая гистограммы и столбчатые диаграммы.
Столбчатые диаграммы
Столбчатые диаграммы строятся функцией plt.bar(), которая получает два
обязательных параметра:
1) список значений x для центральной точки каждого столбца;
2) список значений y для высоты каждого столбца.
Например, следующий код строит столбчатую диаграмму, у которой центры
столбцов находятся в точках 1, 2, 3, 4 и 5 по оси x с высотами 2, 4, 6, 8 и 10 соответственно:
from matplotlib import pyplot as plt
centers = [1, 2, 3, 4, 5]
tops = [2, 4, 6, 8, 10]
plt.bar(centers, tops)
plt.show()
Столбчатая диаграмма выглядит так:
418 Глава 17 Научные вычисления и построение графиков
Для определения центральных точек и высот столбцов вместо списка можно
использовать массив NumPy. Следующий фрагмент строит диаграмму, аналогичную предыдущей, но вместо списков используются массивы NumPy:
from matplotlib import pyplot as plt
import numpy as np
centers = np.arange(1, 6)
tops = np.arange(2, 12, 2)
plt.bar(centers, tops)
plt.show()
Функция plt.bar() весьма гибкая. Первый аргумент не обязан быть списком
чисел — это может быть список строк, представляющих категории данных.
Допустим, вы хотите построить столбчатую диаграмму, на которой выводятся
данные из словаря:
fruits = {
"apples": 10,
"oranges": 16,
"bananas": 9,
"pears": 4,
}
Список названий фруктов можно получить функцией fruits.keys(), а соответствующие значения — функцией fruits.values():
>>> fruits.keys()
dict_keys(['apples', 'oranges', 'bananas', 'pears'])
>>> fruits.values()
dict_values([10, 16, 9, 4])
Списки fruits.keys() и fruits.values() можно передать функции plt.bar(),
чтобы на столбчатой диаграмме были показаны значения, соответствующие
названиям фруктов:
from matplotlib import pyplot as plt
fruits = {
"apples": 10,
"oranges": 16,
"bananas": 9,
"pears": 4,
}
plt.bar(fruits.keys(), fruits.values())
plt.show()
17.2. Построение графиков с помощью Matplotlib 419
Столбцы разделены одинаковыми расстояниями, а названия фруктов обозначены метками на оси x.
Гистограммы
Другая популярная разновидность диаграмм — гистограмма — показывает
распределение данных. Для создания гистограмм в Matplotlib используется
функция plt.hist().
Функция plt.hist() получает два обязательных параметра:
1) список (или массив NumPy) значений;
2) количество категорий на гистограмме.
Функция plt.hist() может вычислить частоту каждого значения в списке значений, а также рассчитать категории за вас. Это сэкономит вам массу усилий
при построении гистограмм.
Рассмотрим пример, который строит гистограмму десяти тысяч случайных
чисел с нормальным распределением, сгруппированных по 20 категориям. Для
генерирования случайных чисел используется функция randn() из модуля
NumPy random. Она возвращает массив случайных чисел с плавающей точкой,
многие из которых близки к нулю.
420 Глава 17 Научные вычисления и построение графиков
Код построения гистограммы:
from matplotlib import pyplot as plt
from numpy import random
plt.hist(random.randn(10000), 20)
plt.show()
Matplotlib автоматически создает 20 категорий одинаковой ширины 0,5.
Гистограммы легко настраиваются. Подробнее о построении гистограмм на
языке Python рассказано в статье «Python Histogram Plotting: NumPy, Matplotlib,
Pandas & Seaborn» (https://realpython.com/python-histograms/) на сайте Real
Python.
Сохранение рисунков в графических файлах
Возможно, вы заметили, что в нижней части окна с графиками расположена
панель инструментов. Она позволяет сохранить построенную диаграмму в графическом файле.
Как правило, никому не хочется сидеть у компьютера и нажимать кнопку Save
для сохранения каждого графика. К счастью, Matplotlib позволяет легко сделать
это программными средствами.
Для сохранения графиков используется функция plt.savefig(). Путь к файлу, в котором вы хотите сохранить свой график, передается в виде строки.
17.2. Построение графиков с помощью Matplotlib 421
Следующие примеры сохраняют простую столбчатую диаграмму с именем
bar.png в текущем рабочем каталоге.
Если вы решите сохранить его в другом месте, передайте при вызове абсолютный путь.
from matplotlib import pyplot as plt
import numpy as np
xs = np.arange(1, 6)
tops = np.arange(2, 12, 2)
plt.bar(xs, tops)
plt.savefig("bar.png")
ПРИМЕЧАНИЕ
Чтобы одновременно и сохранить диаграмму, и вывести ее на экране, обязательно сохраните ее перед выводом!
Функция show() приостанавливает выполнение кода, а при закрытии окна
график уничтожается, так что при попытке сохранения диаграммы после вызова show() вы получите пустой файл.
Работа с графиками в интерактивном режиме
Когда мы начинаем форматировать график, хочется настраивать его внешний
вид и оценивать результат, не тратя время на перезапуск программы.
Проще всего сделать это при помощи оболочки Jupyter Notebook (https://
jupyter.org/), которая создает интерактивный сеанс интерпретатора Python,
выполняемого в браузере.
Блокноты Jupyter стали главным средством взаимодействия с данными и их
анализа, и они отлично работают как с NumPy, так и с Matplotlib. Интерактивный учебник по использованию Jupyter Notebook представлен в руководстве
«IPython in Depth» (https://realpython.com/pybasics-ipython-in-depth) на сайте
Real Python.
Упражнения
1. Постройте как можно больше графиков, приведенных в этом разделе, — напишите собственные программы, не подглядывая в приведенный код.
422 Глава 17 Научные вычисления и построение графиков
2. Способствуют ли пираты глобальному потеплению? В папке practice_files
главы 17 находится файл CSV с данными о количестве пиратов и глобальной температуре. Напишите программу для анализа связи между ними;
программа должна читать файл pirates.csv и строить график, на котором
количество пиратов указано на оси x и температура — на оси y. Добавьте
заголовок и названия осей графиков, затем сохраните полученный график
в виде файла в формате PNG.
17.3. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы узнали о научных вычислениях и визуализации данных в Python.
В частности, вы научились:
zz
работать с массивами и матрицами средствами NumPy;
zz
строить графики при помощи Matplotlib.
Чтобы на должном уровне рассказать про научные вычисления, анализ и визуализацию данных, не хватит и отдельной книги. Однако понимания базовых
понятий, с которыми вы познакомились в этой главе, достаточно для начала
самостоятельной работы.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-scientific-computing
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
zz
«Look Ma, No For-Loops: Array ProgrammingWith NumPy» (https://
realpython.com/numpy-array-programming/)
zz
«Data ScienceWith Python Core Skills (Learning Path)» (https://realpython.
com/learning-paths/data-science-python-core-skills/)
ГЛАВА 18
Графические интерфейсы
До сих пор в этой книге я рассказывал о приложениях командной строки —
программах, которые запускаются и выводят результаты в окне терминала.
Приложения командной строки хорошо подходят для создания инструментов,
которыми пользуются разработчики, но большинство пользователей программ
никогда не открывают окно терминала!
Графический интерфейс пользователя, или GUI (Graphical User Interface), — это
окна с элементами (кнопками, текстовыми полями и т. д.), которые предоставляют пользователю знакомый и наглядный способ взаимодействия с программой.
В этой главе вы научитесь:
zz
добавлять простой графический интерфейс к приложениям командной
строки с помощью EasyGUI;
zz
создавать полнофункциональные GUI-приложения с использованием
Tkinter.
Итак, за дело!
18.1. ДОБАВЛЕНИЕ ЭЛЕМЕНТОВ GUI
С ПОМОЩЬЮ EASYGUI
Библиотека EasyGUI позволяет быстро добавить в программу графический
интерфейс. Возможности EasyGUI несколько ограниченны, но библиотеку
удобно применять для создания простых инструментов, которым нужен небольшой объем входных данных от пользователя.
В этом разделе мы используем EasyGUI для создания короткой программы,
которая позволяет выбрать файл PDF на жестком диске и повернуть его страницы на заданный угол.
424 Глава 18 Графические интерфейсы
Установка EasyGUI
Прежде всего необходимо установить EasyGUI при помощи pip:
$ python3 -m pip install easygui
Когда библиотека EasyGUI будет установлена, можно вывести расширенную
информацию о пакете командой pip show:
$ python3 -m pip show easygui
Name: easygui
Version: 0.98.1
Summary: EasyGUI is a module for very simple, very easy GUI
programming in Python. EasyGUI is different from other
GUI generators in that EasyGUI is NOT event-driven.
Instead, all GUI interactions are invoked by simple
function calls.
Home-page: https://github.com/robertlugg/easygui
Author: easygui developers and Stephen Ferg
Author-email: robert.lugg@gmail.com
License: BSD
Location: c:\realpython\venv\lib\site-packages
Requires:
Required-by:
Код этой главы написан для EasyGUI 0.98.1 — той же версии, которую вы видите
во фрагменте кода выше.
Первое приложение EasyGUI
EasyGUI хорошо подходит для создания диалоговых окон, предназначенных
для получения пользовательского ввода, и вывода результатов. Библиотека не
особенно годится для создания больших приложений с несколькими окнами,
меню и панелями инструментов.
EasyGUI можно представить как своего рода замену функций input() и print(),
которые ранее мы использовали для ввода и вывода.
Логика EasyGUI-программ обычно работает по следующей схеме.
1. В какой-то момент на экране пользователя появляется визуальный
элемент.
2. Выполнение кода приостанавливается, пока пользователь не введет
данные в визуальном элементе.
3. Пользовательский ввод возвращается в виде объекта, а выполнение кода
возобновляется.
18.1. Добавление элементов GUI с помощью EasyGUI 425
Чтобы получить представление о том, как работает EasyGUI, откройте новое
интерактивное окно в IDLE и выполните следующие команды:
>>> import easygui as gui
>>> gui.msgbox(msg="Hello!", title="My first message box")
При выполнении кода в Windows на экране появляется окно, которое выглядит
примерно так:
Внешний вид окна зависит от операционной системы, в которой выполняется
код. В macOS окно выглядит так:
То же окно в Ubuntu:
426 Глава 18 Графические интерфейсы
В примерах этого раздела мы используем скриншоты для Windows.
ВАЖНО!
Как EasyGUI, так и IDLE написаны с использованием библиотеки Tkinter, о которой речь пойдет в следующем разделе. Такое использование одного ресурса
иногда создает проблемы с выполнением — например, диалоговые окна могут
застывать на месте или блокироваться.
Если вам кажется, что вы тоже с этим столкнулись, попробуйте выполнить свой
код в окне терминала. Интерактивный сеанс Python запускается из терминала
командой python в Windows или командой python3 в macOS/Ubuntu.
Вот что вы увидите в диалоговом окне, сгенерированном приведенным кодом:
1. Строка "Hello!", переданная в параметре msg функции msgbox(), выводится как текст в окне сообщения.
2. Строка "My first message box", переданная в параметре title, выводится
в заголовке окна сообщения.
3. Окно сообщения содержит кнопку с надписью "OK".
Закройте диалоговое окно кнопкой OK и загляните в интерактивное окно IDLE.
Строка "OK" выводится под последней введенной вами строкой кода:
>>> gui.msgbox(msg="Hello!", title="My first message box")
'OK'
При закрытии диалогового окна msgbox() возвращает название кнопки. Если
пользователь закрывает диалоговое окно без нажатия кнопки OK, то msgbox()
возвращает значение None.
Название кнопки можно настроить при помощи третьего необязательного
параметра ok_button. Например, следующая команда создает окно сообщения
с кнопкой "Click me":
>>> gui.msgbox(msg="Hello!", title="Greeting", ok_button="Click me")
Функция msgbox() хорошо подходит для вывода сообщений, но она не предоставляет пользователю достаточно средств для взаимодействия с программой.
EasyGUI содержит несколько функций для вывода различных типов диалоговых
окон. Давайте рассмотрим некоторые из них!
18.1. Добавление элементов GUI с помощью EasyGUI 427
Элементы графического интерфейса в EasyGUI
Кроме msgbox() EasyGUI содержит еще несколько функций для вывода других
видов диалоговых окон. Описание этих функций приведено в следующей таблице.
ФУНКЦИЯ
ОПИСАНИЕ
msgbox()
Выводит сообщение с одной кнопкой и возвращает название
кнопки
buttonbox()
Выводит сообщение с несколькими кнопками и возвращает название выбранной пользователем кнопки
indexbox()
Выводит сообщение с несколькими кнопками и возвращает индекс выбранной кнопки
enterbox()
Предоставляет пользователю поле для ввода текста и возвращает текст, введенный пользователем
fileopenbox()
Предлагает пользователю выбрать файл, который должен быть
открыт, и возвращает абсолютный путь к выбранному файлу
diropenbox()
Предлагает пользователю выбрать каталог, который должен
быть открыт, и возвращает абсолютный путь к выбранному
каталогу
filesavebox()
Предлагает пользователю выбрать место для сохранения файла и возвращает абсолютный путь к месту сохранения
Рассмотрим каждую из этих функций по отдельности.
buttonbox()
Функция EasyGUI buttonbox() отображает диалоговое окно с сообщением
и несколькими кнопками, которые пользователь может нажимать. Название
кнопки, которую нажал пользователь, возвращается вашей программе.
Как и msgbox(), buttonbox() содержит параметры msg и title, которые определяют выводимое сообщение и заголовок диалогового окна. Функция buttonbox()
также поддерживает третий параметр с именем choices, который используется
для настройки кнопок.
Например, следующий фрагмент выводит диалоговое окно с тремя кнопками,
с метками "Red", "Yellow" и "Blue":
>>> gui.buttonbox(
...
msg="What is your favorite color?",
...
title="Choose wisely...",
...
choices=("Red", "Yellow", "Blue"),
... )
428 Глава 18 Графические интерфейсы
Диалоговое окно выглядит так:
Когда вы нажимаете одну из кнопок, ее метка возвращается в виде строки. Например, нажатие кнопки Yellow заставляет buttonbox() вернуть строку "Yellow":
>>> gui.buttonbox(
...
msg="What is your favorite color?",
...
title="Choose wisely...",
...
choices=("Red", "Yellow", "Blue"),
... )
'Yellow'
Как и msgbox(), buttonbox() возвращает значение None, если пользователь закрывает диалоговое окно без нажатия одной из кнопок.
indexbox()
indexbox() выводит диалоговое окно, идентичное тому, которое выводит
buttonbox(). Собственно, indexbox() создается точно так же, как и buttonbox():
>>> gui.indexbox(
...
msg="What's your favorite color?",
...
title="Choose wisely...",
...
choices=("Red", "Yellow", "Blue"),
... )
А вот как выглядит диалоговое окно:
18.1. Добавление элементов GUI с помощью EasyGUI 429
Различие между indexbox() и buttonbox() заключается в том, что indexbox()
возвращает индекс названия кнопки (вместо самого названия) в списке или
кортеже, передаваемом choices.
Например, при щелчке на кнопке Yellow возвращается целое число 1:
>>> gui.indexbox(
...
msg="What's your favorite color?",
...
title="Favorite color",
...
choices=("Red", "Yellow", "Blue"),
... )
1
Так как indexbox() возвращает индекс, а не строку, удобно определить кортеж
для choices за пределами функции, чтобы позже вы могли обращаться к названию по индексу:
>>> colors = ("Red", "Yellow", "Blue")
>>> choice = gui.indexbox(
msg="What's your favorite color?",
title="Favorite color",
choices=colors,
)
>>> choice
1
>>> colors[choice]
'Yellow'
Функции buttonbox() и indexbox() отлично подходят для получения ввода от
пользователя, когда пользователь выбирает из заранее определенного набора
вариантов. Эти функции плохо подходят для получения такой информации,
как имя пользователя или адрес электронной почты. В таких случаях лучше
применять функцию enterbox().
enterbox()
Функция enterbox() предназначена для получения текстового ввода от пользователя:
>>> gui.enterbox(
...
msg="What is your favorite color?",
...
title="Favorite color",
... )
Диалоговое окно, создаваемое enterbox(), содержит текстовое поле, в котором
пользователь может ввести ответ.
430 Глава 18 Графические интерфейсы
Введите название цвета (например, Yellow) и нажмите кнопку OK. Введенный
вами текст возвращается в виде строки:
>>> gui.enterbox(
...
msg="What is your favorite color?",
...
title="Favorite color",
... )
'Yellow'
Диалоговые окна очень часто применяются для выбора пользователем файла
или папки. В EasyGUI существуют специальные функции, разработанные
именно для таких операций.
fileopenbox()
Функция fileopenbox() выводит диалоговое окно для выбора открываемого
файла:
>>> gui.fileopenbox(title="Select a file")
Это диалоговое окно очень похоже на стандартное системное диалоговое окно,
в котором пользователь открывает файл (см. рис. на с. 439).
Выберите файл и щелкните на кнопке Open. Функция возвращает строку с полным путем к выбранному файлу.
ВАЖНО!
Функция fileopenbox() не открывает файл, а только предоставляет возможность выбрать его! Чтобы открыть файл, необходимо воспользоваться
встроенной функцией open(), о которой я рассказывал в главе 12.
Как и msgbox() и buttonbox(), fileopenbox() возвращает значение None, если
пользователь нажмет кнопку Cancel или закроет диалоговое окно, не выбрав
файл.
18.1. Добавление элементов GUI с помощью EasyGUI 431
diropenbox() и filesavebox()
EasyGUI содержит две другие функции для создания диалоговых окон, работающие практически так же, как функция fileopenbox().
1. Функция diropenbox() открывает диалоговое окно, которое может использоваться для выбора папки вместо файла. Когда пользователь нажимает Open, возвращается полный путь к каталогу.
2. Функция filesavebox() открывает диалоговое окно для выбора места
сохранения файла; если выбранное имя уже существует, функция предлагает пользователю подтвердить, что он хочет перезаписать файл. Как
и fileopenbox(), filesavebox() возвращает путь к файлу, если пользователь нажал кнопку Save. Сам файл при этом не сохраняется.
ВАЖНО!
Функции diropenbox() и filesavebox() не открывают каталог и не сохраняют
файл. Они только возвращают абсолютный путь к открываемому каталогу или
сохраняемому файлу.
Чтобы открыть каталог или сохранить файл, вам придется написать соответствующий код самостоятельно.
432 Глава 18 Графические интерфейсы
Обе функции — diropenbox() и filesavebox() — возвращают None, если пользователь закрыл диалоговое окно, не нажав кнопку Open или Save. Это может
привести к аварийному завершению вашей программы, если вы будете недостаточно осторожны.
Например, следующий фрагмент выдает ошибку TypeError, если пользователь
закроет диалоговое окно без выбора:
>>> path = gui.fileopenbox(title="Select a file")
>>> open_file = open(path, "r")
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
TypeError: expected str, bytes or os.PathLike object, not NoneType
То, как вы обрабатываете подобные ситуации, очень сильно влияет на впечатление пользователя от вашей программы.
Корректное завершение программы
Допустим, вы пишете программу для извлечения страниц из файла PDF. Первое,
что может делать такая программа, — вызвать fileopenbox(), чтобы пользователь
мог выбрать файл PDF для открытия.
Что делать, если пользователь решил, что он не хочет запускать программу,
и нажал Cancel?
Вы должны позаботиться о том, чтобы ваша программа корректно обрабатывала
такие ситуации. Она не должна аварийно завершаться или выдавать неожиданные результаты. В описанной выше ситуации программа, скорее всего, должна
просто завершить работу.
Один из способов завершения программы подразумевает использование встроенной функции exit() языка Python.
Например, следующая программа вызывает функцию exit(), чтобы прервать выполнение, когда пользователь нажимает Cancel в диалоговом окне выбора файла:
import easygui as gui
path = gui.fileopenbox(title="Select a file")
if path is None:
exit()
Если пользователь закрывает диалоговое окно, не нажав кнопку OK, программа
закроется, а выполнение будет остановлено, потому что переменная path содержит None, а программа вызывает exit() в блоке if.
18.1. Добавление элементов GUI с помощью EasyGUI 433
ПРИМЕЧАНИЕ
Если программа выполняется в IDLE, функция exit() также закроет текущее
интерактивное окно.
Ключевое слово is из предыдущего примера вам еще не встречалось. Оно сравнивает два объекта и определяет, не являются ли они одним и тем же объектом,
проверяя, имеют ли они одинаковые адреса памяти.
Как мы говорили ранее, адрес памяти объекта можно получить функцией id().
Таким образом, если значения id(a) и id(b) для двух объектов одинаковы, то
выражение a is b дает результат True. В этом случае a и b не являются разными
объектами. Они представляют один объект с двумя разными именами.
Почему же is используется для сравнения значения с None? Потому что объект
None может быть только один. Каждый раз, когда функция возвращает None, она
возвращает тот же объект в памяти, на который указывает ключевое слово None.
Теперь вы умеете создавать диалоговые окна вызовом EasyGUI, и мы можем
применить все ваши приобретенные навыки в реальном приложении.
Упражнение
1. Создайте следующее диалоговое окно.
2. Создайте следующее диалоговое окно.
434 Глава 18 Графические интерфейсы
18.2. ПРИМЕР: ПРОГРАММА ДЛЯ ПОВОРОТА
СТРАНИЦ PDF
EasyGUI очень хорошо подходит для вспомогательных приложений, автоматизирующих простые, но повторяющиеся операции. Если вам частенько
приходится выполнять рутинные операции, например в офисе, то программы
EasyGUI, упрощающие выполнение повседневных задач, могут значительно
повысить производительность вашей работы.
В этом разделе мы воспользуемся некоторыми диалоговыми окнами EasyGUI,
о которых вы узнали в прошлом разделе, чтобы разработать приложение для
поворота страниц PDF.
Дизайн приложения
Прежде чем браться за написание кода, давайте немного поразмыслим над тем,
как должна работать программа.
Она должна сначала спросить у пользователя, какой файл PDF следует открыть,
на сколько градусов должна быть повернута каждая страница и где пользователь
хотел бы сохранить новый файл PDF. Затем программа должна открыть файл,
повернуть страницы и сохранить новый файл.
ПРИМЕЧАНИЕ
На стадии проектирования приложения следует спланировать каждый шаг до
того, как вы начнете программировать. При разработке крупного приложения
желательно нарисовать диаграмму, описывающую логику программы, — это
поможет упорядочить ваши будущие действия.
Итак, опишем последовательность действий, которые затем будем кодировать.
Это упростит нашу задачу.
1. Вывести диалоговое окно выбора файла для открытия документа PDF.
2. Если пользователь отменяет диалоговое окно, завершить работу программы.
3. Предложить пользователю выбрать угол поворота в градусах — одно из
значений 90, 180 или 270 градусов.
4. Открыть диалоговое окно для выбора файла, чтобы сохранить документ
PDF с повернутыми страницами.
18.2. Пример: программа для поворота страниц PDF 435
5. Если пользователь пытается сохранить файл с таким же именем, как
у входного файла:
ƒƒ уведомить пользователя, что операция недопустима;
ƒƒ вернуться к шагу 4.
6. Если пользователь отменит диалоговое окно для сохранения файла, завершить работу программы.
7. Выполнить поворот страниц:
ƒƒ открыть выбранный файл PDF;
ƒƒ повернуть все страницы;
ƒƒ сохранить документ PDF с повернутыми страницами в выбранном файле.
Реализация дизайна приложения
Итак, мы составили план и теперь можем последовательно реализовать каждый
шаг. Откройте новое окно редактора в IDLE.
Начните с импорта EasyGUI и PyPDF2:
import easygui as gui
from PyPDF2 import PdfFileReader, PdfFileWriter
На шаге 1 выводится диалоговое окно выбора файла для открытия документа
PDF. Это можно сделать функцией fileopenbox():
# 1. Вывести диалоговое окно для выбора файла, чтобы открыть документ PDF.
input_path = gui.fileopenbox(
title="Select a PDF to rotate...",
default="*.pdf"
)
Здесь параметру по умолчанию задается значение "*.pdf", которое настраивает
диалоговое окно так, чтобы в нем выводились только файлы с расширением
.pdf. Тем самым мы предотвращаем случайный выбор пользователем файла,
формат которого отличается от PDF.
Путь, выбранный пользователем, присваивается переменной input_path. Если
пользователь закрыл диалоговое окно, не выбрав файл (шаг 2), переменная
input_path содержит None.
Чтобы завершить программу, если пользователь закрыл диалоговое окно, не
выбрав значение, проверьте, равна ли None переменная input_path, и если равна — вызовите exit():
436 Глава 18 Графические интерфейсы
# 2. Если пользователь отменяет диалоговое окно, завершить работу программы.
if input_path is None:
exit()
На третьем шаге следует спросить пользователя, на какой угол он хотел бы повернуть страницы PDF. Пользователь может выбрать 90, 180 или 270 градусов.
Для получения этой информации можно воспользоваться функцией buttonbox():
# 3. Предложить пользователю выбрать угол поворота –
#
90, 180 или 270 градусов.
choices = ("90", "180", "270")
degrees = gui.buttonbox(
msg="Rotate the PDF clockwise by how many degrees?",
title="Choose rotation...",
choices=choices,
)
Созданное диалоговое окно содержит три кнопки с метками "90", "180" и "270".
Когда пользователь щелкает на одной из этих кнопок, метка присваивается
переменной degrees в виде строки. Чтобы повернуть страницы в файле PDF
на выбранный угол, необходимо иметь это значение в виде целого числа, а не
строки. Преобразуйте его в целое число:
degrees = int(degrees)
Затем запросите у пользователя путь к выходному файлу функцией filesa­
vebox():
# 4. Открыть диалоговое окно для выбора файла, чтобы открыть документ PDF
с повернутыми страницами.
save_title = "Save the rotated PDF as..."
file_type = "*.pdf"
output_path = gui.filesavebox(title=save_title, default=file_type)
Как и в случае с fileopenbox(), параметру default присваивается значение *.pdf.
Это гарантирует, что файл будет автоматически сохранен с расширением .pdf.
Пользователю следует запретить перезапись исходного файла (шаг 5). Вы можете использовать цикл while для вывода предупреждения, пока пользователь
не выберет путь, отличный от пути входного файла:
# 5. Если пользователь пытается сохранить файл с таким же именем,
#
как у входного файла:
while input_path == output_path:
# - Уведомить пользователя окном сообщения о том, что операция недопустима.
gui.msgbox(msg="Cannot overwrite original file!")
# - Вернуться к шагу 4.
output_path = gui.filesavebox(title=save_title, default=file_type)
18.2. Пример: программа для поворота страниц PDF 437
Цикл while проверяет, совпадает ли путь input_path с output_path. Если пути
различны, то тело цикла игнорируется. Если input_path и output_path совпадают, то msbox() выводит предупреждение о том, что перезапись исходного
файла запрещена.
После вывода предупреждения filesavebox() выводит другое диалоговое окно
для сохранения файла с таким же заголовком и типом файла по умолчанию,
как и прежде. Эта часть возвращает пользователя к шагу 4. И хотя программа
фактически не возвращается к строке кода, в которой функция filesavebox()
была вызвана в первый раз, эффект остается тем же.
Если пользователь закрывает диалоговое окно для сохранения файла, не нажав
Save, программа должна завершить работу (шаг 6):
# 6. Если пользователь отменяет диалоговое окно сохранения файла,
#
завершить работу программы.
if output_path is None:
exit()
Теперь у вас есть все необходимое для реализации последнего шага программы:
# 7. Выполнить поворот страниц:
#
- Открыть выбранный файл PDF.
input_file = PdfFileReader(input_path)
output_pdf = PdfFileWriter()
# - Повернуть все страницы.
for page in input_file.pages:
page = page.rotateClockwise(degrees)
output_pdf.addPage(page)
# - Сохранить файл PDF с повернутыми страницами в выбранном файле.
with open(output_path, "wb") as output_file:
output_pdf.write(output_file)
Опробуйте новое приложение PDF в деле! Оно одинаково хорошо работает
в Windows, macOS и Ubuntu Linux.
Ниже для удобства просмотра приведен полный код приложения:
import easygui as gui
from PyPDF2 import PdfFileReader, PdfFileWriter
# 1. Вывести диалоговое окно для выбора файла, чтобы открыть документ PDF.
input_path = gui.fileopenbox(
title="Select a PDF to rotate...",
default="*.pdf"
)
438 Глава 18 Графические интерфейсы
# 2. Если пользователь отменяет диалоговое окно, завершить работу программы.
if input_path is None:
exit()
# 3. Предложить пользователю выбрать угол поворота –
#
90, 180 или 270 градусов.
choices = ("90", "180", "270")
degrees = gui.buttonbox(
msg="Rotate the PDF clockwise by how many degrees?",
title="Choose rotation...",
choices=choices,
)
# 4. Открыть диалоговое окно для выбора файла, чтобы сохранить
#
документ PDF с повернутыми страницами.
save_title = "Save the rotated PDF as..."
file_type = "*.pdf"
output_path = gui.filesavebox(title=save_title, default=file_type)
# 5. Если пользователь пытается сохранить файл с таким же именем,
#
как у входного файла:
while input_path == output_path:
# - Alert the user with a message box that this is not allowed.
gui.msgbox(msg="Cannot overwrite original file!")
# - Return to step 4.
output_path = gui.filesavebox(title=save_title, default=file_type)
# 6. Если пользователь отменяет диалоговое окно для сохранения файла,
#
завершить работу программы.
if output_path is None:
exit()
# 7. Выполнить поворот страниц:
# - Открыть выбранный файл PDF.
input_file = PdfFileReader(input_path)
output_pdf = PdfFileWriter()
# - Повернуть все страницы.
for page in input_file.pages:
page = page.rotateClockwise(degrees)
output_pdf.addPage(page)
# - Сохранить документ PDF с повернутыми страницами в выбранном файле.
with open(output_path, "wb") as output_file:
output_pdf.write(output_file)
EasyGUI отлично подходит для быстрого создания пользовательских интерфейсов небольших приложений и утилит. Для более крупных проектов возможностей EasyGUI может быть недостаточно. В таких случаях на помощь приходит
встроенная библиотека Python Tkinter (https://wiki.python.org/moin/TkInter).
Tkinter — GUI-фреймворк, работающий на более низком уровне, чем EasyGUI.
Это означает, что у вас больше возможностей для управления визуальными
18.3. Задача: приложение для извлечения страницы PDF 439
аспектами GUI: размером окна, размером шрифта, цветом шрифта и элементами
графического интерфейса, присутствующими в диалоговом или обычном окне.
Далее в этой главе я расскажу о разработке GUI-приложений со встроенной
библиотекой Python Tkinter.
Упражнение
В GUI-приложении для поворота страниц файла PDF существует проблема.
В программе происходит сбой, если пользователь закрывает окно buttonbox(),
используемое для выбора угла, не выбрав значение.
Исправьте этот недостаток: в цикле while продолжайте выводить диалоговое
окно выбора, пока переменная degrees остается равной None.
18.3. ЗАДАЧА: ПРИЛОЖЕНИЕ ДЛЯ ИЗВЛЕЧЕНИЯ
СТРАНИЦЫ PDF
Сейчас вы примените EasyGUI для написания GUI-приложения, извлекающего
страницы из файла PDF.
Подробный план:
1. Предложить пользователю выбрать файл PDF.
2. Если файл PDF не выбран, завершить программу.
3. Запросить начальный номер страницы.
4. Если пользователь не ввел начальный номер страницы, завершить программу.
5. Допустимыми номерами страниц являются положительные целые числа.
Если пользователь ввел недопустимый номер страницы:
ƒƒ предупредить пользователя о том, что введенное значение недопустимо;
ƒƒ вернуться к шагу 3.
6. Запросить конечный номер страницы.
7. Если пользователь не ввел конечный номер страницы, завершить программу.
8. Если пользователь ввел недопустимый номер страницы:
ƒƒ предупредить пользователя о том, что введенное значение недопустимо;
ƒƒ вернуться к шагу 6.
440 Глава 18 Графические интерфейсы
9. Запросить место (путь) для сохранения извлеченных страниц.
10. Если пользователь не выбрал место для сохранения, завершить программу.
11. Если выбранное место совпадает с путем входного файла:
ƒƒ предупредить пользователя о том, что перезапись входного файла
недопустима;
ƒƒ вернуться к шагу 9.
12. Выполнить извлечение страниц:
ƒƒ открыть входной файл PDF;
ƒƒ записать новый файл PDF, который содержит только страницы из
выбранного диапазона.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
18.4. ЗНАКОМСТВО С TKINTER
Для Python существует много GUI-фреймворков, но Tkinter — единственный
фреймворк, встроенный в стандартную библиотеку Python.
У Tkinter несколько сильных сторон. Прежде всего это кросс-платформенность,
то есть один и тот же код будет работать в Windows, macOS и Linux. Визуальные элементы строятся с использованием собственных элементов интерфейса
операционной системы, так что приложения, построенные на базе Tkinter,
выглядят так, словно они были написаны для той конкретной платформы, на
которой они выполняются.
Хотя Tkinter считается стандартным фреймворком Python GUI, он не лишен
недостатков. Один из наиболее заметных заключается в том, что графические
интерфейсы, построенные с использованием Tkinter, выглядят устаревшими.
Если вам нужен стильный, современный интерфейс, возможно, Tkinter — не
то, что вам подойдет.
Тем не менее Tkinter относительно компактен и прост в использовании по
сравнению с другими фреймворками. Это делает его привлекательным для
построения GUI-приложений на языке Python, особенно если современный
лоск вас не привлекает, а на первый план выходит быстрое построение функционального и кросс-платформенного решения.
Посмотрим, как строятся приложения на базе Tkinter.
18.4. Знакомство с Tkinter 441
ПРИМЕЧАНИЕ
Как я говорил в предыдущем разделе, среда IDLE построена на базе Tkinter.
Из-за этого у вас могут возникнуть трудности при запуске ваших собственных
GUI-программ в IDLE.
Если вы обнаружите, что окно GUI, которое вы пытаетесь создать, внезапно
зависает или IDLE начинает вести себя непредсказуемо, попробуйте запустить
программу из командной строки или терминала.
Ваше первое приложение на базе Tkinter
Основополагающим элементом графического интерфейса Tkinter является
окно. Окна — это контейнеры, в которых размещаются все остальные элементы
GUI. Другие элементы — текстовые поля, надписи, кнопки и т. д. — называются
виджетами. Виджеты размещаются внутри окон.
Создадим окно, содержащее один виджет. Откройте новое интерактивное окно
в IDLE.
Первое, что необходимо сделать, — импортировать модуль Tkinter:
>>> import tkinter as tk
Окно является экземпляром класса Tkinter Tk. Создайте новое окно и присвойте
его переменной window:
>>> window = tk.Tk()
При выполнении этого кода на экране появится новое окно. Его внешний вид
зависит от операционной системы.
Далее в этой главе я буду использовать скриншоты для Windows.
442 Глава 18 Графические интерфейсы
Теперь, когда у вас имеется окно, добавим в него виджет. Применим класс
tk.Label для добавления в окно текста.
Создайте виджет Label с текстом "Hello, Tkinter" и присвойте его переменной
с именем greeting:
>>> greeting = tk.Label(text="Hello, Tkinter")
Окно, созданное ранее, не изменяется. Вы только что создали виджет Label, но
он еще не был добавлен в окно.
Есть несколько способов добавления виджетов в окно. Сейчас мы воспользуемся
методом .pack() виджета Label:
>>> greeting.pack()
Окно принимает следующий вид:
Когда вы вызываете .pack(), Tkinter выбирает для окна минимальный размер,
при котором виджет все еще полностью помещается в окне.
Теперь выполните следующие команды:
>>> window.mainloop()
На первый взгляд, ничего не происходит, но обратите внимание: новое приглашение в оболочке не появляется.
ВАЖНО!
Когда вы работаете с Tkinter из оболочки REPL (например, интерактивного окна
IDLE), обновления в окнах происходят при выполнении каждой строки кода.
При выполнении программы Tkinter из файла Python этого не происходит.
Если вы не включите вызов window.mainloop() в конец программы в файле
Python, то приложение Tkinter выполнено не будет и на экран ничего не выведется.
Вызов window.mainloop() приказывает Python запустить приложение Tkinter
и блокирует выполнение всего последующего кода, пока не будет закрыто окно,
18.5. Работа с виджетами 443
для которого был сделан вызов. Закройте созданное окно, и в командной оболочке появится новое приглашение.
Чтобы создать окно средствами Tkinter, достаточно пары строк кода. Однако
от пустого окна пользы немного! В следующем разделе я познакомлю вас с некоторыми виджетами, доступными в Tkinter, и возможностями их настройки
в соответствии с потребностями приложений.
Упражнения
1. Используя Tkinter в интерактивном окне IDLE, создайте окно с виджетом
Label, в котором выводится текст "GUIs are great!".
2. Повторите упражнение 1 с текстом "Python rocks!".
3. Повторите упражнение 1 с текстом "Engage!".
18.5. РАБОТА С ВИДЖЕТАМИ
Виджеты — подлинная суть Tkinter. Они создают элементы, посредством которых пользователь взаимодействует с вашей программой.
Каждый виджет в Tkinter определяется классом. Несколько примеров доступных виджетов показаны ниже.
КЛАСС ВИДЖЕТА
ОПИСАНИЕ
Label
Виджет для вывода текста на экран
Button
Кнопка, которая может содержать текст и выполняет действие при нажатии
Entry
Виджет для ввода одной строки текста
Text
Виджет для ввода многострочного текста
Frame
Прямоугольная область, используемая для группировки
взаимо­связанных виджетов или для создания отступов
между виджетами
ПРИМЕЧАНИЕ
Tkinter содержит намного больше виджетов, чем перечислено в таблице. Полный список вы найдете в статьях «Basic Widgets» (https://tkdocs.com/tutorial/
widgets.html) и «More Widgets» (https://tkdocs.com/tutorial/morewidgets.html)
в учебном руководстве TkDocs.
444 Глава 18 Графические интерфейсы
О работе со всеми этими виджетами я расскажу в следующих разделах. Поближе
познакомимся с виджетом Label.
Виджеты Label
Виджеты Label используются для вывода текста или графики. Текст, выводимый в виджет Label, пользователь редактировать не может. Этот виджет
предназначен только для вывода.
Как было показано в примере в начале этой главы, чтобы создать виджет Label,
можно создать экземпляр класса Label и передать строку в параметре text:
label = tk.Label(text="Hello, Tkinter")
При выводе текста виджеты Label используют системный цвет текста и цвет
фона по умолчанию. Обычно это черный и белый цвета соответственно, но они
могут быть и другими, если вы изменили соответствующие настройки в своей
операционной системе.
Для управления цветами текста и фона для виджета Label используются параметры foreground и background:
label = tk.Label(
text="Hello, Tkinter",
foreground="white", # Выбрать белый цвет текста
background="black" # Выбрать черный цвет фона
)
Tkinter поддерживает много разных названий цветов, в том числе:
zz "red"
zz "orange"
zz "yellow"
zz "green"
zz "blue"
zz "purple"
ПРИМЕЧАНИЕ
Полный список цветов, включая системные цвета macOS и Windows, определяемые текущей темой ОС, доступен на сайте TkDocs (https://www.tcl.tk/man/
tcl8.6/TkCmd/colors.html).
18.5. Работа с виджетами 445
Многие названия цветов HTML (https://htmlcolorcodes.com/color-names/) также
подходят для Tkinter.
Цвета также возможно задавать шестнадцатеричными кодами RGB:
label = tk.Label(text="Hello, Tkinter", background="#34A2FE")
Эта команда назначает label приятный светло-синий цвет фона.
Шестнадцатеричные коды RGB менее понятны, чем текстовые названия
цветов, но открывают доступ к более широкой цветовой палитре. К счастью,
существуют средства (https://htmlcolorcodes.com/), значительно упрощающие
работу с шестнадцатеричными кодами цветов.
Если вы не хотите постоянно вводить названия foreground и background, используйте сокращенные имена fg и bg для назначения цветов текста и фона:
label = tk.Label(text="Hello, Tkinter", fg="white", bg="black")
Также можно управлять шириной и высотой надписи при помощи параметров
width и height:
label = tk.Label(
text="Hello, Tkinter",
fg="white",
bg="black",
width=10,
height=10
)
Вот как виджет Label будет выглядеть в окне:
446 Глава 18 Графические интерфейсы
Может показаться странным, что виджет в окне не квадратный, хотя и width,
и height присвоено значение 10. Это объясняется тем, что значения height
и width измеряются в текстовых единицах.
Одна горизонтальная текстовая единица определяется шириной символа 0
(цифра ноль) системного шрифта по умолчанию. Аналогичным образом одна
вертикальная текстовая единица определяется высотой этого же символа 0.
ПРИМЕЧАНИЕ
Для обеспечения последовательного поведения приложения на разных платформах Tkinter измеряет высоту и ширину в текстовых единицах вместо дюймов, сантиметров или пикселей.
Определение единиц по ширине символа означает, что размер виджета определяется шрифтом по умолчанию на машине пользователя. Это гарантирует,
что текст будет правильно размещаться в надписях и на кнопках независимо
от того, где выполняется приложение.
Виджеты Label отлично подходят для вывода текста, но они не годятся для
получения ввода от пользователя. Следующие три виджета, которые мы рассмотрим, предназначены для решения именно этой задачи.
Виджеты Button
Виджеты Button применяют для отображения кнопок, которые пользователь
может нажимать. Их можно настроить так, чтобы при щелчке на кнопке вызывалась заданная функция. О том, как вызывать функции при нажатии кнопки,
я расскажу в следующем разделе. А пока я покажу, как создать виджет Button
и применить к нему стилевое оформление.
Между виджетами Button и Label существует определенное сходство. Во многих
отношениях виджет Button — это всего лишь виджет Label, на котором можно
щелкать мышью! Ключевые аргументы, используемые при создании и стилевом
оформлении Label, работают и с виджетами Button. Например, следующий
фрагмент создает виджет Button с синим фоном, желтым текстом и шириной
и высотой 25 и 5 текстовых единиц соответственно:
button = tk.Button(
text="Click me!",
width=25,
height=5,
bg="blue",
fg="yellow",
)
18.5. Работа с виджетами 447
А вот как виджет Button выглядит в окне:
Неплохо!
ПРИМЕЧАНИЕ
Фоны кнопок не работают в macOS. Это ограничение операционной системы,
а не ошибка в Tkinter.
Следующие два виджета, о которых речь пойдет ниже, предназначены для
получения текстового ввода от пользователя.
Виджеты Entry
Если вам нужно получить от пользователя небольшой фрагмент текста (например, имя или адрес электронной почты), используйте виджет Entry. Он выводит
поле, в котором пользователь может печатать текст.
Виджеты Entry создаются практически так же, как Label и Button. Например,
следующая команда создает виджет с синим фоном, желтым текстом и шириной
50 текстовых единиц:
entry = tk.Entry(fg="yellow", bg="blue", width=50)
Впрочем, в виджетах Entry представляет интерес вовсе не оформление, а возможность их использования для получения ввода от пользователя. С виджетами
Entry можно выполнять три основные операции.
1. Получение текста методом .get().
2. Удаление текста методом .delete().
3. Вставка текста методом .insert().
Если вы хотите освоить виджеты Entry, лучше всего создать их и поэкспериментировать с ними. Откройте интерактивное окно IDLE и повторите примеры,
приведенные в этом разделе.
448 Глава 18 Графические интерфейсы
Для начала импортируйте tkinter и создайте новое окно:
>>> import tkinter as tk
>>> window = tk.Tk()
Затем создайте виджеты Label и Entry:
>>> label = tk.Label(text="Name")
>>> entry = tk.Entry()
Виджет Label описывает, какой текст должен быть введен в виджете Entry. Он
не устанавливает никаких требований, а лишь сообщает пользователю, какие
данные нужно здесь ввести с точки зрения вашей программы.
Далее необходимо вызвать .pack() для виджетов в окне, чтобы они стали видимыми:
>>> label.pack()
>>> entry.pack()
Результат выглядит так:
Tkinter автоматически выравнивает Label по центру над виджетом Entry в окне.
Это особенность метода .pack(), о которой вы узнаете позднее.
Щелкните внутри виджета Entry и введите "Real Python".
В виджете Entry введен текст, но он еще не был передан программе.
Используйте метод .get() виджета Entry, чтобы получить текст и присвоить
его переменной с именем name:
>>> name = entry.get()
>>> name
'Real Python'
18.5. Работа с виджетами 449
Текст можно удалить методом .delete() виджета Entry. Целочисленный аргумент, передаваемый .delete(), сообщает методу, какой символ следует удалить.
Например, .delete(0) удаляет из Entry первый символ:
>>> entry.delete(0)
В виджете остается текст "eal Python".
ПРИМЕЧАНИЕ
Символы текста в виджетах Entry, как и символы строковых объектов Python,
индексируются с нуля.
Если потребуется удалить из Entry сразу несколько символов, передайте
.delete() второй целочисленный аргумент с индексом символа, на котором
удаление должно остановиться.
Например, следующая команда удаляет первые четыре буквы измененного
текста в виджете Entry:
>>> entry.delete(0, 4)
Команда удаляет символы "e", "a" и "l" с последующим пробелом. В виджете
остается текст "Python".
ПРИМЕЧАНИЕ
Entry.delete() работает так же, как и срезы строк. Первый аргумент определяет
начальный индекс, а символы удаляются до индекса, переданного во втором
аргументе, — не включая его.
450 Глава 18 Графические интерфейсы
Чтобы удалить весь текст в Entry, передайте специальную константу tk.END во
втором аргументе .delete():
>>> entry.delete(0, tk.END)
Текстовое поле остается пустым:
Вставка текста в виджет Entry выполняется методом .insert():
>>> entry.insert(0, "Python")
Окно выглядит так:
Первый аргумент сообщает .insert(), в какой позиции должен быть вставлен
текст. Если в виджете Entry нет текста, новый текст всегда вставляется в начале
виджета независимо от того, какое значение передается во втором аргументе.
Например, если в приведенном выше вызове .insert() передать в первом аргументе 100 вместо 0, результат окажется тем же самым.
Если виджет Entry уже содержит текст, .insert() вставляет новый текст в заданной позиции и сдвигает весь существующий текст вправо:
>>> entry.insert(0, "Real ")
Теперь виджет содержит текст "Real Python".
18.5. Работа с виджетами 451
Виджеты Entry хорошо подходят для получения небольших фрагментов текста
от пользователя, но так как этот текст всегда отображается в одной строке, они
хуже подходят для получения больших объемов текста. На помощь приходят
виджеты Text.
Виджеты Text
Виджеты Text используются для ввода текста, как и виджеты Entry. Различия
в том, что первые могут содержать несколько строк текста. С виджетом Text
пользователь может вводить целые абзацы — и даже целые страницы!
Виджеты Text, как и виджеты Entry, поддерживают три основные операции:
1) получение текста методом .get();
2) удаление текста методом .delete();
3) вставка текста методом .insert().
И хотя имена методов совпадают с именами методов Entry, работают они немного иначе. Проверим их на практике — создадим виджет Text и посмотрим,
на что он способен.
ПРИМЕЧАНИЕ
Если окно из предыдущего раздела все еще открыто, вы можете закрыть его
следующей командой в интерактивном окне IDLE:
>>> window.destroy()
Также окно можно закрыть вручную, щелкнув на кнопке X в заголовке окна.
В интерактивном окне IDLE создайте новое пустое окно и включите в него
виджет Text вызовом .pack():
>>> window = tk.Tk()
>>> text_box = tk.Text()
>>> text_box.pack()
На экране должно появиться окно с текстовым полем. Щелкните в любой точке
окна, чтобы активизировать текстовое поле. Введите слово "Hello", нажмите
Enter и введите во второй строке слово "World".
Окно должно выглядеть примерно так:
452 Глава 18 Графические интерфейсы
Текст виджета Text, как и текст виджета Entry, можно получить вызовом .get().
Однако вызов .get() без аргументов не возвращает весь текст в текстовом поле,
как это делается для виджетов Entry. Он выдает исключение:
>>> text_box.get()
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
text_box.get()
TypeError: get() missing 1 required positional argument: 'index1'
Метод Text.get() получает как минимум один аргумент. Вызов .get() с одним
индексом возвращает один символ. Для получения нескольких символов необходимо передать два аргумента: начальный и конечный индекс.
Индексы виджетов Text отличаются от индексов виджетов Entry. Так как виджеты Text могут содержать многострочный текст, индекс должен содержать
два значения:
1) номер строки;
2) позицию символа в этой строке.
Номера строк начинаются с 1, а позиции символов — с 0.
Индекс строится в виде строки вида "<строка>.<символ>" (<строка> заменяется
номером строки, а <символ> — номером символа).
Например, индекс "1.0" представляет первый символ первой строки, а "2.3" —
четвертый символ второй строки.
18.5. Работа с виджетами 453
Используем индекс "1.0" для получения первой буквы первой строки текстового поля, созданного ранее:
>>> text_box.get("1.0")
'H'
Так как индексы символов начинаются с 0, а слово "Hello" начинается в первой
позиции текстового поля, индекс буквы o равен 4. Как и в случае со срезами
строк Python, для извлечения всего слова Hello из текстового поля конечный
индекс должен быть на 1 больше индекса последнего читаемого символа.
Таким образом, чтобы получить из текстового поля все слово "Hello", следует
использовать первый индекс "1.0" и второй индекс "1.5":
>>> text_box.get("1.0", "1.5")
'Hello'
Чтобы получить слово "World" во второй строке текстового поля, измените
номер строки в каждом индексе на 2:
>>> text_box.get("2.0", "2.5")
'World'
Чтобы получить весь текст из текстового поля, передайте начальный индекс
"1.0" и специальную константу tk.END вместо второго индекса:
>>> text_box.get("1.0", tk.END)
'Hello\nWorld\n'
Обратите внимание: текст, возвращаемый .get(), включает символы новой
строки. Пример также показывает, что каждая строка в виджете Text, включая
последнюю, завершается символом новой строки.
Метод .delete() используется для удаления символов из текстового поля. Он
работает так же, как и метод .delete() для виджетов Entry.
Метод .delete() может использоваться двумя способами:
1) с одним аргументом;
2) с двумя аргументами.
В версии с одним аргументом .delete() передается индекс одного удаляемого
символа. Например, следующая команда удаляет из текстового поля первый
символ H:
>>> text_box.delete("1.0")
454 Глава 18 Графические интерфейсы
В результате в первой строке остаются символы "ello":
В версии с двумя аргументами передаются два индекса для удаления диапазона
символов, от символа с первым индексом и до символа со вторым индексом
(не включая последний).
Например, чтобы удалить оставшиеся символы "ello" из первой строки текстового поля, используйте индексы "1.0" и "1.4":
>>> text_box.delete("1.0", "1.4")
Текст в первой строке исчезает. На его месте остается пустая строка, а за ней
следует слово "World" во второй строке:
18.5. Работа с виджетами 455
При этом в первой строке остается символ, хотя он и не виден, — символ новой
строки.
В этом можно убедиться при помощи метода .get():
>>> text_box.get("1.0")
'\n'
Если удалить этот символ, то остальное содержимое текстового поля сдвигается
вверх на одну строку:
>>> text_box.delete("1.0")
Теперь слово "World" выводится в первой строке текстового поля:
Сотрем остальной текст из текстового поля. В первом аргументе передается
значение "1.0", а во втором — tk.END:
>>> text_box.delete("1.0", tk.END)
Текстовое поле становится пустым.
Для вставки текста в текстовое поле используется метод .insert():
>>> text_box.insert("1.0", "Hello")
456 Глава 18 Графические интерфейсы
В начале текстового поля появляется слово "Hello":
Посмотрим, что произойдет при попытке вставить слово "World" во вторую
строку:
>>> text_box.insert("2.0", "World")
Однако текст вставляется не во второй строке, а в конец первой строки:
Если вы хотите вставить текст в новую строку, придется вручную добавить
символ новой строки во вставляемый текст:
>>> text_box.insert("2.0", "\nWorld")
18.5. Работа с виджетами 457
Теперь слово "World" оказывается во второй строке текстового поля:
Таким образом, .insert() либо вставляет текст в заданной позиции, если в ней
уже есть текст, либо присоединяет его к заданной строке, если количество символов больше индекса последнего символа в текстовом поле.
Отслеживать индекс последнего символа обычно неудобно. Лучший способ
вставки текста в конец виджета Text — передача tk.END в первом параметре
.insert():
text_box.insert(tk.END, "Put me at the end!")
Не забудьте включить символ новой строки \n в начало текста, если вы хотите
вывести его в новой строке:
text_box.insert(tk.END, "\nPut me on a new line!")
Label, Button, Entry и Text — всего лишь малая часть виджетов, доступных
в Tkinter. Также существуют виджеты для флажков, переключателей, полос
прокрутки и индикаторов прогресса. Дополнительную информацию о других
доступных виджетах вы найдете в учебнике на сайте TkDocs.com (https://tkdocs.
com/tutorial/widgets.html).
В этой главе мы будем работать только с пятью виджетами — с четырьмя вы уже
познакомились, а сейчас пришло время для пятого — Frame. Виджеты Frame играют важную роль в организации расположения виджетов в вашем приложении.
Прежде чем рассказывать о формировании визуального представления виджетов, я покажу, как работают виджеты Frame и как связать их с другими
виджетами.
458 Глава 18 Графические интерфейсы
Связывание виджетов с Frame
Следующая программа создает пустой виджет Frame и связывает его с главным
окном приложения:
import tkinter as tk
window = tk.Tk()
frame = tk.Frame()
frame.pack()
window.mainloop()
Вызов frame.pack() упаковывает фрейм в окно так, чтобы оно имело минимально возможный размер, но при этом вмещало фрейм.
При выполнении приведенного выше кода вы получите абсолютно неинтересный результат:
Пустой виджет Frame практически невидим. Фреймы лучше всего рассматривать как контейнеры для других виджетов. Чтобы связать виджет с фреймом,
задайте значение атрибута master виджета:
frame = tk.Frame()
label = tk.Label(master=frame)
Чтобы вы получили представление о том, как это работает, напишем программу,
которая создает два виджета Frame с именами frame_a и frame_b. Фрейм frame_a
должен содержать виджет Label с текстом "I'm in Frame A", а frame_b — виджет
Label с текстом "I'm in Frame B". Одно из возможных решений выглядит так:
import tkinter as tk
window = tk.Tk()
frame_a = tk.Frame()
frame_b = tk.Frame()
label_a = tk.Label(master=frame_a, text="I'm in Frame A")
label_a.pack()
label_b = tk.Label(master=frame_b, text="I'm in Frame B")
label_b.pack()
frame_a.pack()
frame_b.pack()
window.mainloop()
18.5. Работа с виджетами 459
Обратите внимание: frame_a упаковывается в окне до frame_b. В открывшемся
окне виджет Label в frame_a располагается над виджетом Label в frame_b.
Посмотрим, что произойдет, если поменять местами вызовы frame_a.pack()
и frame_b.pack():
import tkinter as tk
window = tk.Tk()
frame_a = tk.Frame()
label_a = tk.Label(master=frame_a, text="I'm in Frame A")
label_a.pack()
frame_b = tk.Frame()
label_b = tk.Label(master=frame_b, text="I'm in Frame B")
label_b.pack()
# `frame_a` и `frame_b` меняются местами
frame_b.pack()
frame_a.pack()
window.mainloop()
Результат выглядит так:
Теперь виджет label_b располагается сверху. Так как виджет label_b был связан
с frame_b, он перемещается каждый раз, когда вы перемещаете frame_b.
Все четыре типа виджетов, о которых вы узнали, — Label, Button, Entry и Text —
поддерживают атрибут master, который задается при создании экземпляра. Он
позволяет управлять тем, с каким фреймом связывается виджет.
Виджеты Frame очень удобны для логического упорядочения других виджетов.
Взаимосвязанные виджеты можно связать с одним фреймом, чтобы при перемещении фрейма в окне все эти виджеты оставались в одной группе.
460 Глава 18 Графические интерфейсы
Кроме логической группировки виджетов, Frame могут добавить немного лоска
внешнему облику вашего приложения. Далее вы узнаете, как создавать различные визуальные эффекты при отображении виджетов Frame.
Изменение внешнего вида фреймов
с использованием атрибута relief
Виджеты Frame можно настроить атрибутом relief, который создает визуальный эффект вокруг фрейма. Атрибуту relief можно присвоить любое из
следующих значений:
zz tk.FLAT — визуальный эффект отсутствует (значение по умолчанию);
zz tk.SUNKEN — эффект углубления;
zz tk.RAISED — эффект возвышения;
zz tk.GROOVE — эффект выемки;
zz tk.RIDGE — эффект гребня.
Чтобы применить визуальный эффект, необходимо присвоить атрибуту
borderwidth значение больше 1. Этот атрибут задает ширину обрамления
в пикселях.
Чтобы понять, что делает каждый эффект relief , проще всего увидеть их
в действии. Следующая программа упаковывает в окне пять виджетов Frame
с разными значениями аргумента relief:
import tkinter as tk
border_effects = {
"flat": tk.FLAT,
"sunken": tk.SUNKEN,
"raised": tk.RAISED,
"groove": tk.GROOVE,
"ridge": tk.RIDGE,
}
window = tk.Tk()
for relief_name, relief in border_effects.items():
# 1
frame = tk.Frame(master=window, relief=relief, borderwidth=5)
# 2
frame.pack(side=tk.LEFT)
# 3
label = tk.Label(master=frame, text=relief_name)
label.pack()
window.mainloop()
18.5. Работа с виджетами 461
Выполним этот код в несколько этапов.
Сначала мы создаем словарь и присваиваем его переменной border_effects.
Ключами словаря являются названия разных эффектов, доступных в Tkinter,
а значениями — соответствующие объекты Tkinter.
После создания объекта window цикл for используется для перебора всех элементов словаря border_effects. При каждой итерации цикла выполняются
три операции.
1. Создается новый виджет Frame, который связывается с объектом window.
Атрибуту relief присваивается соответствующий визуальный эффект
из словаря border_effects, а атрибуту border присваивается значение 5,
чтобы эффект был заметен.
2. Виджет Frame размещается в окне вызовом .pack(). Ключевой аргумент
side сообщает Tkinter, в каком направлении должны упаковываться объекты frame. О том, как это происходит, мы расскажем в следующем разделе.
3. Создается виджет Label для вывода названия эффекта и упаковывается
в только что созданный объект frame.
Окно, которое создается при выполнении приведенного выше кода, выглядит
примерно так:
На иллюстрации показаны примеры всех рельефных эффектов:
zz tk.FLAT создает плоское обрамление (отсутствие визуального эффекта);
zz tk.SUNKEN добавляет эффект «погружения» фрейма в окно;
zz tk.RAISED добавляет эффект, когда фрейм кажется выступающим из
экрана;
zz tk.GROOVE создает эффект углубленной борозды, окружающей плоский
фрейм;
zz tk.RIDGE создает эффект приподнятой кромки, окружающей фрейм.
Правила именования виджетов
При создании виджету можно присвоить любое имя, если оно является
валидным идентификатором Python. Тем не менее на практике имя класса
462 Глава 18 Графические интерфейсы
виджета часто включается в имя переменной, которой присваивается экземп­
ляр виджета.
Например, если виджет Label используется для вывода имени пользователя,
ему можно присвоить имя label_user_name. А виджет Entry, предназначенный
для ввода возраста пользователя, можно называть entry_user_age.
Включая имя класса виджета в имя переменной, вы помогаете понять любому
читателю вашего кода, к какому типу виджета относится имя переменной.
Включение полного имени класса виджета может привести к появлению длинных имен переменных, поэтому для типов виджетов стоит ввести сокращенные
обозначения. Далее в этой главе в именах виджетов мы будем использовать
следующие префиксы.
КЛАСС ВИДЖЕТА
ПРЕФИКС
ПРИМЕР
Label
lbl
lbl_name
Button
btn
btn_submit
Entry
ent
ent_age
Text
txt
txt_notes
Frame
frm
frm_address
В этом разделе я показал, как создавать окна, использовать виджеты и работать
с фреймами. На данный момент вы умеете создавать простые окна, в которых
выводятся сообщения, но до полноценных приложений дело еще не дошло.
В следующем разделе вы научитесь управлять макетом ваших приложений при
помощи топологических менеджеров Tkinter.
Упражнения
1. Попробуйте воссоздать скриншоты из этого раздела, не подглядывая в исходный код. Если вы зайдете в тупик, загляните в код и завершите упражнение. Затем подождите 10–15 минут и попробуйте снова. Повторяйте,
пока не сможете проделать все это самостоятельно. Сосредоточьтесь на
выводе. Если ваш код будет слегка отличаться от приведенного в книге,
это абсолютно нормально.
2. Напишите программу, которая выводит виджет Button шириной 50 текстовых единиц и высотой 25 текстовых единиц. Виджет должен иметь
белый фон с синим текстом "Click here".
3. Напишите программу для вывода виджета Entry шириной 40 текстовых
единиц, с белым фоном и черным текстом. Используйте метод .insert()
для вставки в виджет Entry текста "What is your name?".
18.6. Управление макетом при помощи менеджеров геометрии 463
18.6. УПРАВЛЕНИЕ МАКЕТОМ ПРИ ПОМОЩИ
МЕНЕДЖЕРОВ ГЕОМЕТРИИ
До настоящего момента мы добавляли виджеты в окна и виджеты Frame вызовом
.pack(), но вы пока не знаете, что именно делает этот метод. Давайте разберемся!
В Tkinter разработчик управляет размещением виджетов в окне приложения
при помощи менеджеров геометрии. Метод .pack() является примером менеджера геометрии, но это не единственный представитель этого типа. Кроме него,
в Tkinter также есть два других метода: .place() и .grid().
Каждое окно и каждый виджет Frame в приложении может использовать только
один менеджер геометрии. Однако разные фреймы могут применять разные
менеджеры, даже если они связываются с фреймом или окном с использованием
другого менеджера геометрии.
Для начала поближе познакомимся с .pack().
Менеджер геометрии .pack()
Метод .pack() использует специальный алгоритм для размещения виджетов
во фрейме или окне в определенном порядке. Алгоритм упаковки состоит из
двух основных этапов.
1. Алгоритм вычисляет прямоугольную область с минимальной высотой
(или шириной), достаточной для размещения виджета, и заполняет
оставшуюся ширину (или высоту) окна пустым пространством.
2. Если вы не задали другой способ размещения, виджет выравнивается
по центру области.
Метод .pack() очень мощный, но его трудно представить себе наглядно. Понять,
как он работает, лучше всего на примерах.
Посмотрим, что происходит при размещении трех виджетов Label в виджете
Frame вызовами .pack():
import tkinter as tk
window = tk.Tk()
frame1 = tk.Frame(master=window, width=100, height=100, bg="red")
frame1.pack()
frame2 = tk.Frame(master=window, width=50, height=50, bg="yellow")
frame2.pack()
464 Глава 18 Графические интерфейсы
frame3 = tk.Frame(master=window, width=25, height=25, bg="blue")
frame3.pack()
window.mainloop()
По умолчанию .pack() размещает каждый виджет Frame под предыдущим в том
порядке, в котором они связывались с окном.
Каждый виджет Frame размещается в высшей доступной позиции. Красный
виджет Frame размещается у верхнего края окна. Желтый виджет Frame размещается непосредственно под красным, а синий — под желтым.
Существуют три невидимые области, каждая из которых содержит один из трех
виджетов Frame. Каждая область имеет ширину окна и высоту виджета Frame,
который в ней содержится. Так как при вызове .pack() для каждого виджета
Frame якорная точка не указывалась, все они выравниваются по центру своих
областей, а следовательно, по центру окна.
Метод .pack() получает ключевые аргументы, позволяющие точнее настроить расположение виджетов. Например, ключевой аргумент fill позволяет
указать, в каком направлении должны заполняться фреймы. Поддерживаются
три значения:
1) tk.X — заполнение в горизонтальном направлении;
2) tk.Y — заполнение в вертикальном направлении;
3) tk.BOTH — заполнение в обоих направлениях.
В следующем примере три фрейма размещаются так, чтобы каждый заполнял
все окно по горизонтали:
18.6. Управление макетом при помощи менеджеров геометрии 465
import tkinter as tk
window = tk.Tk()
frame1 = tk.Frame(master=window, height=100, bg="red")
frame1.pack(fill=tk.X)
frame2 = tk.Frame(master=window, height=50, bg="yellow")
frame2.pack(fill=tk.X)
frame3 = tk.Frame(master=window, height=25, bg="blue")
frame3.pack(fill=tk.X)
window.mainloop()
Обратите внимание: для виджетов Frame значение width более не задается. Оно
не нужно, потому что .pack() настраивается на горизонтальное заполнение
в каждом фрейме, с переопределением любой заданной ширины.
Окно, созданное этим фрагментом, выглядит так:
Одно из преимуществ заполнения окна методом .pack() заключается в том, что
заполнение реагирует на изменение размеров окна. Чтобы понять, как это происходит, попробуйте увеличить ширину окна, сгенерированного приведенным
выше кодом.
При увеличении ширины окна ширина трех виджетов Frame увеличивается для
заполнения окна. Однако следует заметить, что виджеты Frame не расширяются
в вертикальном направлении.
Ключевой аргумент side метода .pack() указывает, с какой стороны окна должен размещаться виджет. Допустимые варианты — tk.TOP, tk.BOTTOM, tk.LEFT
и tk.RIGHT. Если сторона не задана, .pack() автоматически использует tk.TOP
и размещает новые виджеты у верхнего края окна или в самой верхней части
окна, которая еще не занята виджетом.
466 Глава 18 Графические интерфейсы
Например, следующая программа размещает три фрейма рядом друг с другом
слева направо и расширяет каждый фрейм, чтобы он заполнял окно по вертикали:
import tkinter as tk
window = tk.Tk()
мframe1 = tk.Frame(master=window, width=200, height=100, bg="red")
frame1.pack(fill=tk.Y, side=tk.LEFT)
frame2 = tk.Frame(master=window, width=100, bg="yellow")
frame2.pack(fill=tk.Y, side=tk.LEFT)
frame3 = tk.Frame(master=window, width=50, bg="blue")
frame3.pack(fill=tk.Y, side=tk.LEFT)
window.mainloop()
На этот раз нам приходится задать ключевой аргумент height по крайней мере
для одного из фреймов, чтобы задать высоту окна.
Полученное окно выглядит так:
Подобно тому как присваивание fill=tk.X меняло ширину фреймов при изменении ширины окна, присваивание fill=tk.Y меняет высоту фреймов при
изменении размеров окна по вертикали. Убедитесь сами!
Чтобы макет полноценно реагировал на корректировку размеров, можно задать исходный размер ваших фреймов при помощи атрибутов width и height.
Затем задайте ключевому аргументу fill метода .pack() значение tk.BOTH,
а ключевому аргументу expand — значение True:
import tkinter as tk
window = tk.Tk()
frame1 = tk.Frame(master=window, width=200, height=100, bg="red")
frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
frame2 = tk.Frame(master=window, width=100, bg="yellow")
frame2.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
18.6. Управление макетом при помощи менеджеров геометрии 467
frame3 = tk.Frame(master=window, width=50, bg="blue")
frame3.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
window.mainloop()
При выполнении этого кода появляется окно, которое изначально похоже на
созданное в предыдущем примере. Различие в том, что теперь при изменении
размеров окна фреймы будут расширяться и заполнять окно соответствующим
образом. Впечатляет!
Менеджер геометрии .place()
Метод .place() позволяет управлять точным расположением виджета в окне
или фрейме. При вызове метода должны передаваться два ключевых аргумента x и y, определяющих координаты левого верхнего угла виджета. Значения x и y
задаются в пикселях, а не в текстовых единицах.
Помните, что начало координат (точка, для которой координаты x и y равны 0)
располагается в левом верхнем углу фрейма или окна. Аргумент y метода
.place() можно рассматривать как расстояние в пикселях от верхнего края
окна, а аргумент x — как расстояние в пикселях от левого края.
Пример использования менеджера геометрии .place():
import tkinter as tk
window = tk.Tk()
# 1
frame = tk.Frame(master=window, width=150, height=150)
frame.pack()
# 2
label1 = tk.Label(master=frame, text="I'm at (0, 0)", bg="red")
label1.place(x=0, y=0)
# 3
label2 = tk.Label(master=frame, text="I'm at (75, 75)", bg="yellow")
label2.place(x=75, y=75)
window.mainloop()
Сначала создается новый виджет Frame с именем frame, имеющий 150 пикселей
в ширину и 150 пикселей в высоту, который размещается в окне вызовом .pack().
Затем создается элемент Label с красным фоном, которому присваивается имя
label1; он размещается в frame1 в позиции (0, 0). Наконец, создается второй
виджет Label c желтым фоном, которому присваивается имя label2, и он размещается в frame1 в позиции (75, 75).
468 Глава 18 Графические интерфейсы
Окно, созданное этим кодом, показано ниже:
Метод .place() используется не так часто. Он имеет два основных недостатка:
1. .place() усложняет управление макетами, особенно если в вашем приложении используется множество виджетов.
2. Макеты, созданные вызовом .place(), не динамичны: они не изменяются
при изменении размеров окна.
Одна из основных трудностей разработки кросс-платформенных графических
интерфейсов связана с созданием макетов, которые выглядят хорошо независимо
от платформы. В большинстве случаев .place() плохо подходит для создания
динамичных кросс-платформенных макетов.
Это не означает, что .place() вообще не стоит использовать. В каких-то случаях
это именно то, что вам нужно. Например, когда вы создаете графический интерфейс для интерактивной карты, .place() может оказаться идеальным вариантом
для размещения виджетов на правильном расстоянии друг от друга на карте.
Метод .pack() обычно работает лучше .place(), но даже у .pack() есть свои недостатки. Например, размещение виджетов зависит от порядка вызова .pack(),
что затрудняет изменение существующих приложений без полного понимания
кода, управляющего макетом.
Как будет показано в следующем разделе, менеджер геометрии .grid() решает
многие из этих проблем.
Менеджер геометрии .grid()
Вероятно, чаще всего вы будете использовать менеджер геометрии .grid().
Он предоставляет всю мощь .pack() в формате, более простом для понимания
и сопровождения.
18.6. Управление макетом при помощи менеджеров геометрии 469
Работа .grid() основана на разбиении окна или фрейма на строки и столбцы.
Вы указываете местоположение виджета, вызывая .grid(), и передаете индексы
строки и столбца в ключевых аргументах row и column соответственно. Индексы
строк и столбцов начинаются с 0, поэтому индекс строки 1 и индекс столбца
2 приказывают .grid() разместить виджет в третьем столбце второй строки.
Например, следующий код создает сетку 3×3 из фреймов, в которых упакованы
виджеты Label:
import tkinter as tk
window = tk.Tk()
for i in range(3):
for j in range(3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack()
window.mainloop()
Построенное окно выглядит так.
В этом примере используются два менеджера геометрии. Каждый виджет Frame
связывается с окном при помощи менеджера .grid(), а каждый виджет Label
связывается со своим фреймом-контейнером вызовом .pack().
Здесь важно понимать, что, хотя .grid() вызывается для объектов Frame, менеджер геометрии применяется к объекту window. Аналогичным образом расположением каждого объекта frame управляет менеджер геометрии .pack().
470 Глава 18 Графические интерфейсы
Фреймы в предыдущем примере размещаются вплотную друг к другу. Чтобы
добавить немного свободного места вокруг каждого виджета Frame, можно назначить отступы для каждой ячейки сетки. Отступ представляет собой пустое
место вокруг виджета, визуально отделяющее его от содержимого.
Существуют два типа отступов: внешние и внутренние. Внешние отступы добавляют свободное место вокруг внешнего контура ячейки. Для управления
ими используются два ключевых аргумента .grid():
1) padx добавляет отступы в горизонтальном направлении;
2) pady добавляет отступы в вертикальном направлении.
Значения padx и pady измеряются в пикселях, а не в текстовых единицах, поэтому одинаковые значения создают одинаковые отступы в обоих направлениях.
Добавим внешние отступы вокруг фреймов из предыдущего примера:
import tkinter as tk
window = tk.Tk()
for i in range(3):
for j in range(3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j, padx=5, pady=5)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack()
window.mainloop()
Получаем окно следующего вида:
18.6. Управление макетом при помощи менеджеров геометрии 471
Метод .pack() тоже содержит параметры padx и pady. Следующий фрагмент
почти идентичен предыдущему, не считая того, что вокруг каждого виджета Label
создаются дополнительные отступы размером 5 пикселей в направлениях x и y:
import tkinter as tk
window = tk.Tk()
for i in range(3):
for j in range(3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j, padx=5, pady=5)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack(padx=5, pady=5)
window.mainloop()
Дополнительные отступы вокруг виджетов Label создают в каждой ячейке сетки
немного свободного места между границей Frame и текстом Label.
Симпатично выглядит! Но если вы попробуете расширить окно в каком-либо
направлении, то увидите, что макет плохо реагирует на изменения. При увеличении окна вся сетка остается в левом верхнем углу.
Чтобы управлять размерами строк и столбцов сетки при изменении размеров
окна, используйте методы .columnconfigure() и .rowconfigure() окна window.
Помните, что сетка связана с окном несмотря на то, что .grid() вызывается
для каждого виджета Frame.
472 Глава 18 Графические интерфейсы
Как .columnconfigure(), так и .rowconfigure() получают три необходимых
аргумента:
1) индекс столбца или строки, который вы хотите настроить (или список
индексов для настройки нескольких строк или столбцов);
2) ключевой аргумент weight, определяющий реакцию столбца или строки
на изменение размеров окна относительно других столбцов и строк;
3) ключевой аргумент minsize, задающий минимальную высоту строки или
ширину столбца в пикселях.
Ключевой аргумент weight (вес) по умолчанию задает значение 0, которое
означает, что столбец или строка не расширяется при изменении размеров
окна. Если каждому столбцу или строке назначен вес 1, все они увеличиваются
в одинаковых пропорциях. Если одному столбцу назначен вес 1, а другому — вес
2, то второй столбец будет увеличиваться вдвое быстрее первого.
Откорректируем предыдущий код, чтобы он лучше обрабатывал изменение
размеров окна:
import tkinter as tk
window = tk.Tk()
for i in range(3):
window.columnconfigure(i, weight=1, minsize=75)
window.rowconfigure(i, weight=1, minsize=50)
for j in range(0, 3):
frame = tk.Frame(
master=window,
relief=tk.RAISED,
borderwidth=1
)
frame.grid(row=i, column=j, padx=5, pady=5)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack(padx=5, pady=5)
window.mainloop()
Методы .columnconfigure() и .rowconfigure() размещаются в теле внешнего
цикла for. Каждый столбец и каждую строку можно настроить явно за пределами цикла for, но для этого потребуется написать шесть лишних строк кода.
При каждой итерации цикла i-е столбец и строка настраиваются с весом weight,
равным 1. Это гарантирует, что каждая строка и каждый столбец расширяются
с одинаковой скоростью при изменении размеров окна.
18.6. Управление макетом при помощи менеджеров геометрии 473
Аргументу minsize присваивается значение 75 для каждого столбца и 50 для
каждой строки. Это гарантирует, что виджет Label всегда выводит текст без отсечения символов даже при очень малом размере окна. Попробуйте выполнить
код, чтобы получить представление о том, как он работает. Поэкспериментируйте с параметрами weight и minsize и посмотрите, как они влияют на сетку.
По умолчанию виджеты выравниваются по центру своих ячеек. Например,
следующий код создает два виджета Label и размещает их в сетке с одним
столбцом и двумя строками:
import tkinter as tk
window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)
label1 = tk.Label(text="A")
label1.grid(row=0, column=0)
label2 = tk.Label(text="B")
label2.grid(row=1, column=0)
window.mainloop()
Каждая ячейка таблицы — 250 пикселей в ширину и 100 пикселей в высоту.
Виджеты Label размещаются в центре каждой ячейки, как видно из следующей
иллюстрации:
Вы можете изменить расположение каждого виджета Label внутри ячейки сетки
при помощи параметра sticky метода .grid(). Параметру sticky присваивается
строка, содержащая одну или несколько букв:
474 Глава 18 Графические интерфейсы
zz "n" или "N" для выравнивания по центру верхней стороны ячейки;
zz "s" или "S"* для выравнивания по центру нижней стороны ячейки;
zz "e" или "E" для выравнивания по центру правой стороны ячейки;
zz "w" или "W" для выравнивания по центру левой стороны ячейки.
Буквы "n", "s", "e" и "w" происходят от названий сторон света (North, South,
East и West — север, юг, восток и запад).
Например, если присвоить sticky значение "n" для обоих виджетов Label
в предыдущем фрагменте, каждый виджет Label будет расположен в середине
верхней стороны ячейки:
import tkinter as tk
window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)
label1 = tk.Label(text="A")
label1.grid(row=0, column=0, sticky="n")
label2 = tk.Label(text="B")
label2.grid(row=1, column=0, sticky="n")
window.mainloop()
Результат показан ниже:
18.6. Управление макетом при помощи менеджеров геометрии 475
Чтобы разместить каждый виджет Label в углу ячейки, объедините несколько
букв в одной строке:
import tkinter as tk
window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)
label1 = tk.Label(text="A")
label1.grid(row=0, column=0, sticky="ne")
label2 = tk.Label(text="B")
label2.grid(row=1, column=0, sticky="sw")
window.mainloop()
В этом примере параметру sticky виджета label1 присваивается значение
"ne", при котором виджет размещается в правом верхнем углу ячейки. label2
размещается в левом нижнем углу, для чего sticky присваивается строка "sw".
Вот как это выглядит в окне:
При позиционировании виджета с использованием sticky задается минимальный размер виджета, которого достаточно, чтобы он вместил текст и любой
другой контент, находящийся внутри него. Виджет не заполняет всю ячейку.
Чтобы виджет заполнил ячейку, укажите значение "ns" (виджет заполнит ячейку по вертикали) или "ew" (виджет заполнит ячейку по горизонтали). Чтобы
заполнить всю ячейку, присвойте sticky значение "nsew".
476 Глава 18 Графические интерфейсы
Все эти возможности продемонстрированы в следующем примере:
import tkinter as tk
window = tk.Tk()
window.rowconfigure(0, minsize=50)
window.columnconfigure([0, 1, 2, 3], minsize=50)
label1 = tk.Label(text="1", bg="black", fg="white")
label2 = tk.Label(text="2", bg="black", fg="white")
label3 = tk.Label(text="3", bg="black", fg="white")
label4 = tk.Label(text="4", bg="black", fg="white")
label1.grid(row=0, column=0)
label2.grid(row=0, column=1, sticky="ew")
label3.grid(row=0, column=2, sticky="ns")
label4.grid(row=0, column=3, sticky="nsew")
window.mainloop()
Результат выглядит так:
Этот пример показывает, что при помощи параметра sticky менеджера геометрии .grid() можно достичь того же эффекта, что и с параметром fill менеджера геометрии .pack().
Соответствие между параметрами sticky и fill показано в следующей таблице.
.GRID()
.PACK()
sticky=”ns”
fill=tk.Y
sticky=”ew”
fill=tk.X
sticky=”nsew”
fill=tk.BOTH
.grid() — очень мощный менеджер геометрии. Обычно он более понятен, чем
.pack(), и намного более гибок, чем .place(). При создании новых приложе-
ний Tkinter в качестве основного менеджера геометрии стоит рассматривать
именно .grid().
18.6. Управление макетом при помощи менеджеров геометрии 477
ПРИМЕЧАНИЕ
Менеджер геометрии .grid() обладает гораздо более гибкими возможностями,
чем мне удалось показать здесь. Например, вы можете настроить ячейки так,
чтобы они охватывали несколько строк и столбцов.
Дополнительную информацию вы найдете в разделе «Grid Geometry Manager»
(https://tkdocs.com/tutorial/grid.html) учебного руководства TkDocs (https://
tkdocs.com/tutorial/index.html).
Теперь, когда вы познакомились с азами использования менеджеров геометрии
Tkinter, я предлагаю вам сделать следующий шаг — оживите интерфейс вашего
приложения, связывая с кнопками выполняемые действия.
Упражнения
1. Попробуйте воссоздать скриншоты из этого раздела, не подглядывая
в исходный код. Если вы зайдете в тупик, просмотрите код и завершите
упражнение. Затем подождите 10–15 минут и попробуйте снова. Повторяйте, пока не сможете проделать все это самостоятельно. Сосредоточьтесь на выводе. Если ваш код будет слегка отличаться от приведенного
в книге, это абсолютно нормально.
2. Ниже показано окно, созданное в Tkinter. Попробуйте воссоздать его
средствами, о которых вы узнали в этой главе. Используйте любой менеджер геометрии на свое усмотрение.
478 Глава 18 Графические интерфейсы
18.7. ИНТЕРАКТИВНОСТЬ В ПРИЛОЖЕНИЯХ
К настоящему моменту вы уже достаточно хорошо представляете, как создать
окно в Tkinter, добавить виджеты и управлять макетом приложения. И это
прекрасно! Однако приложения должны не только хорошо выглядеть — они
должны что-то делать.
Сейчас мы покажем, как оживить ваши приложения, чтобы они выполняли
определенные действия при возникновении тех или иных событий.
События и обработчики событий
Создавая приложение Tkinter, вы должны запустить цикл событий вызовом
window.mainloop(). В цикле ваше приложение проверяет, произошло ли это
событие. Если оно произошло, приложение может среагировать на него выполнением некоторого кода.
Цикл событий предоставляет Tkinter, так что вам не придется писать код для
проверки возникновения событий. Тем не менее вам придется написать код,
выполняющий некоторое действие в ответ на событие. В Tkinter для событий,
используемых в вашем приложении, предназначены функции, называемые
обработчиками событий.
Что же такое событие и что происходит при его возникновении?
Событие представляет собой любое действие, которое происходит во время
цикла событий (например, нажатие пользователем клавиши или кнопки мыши)
и активизирует некую реакцию в приложении.
При возникновении события выдается объект события; это означает, что
создается экземпляр класса, представляющего данное событие. Вам не нужно
беспокоиться об этих экземплярах — Tkinter создает их за вас автоматически.
Чтобы лучше понять, как работает цикл событий Tkinter, можно написать собственный цикл событий. Это покажет вам, как цикл событий Tkinter интегрируется в ваше приложение и какие его части вы должны написать самостоятельно.
Допустим, список events_list содержит объекты событий. Каждый раз, когда
в программе происходит событие, к events_list присоединяется новый объект события. Вам не нужно реализовать этот механизм обновлений — в нашем
примере он работает как по волшебству.
В бесконечном цикле вы можете непрерывно проверять, присутствуют ли
в events_list какие-либо объекты событий:
18.7. Интерактивность в приложениях 479
# Предполагается, что список обновляется автоматически.
events_list = []
# Запустить цикл событий.
while True:
# Если список events_list пуст, значит, события не происходили
# и можно переходить к следующей итерации цикла.
if events_list == []:
continue
# Если выполнение достигает этой точки, значит, в events_list
# содержится как минимум один объект события
event = events_list[0]
Пока созданный нами цикл событий ничего не делает с событием. Исправим
этот недочет.
Допустим, ваше приложение должно реагировать на нажатия клавиш. Необходимо проверять, было ли сгенерировано событие (нажал ли пользователь клавишу на клавиатуре), и, если было, — передать событие функции-обработчику
события «нажатие клавиши».
Будем считать, что если событие — это нажатие клавиши, то его объект event
имеет атрибут type, которому присвоена строка "keypress", и атрибут .char,
который содержит символ, соответствующий нажатой клавише.
Добавим функцию handle_keypress() и обновим код цикла событий:
events_list = []
# Создать обработчик события
def handle_keypress(event):
"""Вывести символ, связанный с нажатой клавишей"""
print(event.char)
while True:
if events_list == []:
continue
event = events_list[0]
# Если event является объектом события нажатия клавиши
if event.type == "keypress":
# Вызвать обработчик события нажатия клавиши
handle_keypress(event)
Когда вы вызываете метод window.mainloop() из библиотеки Tkinter, выполняется некое подобие этого цикла. А конкретно метод .mainloop() берет на себя
две части этого цикла:
1. Он поддерживает список генерируемых событий.
480 Глава 18 Графические интерфейсы
2. Он выполняет обработчик события при добавлении нового события
в список.
Вы можете обновить свой цикл событий, чтобы вместо вашего собственного
цикла событий использовался метод window.mainloop():
import tkinter as tk
# Создать объект окна
window = tk.Tk()
# Создать обработчик события
def handle_keypress(event):
"""Вывести символ, связанный с нажатой клавишей"""
print(event.char)
# Запустить цикл событий
window.mainloop()
.mainloop() многое делает за вас, но в этом коде чего-то не хватает. Откуда
Tkinter узнает, когда следует вызывать handle_keypress()?
Оказывается, у виджетов Tkinter есть метод .bind(), который помогает в этом.
Метод .bind()
Чтобы обработчик события вызывался каждый раз, когда в виджете происходит событие, можно воспользоваться методом виджета .bind(). Говорят, что
обработчик события связывается с событием, потому что он вызывается при
каждом возникновении события.
Продолжим пример с нажатием клавиши, приведенный в предыдущем разделе, и воспользуемся .bind() для связывания handle_keypress() с событием
нажатия клавиши:
import tkinter as tk
window = tk.Tk()
def handle_keypress(event):
"""Вывести символ, связанный с нажатой клавишей"""
print(event.char)
# Связать событие нажатия клавиши с handle_keypress()
window.bind("<Key>", handle_keypress)
window.mainloop()
18.7. Интерактивность в приложениях 481
Здесь обработчик события handle_keypress() связывается с событием "<Key>"
вызовом window.bind(). Каждый раз, когда во время выполнения приложения
пользователь нажимает клавишу, выводится символ, соответствующий этой
клавише.
Метод.bind() всегда получает два аргумента:
1) событие, представленное строкой в форме "<имя_события>", где имя_события может быть любым из событий Tkinter;
2) обработчик события — имя функции, которая должна вызываться при
возникновении события.
Обработчик события связывается с виджетом, для которого вызывается .bind().
При вызове обработчика события функции-обработчику передается объект
события.
В предыдущем примере обработчик события связывается с самим окном, но его
также можно связать с любым виджетом в вашем приложении. Например, обработчик события можно связать с виджетом Button, который будет выполнять
некоторое действие при щелчке на кнопке:
def handle_click(event):
print("The button was clicked!")
button = tk.Button(text="Click me!")
button.bind("<Button-1>", handle_click)
В этом примере событие "<Button-1>" виджета button связывается с обработчиком события handle_click. Событие "<Button-1>" происходит при щелчке
левой кнопкой мыши, когда указатель мыши находится над виджетом.
Существуют и другие события в ответ на щелчки мыши, включая "<Button-2>"
для средней кнопки (если она существует) и "<Button-3>" для правой кнопки.
ПРИМЕЧАНИЕ
Список часто используемых событий вы найдете в разделе «Event types» справочника Tkinter 8.5 (https://realpython.com/pybasics-event-types).
Обработчик событий можно связать с любой разновидностью виджетов методом
.bind(), но существует и более простой способ связывания обработчиков событий со щелчками на кнопке — он реализуется при помощи атрибута command
виджета Button.
482 Глава 18 Графические интерфейсы
Атрибут command
Каждый виджет Button содержит атрибут command, который можно присвоить
функции. Она выполняется при нажатии кнопки.
Рассмотрим пример. Сначала создадим окно с виджетом Label, содержащим
числовое значение. Слева и справа от виджета Label располагаются кнопки.
Левая кнопка будет уменьшать значение в Label, а правая — увеличивать его.
Ниже приведен код для создания этого окна:
import tkinter as tk
window = tk.Tk()
window.rowconfigure(0, minsize=50, weight=1)
window.columnconfigure([0, 1, 2], minsize=50, weight=1)
btn_decrease = tk.Button(master=window, text="-")
btn_decrease.grid(row=0, column=0, sticky="nsew")
lbl_value = tk.Label(master=window, text="0")
lbl_value.grid(row=0, column=1)
btn_increase = tk.Button(master=window, text="+")
btn_increase.grid(row=0, column=2, sticky="nsew")
window.mainloop()
Окно выглядит так:
Мы определили макет приложения, теперь оживим его, назначив кнопкам
команды.
Начнем с левой кнопки. При ее нажатии значение в виджете Label будет
уменьшаться на 1. Чтобы реализовать эту возможность, необходимо знать, как
выполняются две операции: получение текста Label и обновление текста Label.
У виджетов Label, в отличие от виджетов Entry и Text, нет метода .get(). Однако текст метки можно получить, обратившись к атрибуту text в синтаксисе,
сходном с синтаксисом индексирования словарей:
18.7. Интерактивность в приложениях 483
label = Tk.Label(text="Hello")
# Получить текст Label
text = label["text"]
# Назначить новый текст
label["text"] = "Good bye"
Итак, вы научились получать и назначать текст Label и можете написать функцию, которая увеличивает значение Label на 1:
def increase():
value = int(lbl_value["text"])
lbl_value["text"] = f"{value + 1}"
Функция increase() получает текст из lbl_value и преобразует его в целое
число вызовом int(). Затем она увеличивает полученное значение на 1 и присваивает результат атрибуту text виджета Label.
Также напишем функцию decrease(), которая уменьшает значение lbl_value
на 1:
def decrease():
value = int(lbl_value["text"])
lbl_value["text"] = f"{value - 1}"
Разместите функции increase() и decrease() в коде сразу же после команды
import.
Чтобы связать кнопки с функциями, присвойте функцию атрибуту command
кнопки. Это можно сделать при создании экземпляра кнопки. Например, чтобы
присвоить increase() переменной btn_increase, приведите строку, в которой
создается экземпляр кнопки, к следующему виду:
btn_increase = tk.Button(master=window, text="+", command=increase)
Теперь присвойте decrease() переменной btn_decrease:
btn_decrease = tk.Button(master=window, text="-", command=decrease)
Вот и все, что следует сделать для связывания кнопок с функциями increase()
и decrease() и наделения программы полезной функциональностью. Попробуйте сохранить изменения и запустить приложение.
Ниже для удобства мы приводим полный код приложения:
import tkinter as tk
def increase():
484 Глава 18 Графические интерфейсы
value = int(lbl_value["text"])
lbl_value["text"] = f"{value + 1}"
def decrease():
value = int(lbl_value["text"])
lbl_value["text"] = f"{value - 1}"
window = tk.Tk()
window.rowconfigure(0, minsize=50, weight=1)
window.columnconfigure([0, 1, 2], minsize=50, weight=1)
btn_decrease = tk.Button(master=window, text="-", command=decrease)
btn_decrease.grid(row=0, column=0, sticky="nsew")
lbl_value = tk.Label(master=window, text="0")
lbl_value.grid(row=0, column=1)
btn_increase = tk.Button(master=window, text="+", command=increase)
btn_increase.grid(row=0, column=2, sticky="nsew")
window.mainloop()
Полученное приложение не особенно полезно, но приобретенные сейчас навыки
пригодятся вам при создании любого другого приложения:
zz
используйте виджеты для создания компонентов пользовательского
интерфейса;
zz
используйте менеджеры геометрии для управления макетом приложения;
zz
создавайте функции, которые взаимодействуют с различными компонентами для получения и преобразования пользовательского ввода.
В следующих двух разделах я покажу, как строить приложения, которые делают
что-то полезное. Сначала мы построим программу преобразования температур,
введенных по шкале Фаренгейта, к температурам по шкале Цельсия. Затем мы
создадим текстовый редактор, который может открывать, редактировать и сохранять текстовые файлы.
Упражнения
1. Напишите программу, которая выводит одну кнопку с цветом фона по
умолчанию и черным текстом "Click me". Когда пользователь щелкнет
на кнопке, ее фон должен окрашиваться в цвет, случайным образом выбранный из следующего списка:
["red", "orange", "yellow", "blue", "green", "indigo", "violet"]
18.8. Пример приложения: конвертер температур 485
2. Напишите программу, моделирующую бросок игрального кубика. В программе должна быть одна кнопка с текстом "Roll". Когда пользователь
щелкает на кнопке, должно выводиться случайное целое число от 1 до 6.
Окно приложения должно выглядеть примерно так:
18.8. ПРИМЕР ПРИЛОЖЕНИЯ:
КОНВЕРТЕР ТЕМПЕРАТУР
Сейчас мы создадим программу преобразования температур: пользователь
вводит температуру в градусах по шкале Фаренгейта и щелчком кнопки преобразует ее к значению по Цельсию.
Рассмотрим код шаг за шагом. Полный код приложения мы приводим в конце
этого раздела.
Чтобы извлечь максимум пользы из этого раздела, откройте окно редактора
IDLE и воспроизводите приведенный код.
Прежде чем браться за кодирование, давайте уделим немного времени дизайну
приложения. Нам понадобятся три основных элемента:
1) виджет Entry с именем ent_temperature для ввода значения по шкале
Фаренгейта;
2) виджет Label с именем lbl_result для вывода результата по шкале
Цельсия;
3) виджет Button с именем btn_convert, который читает значение из виджета Entry, преобразует его из значений по Фаренгейту к значениям
по Цельсию и присваивает результат тексту виджета Label при щелчке.
Эти виджеты можно разместить в сетке с одной строкой, где для каждого
виджета выделяется один столбец. Вы получите работающее приложение, но
486 Глава 18 Графические интерфейсы
вряд ли его можно назвать дружественным для пользователя. Все виджеты
желательно снабдить полезными надписями. Разместите виджет Label со
знаком ℉ справа от виджета ent_temperature, чтобы пользователь знал, что
значение ent_temperature должно задаваться в градусах по Фаренгейту. Для
этого присвойте тексту виджета Label значение "\N{DEGREE FAHRENHEIT}",
использующее поддержку именованных символов Юникода в Python для
вывода символа.
Чтобы виджет btn_convert выглядел чуть более стильно, присвойте его тексту
значение "\N{RIGHTWARDS BLACK ARROW}", которое выводит черную стрелку, направленную вправо. Также можно сделать так, чтобы за полем lbl_result всегда
следовал знак ℃; для этого добавьте в конец значение "\N{DEGREE CELSIUS}",
показывающее, что результат выводится в градусах по шкале Цельсия.
Итоговое окно должно выглядеть так:
Теперь, когда вы знаете, какие виджеты вам понадобятся и как будет выглядеть окно, можно переходить к кодированию! Сначала импортируйте tkinter
и создайте новое окно:
import tkinter as tk
window = tk.Tk()
window.title("Temperature Converter")
Вызов window.title() задает заголовок существующего окна. Если вы запустите
приложение, в заголовке окна будет выводиться текст "Temperature Converter".
Затем создайте виджет ent_temperature с виджетом lbl_temp и свяжите оба
виджета с виджетом Frame с именем frm_entry:
frm_entry = tk.Frame(master=window)
ent_temperature = tk.Entry(master=frm_entry, width=10)
lbl_temp = tk.Label(master=frm_entry, text="\N{DEGREE FAHRENHEIT}")
В виджете ent_temperature пользователь вводит значение по шкале Фаренгейта,
а lbl_temp помечает ent_temperature знаком ℉. frm_entry представляет собой
контейнер для группировки ent_temperature и lbl_temp.
18.8. Пример приложения: конвертер температур 487
Надпись lbl_temp должна размещаться непосредственно справа от ent_
temperature, поэтому для размещения виджетов в frm_entry можно воспользоваться менеджером геометрии .grid() с одной строкой и двумя столбцами:
ent_temperature.grid(row=0, column=0, sticky="e")
lbl_temp.grid(row=0, column=1, sticky="w")
Параметру sticky виджета ent_temperature задаем значение "e", чтобы виджет
всегда закреплялся у правого края ячейки. Параметру sticky виджета lbl_temp
задаем значение "w", чтобы он закреплялся у левого края ячейки. Эти действия
гарантируют, что виджет lbl_temp всегда будет находиться непосредственно
справа от ent_temperature.
Теперь можно переходить к созданию btn_convert и lbl_result для преобразования температуры, введенной в ent_temperature, и вывода результата:
btn_convert = tk.Button(
master=window,
text="\N{RIGHTWARDS BLACK ARROW}"
)
lbl_result = tk.Label(master=window, text="\N{DEGREE CELSIUS}")
Как и frm_entry, виджеты btn_convert и lbl_result связаны с окном. Вместе эти
три виджета образуют три ячейки основной сетки приложения. Воспользуемся
вызовом .grid() для их размещения:
frm_entry.grid(row=0, column=0, padx=10)
btn_convert.grid(row=0, column=1, pady=10)
lbl_result.grid(row=0, column=2, padx=10)
Наконец, запустите приложение:
window.mainloop()
Выглядит превосходно, но кнопка еще ничего не делает. В начале файла с кодом,
непосредственно под строкой import, добавьте функцию с именем fahrenheit_
to_celsius() . Эта функция читает значение, введенное пользователем, из
ent_temperature, преобразует его из значения по Фаренгейту к значению по
Цельсию, после чего выводит результат в lbl_result:
def fahrenheit_to_celsius():
"""Преобразовать значение по шкале Фаренгейта
к шкале Цельсия и вставить результат в lbl_result.
"""
fahrenheit = ent_temperature.get()
celsius = (5/9) * (float(fahrenheit) - 32)
lbl_result["text"] = f"{round(celsius, 2)} \N{DEGREE CELSIUS}"
488 Глава 18 Графические интерфейсы
Перейдите к строке, в которой определяется btn_convert, и присвойте его параметру command значение fahrenheit_to_celsius:
btn_convert = tk.Button(
master=window,
text="\N{RIGHTWARDS BLACK ARROW}",
command=fahrenheit_to_celsius # <--- Добавьте эту строку
)
Вот и все! Вы создали полностью работоспособное приложение для преобразования температур всего в 26 строках кода! Впечатляет, не правда ли?
Ниже для удобства приведен полный код приложения:
import tkinter as tk
def fahrenheit_to_celsius():
"""Преобразовать значение по шкале Фаренгейта
к шкале Цельсия и вставить результат в lbl_result.
"""
fahrenheit = ent_temperature.get()
celsius = (5/9) * (float(fahrenheit) - 32)
lbl_result["text"] = f"{round(celsius, 2)} \N{DEGREE CELSIUS}"
# Set up the window
window = tk.Tk()
window.title("Temperature Converter")
window.resizable(width=False, height=False)
# Создать фрейм для ввода значения по шкале Фаренгейта, содержащий виджет Entry
# и Label
frm_entry = tk.Frame(master=window)
ent_temperature = tk.Entry(master=frm_entry, width=10)
lbl_temp = tk.Label(master=frm_entry, text="\N{DEGREE FAHRENHEIT}")
# Разместить виджет Entry для температуры и Label в frm_entry
# с использованием топологического менеджера .grid()
ent_temperature.grid(row=0, column=0, sticky="e")
lbl_temp.grid(row=0, column=1, sticky="w")
# Создать кнопку преобразования и виджет Label для вывода результата
btn_convert = tk.Button(
master=window,
text="\N{RIGHTWARDS BLACK ARROW}",
command=fahrenheit_to_celsius
)
lbl_result = tk.Label(master=window, text="\N{DEGREE CELSIUS}")
# Определить макет с использованием топологического менеджера .grid() geometry
# manager
18.9. Пример приложения: текстовый редактор 489
frm_entry.grid(row=0, column=0, padx=10)
btn_convert.grid(row=0, column=1, pady=10)
lbl_result.grid(row=0, column=2, padx=10)
# Запустить приложение
window.mainloop()
А теперь замахнемся на решение более амбициозной задачи и построим простой текстовый редактор.
Упражнение
Попробуйте воссоздать приложение-конвертер температур из этого раздела,
не подглядывая в исходный код. Если вы зайдете в тупик, просмотрите код
и завершите упражнение. Затем подождите 10–15 минут и попробуйте снова.
Повторяйте, пока не сможете проделать все это самостоятельно. Сосредоточьтесь
на результате. Если ваш код будет слегка отличаться от приведенного в книге,
это абсолютно нормально.
18.9. ПРИМЕР ПРИЛОЖЕНИЯ:
ТЕКСТОВЫЙ РЕДАКТОР
Сейчас мы построим текстовый редактор, который должен уметь создавать,
открывать, редактировать и сохранять текстовые файлы.
Нам понадобятся три основных элемента:
1) виджет Button с именем btn_open — открывает файл для редактирования;
2) виджет Button с именем btn_save — сохраняет файл;
3) виджет TextBox с именем txt_edit — создает и редактирует текстовый
файл.
При помощи виджетов две кнопки разместим в левой части окна, а текстовое
поле — в правой.
Окно должно иметь минимальную высоту 800 пикселей, а поле txt_edit —
минимальную ширину 800 пикселей. Макет сделаем динамичным, чтобы
при изменении размеров окна также корректировались размеры виджета
txt_edit. При этом ширина виджета Frame, содержащего кнопки, меняться
не должна.
490 Глава 18 Графические интерфейсы
Вот скетч будущего окна:
Для построения нужного макета воспользуемся менеджером геометрии .grid().
Макет состоит из одной строки и двух столбцов: узкий столбец слева предназначен для кнопок, а более широкий столбец справа — для текстового поля.
Чтобы задать минимальные размеры для окна и txt_edit, присвоим параметрам
minsize методов .rowconfigure() и .columnconfigure() объекта окна значение
800. Для изменения размеров можно присвоить параметрам weight этих методов
значение 1.
Чтобы обе кнопки размещались в одном столбце, необходимо создать виджет
Frame, которому присвоим имя fr_buttons. Согласно скетчу, две кнопки должны
быть выстроены по вертикали внутри фрейма, кнопка btn_open должна находиться наверху. Воспользуемся менеджерами геометрии .grid() или .pack():
лучше использовать .grid(), потому что с ним чуть проще работать.
План готов, можно переходить к кодированию приложения. Начнем с создания
всех необходимых виджетов:
import tkinter as tk
# 1
window = tk.Tk()
window.title("Simple Text Editor")
# 2
window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)
# 3
txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window)
btn_open = tk.Button(fr_buttons, text="Open")
btn_save = tk.Button(fr_buttons, text="Save As...")
18.9. Пример приложения: текстовый редактор 491
Сначала (#1) импортируем пакет tkinter и создадим новое окно с заголовком
"Simple Text Editor". Затем (#2) настроим конфигурации строк и столбцов.
В завершение (#3) создадим четыре виджета: текстовое поле txt_edit, фрейм
fr_buttons, а также кнопки btn_open и btn_save.
Присмотримся повнимательнее к части #2 . Параметру minsize метода
.rowconfigure() присваивается значение 800, а параметру weight — значение 1.
Первый аргумент равен 0, поэтому эта команда назначает первой строке высоту 800 пикселей и обеспечивает изменение размера строки, пропорциональное
изменению высоты окна. Макет состоит только из одной строки, поэтому эти
настройки применяются ко всему окну.
В следующей строке метод .columnconfigure() используется для назначения
атрибутам width и weight столбца с индексом 1 значений 800 и 1 соответственно.
Помните: индексы строк и столбцов начинаются с нуля, поэтому эти настройки
применяются ко второму столбцу.
Настраивая только второй столбец, мы гарантируем, что текстовое поле будет
естественно расширяться и сужаться при изменении размеров окна, тогда как
столбец с кнопками сохранит фиксированную ширину.
Далее возьмемся за макет приложения. Сначала две кнопки связываем с фреймом fr_buttons с использованием менеджера геометрии .grid():
btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)
Эти две строки кода создают сетку с двумя строками и одним столбцом во фрейме fr_buttons, так как и у btn_open, и у btn_save атрибуту master присвоено
значение fr_buttons. btn_open размещается в первой строке, а btn_save — во
второй, так что btn_open отображается в макете над btn_save, как и было показано на скетче.
Как у btn_open, так и у btn_save атрибуту sticky присвоено значение "ew", которое заставляет кнопки расширяться по горизонтали в обоих направлениях
и заполнять весь фрейм. Тем самым гарантируется, что обе кнопки будут иметь
одинаковые размеры.
Вокруг каждой кнопки создаются отступы величиной 5 пикселей, для чего
параметрам padx и pady присвоим значение 5 . Вертикальные отступы есть
только у btn_open. Так как btn_open располагается сверху, вертикальный отступ немного смещает кнопку вниз от верха окна и создает небольшой отступ
между btn_open и btn_save.
492 Глава 18 Графические интерфейсы
Кнопка fr_buttons размещена и готова к работе, можно переходить к настройке
макета сетки для остальной части окна:
fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")
Эти две строки кода создают в окне сетку с одной строкой и двумя столбцами.
Фрейм fr_buttons располагается в первом столбце, а txt_edit — во втором, так
что fr_buttons располагается слева от txt_edit в макете окна.
Параметру sticky фрейма fr_buttons задаем значение "ns", которое обеспечивает вертикальное расширение всего фрейма и заполнение всей высоты
столбца. Поле txt_edit заполняет всю ячейку сетки, потому что его параметру
sticky присвоено значение "nsew", заставляющее его расширяться во всех
направлениях.
Макет приложения готов. Добавьте вызов window.mainloop() в конец программы,
сохраните и запустите файл. На экране появляется вот такое окно:
Смотрится отлично! Но программа пока ничего не делает, поэтому следует написать команды для кнопок.
Кнопка btn_open открывает диалоговое окно для выбора файла. Затем нужно
открыть этот файл и заполнить txt_edit содержимым файла.
18.9. Пример приложения: текстовый редактор 493
Это делает функция open_file():
def open_file():
"""Open a file for editing."""
# 1
filepath = askopenfilename(
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
)
# 2
if not filepath:
return
# 3
txt_edit.delete("1.0", tk.END)
# 4
with open(filepath, "r") as input_file:
text = input_file.read()
txt_edit.insert(tk.END, text)
# 5
window.title(f"Simple Text Editor - {filepath}")
Сначала (#1) диалоговое окно askopenfilename из модуля tkinter.filedialog
используется для вывода диалогового окна открытия файла, а выбранный путь
к файлу сохраняется в переменной filepath. Если пользователь закрывает
диалоговое окно или щелкает на кнопке Cancel (#2), то, поскольку переменная
filepath содержит None, функция возвращает управление без выполнения кода,
который читает файл и заполняет текст txt_edit.
Если пользователь выбрал файл (#3), то текущее содержимое txt_edit стирается
вызовом .delete(). Затем (#4) выбранный файл открывается, его содержимое
читается вызовом .read() и сохраняется в виде строки в переменной text.
Строка text включается в txt_edit вызовом .insert().
Наконец (#5), в заголовок окна включается путь к открытому файлу.
Теперь можно обновить программу так, чтобы виджет btn_open вызывал open_
file() при щелчке. Для этого необходимо выполнить три действия:
1. Импортировать askopenfilename() из модуля tkinter.filedialog, для
чего следует добавить следующую команду import в начало программы:
from tkinter.filedialog import askopenfilename
2. Добавить определение open_file() непосредственно под командами
import.
494 Глава 18 Графические интерфейсы
3. Присвоить атрибуту command виджета btn_open значение open_file.
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
Сохраните файл, запустите его и убедитесь в том, что все работает. Попробуйте
открыть текстовый файл!
ПРИМЕЧАНИЕ
Если после обновления программа перестанет работать, то посмотрите полный код редактора, который приведен в конце раздела.
Когда btn_open заработает, займемся функцией для btn_save. Она должна открыть диалоговое окно для сохранения файла, чтобы пользователь мог выбрать,
где он хочет сохранить файл. Для этого можно воспользоваться диалоговым
окном asksaveasfilename из модуля tkinter.filedialogmodule. Функция также
должна извлечь текст, в настоящее время содержащийся в txt_edit, и записать
его в файл в выбранном месте.
Следующая функция делает все это:
def save_file():
"""Save the current file as a new file."""
# 1
filepath = asksaveasfilename(
defaultextension="txt",
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
)
# 2
if not filepath:
return
# 3
with open(filepath, "w") as output_file:
text = txt_edit.get("1.0", tk.END)
output_file.write(text)
# 4
window.title(f"Simple Text Editor - {filepath}")
Сначала (#1) диалоговое окно asksaveasfilename получает от пользователя
путь для сохранения информации. Выбранный путь сохраняется в переменной
filepath. Если пользователь закрывает диалоговое окно или щелкает на кнопке
Cancel (#2), то, поскольку переменная filepath содержит None, функция возвращает управление без выполнения кода, который сохраняет текст в файле.
18.9. Пример приложения: текстовый редактор 495
Если пользователь выбрал файл (#3), функция создает новый файл. Текст из
txt_edit извлекается методом .get(), присваивается переменной text и записывается в выходной файл.
Наконец (#4), в заголовок окна включается путь к новому файлу.
Теперь можно обновить программу так, чтобы виджет btn_save вызывал save_
file() при щелчке. Для этого необходимо выполнить три действия:
1. Импортировать asksaveasfilename() из модуля tkinter.filedialog, для
чего следует привести команду import в начале программы к следующему
виду:
from tkinter.filedialog import askopenfilename, asksaveasfilename
2. Добавить определение save_file() непосредственно под определением
open_file().
3. Присвоить атрибуту command виджета btn_save значение save_file:
btn_save = tk.Button(
fr_buttons, text="Save As...", command=save_file
)
Сохраните файл и запустите его. У нас получился простейший, но полностью
функциональный текстовый редактор!
Ниже для удобства мы приводим полный код приложения:
import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename
def open_file():
"""Открыть файл для редактирования."""
filepath = askopenfilename(
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
)
if not filepath:
return
txt_edit.delete(1.0, tk.END)
with open(filepath, "r") as input_file:
text = input_file.read()
txt_edit.insert(tk.END, text)
window.title(f"Simple Text Editor - {filepath}")
def save_file():
"""Сохранить текущий файл как новый."""
filepath = asksaveasfilename(
defaultextension="txt",
496 Глава 18 Графические интерфейсы
filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
)
if not filepath:
return
with open(filepath, "w") as output_file:
text = txt_edit.get(1.0, tk.END)
output_file.write(text)
window.title(f"Simple Text Editor - {filepath}")
window = tk.Tk()
window.title("Simple Text Editor")
window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)
txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window, relief=tk.RAISED, bd=2)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...", command=save_file)
btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)
fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")
window.mainloop()
Вы создали в Python два приложения с графическим интерфейсом. При этом
применили многие концепции, о которых мы рассказывали в книге. Это немалое
достижение, и вы можете заслуженно гордиться тем, что сделали.
Теперь вы готовы к тому, чтобы взяться за самостоятельное программирование!
Упражнение
Попробуйте воссоздать текстовый редактор, не подглядывая в исходный код.
Если вы зайдете в тупик, просмотрите код и завершите упражнение. Затем подождите 10–15 минут и попробуйте снова. Повторяйте, пока не сможете построить
приложение с нуля самостоятельно. Сосредоточьтесь на результате. Если ваш
код будет слегка отличаться от приведенного в книге, это абсолютно нормально.
18.10. ЗАДАЧА: ВОЗВРАЩЕНИЕ ПОЭТА
В этом упражнении мы напишем GUI-приложение, генерирующее стихи. В основу приложения заложен генератор из главы 9. Окно приложения должно
выглядеть примерно так:
18.10. Задача: возвращение поэта 497
Вы можете использовать любой менеджер геометрии на свое усмотрение,
но приложение должно удовлетворять всем требованиям из следующего
списка.
1. Пользователь должен ввести правильное количество слов в каждом
виджете Entry:
ƒƒ не менее трех существительных;
ƒƒ не менее трех глаголов;
ƒƒ не менее трех прилагательных;
ƒƒ не менее трех предлогов;
ƒƒ хотя бы одно наречие.
Если в каком-либо из виджетов Entry введено слишком мало слов, в той
области, где выводится сгенерированное стихотворение, должно выводиться сообщение об ошибке.
2. Программа должна случайным образом выбрать из пользовательского
ввода три существительных, три глагола, три прилагательных и три предлога, а также одно наречие.
3. Программа должна сгенерировать стихотворение по следующему шаблону:
498 Глава 18 Графические интерфейсы
{A/An} {прил1} {сущ1}
{A/An} {прил1} {сущ1} {гл1} {пред1} the {прил2} {сущ2}
{нареч1}, the {сущ1} {гл2}
the {сущ2} {гл3} {пред2} a {прил3} {сущ3}
4. Приложение должно предоставить пользователю возможность экспорта
стихотворения в файл.
5. Дополнительное задание: удостоверьтесь, что пользователь вводит
в виджетах Entry уникальные слова. Например, если пользователь введет
одно и то же существительное в виджете Entry дважды, то при попытке
сгенерировать стихотворение приложение должно вывести сообщение
об ошибке.
Решение этой задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
18.11. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе я рассказал, как строить несложные графические интерфейсы
(GUI).
Сначала вы узнали, как использовать пакет EasyGUI для создания диалоговых
окон, которые выводят сообщения и получают пользовательский ввод, а также
позволяют пользователю выбрать файл для чтения и записи. Затем мы рассмотрели Tkinter — встроенный GUI-фреймворк Python. Tkinter сложнее EasyGUI,
но зато он обладает большей гибкостью.
Вы научились работать с виджетами в Tkinter — Frame, Label, Button, Entry
и Text. Виджеты можно настраивать, присваивая значения их атрибутам. Например, присваивание значения атрибуту text виджета Label заполняет виджет
текстом.
Затем я показал, как при помощи менеджеров геометрии Tkinter .pack() ,
.place() и .grid() выстраивать макет GUI-приложений. Вы научились управлять различными параметрами макета, например внутренними и внешними отступами, а также создавать динамичные макеты с использованием менеджеров
.pack() и .grid().
Наконец, мы закрепили полученные навыки, создав два завершенных GUIприложения — конвертер температур и простой текстовый редактор.
18.11. Итоги и дополнительные ресурсы 499
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усвоенных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-gui
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
zz
«Tkinter tutorial» (https://tkdocs.com/tutorial/index.html)
ГЛАВА 19
Мысли напоследок
и следующие шаги
Поздравляю! Вы дочитали книгу до конца. Вы уже знаете, как сделать много
полезных вещей на языке Python, но сейчас начинается самое интересное: пришло время самостоятельных исследований!
Учиться лучше всего на реальных задачах, с которыми вы сталкиваетесь в повседневной жизни. Конечно, на первых порах ваш код будет не самым красивым или эффективным, но он будет полезным. Нужен источник вдохновения?
В статье «13 Project Ideas for Intermediate Python Developers» (https://realpython.
com/intermediate-python-project-ideas/) вы найдете некоторые идеи, которые
помогут вам начать!
Есть еще один факт, который делает программирование на Python таким привлекательным, — сообщество питонистов. Знаете других людей, изучающих
Python? Помогите им! Если вы хотите по-настоящему глубоко понять какую-то
концепцию, попробуйте объяснить ее другим.
Затем обратитесь к более сложному материалу — на сайте realpython.com — или
к статьям и учебникам, рекомендуемым в новостной рассылке PyCoder’s Weekly
(https://pycoders.com/).
А когда почувствуете, что готовы, подумайте об участии в проекте с открытым
исходным кодом на GitHub (https://github.com/topics/python). Если же вы
предпочитаете решать головоломки, попробуйте заняться математическими
задачами на сайте Project Euler (http://projecteuler.net/problems).
Если в какой-то момент вы зайдете в тупик, знайте: почти наверняка кто-то
уже сталкивался с точно такой же проблемой (и, возможно, успешно решил
ее!). Поищите ответы в интернете, особенно на сайте Stack Overflow (https://
stackoverflow.com/questions/tagged/python), или найдите сообщество питонистов
(например, https://realpython.com/community) — они всегда придут на помощь.
19.2. Книга «Чистый Python» 501
Если все попытки окажутся безуспешными, выполните import this и помедитируйте над тем, что есть Python.
P. S. Посетите нас в интернете и продолжите свое путешествие в мир Python
на сайте realpython.com и в аккаунте Twitter @realpython.
19.1. ЕЖЕНЕДЕЛЬНЫЕ БЕСПЛАТНЫЕ СОВЕТЫ
ДЛЯ ПИТОНИСТОВ
Хотите еженедельно получать подборку советов от разработчиков Python
о том, как повысить производительность и оптимизировать рабочие процессы?
Хорошие новости — у нас есть бесплатная рассылка по электронной почте для
таких же разработчиков Python, как и вы.
Наша рассылка не относится к материалам типа «вот вам список популярных
статей». Мы стараемся поделиться хотя бы одной оригинальной мыслью в неделю в формате (короткого) очерка.
Если вам захотелось узнать, о чем идет речь, переходите на realpython.com/
newsletter и вводите свой адрес электронной почты в форму. С нетерпением
ожидаем встречи!
19.2. КНИГА «ЧИСТЫЙ PYTHON»
Итак, вы овладели азами Python. Настало время изучить язык поглубже, дополнить и расширить ваши знания.
В книге «Python Tricks: A Buffet of Awesome Python Features»1 вы найдете
эффективные приемы программирования на Python, а также ощутите всю
мощь красивого питонического кода с простыми примерами и пошаговыми
описаниями.
Вы еще на шаг приблизитесь к профессиональному владению Python и сможете
писать чистый идиоматический код — легко и непринужденно.
Изучить все хитросплетения Python достаточно сложно. Эта книга поможет
вам поднять ваши навыки работы с Python на следующий уровень.
1
Бейдер Д. Чистый Python. Тонкости программирования для профи. — СПб.: Питер.
502 Глава 19 Мысли напоследок и следующие шаги
Найдите тайные сокровища, скрытые в стандартной библиотеке Python, и начинайте писать чистый питонический код прямо сегодня. Ознакомительную
главу книги можно загрузить бесплатно с realpython.com/pytricks-book1.
19.3. БИБЛИОТЕКА ВИДЕОКУРСОВ
REAL PYTHON
Большая (и постоянно растущая) подборка учебников Python и учебных материалов поможет вам достичь уровня всесторонне развитого питониста. Новые
материалы публикуются еженедельно, и вы всегда найдете что-то интересное
для повышения вашей квалификации.
1
zz
Осваивайте практически значимые навыки программирования на языке
Python: созданием, отбором и контролем наших учебных материалов занимается целое сообщество опытных разработчиков. На сайте Real Python
вы найдете ресурсы, заслуживающие доверия, которые пригодятся вам
при совершенствовании мастерства программирования на Python.
zz
Знакомьтесь с другими питонистами: присоединяйтесь к чату сообщества Real Python и еженедельным Q&A-сессиям, чтобы познакомиться
с командой Real Python и с другими неофитами. Получайте ответы на
свои вопросы, относящиеся к Python, обсуждайте вопросы программирования и построения карьеры или просто проводите с нами время у этого
виртуального кофейного автомата.
zz
Пройдите интерактивные тесты и траектории обучения: реальные задачи
по кодированию, интерактивные тесты и программы обучения, ориентированные на получение необходимых навыков, позволят вам оценить
ваш текущий уровень и потренироваться в применении новых знаний.
zz
Отслеживайте ход обучения: помечайте уроки как завершенные или
«в процессе», занимайтесь в наиболее подходящем для вас темпе. Создавайте закладки на самых интересных уроках и просматривайте их позднее,
чтобы запомнить надолго.
zz
Получайте сертификаты об окончании курсов: по окончании каждого
курса вы получаете сертификат, который можно предъявить (в электронном или печатном виде). Включайте сертификаты в свой проектный
портфель, резюме на LinkedIn или на других веб-сайтах, чтобы показать
миру, что вы продвинутый питонист.
Или полистать главу на русском языке на сайте издательства «Питер» www.piter.com. —
Примеч. ред.
19.4. Благодарности 503
zz
Идите в ногу со временем: поддерживайте свои рабочие навыки на должном уровне и не отставайте от технологических новинок. Мы постоянно
выпускаем новые учебные материалы только для зарегистрированных
участников, а также регулярно обновляем контент.
За информацией о доступных курсах обращайтесь на realpython.com/courses.
19.4. БЛАГОДАРНОСТИ
Эта книга появилась на свет только благодаря помощи и поддержке многих
друзей и коллег. Мы хотим поблагодарить всех вас за содействие.
Нашим семьям: спасибо за то, что вы мирились с нашими авралами, когда мы
работали днем и ночью, чтобы книга вовремя попала в руки читателей.
Команде CPython: спасибо за то, что вы создали замечательный язык программирования и инструменты, которые мы обожаем и используем в повседневной
работе.
Сообществу Python: спасибо за вашу усердную работу над тем, чтобы сделать
Python самым гостеприимным и доброжелательным языком программирования в мире, за проведение конференций и поддержание такой критической
инфраструктуры, как PyPI.
Читателям realpython.com — таким, как вы: спасибо за то, что читаете наши
материалы и купили эту книгу. Без вашей поддержки и внимания вся наша
работа была бы напрасной!
Надеемся, вы продолжите активно участвовать в работе сообщества, задавая
вопросы и делясь советами. Читательские отклики сформировали эту книгу
и продолжают помогать нам вносить улучшения в будущих изданиях, поэтому
мы надеемся услышать ваше мнение.
Наша искренняя благодарность всем, кто поддержал нас на Kickstarter, — тем,
кто поверил в будущее этого проекта в 2012 году. Мы никогда не рассчитывали
собрать такую большую группу отзывчивых, доброжелательных людей.
Наконец, мы хотим поблагодарить читателей ранней версии этой книги за превосходную обратную связь; вот их имена: Зохеб Айнапор (Zoheb Ainapore),
Лютер Рид (Luther Reed), Роб Сандаски (Rob Sandusky), Лютер (Luther), Марк
(Marc), Рики Митчелл (Ricky Mitchell), Роберт Ливингстон (Robert Livingston),
Уэйн (Wayne), Том Моэнс (Tom Moens), Меир Гуттман (Meir Guttman), Ларри
Айзенберг (Larry Eisenberg), Рики (Ricky), Фу Ле (Phu Le), Джеффри Хансен
504 Глава 19 Мысли напоследок и следующие шаги
(Jeffrey Hansen), Албрехт (Albrecht), Марк Пали (Mark Palie), Питер Аронофф
(Peter Aronoff), Килимандарос (Kilimandaros), Патрицио Уррутиа (Patricio
Urrutia), Джоанна Яблонски (Joanna Jablonski), Мигель Алвес (Miguel Alves),
Мурсалин Симпсон (Mursalin Simpson), Сю Чуньян (Xu Chunyang), Лукас
(Lucas), Уорд Уокер (Ward Walker), W., Влад (Vlad), Джим Андерсон (Jim
Anderson), Мохамед Альшихани (Mohamed Alshishani), Мелвин (Melvin), Албрехт Кадауке (Albrecht Kadauke), Патрик Старренбург (Patrick Starrenburg),
Вивек (Vivek), Шринивасан Самуэль (Srinivasan Samuel), Сампат (Sampath),
Сиджей Сервантес (Ceejay Cervantes), Лиам (Liam), TyWait, Marp, Хорхе Алберк
(Jorge Alberch), Эдит (Edythe), Мигель Галан (Miguel Galán), Том Карневаль
(Tom Carnevale), Флорент (Florent), Питер (Peter), Джон Рэдью (Jon Radue),
Мэтт Гарднер (Matt Gardner), Роберт (Robert), Шон Янг (Sean Yang), Дэвид С.
(David S.), Ханс ван Нилен (Hans van Nielen), Юрий Торчальский (Youri
Torchalski), Гэвин (Gavin), Карен Калхаун MD (Karen H Calhoun MD), Роман
(Roman), Роберт Робб Ливингстон (Robert Robb Livingston), Теренс Филипс
(Terence Phillips), Нико (Nico), Дэниел (Daniel), W, Кейро Деголар (Cairo
DeGaillard), Лукас дас Дорес (Lucas das Dores), Дэвид (David), Дэйв (Dave),
Тони Деннинг (Tony Denning), Шон (Sean), Питер Кронфельд (Peter Kronfeld),
Марк (Mark), Деннис Миллер (Dennis Miller), Джозеф Аранета мл. (Joseph
Araneta Jr.), Натан Эгер (Nathan Eger), Кумаран Раджендиран (Kumaran
Rajendhiran), Дэвид Фуллертон (David Fullerton), Никлас (Nicklas), Джейкоб
Андерсен (Jacob Andersen), Марио (Mario), Алехандро Рамос (Alejandro Ramos),
Beni_begin, AJ, Дон Эдвардс (Don Edwards), Джон (Jon), Ридван Мизан (Ridwan
Mizan), Грэхем Нин (Graham Kneen), Илиян (Iliyan), Хельмут (Helmut), Айзек
Зикер (Izak Zycer), Майк (Mike), Норман Гринвуд (Norman Greenwood), Форрест (Forrest), Патрицио (Patricio), Рене (Rene), Ричард Мерц (Richard Mertz),
Крис Робинсон (Chris Robinson), Пит Сторер (Pete Storer), Расс Гарсайд (Russ
Garside), Мэтт (Matt), Ричард (Richard), Тиаго Мендес (Tiago Mendes), Майкл
(Michael), Дэниел Алвес Мертинс (Daniel Alves Mertins), Марко Умек (Marko
Umek), Крис Дженкс (Chris Jenks), Эдди (Eddy), Дмитрий (Dmitry), Келсанг
Шераб (Kelsang Sherab), Томас (Thomas), Дом Дженнингс (Dom Jennings),
Мартин (Martin), Энтони Шеффилд (Anthony Sheffield), S F, Велу В (Velu V),
Питер Кавалларо (Peter Cavallaro), Чарли Браунинг 3 (Charlie Browning 3),
Милинд Махаджани (Milind Mahajani), Джейсон Барнс (Jason Barnes), Люсьен
Боланд (Lucien Boland), Адам Бретел (Adam Bretel), Уильям (William), Велтейн
(Veltaine), Джерри Петри (Jerry Petrey), Джеймс (James), Рэймонд Е. Роджерс
(Raymond E Rogers), Тай Уэйт (Ty Wait), Бимперн Уэн (Bimperng Uen), СиДжей Хван (CJ Hwang), Гвидо (Guido), Эван (Evan), Мигель Галан (Miguel
Galan), Хан Ци (Han Qi), Джим Бремнер (Jim Bremner), Мэтт Чан (Matt Chang),
Дэниел Дразан (Daniel Drazan), Коул (Cole), Боб (Bob), Риб Ховальд (Reed
Howald), Эдвард Дуарте (Edward Duarte), Майк Паркер (Mike Parker), Аарт
Клейнендорст (Aart Kleinendorst), Рок (Rock), Джонни (Johnny), Рок Ли (Rock
19.4. Благодарности 505
Lee), Душан Ранисавлиев (Dusan Ranisavljev), Грант (Grant), Джек (Jack),
Рейнхард (Reinhard), Вивек Вашист (Vivek Vashist), Дэн (Dan), Гаретт (Garett),
Джун Ли (Jun Lee), Джеймс Силк (James Silk), Ник Сингал (Nik Singhal), Чарльз
(Charles), Аллард Шмидт (Allard Schmidt), Джефф Десаль (Jeff Desalle), Мигель
(Miguel), Стив По (Steve Poe), Джонатан Сейберт (Jonathan Seubert), Марк
Пулен (Marc Poulin), Ли Джордан (Lee Jordan), Мэтью Чин (Matthew Chin),
Джеймс Митчелл (James Mitchell), Уэйн (Wayne), Зарата (Zarata), Лиза (Lisa),
Райан Отеро (Ryan Otero), Ли (Lee), Рафаэль Байтбир (Raphael Bytebier), Грэм
Эдвардс (Graeme Edwards), Джефф Скиппер (Jeff Skipper), Боб Д (Bob D),
Андерсон Томазели (Anderson Tomazeli), Селемани Саид Джава (Selemani Said
Jawa), Мью Картер (Meow Carter), Расс Гарсайд (Russ Garside), Луис Шелдон
(Louis Sheldon), Джеймс Рэдфорд (James Radford), Николай Джонс (Nikkolai
Jones), Джордж Загас (George Zagas), Лен Гульд (Len Gould), Дэниел Капитан
(Daniel Kapitan), Крис (Chris), Шен Джун (Sheng Jun), Уолт Бюссе (Walt Busse),
Мелисса Грегуар (Melissa Gregoire), Мохаммад Нассар (Mohammad Nassar),
Карлес Касадемун (Carles Casademunt), Форрест Смит (Forrest Smith), Орел
Вайссванге (Aurel Weisswange), Расс (Russ), Вольфрам Блехнер (Wolfram
Blechner), Тони Деннинг (Tony Denning), Рон Фенимор (Ron Fenimore), Эдвард
Райт (Edward Wright), Джастин (Justin), Даррен Олив (Darren Olive), Чарли
Клеммер (Charlie Clemmer), Дуэйн Рейд (Dwayne Reid), Уэйман Яу (Waiman
Yau), Скотт Киппен (C. Scott Kippen), Джимми (Jimmy), Вольфрам Блехнер
(Wolfram Blechner), Марк Мэтьюсон (Mark Mathewson), Франко Брюне (Franços
Brunet), Джефф Кабрал (Jeff Cabral), Бьорн (Bjorn), Джейсон Уильямс (Jason
Williams), Скотт Пейдж (Scott Page), Мэрилин Гартли (Marilyn Gartley), Лиф
Рутцебек (Lief Rutzebeck), Мустафа Адаоглу (Mustafa Adaoglu), Thejan, Теян
Ратнаяк (Thejan Rathnayake), Синди Анкрам (Cindy Ancrum), Тати Карвало
(Tati Carvalho), Марек Ратиборски (Marek Ratiborsky), Бен (Ben), Фрэнсис
Адеподжу (Francis Adepoju), Нир (Nir), Прабху (Prabhu), Стив Фишер (Steve
Fisher), Карлос (Carlos), Аарон (Aaron), Дэвид Майетта (David Maietta), Майкл
Хаклберри (Michael Huckleberry), Павел (Pawel), Хулио Сезар Зебадуа (Julio
Cesar Zebadua), Венцислав Шойков (Vencislav Shoykov), Майкл Кленгель
(Michael Klengel), Керри Альфред (Kerry Alfred), Афиз Попула (Afeez Popoola),
Синди А. (Cindy A.), LC, tfig, Тиаго (Tiago), Софи Ван (Sophie Wang), Тосико
(Toshiko), Фами (Fahmi), Пол Пеннингтон (Paul Pennington), Wer, Джефф
Джонсон (Jeff Johnson), Dutchy, Сезар (Cesar), Албрехт Кадауке (Albrecht
Kadauke), Джим Браун (Jim Brown), Эрик (Eric), Кристофер Эванс (Christopher
Evans), МЕЛВИН (MELVIN), Идрис (Idris), Джон Чирико (John Chirico),
Уинетт Эспиноса (Wynette Espinosa), J.P., Грегори (Gregory), Марк Эдгеллер
(Mark Edgeller), Дэвид Мелансон (David Melanson), Рауль Пена (Raul Pena),
Даррелл (Darrell), Шрирам (Shriram), Том Флинн (Tom Flynn), Велу (Velu),
Майкл Линдси (Michael Lindsey), Суло Колемайнен (Sulo Kolehmainen), Джей
(Jay), Майлос «Оззикс» Косик (Milos “Ozzyx” Kosik), Ханс де Кок (Hans de
506 Глава 19 Мысли напоследок и следующие шаги
Cocq), Гленн Мьюлз (Glen Mules), Натан Лунднер (Nathan Lundner), Фил (Phil),
Шуб (Shubh), Пувэй Ван (Puwei Wang), Алекс Мак (Alex Mück), Алекс (Alex),
Хитоси (Hitoshi), Бруно Ф. Де Лима (Bruno F. De Lima), Дарио Дэвид (Dario
David), Раджеш (Rajesh), Харольдас Вальчукас (Haroldas Valčiukas), GVeltaine,
Сьюзен Фаул (Susan Fowle), Джаред Симмс (Jared Simms), Нейтан Коллинз
(Nathan Collins), Дилан (Dylan), Лес Черчмэн (Les Churchman), Стефан ЛиТяо-Тэ (Stephane Li-Thiao-Te), Фрэнк П. (Frank P), Пол (Paul), Дэмьен Муртах
(Damien Murtagh), Джейсон (Jason), Тхан ле Куан (Thắng Lê Quang), Нейл
(Neill), Леле (Lele), Чарльз Уилсон (Charles Wilson), Дэмьен (Damien), Кристиан (Christian), Андреас Крейзиг (Andreas Kreisig), Марко (Marco), Марио
Панагиотопулос (Mario Panagiotopoulos), Нерино (Nerino), Мариуш (Mariusz),
Михаил (Mihhail), Микениг (Mikönig), Фабио (Fabio), Скотт (Scott), A, Педро
Торрес (Pedro Torres), Матиас Йоханссон (Mathias Johansson), Джошуа С.
(Joshua S.), Матиас (Mathias), Скотт (Scott), Дэвид (David Koppy), Рохит
Бхарти (Rohit Bharti), Филип Дуглас (Phillip Douglas), Джон Стивенсон (John
Stephenson), Джефф Джонс (Jeff Jones), Джордж Маст (George Mast), Аллардс
(Allards), Палак (Palak), Никола Н. (Nikola N.), Палак Калси (Palak Kalsi),
Аннекатрин (Annekathrin), Цун-Ю Ян (Tsung-Ju Yang), Ник Хантингтон (Nick
Huntington), Сай (Sai), Джордан (Jordan), Вим Алсемгеест (Wim Alsemgeest),
Ди Джей (DJ), Боб Харрис (Bob Harris), Эндрю (Andrew), Регги Смит (Reggie
Smith), Стив Санти (Steve Santy), Мохи Джарада (Mohee Jarada), Марк Арзага (Mark Arzaga), Пулос Маттен (Poulose Matthen), Брент Гордон (Brent
Gordon), Гэри Батлер (Gary Butler), Брайан (Bryant), Дана (Dana), Коджак
(Koajck), Регги (Reggie), Луис Браво (Luis Bravo), Элайджа (Elijah), Николай
(Nikolay), Эрик Льетч (Eric Lietsch), Фред Янссен (Fred Janssen), Дон Стиллуэлл (Don Stillwell), Гаурав Шарма (Gaurav Sharma), Майк Маккенна (Mike
McKenna), Картик Бабу (Karthik Babu), Булат Мансуров (Bulat Mansurov),
Август Трилланес (August Trillanes), Дэррен Со (Darren Saw), Джагадиш
(Jagadish), Кайл (Kyle), Техас Шетти (Tejas Shetty), Баба Сариффодин (Baba
Sariffodeen), Дон (Don), Ян (Ian), Ян Барбур (Ian Barbour), Редуан (Redhouane),
Уэйн Розинг (Wayne Rosing), Эмануэль (Emanuel), Тойгонгонбай (Toigongonbai),
Джейсон Кастильо (Jason Castillo), Кришна Чайтанья Свами Кесаварупу
(Krishna Chaitanya Swamy Kesavarapu), Кори Хагалей (Corey Huguley), Ник
(Nick), Сючуньян (Xuchunyang), Дэниел Буис (Daniel Buis), Кеннет (Kenneth),
Леоданис Позо Рамос (Leodanis Pozo Ramos), Джон Феникс (John Phenix),
Линда Моран (Linda Moran), Лало (W Laleau), Трой Флинн (Troy Flynn), Эбер
Нильсен (Heber Nielsen), Рок (Rock), Майк Лерой (Mike LeRoy), Томас Дэвис
(Thomas Davis), Джейкоб (Jacob), Шаболч Синка (Szabolcs Sinka), Калайселван
(Kalaiselvan), Леанна Кусс (Leanne Kuss), Андрей (Andrey), Омар (Omar),
Джейсон Уоден (Jason Woden), Дэвид Себало (David Cebalo), Джон Миллер
(John Miller), Дэвид Буи (David Bui), Нико Занферрари (Nico Zanferrari), Ариэль (Ariel), Борис (Boris), Борис Эндер (Boris Ender), Чарли3 (Charlie3), Осси
19.4. Благодарности 507
(Ossy), Маттиас Кюль (Matthias Kuehl), Скотт Кох (Scott Koch), Хесус Авина
(Jesus Avina), Чарли (Charlie), Авадеш (Awadhesh), Энди (Andie), Крис Джонсон (Chris Johnson), Малан (Malan), Киро (Ciro), Тамижселван (Thamizhselvan),
Неха (Neha), Кристиан Лангпап (Christian Langpap), Иван (Ivan), доктор Крейг
Леви (Dr. Craig Levy), Х. Б. Робинсон (H. B. Robinson), Стефан (Stéphane),
Стив Макилри (Steve McIlree), Ив (Yves), Тереза (Teresa), Аллард (Allard), Том
Коун мл. (Tom Cone Jr.), Дирк (Dirk), Иоахим ван дер Вейден (Joachim van der
Weijden), Джим Вудворд (Jim Woodward), Кристоф Липка (Christoph Lipka),
Джон Вергелли (John Vergelli), Джерри (Gerry), Лу (Lu), Роберт Р. (Robert R.),
Влад (Vlad), Ричард Хитвол (Richard Heatwole), Гэбриел (Gabriel), Кшиштоф
Суровецки (Krzysztof Surowiecki), Александра Дэвис (Alexandra Davis), Джейсон Волл (Jason Voll) и Дуэйн Девер (Dwayne Dever).
Если мы забыли упомянуть ваше имя, пожалуйста, знайте, что мы в высшей
степени благодарны вам за помощь. Спасибо всем!
Дэн Бейдер,
Дэвид Эймос, Джоанна Яблонски, Флетчер Хейслер
Знакомство с Python
Перевел с английского Е. Матвеев
Руководитель дивизиона
Ю. Сергиенко
Ведущий редактор
Е. Строганова
Литературный редактор
Ю. Леонова
Художественный редактор
В. Мостипан
Корректоры
С. Беляева, Л. Галаганова
Верстка
Л. Егорова
Изготовлено в России. Изготовитель: ООО «Прогресс книга».
Место нахождения и фактический адрес: 194044, Россия, г. Санкт-Петербург,
Б. Сампсониевский пр., д. 29А, пом. 52. Тел.: +78127037373.
Дата изготовления: 09.2022. Наименование: книжная продукция. Срок годности: не ограничен.
Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12 —
Книги печатные профессиональные, технические и научные.
Импортер в Беларусь: ООО «ПИТЕР М», 220020, РБ, г. Минск, ул. Тимирязева, д. 121/3, к. 214, тел./факс: 208 80 01.
Подписано в печать 28.07.22. Формат 70×100/16. Бумага офсетная. Усл. п. л. 41,280. Тираж 1500. Заказ 0000.
Никола Лейси
PYTHON, НАПРИМЕР
Это «Python, например»! Познакомьтесь с самым быстрорастущим языком
программирования на сегодняшний день.
Легкое и увлекательное руководство поможет шаг за шагом прокачать
навыки разработки. Никаких архитектур компьютера, теорий программирования и прочей абракадабры — больше практики! В книге 150 задач,
которые плавно перенесут читателя от изучения основ языка к решению
более сложных вещей. Руководство подойдет всем, у кого голова идет кругом от технического жаргона и пространных объяснений — автор уверен,
что учить можно и без этого.
КУПИТЬ
Эл Свейгарт
БОЛЬШАЯ КНИГА ПРОЕКТОВ PYTHON
Вы уже освоили основы синтаксиса Python и готовы программировать?
Отточите свои навыки на самых интересных задачах — графике, играх,
анимации, расчетах и многом другом. Вы можете экспериментировать,
добавляя к готовым проектам собственные детали.
В 256 строк кода поместится все — «винтажная» экранная заставка, забег
улиток на скорость, рекламный заголовок-приманка, вращающаяся спираль
ДНК и так далее. Добавьте к этому пару строк своего кода, и вы сможете
делиться собственными уникальными проектами в интернете.
КУПИТЬ
Эл Свейгарт
PYTHON. ЧИСТЫЙ КОД ДЛЯ ПРОДОЛЖАЮЩИХ
Вы прошли обучающий курс программирования на Python или прочли
несколько книг для начинающих. Что дальше? Как подняться над базовым
уровнем, превратиться в крутого разработчика?
«Python. Чистый код для продолжающих» — это не набор полезных советов и подсказок по написанию чистого кода. Вы узнаете о командной
строке и других инструментах профессионального разработчика: средствах форматирования кода, статических анализаторах и контроле версий.
Вы научитесь настраивать среду разработки, давать имена переменным
и функциям, делающие код удобочитаемым, грамотно комментировать
и документировать ПО, оценивать быстродействие программ и сложность
алгоритмов, познакомитесь с ООП. Такие навыки поднимут вашу ценность
как программиста не только в Python, но и в любом другом языке.
Ни одна книга не заменит реального опыта работы и не превратит вас из
новичка в профессионала. Но «Чистый код для продолжающих» проведет
вас чуть дальше по этому пути: вы научитесь создавать чистый, грамотный,
читабельный, легко отлаживаемый код, который можно будет назвать истинно питоническим.
КУПИТЬ
Дэн Бейдер
ЧИСТЫЙ PYTHON. ТОНКОСТИ
ПРОГРАММИРОВАНИЯ ДЛЯ ПРОФИ
Изучение всех возможностей Python — сложная задача, а с этой книгой вы
сможете сосредоточиться на практических навыках, которые действительно важны. Раскопайте «скрытое золото» в стандартной библиотеке Python
и начните писать чистый код уже сегодня.
•
Если у вас есть опыт работы со старыми версиями Python, вы сможете
ускорить работу с современными шаблонами и функциями, представленными на Python 3;
•
Если вы работали с другими языками программирования, и хотите
перейти на Python, то найдете практические советы, необходимые для
того, чтобы стать эффективным питонщиком;
•
Если вы хотите научиться писать чистый код, то найдете здесь самые
интересные примеры и малоизвестные трюки.
КУПИТЬ