Author: Бейдер Д. Эймос Д. Яблонски Дж. Хейслер Ф.
Tags: языки программирования программирование искусство программирования компьютерные технологии язык программирования python
ISBN: 978-5-4461-1924-0
Year: 2023
Python Basics: А Practical Introduction
to Python З
Real Python
~ &И&ЛИОТЕКА
Ttl ПРОГРАММИСТА
знакомство с
PYTHON
Дэн Бейдер, Дэвид Эймос
Джоанна Яблонски, Флетчер Хейслер
~nnTEP®
Санкт-Петербург • Москва • Минск
2023
ББК 32.973 .2 -018.1
УДК 004.43
641
Бейдер Дэн, Эймос Дэвид, Яблонски Джоанна, Хейслер Флетчер
Б41 Знакомство с Python. - СПб.: Питер, 2023 . - 512 с.: ил. - (Серия «Библио
тека программиста»).
ISBN 978-5 -4461 -1924 -0
Пытаетесь найти что-нибудь для начинающих о языке Python в интернете? Не можете решить,
с чего начать? Как структурировать это море информации? В каком порядке изучать?
Если вы задаетесь подобными вопросами, потому что хотите заложить фундамент будущей
карьеры питониста, - эта книга для вас'
Вместо скучного перечисления возможностей языка авторы рассказывают, как сочетать разные
структурныеэлементыPython,чтобы сразу создавать скриптыи приложения.
Книга построена по принципу 80/20: большую часть полезной информации можно усвоить,
изучив несколько критически важных концепций. Освоив самые популярные команды и приемы,
вы сразу сосредоточитесь на решении реальных повседневных задач.
16+ (В соответствии с Федеральным законом от 29декабря 2010 r. No 436-ФЗ.)
ББК 32.973 .2 -018 .1
УДК 004.43
Права на издание nолучены no соглашению с DevAcademy Media lnc. Все nрава защищены. Никакая часть
данной книги не может быть воспроизведена в какой бы то ни было форме без nисьменного разрешения
владельцев авторских nрав.
Информация, содержащаяся в данной книге, nолучена из источников, рассматриваемых издательством как
надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки. издательство не
может гарантировать абсолютную точность и nолноту nриводимых сведений и не несет ответственности за
возможные ошибки, связанные с исnользованием книги. Издательство не несет ответственности за достуn
ность материалов, ссылки на которые вы можете найти в этой книге. На момент nодготовки книги к изданию
все ссылки на интернет-ресурсы были действующими.
ISBN 978-1775093329 англ.
ISBN 978-5 -4461 -1924 -0
© Real Python (гealpython.com)
©Перевод на русский язык ООО «Прогресс книга», 2022
©Издание на русском языке, оформление ООО «Прогресс книга»,
2022
©Серия «Библиотека nрограммиста», 2022
Оглавление
Об ав т о р а х .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... ... .... ... 1 4
Предисповие ........................................................................................ 15
Python как язык полного спектра ............................................................................ 15
Глава1.Введение•..............•.•••.••..............•.•.•...........••.••••.•.....••••••.••••••• 21
1.1 . Почему именно эта книга? .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... .... .... ... 22
1. 2. О R e al Py tho n . .... .... ... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... ... 2 3
1.3. Как пользоваться книгой .. .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... ... .... . 24
1.4. Дополнительный материал и учебные ресурсы . .... .... .... .... .... .... .... .... .... .... . 25
Глава 2.Установка и настройка Python .............................................. 27
2 . 1. О верс иях Pyt ho n .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... . 2 7
2. 2. Wi ndo ws ... .... .... ... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... . 2 8
2. 3. ma c OS .. .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... . 3 1
2 .4 . L inux .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... . 34
Глава З. Первая проrрамма Python .................................................... 38
3. 1 . На пис ани е п ро гр ам мы Pyth on .... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... ... 38
3 .2 . Ошибк и .. .... ... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... ... 4 2
3 .3 . Созд ание п ере менн ой .. .... ... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... ... 4 4
3 .4 . Пр ос мо тр з начений в инте ракт ивном окне .... .... .... .... .... .... .... .... .... .... .... .... ... 48
3 .5 . Зам етк и на п амять ... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... 5 0
3 .6 . Ит оги и доп олн ите льн ые рес урсы .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... .... 52
6 Оглавление
Глава 4. Строки и строковые методы
..•.•.••..•....•......•..•.....•.......•...•....54
4.1 . Что такое строка? . .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... .... .... ... 54
4 .2 . Конкатенация, индекс ирова ние и с р е з ы .... .... .... .... .... .... .... ..... .... .... .... .... .... ... 60
4.3 . Манипуляции со строками с использованием методов ... .... ..... .... .... .... .... 68
4 .4 . Взаи модей ствие с пользовательски м вво дом .. .... .... .... ..... .... .... .... .... .... ..... ... 7 3
4.5 . Задача: разбор пользовательского ввода ... .... .... .... .... ..... .... .... .... .... .... .... .... .... 75
4 .6 . Работа со строк ами и ч исл ами ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... 7 6
4.7 . Упрощение команд вывода . .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... 80
4.8 . Поиск подстроки в строке ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... 82
4 .9 . З ад ач а: преобразов ание т е кс т а .. .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... 8 4
4 .1 0 . Ит ог и и до пол нит ель ные ресурсы .. .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... . 8 5
Глава5.Числаиматематическиевычисления..................................87
5.1 . Целые числа и числа с плавающей точкой .. .... .... .... .... .... .... .... ..... .... .... .... .... . 87
5.2 . Арифметические операторы и выражения .. .... .... .... .... .... .... .... .... .... .... .... .... .. 91
5.3 . Задача: выполнение вычислений с пользовательским вводом ...... ..... ... 98
5. 4. Ког да Py th on г о в о р и т неправду .. .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .. 98
5.5. Математические функции и числовые методы .... ..... .... .... .... .... .... .... .... .... 100
5.6. Оформление чисел при выводе ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... 104
5. 7. К о мп ле кс ны е чис ла ... .... ... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... ... .... .... . 1 07
5.8 . Итоги и дополнительные ресурсы .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... . 109
Глава6.Функцииициклы •.•.......•..•...................................................111
6.1. Что же такое функция? ... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... ... .... . 111
6.2. Написание ваших собственных функций ... .... .... .... .... .... .... .... .... .... .... .... .... . 115
6.3. Задача: конвертер температур . .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... . 122
6 .4. Ц ик лич еск ое в ып олн ен ие . .... .... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... . 1 2 3
6.5. Задача: отслеживание прибыли по вкладу .... .... .... .... .... ..... .... .... .... .... .... .... . 130
Оглавление 7
6 .6 . Обл асть в иди мос ти в Pyt ho n ... ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... . 13 1
6.7 . Итоги и дополнительные ресурсы .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... . 136
Глава7.Поискиисправлениеошибоквкоде ...•.........•............•..•.•.137
7.1 . Ис поль зов ание о кна De b ug Cont ro l .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .. 1 3 7
7.2 . Исправление ошибок . .... ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .. 143
7 . 3. И тог и и д опол нит ель ные ресурсы ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .. 1 4 9
Глава8.Условнаялоrикаиуправлениепроrраммой.............•.•..•.. 151
8. 1 . Срав нен ие значе ний .. .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... 1 5 1
8. 2 . Д об а ви м н емного ло гики . .... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... ... 1 5 4
8.3 . Управление последовательностью выполнения программы .. ..... .... ..... 161
8.4 . Задача: поиск множителей числа ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... 170
8 .5 . Упр авле ние ц ик ла ми .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... 17 1
8 .6 . Восста новление п о с л е о ши б ок .... .... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... . 17 4
8.7. Моделирование событий и вычисление вероятностей . .... .... .... .... ..... .... . 179
8.8 . Задача: моделирование эксперимента с броском монеты .. ..... ..... .... ..... . 183
8.9 . Задача: моделирование выборов ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .. 184
8 . 1 0 . Ито ги и допо лни тел ьны е ресурсы .. .... ..... .... .... .... .... .... .... .... .... .... .... .... .... .... .. 1 8 4
Глава9.Кортежи.спискиис.nовари .................................................186
9.1 . Кортежи как неизменяемые последовательности .... .... .... .... .... .... .... .... .... 186
9.2 . Списки: изменяемые последовательности . .... .... ..... .... .... .... .... .... ..... .... .... ... 195
9 .3 . Вл ожение , коп иров ание и сортир овка к орте жей и сп исков ... .... ..... ..... 206
9 . 4 . З ад а ча : спи сок сп исков . .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... . 210
9. 5. З ад а ча : приступ вдохно вения .... .... .... .... .... ..... .... .... .... .... .... .... .... .... .... .... .... .... . 2 1 2
9 .6 . Х ран ит е о тн ош ен ия в словарях .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .. 213
9 .7 . З а да ча : цик л по ст олиц ам .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .. 222
9 .8 . Ка к вы брать структуру д ан ны х .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... 223
8 Оглавление
9.9. Задача: коты в шляпах .. .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... .... .... ... 223
9.10 . Итоги и дополнительные ресурсы .. .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... 224
Гпа ва 1 О. Объектно- ориентированное про rрам миров ание
(ООП)..•...•........•.........•........••........•..•...................................................226
1 0 .1 . Опр еде лен ие к л а сс а ... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... 226
1 0 .2 . Соз дани е э кземпл яров ( инстанцирование ). .... .... .... .... .... .... .... .... .... .... .... . 230
10.3 . Наследование от других классов . .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .235
10 .4 . За да ч а: мод ель фермы .. .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... . 24 2
10 .5 . Ит ог и и до пол нит ель ные р есурс ы .. .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .. 243
Глава11.Модулиипакеты .......•.•....•....•..•....•....•....•....•.•.•......•.•....•...244
1 1. 1. Работа с м од ул я ми .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... .... .... .. 2 4 4
1 1. 2 . Работа с пак етами . .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... ... 253
11.3 . Итоги и дополнительные ресурсы . ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... 260
Гпава12.Операциивводаивыводасфайпами•..•.•..•.•.•••.•..•.......... 261
1 2 .1 . Фай лы и ф айл ова я система. .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... .... 2 6 1
12.2 . Работа с путями к файлам в Python ............................................................265
12.3 . Основные операции файловой системы .... .... .... .... .... .... .... .... ... .... .... .... .... .272
12.4 . Задача: перемещение всех графических файлов
в но в ый к а та л о г ... .... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... .... .... 285
12.5 . Чтение и запись файлов .. .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .286
12.6 . Чтение и запись данных CSV .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... 298
12.7 . Задача: создание списка рекордов . .... .... .... .... .... .... .... .... .... .... .... ..... .... .... .... .307
12 . 8. И тог и и до пол нит ель ны е ресурсы .. .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... . 3 07
Гпава1З.Установкапакетовспомощьюpip....•.....•..••...•......•.•....... 309
1 3. 1 . Установка сто ронни х п а ке т о в с помощью pip ... .... .... .... .... .... .... ..... .... .... .. 309
13.2 . Подводные камни сторонних пакетов . .... .... .... .... .... .... .... .... .... .... .... .... .... ...318
13.3 . Итоги и дополнительные ресурсы .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ...320
Оглавление 9
Глава 14. Создание и изменение файлов PDF """""""""""""""" . 3 2 1
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 . За да ч а: восстановление поря дка страни ц ... .... .... .... .... .... .... .... .... .... .... .... .. 3 5 1
14.8 . Создание файла PDF с нуля .. .... .... ... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... ..352
14 .9 . Ит ог и и до пол нит ель ные р есурсы .. .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .. 3 57
Глава15.Базыданных......•....•.•.•..•.•..•.••.•.•..••........................••••••.••.••359
1 5.1 . Зна комс тво с SQ Li te . .... .... .... .... ... .... .... .... .... .... .... ... .... .... .... .... .... .... ... .... .... .... ... 3 59
15.2 . Библиотеки для работы с другими базами данных SQL ..................... 369
15 . 3. И тог и и до пол нит ель ные ресурс ы . .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... ... 370
Глава16.Веб-проrраммирование """"""""""""""""""""""""""372
16.1. Скрапинг и парсинг текста с веб-сайтов . .... .... .... .... .... .... .... .... .... .... .... .... ... 372
16.2. Использование парсера HTML для извлечения веб-данных .... ..... .... 380
1 6 .3 . Работа с НТМL -фор мами . .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... .... .... ... 385
16.4 . Взаимодействие с веб-сайтами в реальном времени .... .... .... ..... .... .... .... 390
1 6 .5 . И то ги и д опо лни тел ьные ресур сы .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .... 393
Глава 17. Научные вычисления и построение графиков"""""""" 395
17.1 . Использование NumPy для матричных вычислений............................ 395
1 7 .2 . Пост роен ие гр афико в с помощью Ma tplot lib . .... .... .... .... ..... .... .... .... .... .... 4 0 4
17.3 . Итоги и дополнительные ресурсы ... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... .422
Глава18.Графическиеинтерфейсы """""""""""""""""""""""".423
18.1 . Добавление элементов GUI с помощью EasyGUI .................................423
18 .2 . Пр имер: пр огра мма для поворота страниц P DF ... .... .... .... ..... .... .... .... .... . 4 3 4
1О Оглавление
18.3. Задача: приложение для извлечения страницы PDF .... .... .... .... ..... .... ... 439
1 8. 4. Зна комс тво с T kin te r . .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... 440
1 8. 5 . Р а б о т а с в идж ета ми ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... 443
18.6 . Управление макетом при помощи менеджеров rеометрии ... ..... ..... .... 463
1 8 .7 . И нтера ктивно сть в пр ил ож ен ия х ... .... .... .... .... .... .... .... .... .... ..... .... .... .... .... .... 478
18.8 . Пример приложения: конвертер температур .... ..... .... .... .... .... .... .... ..... .... .485
18.9 . Пример приложения: текстовый редактор .... .... .... .... .... ..... .... .... .... .... .... ..489
1 8 .1 0 . За да ча : воз враще ние п о э та ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .. 496
1 8 .1 1 . Ит оги и доп олн ите льн ые рес урсы .... .... .... .... .... .... .... .... .... .... .... ..... .... .... .... .. 498
Гпава19.Мыспинапоследокиследующиеwarи .........•.................500
19.1 . Еженедельные бесплатные советы для питонистов . .... .... ..... .... ..... .... ... 501
19 . 2. Кн ига ~ чи ст ы й Py th on i.> .. .... .... .... .... .... .... .... .... .... .... .... .... .... ..... .... .... .... .... .... ... 5 01
19 . 3. Би блио тек а видеокурсов R e al Py th on .. ..... .... .... .... .... .... ..... .... .... .... .... .... .... 502
19 . 4. Благ одарности . .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ... .... .... .... .... .... .... . 503
Что питонисты говорят о книге «Знакомство с Python»
«Обожаю [эту книгу]! Книга написана доступным языком, материал понятен,
а последовательность изложения выглядит логично. Я никогда не терялся
в материале, а интенсивность изложения не слишком велика, что позволяет
мне снова и снова возвращаться к предыдущим главам.
Я просмо трел бо л е е 10 разн ых уче бников/ книг/се тевых курсов о P y thon . По
жалуй, больше всего я узнал именно из материалов Real Python1!»
Thomas Wong (Томас Вон)
«Прошло три года, ая все еще возвращаюськ книгам Real Python, когда требу
ется быстро освежить в памяти важные команды языка Python».
Роб Фаулер (Rob Fowler)
«Я долгое время заставлял себя заняться самообучением. Я продирался через
десятки неполных сетевых учебников. Я засыпал при просмотре многочасовых
скучных видеороликов. Я разочаровался в бесчисленных заумных книгах от
именитых издателей. А потом я открыл для себя Real Python.
Доступные пошаговые инструкции разделяют большие темы на легко усваива
емые части, написанные простым языком. Авторы никогда не забывают о своих
читателях, их объяснения неизменно обстоятельны и подробны. Сейчас я уже
перешел к самостоятельной работе, но постоянно возвращаюсь к книге за на
ставлениями».
Джаред Нильсен (Jared Nielsen)
«Мне нравится эта книга, потому что каждый урок завершают реальные и ин
тересные задачи. Я только что написал программу для подсчета сбережений,
которая учитывает состояние моего сберегательного счета, - класс!»
Дрю Прескотт (Drew Prescott)
1 Real Python - проектДэна Бейдера, одного изавторов книги «Знакомство с Python»
и автора бестселлера «Чистый Python. Тонкости программирования для профи».
Подробнее обэтом ресурсе - в разделе «06 авторах». - Примеч. ред.
12 Что питон исты говорят о книге «Знакомство с Python»
«Чтобы потренироваться в том, что я узнал, я начал строить простые сценарии,
упрощающие повседневную работу моей команды. Когда руководство заметило
это, мне предложили новую должность разработчика. Я знаю, что мне еще многое
предстоит узнать и проблем будет немало, но я наконец-то начал заниматься
тем, что мне действительно нравится.
Еще раз: ОГРОМНОЕ СПАСИБО!»
KaмWl (Kamil)
«В курсах Real Python мне больше всего нравится то, что они объясняют все
на максимально простом уровне.
Для освоения многих учебных курсов - притом практически в любой дисци
плине - необходимо изучать массу специальных терминов, тогда как на самом
деле материал можно объяснить быстро и лаконично. Авторы Real Python ста
раются использовать интересные примеры, и у них это отлично получается».
Стивен Грэди (Stephen Grady)
«После освоения первого курса Real Python я написал программу для автома
тизации моих повседневных операций на работе. То, на что раньше требовалось
от трех до пяти часов, теперь выполняется менее чем за 10 минут!»
Брэндон Янгдейл (Brandon Youngdale)
«Честно говоря, осваивая материал книги, я усердно искал, что бы можно было
в ней добавить или у лучшить, но учеб ник полу чился п р о с т о замечательный!
Вы прекрасно умеете объяснять и обучать Python на уровне, доступном даже
для людей вроде меня, то есть полных новичков.
Последовательность изложения материала работает идеально. Упражнения очень
сильнопомогают, и после освоения материала книги вы справедливоощущаете
гордость. Мне кажется, у вас есть особый дар - делать так, чтобы Python казался
более досягаемым для людей, не принадлежащих к миру программирования.
Я никогда не думал, что буду иметь дело с Python или изучать его. Но теперь
с небольшой поддержкой с вашей стороны я изучаю его - и вижу, что в будущем
мне это принесет только пользу!»
Ши Клусевич (Shea Кlu.sewicz)
Что питон исты говорят о книге «Знакомство с Python»
1З
«Авторы курсов НЕ забыли о том, каково быть новичком (а об этом не помнят
многие авторы!). Они ничего не требуют от своих читателей, и поэтому их
книги так хорошо читаются. К курсам прилагаютсяпревосходные видеоролики,
а также множество ссылок для дополнительного обучения и домашней работы
и примеры кода, с которыми вы можете экспериментировать.
Мне очень понравилось, что все уроки сопровождались полными примерами
кода и каждая строка прокомментирована, чтобы вы понимали, что происходит.
У меня много книг о 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, а также веду
щий разработчик образовательной платформы гealpython.com. Дэн занимается
программированием более 20 лет, он имеет степень магистра в области компью
терных технологий. А кроме того, Дэн нависал «Python Tricks»1 - понулярную
книгу для п ро дв ину ты х разработчиков P y thon .
Джоанна Яблонски - главный редактор сайта Real Pytlюn. Она любит есте
ственные языки в той же степени, что и я:iыки программирования. Ее при
страстие к загадкам, закономерностям и нудным мелочам привело к тому, что
она выбрала карьеру переводчика. Прошло совсем немного времени, и она
влюбилась в новый язык - Python! Джоанна присоединилась к проекту Real
Python в 2018 году и с тех пор помогает программистам Python повышать нро
фессиональную квалификацию.
Флетчер Хейслер - основатель нроекта Hunter2, он обучает разработчиков
тонкостям программирования и построению безопасных современных веб
приложений. Флетчер, один из основателей Real Python, в 2012 году написал
первую версию учебного курса Python, на котором основана эта кни1·а.
1 Бейдер Д. Чистый Python. То11кости программирова11ия для 11рофи. - С\16.: Питер.
Предисловие
Добро пожаловать! Надеюсь, вы готовы узнать, почему Python привлекает
столь многих разработчиков - как профессионалов, так и любителей - и как
вы можете применять его в ваших проектах, мелких и крупных.
Эта книга написана для новичков, которые либо немного владеют навыками
программирования, но не языком и экосистемой Python, либо начинают осваи
вать язык с нуля без какого-либо предварительного опыта программирования.
Если у вас нет диплома в области компьютерных технологий, не огорчайтесь.
Дэвид, Дэн, Джоанна и Флетчер разъяснят вам основные концепции программи
рования в процессе изложения основ Python; и что не менее важно - на первых
порах не будут отвлекаться на несущественные подробности.
РУТНОN КАК ЯЗЫК ПОЛНОГО СПЕКТРА
При изучении нового языка программирования у вас еще нет опыта для того,
чтобы судить, насколько хорошо он послужит вам в долгосрочной перспективе.
Есливы намереныизучать Python, позвольте заверить, что вы сделали хороший
выбор. Одна из важнейших причин заключается в том, что Python является
языком полноrо спектра.
Что мы имеем в виду? Некоторые языки очень хорошо подходятдля новичков.
Они ведут их за руку, и программирование становится невероятно простым.
Этот подход доводится до крайности в таких визуальных языках, как Scratch.
В Scratchвы имеете дело с блоками, представляющими концепции программи
рования: переменные, циклы, вызовы методов и т. д. Эти блоки перетаскиваются
на визуальной поверхности. Возможно, Scratch хорошо подходит для написания
первых, очень простых программ, но профессиональное приложение на нем не
построишь. Назовите хотя бы одну компанию из списка Fortune 500, которая
реализует свою основную бизнес-логику на Scratch.
Не получилось? У меня тоже, потому что это было бы полным безумием.
Другие языки обладают невероятной мощью - но в руках опытных разработ
чиков. Пожалуй, самым популярным в этой категории является С++ и его бли
жайший родственник С. Любой веб-брауэер, которым вы пользовались сегодня,
16 Предисловие
с большой вероятностью был написан на С или С++. Операционная система,
в которой этот браузер работает, тоже, скорее всего, написана на С/С++. Ваша
любимая стрелялка или стратегическая видеоигра? Верно: С/С++.
На этих языках можно делать невероятные вещи, но они совершенно недру
желюбны к новичкам , к оторы е пре дпоч итаю т с покойн ое зна комство с н овой
областью.
Вы еще не видели код С++? Иногда от него начинают слезиться глаза. Приведу
пример (относительно сложный):
template <typename Т>
_Defer<void(*(PID<T>, void (T::*)(void)))
(const PID<T>&, void (T::*)(void))>
defer(const PID<T>& pid, void (Т: :*method)(void))
{
}
void (*dispatch)(const PID<T>&, void (T::*)(void))
&process::template dispatch<T>;
return std::trl::bind(dispatch, pid, method);
Пожа луйста, только н е э то .. .
И Scratch, и С++ определенно не являются тем, что я наэываю языками пол
ного спектра. Scratch упрощает начало программирования, но для построе
ния реальных приложений придется переключиться на «настоящий» язык.
И, наоборот, на С++ можно строить реальные приложения, но на деликатное
введение в тему лучше не рассчитывать. Вы с головой погружаетесь во всю
сложностьязыка, который создавался для поддержки полнофункциональных
приложений.
С другой стороны, Python отличается от обеих крайностей - это язык полного
спектра. Мы часто судим о простоте языка по программе Hello, World. Иначе
говоря, какой синтаксис и действия необходимы, чтобы программа вывела
сообщение Hello, World? На яэыке Python трудно представить что-то проще:
print("Hello, World")
И все! Тем не менее вряд ли это можно назвать rюлноценным критерием.
Тест Hello, World полеэен, но его недостаточно для того, чтобы продемон
стрировать всю мощь или сложность языка. Рассмотрим другой пример. Не
старайтесь понять все от начала до конца - просто следите за процессом,
чтобы уловить суть. Все эти (и многие другие) концепции будут рассмотрены
в книге. Следующий пример будет вам вполне по силам, когда вы доберетесь
до конца книги.
Python какязык полного спектра
17
Итак, новый критерий: насколько трудно написать программу, которая обра
щается к внешнему веб-сайту, загружает контент в память вашего приложения,
а затем выводит часть этого контента для пользователя? Для этого эксперимен
та мы воспользуемся Python 3 с пакетом requests (который вам необходимо
установить - см. главу 12):
import requests
resp = requests.get("http://olympus.realpython.org")
html = resp.text
pr int( html [86:1 32])
Вы не поверите, но это все! При запуске программа выводит результат следу
ющего вида:
<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 Oveгflow поддерживает сайт StackOveгflowTrends, на котором можно
искать информацию о трендах различных технологий. Если сравнить Python
с другими возможными кандидатами для изучения программирования, вы
увидите, что Python выделяется на их фоне.
~ 16.00% -,-~--,.---,---,----..--~---.,..-~---,.---.--~-~
·
u
Язык
~
1 8 python
.-
14.00% -i-+---+---+---+---+---t---+--+---+---1--f---11-128javascript
~
з8java
~
48С
~ 12.00% +-+---t--+--+--t---t---+-r-""1'~-'dt""""'~:---l'il/--'-t--
1
;:
о
't
Qj
>
о
iJ 8.00% -i" ' r +- - -l~,_. 1' +--'F"--'P' ---+---t---+--+-- F+- \~· \~" " \1 - ---+ --
I
~V1
~ 6.00% ~--!-~Nd-'--+--+---l---+--,,J<""'-='-+--+-~--1----i--
I
2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020
Год
Если вы захотите изучить эти тренды, воспользуйтесь данными
insights.stackoverflow.com/trends.
Обратите внимание на невероятный рост применения Python по сравнению
с графиками других кандидатов - горизонтальными и даже снижающимися!
Если ваше будущее зависит от успеха конкретной технологии, то какой язык
вы бы выбрали из этого списка?
Впрочем, это всего лишь один график - что он говорит нам? Рассмотрим дру
гой. Stack Overflow проводит среди разработчиков ежегодный опрос, очень
исчерпывающий и очень хорошо продуманный. Полные результаты за 2020 год
можно найти на insights.stackoverflow.com/survey/2020.
Pythoп как язык полного спектра 19
В этом описании обратите внимание на раздел «Самые любимые языки» («Most
Wanted»). В нем приведены данные о доле «разработчиков, которые не исполь
зуют язык или технологию, но выразили интерес к разработке на этом языке».
И снова из диаграммы видно, что Python стоит на первом месте со значительным
отрывом даже от второго места.
Pythoп 25.7%
JavaScript 17.8%
Go 15.Оо /о
TypeScript 14.6%
Kotliп 11.1%
Rust 9.5%
С++ 9.1%
We bAssemЫy 8.9 %
Есл и вы согласны с т ем, ч т о относительная п опулярн ость я зык а прогр аммир о
вания важна, то Python, очевидно, является хорошим вариантом.
Вам не надо знать теорию
Еще одно обстоятельство, которое я хочу подчеркнуть в начале вашего путеше
ствия в мир Python: вам не обязательно быть специалистом по информатике.
Если вы к этому стремитесь - прекрасно. Изучение Python станет большим
шагом в этом направлении. Однако приглашение к изучению программирования
часто преподносится в виде: «У нас столько свободных вакансий! Нам нужны
разработчики!»
Это может быть правдой, а может и не быть. Но что гораздо важнее, про
граммирование (даже простейшие навыки) может стать вашей персональной
суперсилой.
Представьте, что вы - биолог. Стоит ли бросать биологию и поступать на долж
ность программиста, специализирующегося на построении веб-интерфейсов?
Возможно, нет. Но навыки вроде того, который был показан в начале преди
словия (с использованием requests для получения данных с веб-сайта), могут
оказаться невероятно полезными и для биолога.
20 Предисловие
Вам не придется вручную экспортировать и извлекать данные с веб-сайтов или
из электронных таблиц, вы просто используете Python для обработки тысяч ис
точников данных или электронных таблиц за время, необходимое для ручной
обработки всего одного источника. Владение языком Python переформатирует
вашу повседневнуюработу в качестве биолога, выведет ее на другой уровень -
значительно выше уровня ваших коллег, так что она станет вашей суперсилой.
Дэн и Real Python
Позвольте мне напоследок сказать пару слов об авторах. Дэн Бейдер и другие
авторы изо дня в день трудятся над тем, чтобы доступно и ярко объяснять
концепции Python на сайте realpython.coт.
Они обладают неповторимыми взглядами на экосистему Python и ориентиру
ются на то, что должны знать начинающие программисты.
Я со спокойной душой доверяю вас им в путешествии по миру Python. Отправ
ляйтесь в путь и изучайте этот замечательный язык по отличной книге. А самое
главное - получайте удовольствие от процесса!
От издательства
Майкл Кеннеди,
основатель Talk Python (@тkennedy)
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piteг.
com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию
о наших книгах.
ГЛАВА 1
Введение
Добро пожаловать! Встречайте новое издание книги «Знакомство с Python»,
полностью обновленное для Python 3.9. Книга посвящена методам програм
мирования на языке Python, которые мы иллюстрируем интересными прак
тическими примерами. Кем бы вы ни были - начинающим программистом
или профессионалом, желающим освоить новый язык, - здесь вы найдете все
необходимое для того, чтобы начать самостоятельную работу на Python.
Если ваша деятельность связана с компьютером, то, каковы бы ни были ваши
цели, вы откроете для себя огромное количество возможностей упростить свою
жизнь за счет автоматизации задач и решения проблем в написанных вами про
граммах Python. Достаточно изучить материал этой книги.
Но чем же так хорош Python как язык программирования? Прежде всего он
свободно распространяется с открытым кодом, а это означает, что вы можете
бесплатно загрузить его и использовать для любых целей (в том числе ком
мерческих).
Кроме того, приверженцы Python создали сообщество и разработали целый
ряд полезных инструментов, которыми вы можете пользоваться в своих про
граммах. Понадобилось поработать с документами PDF? Для этого вам пред
лагается многогранный инструментарий. Извлечь данные с веб-страниц? Вам
не придется начинать с нуля!
Python создавался с таким расчетом, чтобы им было проще пользоваться, чем
любым другим языком программирования. Как правило, код на Python намного
легче читается и намного быстрее пишется, чем на других языках.
Во т пр остейша я прогр амма на С, д ругом поп уля рно м языке программирован ия:
#include <stdio.h>
int main(void)
{
printf("Hello, World\n");
22 ГЛАВА 1 Введение
Эта программа просто выводит текст Hello, World на экран. Многовато работы
для вывода одной фразы! А вот как выглядит та же программа, написанная на
Py tho n:
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: большую часть нужной информации мож
но усвоить, изучив несколько критически важных концепций. Мы рассмотрим
команды и приемы, используемые в большинстве ситуаций, и сосредоточимся
на решении реальных повседневных задач.
Тем самым я гарантирую, что вы:
• быстро освоите полезные приемы программирования;
• потратите меньше времени на борьбу с лишними сложностями;
• начнете применять Pythonнапрактике;
• получите больше удовольствия от процесса.
Книга дает вам возможность получить базовые знания, и дальнейшие ваши
вылазки на более сложную территорию будут проходить намного проще.
За основу мы взяли материал первой части исходного курса «Real Python Course»,
выпущенного в 2012 году. За прошедшие годы этот курс опробовали тысячи
программистов на Python, экспертов по работе с данными и разработчиков,
трудящихся в компаниях разных уровней, включая Amazon, Red Hat и Microsoft.
Для этой книги мы тщательно доработали, расширили и обновили материал,
чтобы вы могли быстро и эффективно развивать свои навыки работы на Python.
1.2. О REAL РУТНОN
Сайт Real Python даст вам возможность освоить навыки реального программи
рования в сообществе профессиональных питонистов.
Веб-сайт realpython.com был запущен в 2012 году. В настоящее время он еже
месячно помогает более чем трем миллионам разработчиков на Python, предо
ставляя доступ к книгам, учебникам и другим учебным ресурсам.
Все, кто работал над этой книгой, - практикующие программисты из команды
Real Python с многолетним профессиональным опытом.
Контактные данные Real Python в интернете:
• realpython.com
• @realpython в Twitter (https.j/twitter.com/realpython)
24 ГЛАВА 1 Введение
• The Real Python Newsletter (https.j/twitter.com/newsletter)
• The Real Python Podcast (https.j/twitter.com/podcast)
1.3. КАК ПОЛЬЗОВАТЬСЯ КНИГОЙ
Первая половина книги - краткий, но разносторонний обзор всех фундамен
тальных возможностей Python. Никакой предшествующий опыт программиро
вания вам для этого не понадобится. Вторая половина - практические решения
интер есны х реа льны х за д ач пр ограмми ровани я.
Новичкам мы рекомендуемизучить первую половину книги от начала до конца.
Темы во второй половине книги в меньшей степени связаны друг с другом, так
что вам будет проще осваивать их по отдельности, однако имейте в виду: чем
дальше, тем материал сложнее.
Если у вас уже есть опыт программирования, возможно, вам стоит с ходу об
ратиться ко второй части книги. Но все же подумайте над тем, чтобы сначала
разобраться в основах, а потом уж заполнять информационные пробелы в про
цессе решения практических задач.
Большинство разделов каждой главы завершается упражнениями, которые
помогут вам убедиться в том, что вы хорошо усвоили учебный материал. Также
в книге предлагаются сложные задачи, для решения которых вам придется
воспользоваться знаниями, полученными из предыдущих глав.
В файлах, прилагаемых к книге, содержатся полные решения задач и самых
сложных упражнений. Но чтобы извлечь максимум пользы, постарайтесь решать
задачи самостоятельно, а не с ходу заглядывать в ответы.
Если у вас вообще нет опыта программирования, первые главы желательно под
крепить дополнительной практикой. Мы рекомендуем проработать учебники
начального уровня, которые можно бесплатно загрузить с сайта realpython.com
(https.j/realpython.com/python-basics), - они помогут у б е д и т ь с я в то м, ч то м а
териал вы усвоили.
А если у вас появятся вопросы или вы захотите поделиться своим мнением,
то всегда можете обратиться к нам напрямую (https.j/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
ПРИМЕЧАНИЕ
'
Код, приведенный в книге, был протестирован с Pythoп 3.9 для Windows,
macOS и Linux.
Лицензия на примеры кода
Сценарии Python, имеющие отношение к книге, распространяются на условиях
лицензии ССО (Cгeative Commons PuЬ\ic Domain). Это означает, что вы можете
свободно использовать в своих программах любые части кода для любых целей.
Обратная связь и опечатки
Мы охотно примем ваши идеи, предложения и даже критику. Какая-то тема
показалась вам непонятной? Вы нашли ошибку в тексте или в коде? Мы про
пустили тему, о которой вам хотелось бы узнать побольше? Мы всегда рады
возможностям улучшить свои учебные материалы. Вы можете поделиться
с нами вашим мнением на:
realpython.com/python-basics/feedback
ГЛАВА2
Установка и настройка Python
Эта книга посвящена программированию на языке Python. Вы можете прочи
тать ее от корки до корки, ни разу не прикоснувшись к клавиатуре, но так вы
упустите самое интересное - программирование!
Чтобы получить максимум пользы от книги, вам понадобится компьютер с уста
новленной версией Python, а также средства для создания, редактирования
и сохранения файлов с кодом, который вы будете создавать.
В этой главе вы узнаете, как:
• установить последнюю версию Python3навашемкомпьютере;
• запу стить ID LE - интегрированную среду разработки и обучения
(Integratcd Development andLearningEnvironment), встроенную в Python.
Итак, за дело!
2.1 . О ВЕРСИЯХ РУТНОN
Многие операционные системы, включая 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 З
Запустите браузер и перейдите на
h t tp s.j/www. py th on. o rg/do wnloa ds/ windo ws/
2.2. Windows 29
Щелкните на ссылке Latest Python 3 Release - Python З.х.х под заголовком Python
Releases for Windows в верхней части страницы. На момент написания книги
новейшей версией была Python 3.9 .
Затем прокрутитестраницу вниз ищелкнитена ссылкеWindows х86-64 executaЫe
installer, чтобы начать загрузку.
ПРИМЕЧАНИЕ
Если ваша система оснащена 32-разрядным процессором, выберите 32-раз
рядную программу установки. Если вы не уверены в том, является ли ваш
к омп ьюте р 32-раз рядным и л и 6 4 - разрядным, вы бирай те 64-разрядную про
граммуустановки, о которой мы говорили выше.
Шаг 2. Запустите программу установки
Откройте папкуЗагрузки в Проводнике Windows и дважды щелкните на файле,
чтобы запустить программу установки. На экране появляется диалоговое окно,
которое выглядит примерно так:
~ Python 3.9.1 (64-blt) Setup
python
for
windows
о
lnstall Python 3 .9 .1 (64-blt)
Select lnstall Nowto install Pythonwith default settings, or choose
Customizeto еnаЫе or disaьte features.
lпstall Now
C:\Uшs\damos\AppData\Local\Programs\Python\PythonЗ9
lnclude:sIDLE,pipanddocumentation
Create:sshortcutsandfile associations
~ Customizeinstallation
Chooselocationandfeatures
0 lnstall launcherfor all users (recommended)
0 AddPython 3. 9 to РАТН
Cancel
х
Если номер версии Python окажется больше 3.9 .1 , это нормально - главное,
чтобы он был не меньше 3.
30 ГЛАВА 2 Установка и настройка Python
ВАЖНО!
.
.
'
.
.
.
Обязательно включите флажок Add Pythoп З.х to РАТН. Если вы установили
Pythoп, не выбрав этот флажок, снова запустите программу установки и вы
берите его.
Щелкните на кнопке 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 выглядит примерно так:
IDLE Shell 3.9. 1
о
х
File Edit Shell Debug Options Window Help
Python 3.9.1 (tag3/v3.9. l :le5d3Зe, Dec 7 2020, 17 :08:21) [MSC v . 1927 64 Ьit (АМ "
Dб4)] on win32
Туре "help", "copyright", "creditэ" or "licenэe() " for more information.
>>>
Ln:3Col:4
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 З
Запустите браузер и п ерейдите на страницу:
https.j/www.python.org/do wnloads/ mac- osx/
Щелкните на ссылке Latest Python 3 Release - Python З.х.х под заголовком Python
Releases for шасОS в верхней ч асти страницы. На момент написания книги по
следней версией была Python 3.9 .
32 ГЛАВА 2 Установка и настройка Python
Затем прокрутите страницу вниз и щелкните на ссылке macOS 64-Ьit installer,
чтобы начать загрузку.
Шаг 2. Запустите программу установки
Откройте Findeг и дважды щелкните на файле, чтобы запустить программу
установки. На экране появится диалоговое окно, которое выглядит пример
но так:
•••
lnstall Python
Welcome to the Python lnstaller
Тhis package will install Python3.9 .1 formacOS 10.9or later.
Pythonfor macOSconsistsofthe Моп programminglanguage
interpreterand its batteries-included standard library to allow easy
accessto macOSfeatures. 11also includes the Python integrated
development environment,IDLE. You сап also use the included plpto
download and install third-partypackagesfrom the fython Packa~
.1.шШх.
At the end ofthisinstall,cfick оп Install Certificatesto install а
set ofcurrent SSL гооt certificates.
Go Back Continue
8
Несколько раз нажмите 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 «Поиск и исг1равление ошибок в коде») работа построена исключительно
на использовании IDLE.
Окно командной оболочки Python выглядит примерно так:
••
IDLE Shell3.9 .1
Python 3.9.1 (v3.9.l:le5d33e9b9, Dec 7 2020, 12:10:52)
[Clang 6.0 (clang-600 .0.57)] оп darwin
Туре "help", "copyright", 11credits" or "license()" for more information.
>>>
Ln:4 Со1: 4
1
В верхней части окна выводится номер версии Python и информация об опе
рационной системе. Если номер меньше 3.9 , возможно, вам стоит вернуться
к инструкциям по установке из предыдущего раздела.
34 ГЛАВА 2 Установка и настройка Python
Символы»> образуют так называемое приглашение. Когда вы видите его, это
означает, что Python ожидает от вас инструкций.
ИНТЕРАКТИВНЫЙ ТЕСТ
Кэтой главе прилагается бесплатныйинтерактивный тест для проверки усво·
енных вами знаний. Тест доступен на телефоне или компьютере:
realpython.comlquizzes/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
$ pythonЗ ··version
Одна или обе команды могут вывести номер версии:
$ pythonЗ --version
Python 3.9.1
2.4. Linux 35
Если версия Python -- 2.х или меньше 3 .9 , то вам стоит заняться установкой
Python. Способ установки Python в Ubuntu зависит от версии Ubuntuна вашем
компьютере. Для проверки локальной версии Ubuntu можно воспользоваться
следующей командой:
$ lsb_release -а
No LSB modules are availaЫe.
Di str ibu tor ID: Ubu ntu
De scr iption:
Release:
Codename:
Ubuntu 18.04 .1 LTS
18.04
bionic
Найдите номерверсиив строке Release ивыполните инструкции, приведенные
ниже.
Ubuntu 18.04 и выше
Ubuntu версии 18.04 по умолчанию не включает Python 3.9 , но пакет доступен
в репозитории Universe. Чтобы установить его, выполните следующие команды
в окне терминала:
$ sudo apt-get update
$ sudo apt-get install pythonЗ.9 idle-pythonЗ.9 pythonЗ-pip
Учтите, что обновление репозитория Universe обычно отстает от графика вы
пуска Python. Возможно, загруженная версия Python 3.9 не будет новейшей.
Тем не менее для этой книги годится любая версия Python 3.9 .
Ubuntu 17 и ниже
Для Ubuntu версий 17 и ниже Python 3.9 недоступен в репозитории Universe.
Его необходимо загрузить из архива РРА (Personal Package Archive). Чтобы уста
новить Python из deadsnakes РРА (https//launchpad.net/-deadsnakes/+archive/
ubuntu/ppa), выполните следующие команды в окне терминала:
$ sudo add-apt-repository ppa:deadsnakes/ppa
$ sudo apt-get update
$ sudo apt-get install pythonЗ.9 idle-pythonЗ.9 pythonЗ-pip
Чтобы проверить, что установлена правильная версия Python, введите коман
дуpythonЗ --lJersion. Если будет выведен номер версии меньше 3 .9, возможно,
следует ввести командуpythonЗ.9 --version. Теперь вы можете запустить IDLE
и приготовиться к созданию вашей первой программы на языке Python.
36 ГЛАВА 2 Установка и настройка Python
Запуск IDLE
Чтобы запустить IDLE из командной строки, введите следующую команду:
$ idle-pythonЗ.9
В некоторых установках Linux можно запустить IDLE сокращенной командой:
$ idleЗ
IDLE открывает командную оболочку (shell) Python в новом окне. Оболочка
Python - интерактивная среда, в которой можно ввести код Python и немед
ленно выполнить его. Она отлично подходит для изучения Python!
ПРИМЕЧАНИЕ
Хотя ничто не мешает вам использовать вместо IDLE другой редактор кода,
если он вам больше нравится, учтите, что в некоторых главах (особенно в гла
ве 7 «Поиск и исправление ошибок в коде») работа построена исключительно
на использовании IDLE.
Окно командной оболочки Python выглядит примерно так:
IDLE Shell 3.9 .1
,-
file .§dit She!I QeЬug Qptions ~indow .1:telp
Python 3.9.1 (default, Dec 8 2828, 03:24:52)
~
[GCC 7.5.6] on linux
Туре "help•, •copyright", •credits" ог "license()• for more information.
>>>
-:;
Ln: 4 Col:'!
В верхней части окна выводится номер версии Python и информация об опе
рационной системе. Если номер меньше 3.9, возможно, вам стоит вернуться
к инструкциям по установке из предыдущего раздела.
2.4 .Linux 37
ВАЖ НО!
Если вы запустили IDLE командой idleЗ и в окне оболочки Python выводит
ся номер версии меньше 3.9 , значит, IDLE нужно запустить командой idle-
pythonЗ.9 command.
Символы >>> образуют так называемое приглашение. Когда вы видите его, это
означает, что Python ожидает от вас инструкций.
ИНТЕРАКТИВНЫЙ ТЕСТ
Кэтой главе прилагается бесплатный интерактивный тестдля проверки усво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-setup
Итак, Python установлен в вашей системе, и мы можем написать первую про
грамму Python! Переходите к главе 3.
ГЛАВАЗ
Первая программа Python
Итак, вы установили последнюю версию Python - пора браться за програм
мирование!
В этой главе мы:
• напишем нашу первую программу на Python;
• запустимпрограмму, содержащую ошибку, и посмотрим, что происходит;
• объявим переменную и просмотрим ее значения;
• напишем комментарии.
Готовы сделать первые шаги в мир Python? Вперед!
3.1 . НАПИСАНИЕ ПРОГРАММЫ РУТНОN
Если вы еще не открыли IDLE, сделайте это сейчас. В IDLE вы будете работать
с двумя основными окнами: интерактивным, которое открывается при ;iа11уске
IDLE, и окном редактора.
Код можно вводить в любом из этих окон. Но они отличаются тем, как в них
выполняется код. В этом разделе мы расскажем, как выполнять код Python
в обоих окнах.
Интерактивное окно
Интерактивное окно IDLE содержит командную оболочку Python - текстовый
интерфейс, иснользуемый для взаимодействия с языком Python. Если ввести
фрагмент кода Python в интерактивном окне и нажать клавишу Enter, вы не
медленно увидите резул1,таты - отсюда и название «интерактивное окно».
Это окно открывается автоматически при запуске IDI,E . В верхней его части
появляется следующий текст (с нс:тачительными отличиями, которые :швисят
от конфи гурации):
Python 3.9.1 (tags/v3.9.l:lb293b6)
[MSC v.1916 32 bit (Intel)] оп win32
3.1 . Написание программы Pythoп
39
Туре "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 ГЛАВА З Первая программа Python
Круглые скобки приказывают Python вызвать функцию print (). В них также
заключено все, что должно передаваться функции на вход. Кавычки означают,
что "Hello, World" - обычный текст, а не что-то иное.
ПРИМЕЧАНИЕ
В процессе ввода IDLE выделяетчасти вашего кода разными цветами, чтобы
вам было проще их распознать. По умолчанию функции выделяются фиоле
товым, а текст - зеленым цветом.
Интерактивное окно выполняет одну строку кода за раз. Такой режим вы
полнения удобен для небольших примеров и знакомства с языком Python, но
у него есть серьезный недостаток: вам приходится вводить код по одной строке!
Также возможен другой вариант: сохранить код Python в текстовом файле,
а затем выполнить сразу весь код из файла.
Окно редактора
Вы будете создавать ваши файлы Python в окне редактора IDLE. Окно редакто
ра можно открыть командой File ~ New File меню, находящегося в верхней части
интеракти вного ок на.
Когда открывается окно редактора , интерактивное окно остается открытым.
В нем отображается вывод, генерируемый кодом в окне редактора, поэтому окна
желательно расположить так, чтобы они были видны одновременно.
Введите в окне редактора тот же код, который использовался для вывода со
общения "Hello, World" в интерактивном окне:
print("Hello, World")
IDLE выделяет цветом код в окне редактора точно так же, как и в интерактив
ном окне.
ВАЖНО!
Если вы пишете код в файле Pythoп, не нужно включать в код приглашение
>>>.
Прежде чем запускать программу, ее необходимо сохранить. Выберите в меню
команду File ~ Save и сохраните файл под именем hello_world.py.
3.1 .Написание программы Pythoп 41
ПРИМЕЧАНИЕ
В некоторых системах для сохранения файлов в IDLE по умолчанию исполь
зуется каталог установки Python. Не сохраняйте свои файлы в этом каталоге.
Сохраните их на рабочем столе или в папке, расположенной в домашнем
каталоге пользователя.
Расширение .ру означает, что файл содержит код Python. Более того, при сохра
нении файла с любым другим расширением цветовая разметка кода исчезнет.
IDLE применяет ее только в коде Python внутри файлов .ру.
Запуск программ Python в окне редактора
Чтобы запустить программу, выберите команду Run ~ Run Module в меню окна
редактора.
Нажатие клавиши FS также запускает программу из окна редактора.
Вывод программы всегда отображается в интерактивном окне.
Каждый раз, когда вы запускаете код из файла, в интерактивном окне появляется
сообщение, которое выглядит примерно так:
>>> =================== RESTART ===================
IDLE перезапускает интерпретатор Python - компьютерную программу, которая
фактически выполняет ваш код при каждом запуске файла. Это гарантирует,
что программы будут каждый раз выполняться в одинаковых условиях.
Открытие файлов Python в окне редактора
Чтобы открыть существующий файл в 1DLE, выберите в меню команду File ~ Open,
а затем выделите файл, который вы хотите открыть. IDLE открывает каждый
файл в новом окне редактора, так что вы можете открыть несколько файлов
одновременно.
Файл можно также открыть из файлового менеджера - такого, как Про
водник Windows или macOS Finder. Щелкните правой кнопкой мыши на
значке файла и выберите команду Edit with IDLE, чтобы открыть файл в окне
редактора IDLE.
42 ГЛАВА 3 Первая программа Python
Двойной щелчок на файле .ру в файловом менеджере запускает программу.
Однако файл обычно выполняется системной версией Python, и окно програм
мы исчезает сразу же после ее завершения - нередко до того, как вы увидите
какой-либо в ы в о д.
Апока, если вы хотите запустить программу Python, лучше всего сделать это
в редакторе IDLE.
3.2 . ОШИБКИ
Все совершают ошибки - особенно в программировании! Даже если вы еще не
сделали ни одной, мы сыграем на опережение и нарочно испортим код, чтобы
посмотреть, что из этого выйдет.
Ошибки в программах делятся на два основных типа: синтаксические ошибки
и ошибки времени выполнения.
Синтаксические ошибки
Синтаксическая ошибка возникает, если вы написали код, недопустимый
в Python.
Давайте намеренно создадим синтаксическую ошибку. Удалите последнюю
кавычку из кода в файле hello_ world.py , созданного в предыдущем ра:щеле:
print("Hello, World)
Сохраните файл и запустите его нажатием клавиши FS. Программа рuботать не
будет! IDLE отображает окно сообщения со следующим текстом:
EOL while scanning string literal.
(EOL при сканировании строкового литерuла.)
В этом тексте встречаются двu термина, которые могут быть вuм нез11uкомы.
1. Строковый литерал (string literal) - текст, заключенный в кавычки.
" Hello, World" является строковым литералом.
2. EOL означает конец строки (End of Line).
Таким образом, IПLE сообщает, что Pythoп достиг конца строки во время чте
ния строкового литерала. Строковые литералы должны эавершаться кавычкой,
предшествующей концу строки.
3.2 . Ошибки 43
IDLE выделяет красным цветом строку с командой print("Hello, World), чтобы
вы смогли быстро найти строку кода с синтаксической ошибкой. Без второй
кавычкивсе символы, следующие после первой, включаязавершающую круглую
скобку, считаются частью строкового литерала.
Ошибки времени выполнения
IDLE обнаруживает синтаксические ошибки еще до того, как программа начнет
работу. Напротив, ошибки времени выполнения происходят только в работа
ющих программах.
Чтобы сгенерировать ошибку времени выполнения, удалите обе кавычки
в файле hello_world.py:
print(Hello, World)
Вы заметили, что при удалении кавычек цвет кода меняется на черный? IDLE
уже не распознает Hello, World как текст. Как вы думаете, что произойдет при
запуске программы? Нажмите FS, чтобы узнать!
В интерактивном окне следующий текст выводится красным цветом:
Traceback (most recent call last):
File "/home/hello_world.py", line 1, in <module>
print(Hello, World)
NameError: name 'Hello' is not defined
Каждый раз, когда в программе происходит ошибка, Python останавливает
программу и выводит несколько строк текста - так называемую обратную
трассировку. Она содержит полезную информацию об ошибке.
Обратную трассировку лучше читать снизу вверх.
•
В последней строке обратной трассировки содержится имя ошибки и со
общение об ошибке. В данном случае ошибка NameError возникла из-за
того, что имя Hello нигде не определено.
•
В предпоследней строке выводится код, который стал причиной ошибки.
Файл hello_world.py состоит всего из одной строки, так что понять, где
возникла проблема, несложно. Но такая информация пригодится для
больших файлов.
• Третья строка с конца сообщает имя файла и номер строки, чтобы вы
могли перейти к строке кода, в которой произошла ошибка.
В следующем разделе вы узнаете, как определять именадля значений в коде.
Но прежде чем двигаться дальше, советую вам потренироваться в устранении
44 ГЛАВА З Первая программа Python
синтаксических ошибок и ошибок времени выполнения - в обзорных упраж
нениях.
Упражнения
1. Напишите программу, которая не будет запускаться в IDLE из-за син
таксической ошибки.
2. Напишите программу, в которой ошибка происходит только во время вы
полнения, потому что программа содержит ошибку времени выполнения.
3.3 . СОЗДАНИЕ ПЕРЕМЕННОЙ
В языке Python переменной называется имя, с которым можно связать зна
чение, чтобы в дальнейшем в коде программы ссылаться на значение по имени.
Переменные чрезвычайно важны для программирования по двум причинам.
1. Переменные предоставляют быстрый доступ к значениям. Например,
результат некоторой продолжительной операции можно присвоить
переменной, чтобы вашей программе не приходилось заново выполнять
операцию каждый раз, когда потребуется использовать результат.
2. Переменные наделяют значения контекстом. Число 28 может означать
что угодно: количество студентов в группе, количество обращений поль
зователя к неб-сайту и т. д. Если связать значение 28 с именем (например,
num_students), его смысл становится более понятным.
В этом разделе вы научитесь использовать переменные,а также освоите неко
торые соглашения, которые соблюдаются программистами Python при выборе
им ен переме нных.
Оператор присваивания
Оператор представляет собой знак (например, +), выполняющий операцию
с одним или несколькими значениями. Например, оператор + получает два
числа (одно слева, а другое справа) и складывает их.
Для присваивания значений именам переменных используется специальный
знак, называемый оператором присваивания (=) . Оператор= получает значе
ние, указанное справа от оператора, и присваивает его имени, указанному слева.
Изменим файл hello_uюrld.py из предыдущего раздела, чтобы программа при
сваивала текст переменной file, а затем выводила ее на экран:
>» greeting = "Hello, World"
>>> print(greeting)
Hello, world
3.3. Создание переменной 45
В первой строке создается переменная с именем greeting, которой при помощи
оператора= присваивается значение "Hello, World" .
Команда print(greeting) выводиттекст Hello, World, потому что Python ищет
имя greeting,узнает, что ему было присвоено значение "Hello, World", иперед
вызовом функции заменяет имя переменной значением.
Если не выполнить команду greetiпg = "Hello, World" перед выполнением
команды priпt(greetiпg), вы получите ошибку NameError, как при попытке
выполнения команды priпt(Hello, World) в предыдущем разделе.
ПРИМЕЧАНИЕ
Хотя символ= известен нам из курса математики какзнак равенства, в Pythoп
он имеет другой смысл. Это важный момент, который может сбить с толку
многих начинающих программистов.
Простозапомните: когда вы видите оператор=,значение в правойчасти при
сваивается переменной в левой части.
В именах переменных важен регистр символов, так что greetiпg и Greetiпg -
две разные переменные. Например, при попытке выполнения следующего кода
будет выдана ошибка NameError:
>>> greeting = "Hello, World"
>>> print(Greeting)
Traceback (most recent call last):
File "<stdin>", line 1, in cmodule;
NameError: name 'Greeting' is not defined
Если у вас возникнут сложности при выполнении какого-либо примера из книги,
проверьте каждый символ в вашем коде, включая пробелы, - ваш код должен
абсолютно соответствовать примеру в книге. Компьютер точно выполняет то,
что вы прикажете, так что «почти правильно~> будет недостаточно!
Правила выбора имен переменных
Имена переменных могут быть сколь угодно длинными или короткими, но есть
несколько правил, которые необходимо соблюдать. Имена переменных могут
46 ГЛАВА З Первая программа Pythoп
состоять из латинских букв верхнего и нижнего регистра (A-Z , a-z), цифр (0-9)
и символов подчеркивания (_) , ноони не могут начинаться с цифры.
Например, каждое из следующих имен является действительным именем пере
менной Python:
• stringl
• _alp4a
•
list_of_names
А вот такие имена не являются действительными именами переменных, потому
что они начинаются с цифры:
• 9lives
• 99_balloons
• 2beOrNot2Be
Кроме латинских букв и цифр, имена переменных в Python могут содержать
многие символы Юникода.
Юникод (Unicode) - стандарт цифрового представления символов, исполь
зуемых в большинстве мировых письменных систем
.
Это значит, что имена
переменных могут содержать буквы из других алфавитов, например буквы
с диакритическими знаками (е, ti и т. д.), и даже знаки китайской, японской
и арабской письменности.
Тем не менее не каждая система нормально отображает расширенные символы,
поэтому лучше избегать их, если только вы не собираетесь обмениваться своим
кодом с жителями других стран.
'
'
.
'
ПРИМЕЧАНИЕ .
.
.
.
·
.
·
О Юникоде мы более подробно поговорим в главе 12.
Также о поддержке Юникода в Python можно узнать в официальной докумен
тации Pythoп (https://docs.python.org/3/howto/unicode.html#python-s-unicode-
suppo rt) .
Даже если имя переменной допустимо, оно не всегда может оказаться хорошим.
Выбрать хорошее имя для переменной иногда на удивление нелегко. К счастью,
су щес тву ют рекоме ндации, к оторые помогут в ам .
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
Нет правил, которые бы требовали, чтобы имена переменных следовало запи
сывать в нижнем регистре с подчеркиваниями. Однако эта практика закреплена
в документе, который называется РЕР 8, и часто его считают официальным
руководством по стилю при написании кода Python.
ПРИМЕЧАНИЕ
Сокращение РЕР означает «предложение по улучшению Pythoп» (Pythoп
Enhancement Proposal). РЕР - проектировочный документ, используемый со
обществомPythonдля предложений о включении новых возможностей в язык.
Соблюдение стандартов, описанных в РЕР 8, гарантирует, что ваш код Python
будет понятен большинству Руthоn-программистов. Это упрощает коммуни
кацию и сотрудничество при обмене кодом для всех участников разработки.
Упражнения
1. В интерактивном окне выведите какой-либо текст вызовом функции
print().
2. В интерактивном окне присвойте строковый литерал переменной. Затем
выведите содержимое переменной вызовом функции print().
3. Повторите первые два упражнения в окне редактора.
3.4 . ПРОСМОТР ЗНАЧЕНИЙ
В ИНТЕРАКТИВНОМ ОКНЕ
Введите следующий фрагмент в интерактивном окне IDLE:
»> greeting = "Hello, World"
»> gre e ting
'He llo, Wo rld '
Когда вы нажмете 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() выводит более удобочитаемое представление зна
чения переменной, что для строковых литералов означает вывод текста без
кавычек.
Иногда вывод и проверка переменной выдают одинаковые результаты:
>>>х=2
»>х
2
»> print(x)
2
Здесь переменной х присваивается число 2. Как при вызове print(x), так и при
проверке х результат выводится без кавычек, потому что 2 является числом,
а не текстом. В большинстве случаев проверка переменной дает более полезную
информацию, чем print().
П ре дп ол ожи м, им еют ся д в е переменные: х, к оторой присваивается значение 2,
и у, которой присваивается строковый литерал "2 ". В это м сл у ч а е print(x)
и print(y) выводят одно и то же:
»>х=2
»>у="2"
»> print(x)
2
»> print(y)
2
50 ГЛАВА З Первая программа Python
Однако при проверке х и у проявляются различия между значениями пере
ме нных:
»>х
2
»>у
'2'
Главный вывод таков: print () выводит удобочитаемое представление значе
ния переменной, тогда как проверка переменной выводит значение в том виде,
в котором оно встречается в коде.
Учтите, что проверка переменной работает только в интерактивном окне. На
пример, попробуйте выполнить следующую программу из окна редактора:
greeting = "Hello, World"
gre e ting
Программа выполняется без ошибок, но ничего не выводит!
3.5. ЗАМЕТКИ НА ПАМЯТЬ
Программисты нередко читают код, написанный ими же некоторое время назад,
и спрашивают себя: «Что он делает?» Если вы давно не обращались к своему
коду, вам трудно будет вспомнить, почему вы написали его именно так, а не
иначе!
Чтобы избежать этой проблемы, вы можете оставить комментарии в своем коде.
Комментарии - это текст, который никак не влияет на вьшолнение програм
мы. Они только описывают, что делает код или почему программист 11ринял
ко нкре тные р ешения.
Как пишутся комментарии
Самый простой способ вставить комментарий - это разместить символ #
в начале новой строки. Во время выполнения кода Python игнорирует строки,
начинающиеся с #.
Комментарии, начинающиеся с новой строки, называются блоковыми. Также
можно использовать встроенныекомментарии,ониразмещаются водной строке
с кодом, к которому относятся. Просто поставьте в конце строки символ#, а за
ним разместите текст комментария.
3.5 . Заметки на память 51
Пример программы, содержащей комментарии обоих типов:
# Б локо вый ком ментар ий.
greetiпg = "Hello, World"
pri пt(g reet ing) # В стро енны й к оммен тарий.
Символ# можно использовать и в строке. Например, Python не примет символ#
в этой строке за начало комментария:
»> print("#l ")
#1
В общем случае рекомендуется использовать как можно более короткие ком
ментарии, но иногда требуется больше текста, чем помещается в одной строке.
В таком случае можно продолжить комментарий в новой строке, которую также
следует начать с символа#:
# Моя перв ая п рограмм а.
#Она выводит фразу "Hello, World".
#Комментарии в ней длиннее кода!
greeting = "Hello, World"
print(greeting)
Также комментарии используют для временного исключения кода в процессе
тестирования программы. Разместив символ # в начале строки, можно вы
полнить программу так, словно эта строка не существует, но без фактического
уд аления код а.
Чтобы закоммснтировать фрагмент кода в IDLE, выделите этот фрагмент и на
жмите соответствующую комбинацию клавиш:
• Windows: Alt+З;
•
macOS: Ctrl+З;
• Ubuntu Linux: Ctrl+D.
Чт о бы у бр а т ь комментарии, выд елите зак омме нтир ова нныс строки и нажмите:
• Windows: Alt+4;
•
macOS: Ctrl+4;
• Ubuntu Linux: Ctrl+Shift+D.
Рассмотрим некоторые распространенные соглашения, относящиеся к ком
ментариям в коде.
52 ГЛАВА З Первая программа Pythoп
Соглашения и вредные привычки
Согласно РЕР 8, комментарии надо всегда записывать в виде завершенных
предложений, а символ# следует отделять от первого слова комментария од
ним пробелом:
# Этот комментарий отформатирован по правилам РЕР 8.
#А этот нет
Для встроенных комментариев РЕР 8 рекомендует отделять код от символа#
как минимум двумя пробелами:
phrase = "Hello, World" # Этот комментарий соответствует РЕР 8.
print(phrase)# А этот нет.
РЕР 8 рекомендует осмотрительно использовать комментарии. Главной вредной
привычкой программисты считают комментарии, описывающие то, что и так
очевидно из чтения кода.
Например, комментарий в следующем коде не нужен:
# Вывести "Hello, World"
print("Hello, World")
Этот комментарий лишний, потому что сам код наглядно показывает, что
он делает. Комментарии лучше использовать для пояснения трудного для
понимания кода или объяснения того, почему что-то вы делаете именно так,
а не иначе.
3.6 . ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы написали и выполнили свою первую программу на языке
Python! Это была небольшая программа, которая выводит текст "Hello, World"
при помощи функции print().
Далее вы узнали о синтаксических ошибках, которые возникают до выпол
нения программы с недействительным кодом Python, и об ошибках времени
выполнения, происходящих только во время выполнения программы.
Вы на учили сь присваивать з начения пе р ем ен ны м опе ратор ом прис ваив ания
=, а также проверять значения переменных в интерактивном окне. Наконец,
вы узнали, как включать в код полезные комментарии на случай, если вы или
кто-то другой будете просматривать его в впоследствии.
3.6 . Итоги и дополнительные ресурсы
53
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpy t hon. com /qu izz es !py bas ic s-f irs t -progr am
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
• «11 Beginner Tipsfor Learning Python Programming» (https.j/realpython.
com/python-beginner-tips/)
• «Writing Comments in Python (Guide)» (https.j/ realpython.com/python-
comments-guide/)
ГЛАВА4
Строки и строковые
методы
Многие программисты разных специализаций ежедневно имеют дело с текстом.
Например, веб-разработчики работают с текстовыми данными веб-форм. Экс
перты по аналитическим данным обрабатывают текст для извлечения данных
и выполнения таких операций, как анализ эмоциональной окраски, для иден
тификации и классификации мнений в блоке текста.
Текстовые блоки в Python называются строками (strings). Для обработки строк
и споль зуются спец иаль ные фу нкци и, на зыв аем ые ст ро к ов ым и методами. Су
ществуют строковые методы для преобразования строки из нижнего регистра
вверхний, удаления пропусков в начале или конце строки, замены частей строки
другим текстом и многих других операций.
В этой главе вы научитесь:
• обрабатывать строки с и спольз ованием строк овых м е т од о в;
• работать с пользовательским вводом;
• работать с числовыми данными в строках;
• форматировать строки для вывода.
Задело!
4.1. ЧТО ТАКОЕ СТРОКА?
В главе 3 вы создали строку "Hello, World" и вывели ее в интерактивном окне
1DLE при помощи функции print(). В этом разделе мы более детально разбе
ремся в том, что собой представляют строки, а также изучим различные способы
их создания в Pythoв.
4.1. Что такое строка? 55
Строковый тип данных
Строки принадлежат к числу фундаментальных типов данных Python. Тип
данных определяет, какого рода данные представляет значение. Строки ис
пользу ются для представления те кста .
ПРИМЕЧАНИЕ
В Python существуют другие встроенные тиnы данных. Наnример, числовые
типы данных будут рассматриваться в главе 5, а логические типы данных -
в главе 8.
Строки называют фундаментальным типом данных, потому что они не могут
быть разбиты на меньшие значения другого типа. Не все типы данных являются
фундаментальными. В главе 9 я расскажу о составных типах данных, также на
зываемых структурами данных.
Для строкового типа данных в Python есть специальное сокращенное имя: str.
Его можно вывести при помощи функции type( ), которая используется для
определения типа данных заданного значения.
Введите следующие команды в интерактивном окне IDLE:
»> type("Hello, World")
<class 'str'>
Вывод<class 'str'>означает, что значение "Hello, World" являетсяэкземпля
ромтипаданныхstr.Другими словами, "Hello, World" - строка.
ПРИМЕЧАНИЕ
Пока слово class можно рассматривать как синоним для типа данных, хотя
на самом деле оно имеет более конкретный смысл. О том, что такое классы,
мы расскажем в главе 1О.
Функция type() также работает для значений, которые были присвоены пере
менной:
»> phrase = "Hello, World"
»> type(phrase)
<class 'str'>
56 ГЛАВА 4 Строки и строковые методы
Строки обладают тремя важными свойствами.
1. Строки содержат отдельные буквы или знаки, которые называются
симво лами.
2. У строк есть длина - количество символов, содержащихся в строке.
3. Символы в строке образуют последовательность; это означает, что каждый
символ в строке занимает позицию с определенным номером.
Посмотрим, как создаются строки.
Строковые литералы
Как вы уже видели, строку можно создать, заключив текст в кавычки:
stringl
st rin g2
'Hello, World'
"1234"
Для создания строки можно использовать как одинарные кавычки (stringl),
так и двойные (string2) - важно лишь, чтобы в начале и в конце строки ис
пользовались к ав ычк и одного ви да.
Любая строка, созданная заключением текста в кавычки, называется строковым
литералом. Все строки, которые встречались вам до настоящего момента, были
строковыми литералами.
ПРИМЕЧАНИЕ
Не каждая строка является строковым литералом. Некоторые строки вво
дятся пользователем или читаются из файла. Так как в этом случае они
не заключаются в кавычки, такие строки не являются строковыми литера
лами.
Кавычки, в которые заключена строка, называются ограничителями, потому
что они сообщают Python, где начинается и где завершается строка. Если одну
разновидность кавычек вы используете в качестве ограничителя, другую раз
новидность можете использовать внутри строки:
stringЗ = "We're #1!"
string4 = 'I said, "Put it over Ьу 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 выдаст ошибку SyпtaxError, потому что решит, что строка завершается
после второй двойной кавычки ",и не будет знать, как интерпретировать оста
ток строки. Если вам понадобится включить в строку кавычку, совпадающую
с ограничителем, вы можете экранировать символ обратной косой чертой:
>>> text = "S he said, \"What time is it?\""
»> print(text)
She said, "What time is it?"
ПРИМЕЧАНИЕ
Когда вы работаете над проектом, рекомендуется использовать только одинар
ные кавычки (или только двойные) в качестве ограничителей для всех строк.
Помните, что здесь нет однозначно правильного или ошибочного варианта!
Нужно только действовать последовательно, потомучтоэто упроститчтение
и пон има ние вашего к ода.
Строки могут содержать произвольные символы Юникода. Например, строка
"We' re #1 !" содержитсимвол «решетка» (#),а строка "1234" содержит цифры.
"xPytnФrJ x " также является действительной строкой Python!
Определение длины строки
Количество символов в строке, включая пробелы, называется длиной строки.
Например, длина строки "аЬс" - 3 , а длина строки "Dоп 't Рапiс" равна 11.
В Python существует встроенная функция lеп(), предназначенная для опре
деления длины строки. Чтобы увидеть, как она работает, введите следующую
команду в интерактивном окне 1DLE:
»> len("abc")
з
58 ГЛАВА 4 Строки и строковые методы
Функция len () также может использоваться для получения длины строки,
присвоенной переменной:
>>> letters = "аЬс"
>>> len(letters)
3
Сначала строка "аЬс" присваивается переменной letters. Затем вызов функции
len() возвращаетдлину letters, которая равна 3.
Многострочный текст
Стилевое руководство РЕР 8 рекомендует, чтобы каждая строка кода Python
содержала не более 79 символов, включая пробелы.
'
'
ПРИМЕЧАНИЕ
Длинастрокив79символов,указаннаявРЕР8, - рекомендация, а не правило.
Некоторые Руthоn-программисты предпочитают несколько большую длину
строки.
В этой книге четко соблюдается длина строки, рекомендованная в РЕР 8.
Независимо от того, следуете ли вы рекомендациям РЕР 8 или нет, иногда
возникает необходимость создать строку, количество символов в которой пре
вышает указанное ограничение.
Чтобы работать с более длинными строками, можно разбить их на несколько
«логических строк~.. Допустим, необходимо включить следующий текст в стро
ковый литерал:
This planethas - or rather had - а proЬlem, which was
this: most ofthepeople living on it were unhappy for
pretty much ofthetime. Many solutions were suggested
for thisproЬlem, but most of these were largely con-
cerned with the movements of small green pieces of
paper, which is odd because он the whole it wasn't the
small green pieces of paper that were unhappy.
Дуглас Адамс. «Автостопом по галактике~.
Абзац содержит намного более 79 символов, так что любая строка кода, содер
жащая этот текст в виде строкового литерала, нарушает РЕР 8. Что же делать?
4.1 . Что такое строка?
59
Есть пара возможных решений. Вы можете разбить строку на несколько частей
и поставить обратную косую черту \ в конце каждой части, кроме последней.
Для выполнения рекомендаций РЕР 8 общая длина каждой части, включая \,
не должна превышать 79 символов.
Пример записи абзаца в виде многострочного текста с использованием\:
paragraph : "This plaпet has-or rather had-a proЫem, which was \
this: most of the people liviпg оп it were uпhappy for pretty much \
of the time. Мапу solutioпs were suggested for this proЫem, but \
most of these were largely сопсеrпеd with the movemeпts of small \
greeп pieces of paper, which is odd because оп the whole it wasп't \
the small greeп pieces of paper that were uпhappy."
Обратите внимание: закрывать каждую строку кавычкой не нужно. В обычной
ситуации Python доберется до конца первой строки и пожалуется на то, что
строка не закрывается парной двойной кавычкой. Но если в конце стоит сим
вол \, Python понимает, что вы просто продолжаете ту же строку в следующей
строке кода.
При выводе функцией print () многострочного текста, разбитого символами \,
вывод отображается в одну строку:
»> loпg_striпg : "This multiliпe striпg is \
displayed оп опе liпe"
>>> priпt(loпg_striпg)
This multiliпe striпg is displayed оп опе liпe
Также можно создавать многострочный текст, используя в качестве ограни
чителей утроенные кавычки (""" или ' ' '). Пример записи длинного абзаца
подобным способом:
paragraph : """This plaпet has-or rather had-a proЫem, which was
this: most of the people liviпg оп it were uпhappy for pretty much
of the time. Мапу solutioпs were suggested for this proЫem, but
most of these were largely сопсеrпеd with the movemeпts of small
greeп pieces of paper, which is odd because оп the whole it wasп't
the small greeп pieces of paper tt1at were uпhappy. """
Строки в утроенных кавычках сохраняют символы пропуска (whitespace), ко
торые включают пробелы и символы начала новой строки. Это означает, что
команда print(paragraph) выведет строку с разбиением на части - в том виде,
в котором она определяется в строковом литерале. Иногда это именно то, что
нужно, иногда - нет, поэтому вам стоит обдумать, какой вывод вас устроит, до
то го, к а к выберете с п о с о б записи многострочного т екст а.
60 ГЛАВА 4 Строки и строковые методы
Чтобы увидеть, как пробелы сохраняются внутри строки в утроенных кавычках,
введите следующий фрагмент в интерактивном окне IDLE:
>>> print("""An example of а
string that spans across multiple lines
and also preserves whitespace. "" ")
An example of а
string that spans across multiple lines
and also preserves whitespace.
Обратите внимание: вторая и третья строки вывода содержат точно такие же
отступы, как в строковом литерале.
Упражнения
1. Выведите строку, внутри которой используется двойная кавычка.
2. Выведите строку, внутри которой используется апостроф.
3. Выведите многострочный текст с сохранением пробелов.
4. В ыв еди те с тр оку , которая определяется в мн ого стр очно м формате, но
выводится в одной строке.
4.2 . КОНКАТЕНАЦИЯ, ИНДЕКСИРОВАНИЕ
И СРЕЗЫ
Итак, теперь вы знаете, что такое строка и как объявляются строковые лите
ралы в коде. Рассмотрим некоторые операции, которые часто выполняются со
строками.
В этом разделе рассматриваются три основные операции:
1) конкатенация, или объ единен ие двух ст рок ;
2) индексирование, или получение одного символа из строки;
3) срезы, или получение сразу нескольких символов из строки.
Конкатенация строк
Две строки можно объединить оператором+ (эта операция называется конка
тенацией):
>>> stringl = "abra"
>>> string2 = "cadabra"
4.2. Конкатенация, индексирование и срезы
61
>>> magic_string stringl + string2
>>> magic_string
'abracadabra'
В этом примере конкатенация выполняется в третьей строке. Для конкатена
ции stringl и 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[l]
'i'
flavor[l] возвращает символ в позиции 1строки "fig pie", то есть i .
Стоп! Разве первый символ "fig pie" не f?
В Python - как и в большинстве других языков программирования - отсчет
всегда начинается с нуля. Чтобы получить символ в начале строки, необходимо
задать позицию О:
»> flavor[0]
'f'
62 ГЛАВА 4 Строки и строковые методы
. ВАЖ НО!
.
.
.
.
.
,
.
Если вы забудете, что нумерация индексов начинается с нуля, и попробуете
обратиться к первому символу в строке по индексу 1, произойдет ошибка
смещения на 1.
Ошибки смещения на 1 часто создают проблемы как начинающим,так иопыт
ны м прог раммистам!
На следующей диаграмме показаны индексы всех символов строки "fig pie" .
f
i
0
1
g
2
3
р
4
i
5
е
6
При попытке обращения по индексу, значение которого больше, чем число
символов в строке, Python выдает ошибку IndexError:
»> flavor[9]
Traceback (most recent call last):
File "<pysl1ell#4>", line 1, in <module>
flavor[9]
IndexError: string index out of range
Наибольший индекс символа в строке всегда на 1 меньше длины строки. Так
как длина "fig pie" равна 7, наибольший допустимый индекс равен 6 .
В строках можно также использовать отрицательные индексы:
»> flavor[ -1]
'е'
Последнему символу в строке соответствует индекс -1; лля строки "fig pie" это
буква е. Предпоследнему символу i соответствует индекс -2 и т. д.
На следующей диаграмме представлены отрицательные индексы всех сим1юлов
строки "fig pie" .
f
-7
i
-6
g
-5
-4
р
-3
i
-2
е
-1
4.2 . Конкатенация, индексирование и срезы
63
Как и в случае с положительными индексами, при попытке обращения по от
рицательному индексу, значение которого меньше значения индекса первого
символа в строке, Python выдает ошибку IndexError:
»> flavor[ -10]
Traceback (most recent call last):
File "cpyshell#S>", line 1, in cmodule>
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[l] + flavor[2]
>>> first_three_letters
'fig'
Если вам нужно больше символов, чем несколько первых букв строки, возврат
каждого символа по отдельности с последующей конкатенацией выглядит
громоздким и неудобным. К счастью, Python позволяет решить эту задачу
компактно.
64 ГЛАВА 4 Строки и строковые методы
Чтобы выделить часть строки, называемую подстрокой, поставьте двоеточие
между двумя индексами внутри квадратных скобок:
>>> flavor = "fig pie"
» > flavor[0:З]
'fig'
Выражение flavor[0:З] возвращает первые три символастроки, присвоенной
flavor, начиная с символа с индексом О и заканчивая символом с индексом 3 (не
включая последний). Часть [0:з] выражения flavor[0:з] называется срезом.
В данном случае она возвращает срез строки "fig pie".
Понятие срезов нередко усваивается с трудом, потому что подстрока, возвра
щаемая срезом, включает символ с первым индексом, но не включает символ
со вторым индексом. Чтобы запомнить, как работают срезы, представьте себе
строку как последовательность квадратных ячеек. Левая и правая границы
каждой ячейки нумеруются последовательно от нуля до длины строки, и каждая
ячейка заполняется символом строки.
Вот как это выглядит для строки "fig pie":
f
i
g
р
i
е
0
1
2
3
4
5
6
7
Таким образом, для "fig pie" срез (0: З] возвращает строку "fig", а срез [3:7]
возвращает с троку" pie " .
Если опустить первый индекс в срезе, Python считает, что вы хотите начать
с индекса О:
»> flavo r[:З]
'fig'
Срез [:з] эквивалентен [0:з], так что flavor[ :з] вернет первые три символа
строки "figpie".
Аналогичным образом, если опустить второй индекс в срезе, Python считает,
что вы хотите получить подстроку, начинающуюся с первого индекса и завер
шающуюся последним символом в строке:
»> flav or[З: ]
' pie'
4.2 . Конкатенация, индексирование и срезы 65
Для строки "fig pie" срез [З:] эквивалентен срезу [З:7].Так как символ
с индексом з является пробелом, flavor[ з: 7] возвращает подстроку, которая
начинается с пробела и завершается последней буквой: " pie" .
Если в срезе опущен как первый, так и второй индекс, вы получите строку, ко
торая начинается с символа с индексом О и заканчивается последним символом.
Иначе говоря, в этом случае возвращается вся строка:
»> 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_stringl
non_empty_string2
n on_ emp ty_s tri ngЗ
Хотя эти строки не содержат видимых символов, пустыми они не считаются,
потому что содержат пробелы.
66 ГЛАВА 4 Строки и строковые методы
В срезах можно использовать отрицательные числа. Срезы с отрицательными
индексами подчиняются тем же правилам, что и срезы с положительными
числами. Попробуйте наглядно представить строку в виде последовательности
ячеек, границы которой помечены отрицательными числами.
f
i
g
р
i
е
-7
-6
-5
-4
-3
-2
-1
Как и прежде, срез [х: у] возвращает подстроку, которая начинается с индексах
и идет до индекса у (не включая последний). Например,срез [-7: -4] возвращает
первые три буквы строки "fig pie":
>>> flavor[-7:-4]
'fig'
Однако следует учитывать, что крайняя правая граница строки не имеет от
рицательного индекса. Логично предположить, что этой границе соответствует
число О, но такая запись не работает.
Вместо всей строки [-7:0] возвращает пустую строку:
»> flavor[-7:0]
Это происходит из-за того, что второе число в срезе должно обозначать границу,
которая находится справа от границы, соответствующей первомучислу всрезе.
Но в нашем случае -7, и 0 относятся к крайней левой границе диаграммы.
Если вы хотите включить в срез последний символ строки, опустите второе
число:
»> flavor[-7:]
'fig pie'
Конечно, использование flavor[ -7:] для получения всей строки выглядит
немного странно - ведь для этого достаточно применить переменную flavor
без среза!
Тем не менее срезы с отрицательными индексами полезны для получения
нескольких последних символов строки. Например, выражение flavor [ -З: ]
в озвращает "p ie " .
4.2. Конкатенация, индексирование и срезы
67
Неизменяемость строк
Прежде чем завершать этот раздел, обсудим важное свойство строк. Строки
являются неизменяемыми (immutaЬle); это означает, что после создания их
невозможно изменить. Например, посмотрите, что происходит при попытке
присвоить новую букву в определенной позиции строки:
>>> word = "goal"
>>> word[0] = "f"
Traceback (most recent call last):
File "<pyshell#lб>", line 1, in cmodule>
word[0] = "f"
TypeError: 'str' object does not support i tem assignment
Python выдает ошибку TypeError исообщает, что объекты str не поддерживают
присваивание элементов.
Если вы захотите изменить строку, придется создать новую строку. Чтобы
преобразовать строку "goal" в строку "foal", можно воспользоваться срезом
и объединить букву "f" со всеми буквами слова "goal", кроме первой:
>» word
"goal"
>>> word
"f " + word[l:]
»> word
'foal'
Сначала строка "goal" присваивается переменной word. Затем срез word [1:] ,
то есть строка "oal",объединяется с буквой "f " для получения строки "foal" .
Если вы получили другой результат, проверьте, что вы не забыли включить
в срез двоеточие (:).
Упражнения
1. Создайте строку и выведите ее длину при помощи функции len().
2. Создайте две строки, выполните их конкатенацию и выведите получен
ную строку.
3. Создайте две строки, воспользуйтесь конкатенацией для добавления
пробела между ними и выведите результат.
4. Выведите строку "zing", используя синтаксис срезов для определения
правильного диапазона символов в строке "bazinga" .
68 ГЛАВА 4 Строки и строковые методы
4.3 . МАНИПУЛЯЦИИ СО СТРОКАМИ
С ИСПОЛЬЗОВАНИЕМ МЕТОДОВ
В Python существуют специальные функции для работы со строками; они
называются строковыми методами. Их множество, но мы ограничимся лишь
наиболее часто используемыми.
В этом разделе вы научитесь:
• преобразовывать строку к верхнему или нижнему регистру;
• удалять пропуски из строк;
• определять, содержатся ли определенные символы в начале или конце
строки .
Преобразование регистра
Чтобы преобразовать все буквы строки к нижнему регистру, используйте метод
. lower(). Вызов метода . lower() добавляется непосредственно после преоб
разуемой строки:
»> "Jean-Luc Picard" .lower()
'jean-luc picard'
Точка (.) сообщает Python, что далее следует имя метода - в данном случае
метода lower().
ПРИМЕЧАНИЕ
В тексте строковые методы будут обозначаться точкой в начале имен. Напри
мер,. lower() записывается с начальной точкой вместо lower().
При таких обозначениях вам будет проще отличать строковые методы от
встроенныхфункций - например, priпt () или type().
Строковые методы работают не только со строковыми литералами. Также можно
вызвать .lower() для строки, присвоенной переменной:
>>> пате = "Jean-Luc Picard"
» > name. lower()
'jean-luc picard'
4.3 . Манипуляции со строками с использованием методов
69
Для . lower() существует парный метод. upper( ), который преобразует каждый
символ строки к верхнему регистру:
>» name.upper()
'J EA N-LUC PICARD'
Сравните строковые методы . upper() и . lower () с функцией len (),описанной
ранее. Кроме того, что различаются результаты этих функций, важно обратить
внимание на то, как они используются.
len() - автономная функция. Если вы хотите определить длину строки name,
вы вызываете функцию len () напрямую:
»> len(name)
15
А методы . upper() и .lower() должны вызываться в соединении со строкой.
Они не существуют независимо.
Удаление пропусков из строки
Пропусками (whitespace) называются любые символы, которые выводятся как
пустое место. К ним относятся пробел и новая строка - специальный символ,
продолжающий вывод со следующей строки.
Иногда возникает необходимость в удалении пропусков в начале или конце строки.
Эта операция особенно полезна при работе со строками, полученными от пользо
вателя, так как в такие строки могут быть случайно включены лишние пропуски.
Три строковых метода, которые могут использоваться для удаления пропусков
из строки:
1. . rstrip()
2. .lstrip()
3. .strip()
. r strip() удаляет пропуски в правой части строки:
>>> 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(), чтобы
определить, начинается ли строка с букв «е~ и «п~:
>>> starship = "Enterprise"
>>> starship.startswith("en ")
False
Чтобы сообщить .startswith(), какие символы следует искать, вы передаете
строку, содержащую эти символы. Таким образом, чтобы определить, начина
етсяли"Enterprise" сбукв «е~ и «n~. мы вызываем.startswith("en" ). Метод
возвращает False. Как вы думаете, почему?
4.3. Манипуляции со строками с использованием методов
71
Если вы предположили, что .startswith("en") возвращает False, потому что
"Enterprise" начинается с буквы «Ei>вверхнемрегистре - выабсолютноправы!
Метод . startswith() различает регистр символов. Чтобы вызов . startswith()
вернул True, необходимо передать ему строку "En":
>>> starship.startswith("En")
Tru e
Также при помощи метода .endswith() можно определить, завершается ли
строка заданными символами:
>>> starship.endswith("rise")
Tru e
Как и . startswith(), метод .endswi th() различает регистр символов:
>>> 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 реализована еще одна похожая возможность: клавиша ТаЬ автоматически
заполняет текст без необходимости вводить длинные имена. Например, если
вы введететолькоstarship.u и нажметеТаЬ, то IDLE автоматическидополнит
текст до starship. upper, потому что starship содержит только один метод, имя
которого начинается с u.
Этот прием работает даже с именами переменных. Попробуйте ввести не
сколько первых букв имени starship и нажать ТаЬ. Если в программе не были
определены другие переменные, начинающиеся с тех же букв, IDLE завершит
имя starship за вас.
4.4. Взаимодействие с пользовательским вводом
73
Упражнения
1. Напишите программу, которая преобразует следующие строки к нижне
му регистру: "Animals", "Badger", "Нопеу Вее", "Honey Badger". Каждый
результат преобразования должен выводиться с новой строки.
2. Повторите упражнение 1 , но преобразуйте каждую строку к верхнему
регистру, а не к нижнему.
3. Напишите программу, которая удаляет пропуски из следующих строк,
а затем выводит полученные результаты:
stringl = " Filet Mignon"
string2 "Brisket
stringЗ = "
Cheeseburger
4. Напишите программу, которая выводит результат вызова
. start-
swith("Ье") для каждой из следующих строк:
stringl "Becomes"
string2 "becomes"
strin gЗ "B EAR "
string4 = " bEautiful"
5. Используя строки из упражнения 4 , напишите программу, которая ис
пользует строковые методы для изменения каждой строки, чтобы вызов
. startswith("Ье") возвращал True для всех строк.
4.4 . ВЗАИМОДЕЙСТВИЕ С ПОЛЬЗОВАТЕЛЬСКИМ
вводом
Итак, вы научились работать со строковыми методами - теперь давайте зай
мем ся интерактивностью!
Сейчас вы узнаете, как получить информацию от пользователя при помощи
функции input(). Мы напишем программу, которая предлагает пользователю
ввести некоторый текст, после чего выводит текст в верхнем регистре.
Введите следующую команду в интерактивном окне IDLE:
»> inpu t()
74 ГЛАВА 4 Строки и строковые методы
Казалось бы, при нажатии клавиши Enter ничего не происходит. Курсор пере
мещается в новую строку, но приглашение »> в ней не появляется. Python
ожидает, когда вы введете какой-нибудь текст!
Наберите какой-нибудь ответ и нажмите Enter:
»> input()
Hello there!
'Hello there! '
»>
Текст повторяется в новой строке в одинарных кавычках. Это объясняется
тем, что input() возвращает в виде строки любой текст, введенный пользова
телем. Чтобы функция input() стала чуть более удобной, можно задать при
глашение, которое должно выводиться для пользователя. Оно представляет
собой обычную строку, которая заключается в круглые скобки input( ). Это
может быть все что угодно: слово, знак, фраза - все, что является допустимой
строкой Py tho n.
Функция input() выводит приглашение и ожидает, когда пользователь введет
какой-нибудь текст. Если пользователь нажмет Enter, input() возвратит текст
пользователя в виде строки, которую можно присвоить переменной и обрабо
тать в программе.
Чтобы увидеть, как работает input( ), введите следующий код в окне редактора
IDLE:
prompt = "Неу, what's up? "
user_input = input(prompt)
print("You said: " + user _input)
Запустите программу клавишей FS. В интерактивном окне появится текст Неу,
what's up? с мигающим курсором.
Для чего нужен пробел в конце строки "Неу, what' s up? "? Когда пользова
тель начнет вводить текст, он будет отделен от приглашения пробелом. Когда
пользователь введет ответ и нажмет Enter, его ответ присваивается переменной
user_input.
Пример выполнения программы:
Неу, what's up? Mind your own business.
You said: Mind your own business.
4.5 . Задача: разбор пользовательского ввода
75
Получив от пользователя введенную им информацию, вы можете с ней что
нибудь сделать. Например, следующая программа получает текст, преобразует
его к верхнему регистру вызовом . upper(), а затем выводит результат:
response = input("What should 1 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*З
'121212'
num*З выполняет конкатенациютрех строк "12" и возвращаетстроку "121212" .
Сравните эту операцию с арифметическими вычислениями. Когда вы умно
жаете число 12 на число 3, результат будет таким же, как при сложении трех
чисел 12. Этот принцип относится и к строкам. Иначе говоря, "12 " * з м о ж н о
4.6 . Работа со строками и числами
77
интерпретироватькак"12"+ "12"+"12". В общем случае умножение строки на
целоечислоn выполняетконкатенацию n копий этой строки.
Число в правой части выражения num*З можно переместнть в левую часть,
результат от этого не изменится:
»>3*num
'121212'
Как вы думаете, что произойдет, если использовать оператор* с двумя строками?
Введите "12"*"з" в интерактивном окне и нажмите Enter:
>>> "12" * "3"
Traceback (most recent call last):
File "<stdin>", line 1, in cmodule>
TypeError: can't multiply sequence Ьу non-int of type 'str'
Pythonвыдаетошибку TypeErrorи сообщает, что последовательность (sequence)
нельзя умножить на значение, которое не является целым числом.
ПРИМЕЧАНИЕ
Посnедоватепьностью называется любой объект Python, поддерживающий
обращение к элементам по индексу. Строки являются последовательностя
ми.Другие разновидности последовательностей рассматриваются в главе9.
При использовании оператора *со строкой Python всегда ожидает, что в другой
части оператора стоит целое число.
Как вы думаете, что произойдет при попытке сложить строку с целым числом?
>>>"3"+3
Traceback (most recent call last):
File "cstdin>", line 1, in cmodule>
TypeError: сап only concatenate str (not "int") to str
Python выдает ошибку TypeError, потому что ожидается, что объекты с двух
сторон оператора + относятся к одному типу.
Если объект с одной стороны + является строкой, Python пытается выполнить
конкатенацию строк. Сложение выполняется только в том случае, если оба
объектаявляются числами. Таким образом, чтобы выполнить сложение "з" + З
и получить 6, необходимо сначала преобразовать строку "з" в число.
78 ГЛАВА 4 Строки и строковые методы
Преобразование строк в числа
Примеры с TypeError из предыдущего раздела подчеркивают ти11ичную пробле
му несоответствия типов, которая возникает при передаче пользовательского
ввода операции, требующей числа, а не строки.
Рассмотрим пример. Сохраните и запустите следующую программу:
num = input("Enter а number to Ье douЬled: ")
douЫed_num = num * 2
print(douЫed_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
И хотя лишний О в дробной части фактически не изменяет число, Python не
превратит 12.0 в 12 , потому что это приведет к потере точности.
Вернемся к программе в начале раздела и посмотрим, как ее исправить. По
вторим ее код:
num = input("Enter а number to Ье douЫed: ")
douЫed_num = num * 2
print(douЫed_num)
Проблема возникает в строке douЫed_num = num * 2, потому что num является
строкой, а 2 - целым числом.
Проблему можно решить передачей num функции int() или float(). Так как
приглашение предлагает пользователю ввести число, причем необязательно
целое, преобразуем num к формату с плавающей точкой:
num = input("Enter а number to Ье douЫed: ")
douЫed_num = float(num) * 2
print(douЫed_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 cmodule>
TypeError: сап 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 3arms".
Такое включение целых чисел в строку называется строковой интерполяцией.
Одно из возможных решений основано на конкатенации строк:
>>> 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. Эти выражения
заменяются в строке результатами их вычисления:
»>п=3
>»m=4
>>> f"{n} times {m} is {n*m}"
'3 times 4 is 12'
Выражения, используемые в f-строках, должны быть простыми, насколько это
возможно. Включив несколько сложных выражений в строковый литерал, вы
можете создать код, который трудно читать и сопровождать.
F-строки доступны только в Python версии 3.6 и выше. В более ранних верси
ях Python для получения тех же результатов используется метод . fo rmat ().
В предыдущем примере форматирование строки методом . format () может
выглядет ь при ме рн о так:
>>> "{} has {} heads and {} arms".format(name, heads, arms)
'Zaphod has 2 heads and 3 arms'
F-строки короче кода с использованием .format(),а иногда илучше читаются.
В книге мы будем использовать f-строки.
Подробное описание f-строк и сравнение их с другими способами формати
рования строк вы найдете в руководстве «Python З's f-Strings: An Improved
StringFormatting Syntax(Guide)» на сайте Real Python(https.j/realpython.com/
python-f-stnngs/).
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" начинается с пятого символа строки "thesurprise
is in here somewhere", который имеет индекс 4, потому что нумерация начи
нается с О.
Если . find () не находит нужную подстроку, вместо индекса возвращается -1:
>>> phrase = "the surprise is in here somewhere"
> >> phr ase .fi nd( "ey jafj all ajб kul l")
-1
Учтите, что проверка совпадений осуществляется символ за символом и с учетом
регистра. Например, если вы попытаетесь найти подстроку "SURPRISE' ' , вызов
. find () вернет -1:
>>> "the surprise is in here somewhere".find("SURPRISE")
-1
4.8 . Поиск подстроки в строке
83
Если подстрока встречается в строке более одного раза, .find () возвращает
индекс только первого вхождения от начала строки:
>>> "I put а string in your string".find("string")
8
В строке"! put а stringiп your string" подстрока "string" встречаетсядважды,
с индексами 8 и 23, но .find () возвращает только8.
Метод . find () получает только строку. Если вы хотите найти целое число
в строке, придется передать . find () число в виде строки. В противном случае
Python выдаст ошибку TypeError:
>>> "Му number is 555-555-5555".find(S)
Traceback (most recent call last):
File "<stdin>", line 1, in cmodule>
TypeError: must Ье str, not int
>>> "Му number is 555-555-5555".find("5")
13
Иногда требуется найти все вхождения конкретной подстроки и заменить их
другой строкой. Так как .find() возвращает индекс первого вхождения под
строки, этот метод не так просто использовать для замены. Но у строк есть метод
. r eplace( ), который заменяет каждое вхождение подстроки другой строкой.
Как и в случае с . find( ), вызов . replace() следует после имени переменной или
строкового литерала. Однако на этот раз в круглых скобках . replace() необхо
димо указать две строки, разделив их запятой. Первая строка содержит искомую
подстроку, а вторая - текст, которым заменяется каждое вхождение подстроки.
Например, следующий код заменяет каждое вхождение "the truth" в строке
"! '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 () не изменяет
m y_stor y.
Если немедленно ввести 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() для поиска под
строки "а" в строке "ААА". Результат должен быть равен -1.
2. Заменитекаждоевхождениесимвола "s" на "х" в строке "Somebody said
somethingto Samantha."
3. Напишите программу, которая запрашивает у пользователя ввод функ
цией input() и выводит результат вызова .find()для поиска конкретной
буквы во введенном тексте.
4.9 . ЗАДАЧА: ПРЕОБРАЗОВАНИЕ ТЕКСТА
Напишите программу translate.py, которая предлагает пользователю ввести
текст со следующим приглашением:
Enter some text:
Используйте . replace(),чтобы зашифровать текст, введенный пользователем.
Для этого выполните следующие преобразования с буквами нижнего регистра.
•
Буква а преобразуется в 4.
•
Буква Ь преобразуется в 8.
• Буква е преобразуется в 3.
4.1 О. Итоги и дополнительные ресурсы 85
• Бу ква 1 преобразуется в 1.
• Буква о преобразуется в0.
• Бу ква s преобразуется в 5.
• Бук ва t преобразуется в 7.
Затем ваша программа должна вывести полученную строку. Ниже приведен
пример выполнения программы:
Enter some text: 1 like to eat eggs and spam.
1 likЗ 70 347 ЗggS 4nd Sp4m.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
4.1 О. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе мы рассказали о достоинствах и недостатках строковых объектов
Python. Вы узнали, как обращаться к разным символам строки по индексам
и с помощью срезов и как определить длину строки при помощи функции
len ().
Строки также содержат различные методы:. upper () и . lower () преобразуют
все символы строки к верхнему и нижнему регистру соответственно,. rstrip(),
. lstrip() и . strip() удаляютпропускииз строк, а. startswith() и .endswith()
проверяют, начинается или завершается строка заданной подстрокой.
Также вы научились сохранять в строке текст, введенный пользователем, при
помощи функции input () и преобразовывать этот ввод в число, используя int ()
и float ( ). Для преобразования чисел и других объектов в строки применяется
функция str().
Наконец, вы научились использовать методы . find () и . replace () для поиска
и замены подстрок новыми строками.
ИНТЕРАКТИВНЫЙ ТЕСТ
Кэтой главе прилагается бесплатныйинтерактивный тестдля проверки усво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com!quizzes/pybasics-strings
86 ГЛАВА 4 Строки и строковые методы
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
• «Python String Formatting Best Practices» (https.j/realpython.com/python-
s trin g- for ma tting l)
• «Splitting, Concatenating, andjoiningStringsin Python» (https.j/realpython.
com/python-string-split-concatenate-join/)
ГЛАВАS
Числа и математические
вычисления
Чтобы хорошо программировать, не обязательно быть гением в математике.
Откровенно говоря, лишь немногим программистам нужно иметь знания, вы
ходящие за курс базовой алгебры.
Конечно, уровень необходимых математических знаний задает приложение,
над которым вы работаете. В общем же случае уровень математической под
готовки, необходимый для программирования, ниже того, что можно было
бы ожидать.
И хотя математика и программирование связаны не так тесно, как считают
некоторые, числа являются неотъемлемой частью любого языка программи
рования, и Python не исключение.
В этой главе вы научитесь:
• соэдавать целые числа и числа с плавающей точкой;
• округлять числа до заданной точности;
• форматировать и выводить числа в строках.
Итак, за дело!
5.1 . ЦЕЛЫЕ ЧИСЛА И ЧИСЛА
С ПЛАВАЮЩЕЙ ТОЧКОЙ
В Python поддерживаются три встроенных числовых типа данных: целые
числа, числа с плавающей точкой и комплексные числа. В этом разделе рас
сматриваются целые числа и числа с плавающей точкой - два наиболее часто
испольэуемых типа. О комплексных числах я расскажу в разделе 5.7 .
88 ГЛАВА 5 Числа и математические вычисления
Целые числа
Целое число не имеет дробной части. Например, 1 - целое число, а 1.0 - нет.
Целочисленный тип данных обозначается именем int, в чем нетрудно убедиться
при помощи функции type():
>» type(l)
<class 'int'>
Чтобы создать целое число, достаточно его ввести. Например, следующий
фрагмент присваивает целое число 25 переменной num:
»>num=25
Когда вы создаете целое число подобным образом, значение 25 называется
ц е ло чи сл е нн ым литералом. Зн ачен ие целочис ленного литерала за д ае т ся не
посредственно в к оде.
Из главы 4 вы узнали, как преобразовать строковое представление целого чис
ла в число при помощи функции int( ). Например, следующий фрагмент кода
преобразует строку "25 " в целое число 25:
»> int("25 ")
25
int("25 ") не является целочисленным литералом, потому что целое число
создается на основе строки.
Ког да в ы записываете б ол ьш ие числа вручную, цифры о бы чн о об ъед иняю тся
в груп пы по т ри, раздел яемые з апят ыми и ли точками 1
•
Число 1,000 ,000 читается
нам ного проще , чем 1000000.
В языке Python запятые в целочисленных литералах запрещены, но вы можете
использовать символы подчеркивания(_). Оба следующих варианта являются
допустимыми способами представления одного миллиона в виде целочислен
ного литерала:
»> 1000000
10 00 000
»> 1_000_000
10 00 000
1 Так принято записывать числа в англоязычных странах. - Примеч. ред.
5.1. Целые числа и числа с плавающейточкой 89
Максимальное значение целого числа не ограничено, что удивительно, если
учесть, что компьютеры имеют лимитированный объем памяти. Попробуйте
ввести самое большое число, которое сможете придумать, в интерактивном
окне IDLE. Python справится с ним без проблем!
Числа с плавающей точкой
Число с плавающей точкой имеет дробную часть: 1.0 - число с плавающей точ
кой, как и -2.75. Тип данных с плавающей точкойобозначается именем float:
>» type{l.0)
<class 'float'>
Числа с плавающей точкой, как и целые числа, могут создаваться в видели
тералов с плавающей точкой или преобразованием строки вызовом float ( ):
>>> float{"l.25")
1. 25
Существуют три способа представления литералов с плавающей точкой. Каж
дый из следующих вариантов записи создает литерал с плавающей точкой со
з начен ием од ин миллио н:
» > 1000000.0
1000000.0
»> 1_000_000 .0
1000000.0
»> lеб
1000000.0
Первые два варианта похожи на два способа создания целочисленныхлитералов.
Третий вариант использует экспоненциальную запись для создания литерала
с плавающей точкой.
ПРИМЕЧАНИЕ
Возможно,экспоненциальную запись вы уже встречали в калькуляторах- она
используется для представления слишком большихчисел, не помещающихся
на экране.
Чтобы записать литерал с плавающей точкой в экспоненциальной форме,
введите число, за которым следует буква «е» и другое число. Pythoп берет
число слева от «е» и умножает его на 1О в степени справа от «е». Таким об
разом, запись 1е6 эквивалентна 1 х 106 •
90 ГЛАВА 5 Числа и математические вычисления
Python также использует экспоненциальную запись для вывода больших чисел
с плавающей точкой:
>>> 200000000000000000.0
2е+1 7
Число 200000000000000000.0 выводится в виде 2е+17. Знак+ указывает, что
показатель степени 17 является положительным числом. Также показатель
степени может быть отрицательным:
>» le-4
0.0001
Литерал le-4 интерпретируется как 10 в степени -4 ,то есть 1/10000, или 0 .0001 .
Значение чисел с плавающей точкой, в отличие от целых чисел, ограничено.
Максимальное значение зависит от вашей системы, но число вроде 2е400 вы
ходит за пределы возможностей большинства машин. Это выражение равно
2х 10400, что намного больше общего количества атомов во Вселенной!
При достижении максимального значения числа с плавающей точкой Python
возвращает специальное значение, inf:
»> 2е40 0
inf
inf (от infinity - бесконечность) означает лишь то, что число, которое вы по
пыталисьсоздать, превышает максимальное значение числа с плавающей точкой,
допустимое для вашего компьютера. При этом infимеет тип float:
»>n=2е400
»>n
inf
»> type(n)
<class 'float'>
В Python также используется значение -inf (отрицательная бесконечность).
Оно представляет отрицательное число с плавающей точкой, которое
меньше наименьшего числа с плавающей точкой, допустимого на вашем
компью тере:
»> -2е400
- inf
5.2 . Арифметические операторы и выражения
91
Скорее всего, вы будете нечасто сталкиваться с inf и -inf при программирова
нии - если, конечно, вам не приходится регулярно работать с очень большими
числами.
Упражнения
1. Напишите программу, которая создает две переменные, numl и num2. При
свойте каждой целочисленныйлитерал 25000000 - с разделителями-под
черкиваниями и без. Выведите numl и 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.
ПРИМЕЧАНИЕ
РЕР 8 рекомендует отделять оба операнда от оператора пробелом.
Pythoп прекрасно справится с вычислением 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
Из этих четырех примеров первый лучше всего соответствует рекомендациям
РЕР 8. При этом вы можете заключить -3в круглыескобки, чтобы ещенагляднее
показать, что второй знак - относится к 3:
»>1-(-3)
4
5.2. Арифметические операторы и выражения
93
Использование круглых скобок - хорошая практика, потому что с ними код
более явно выражает намерения программиста. Компьютеры выполняют код,
а люди его читают. Все, что делается дляупрощения чтения и понимания вашего
кода, можно только приветствовать.
Умножение
Для умножения двух чисел используется оператор*:
»>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 "cstdin>", line 1, in <module>
ZeroDivisionError: division Ьу zero
Pythonвыдает ошибку ZeroDivisionError, сообщая вам о том, что вы попытались
нарушитьодно из основополагающих правил Вселенной - нельзя делить на ноль!
Возведение в степень
Для возведения числа в степень используется ш1сратор **:
»>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 возвращает число с плавающей точкой з. 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 равно О. Как вы думаете, что возвращает 1 %0? Попробуем:
»>1%0
Traceback (most recent call last):
File "cstdin>", line 1, in cmodule>
ZeroDivisionError: integer division or modulo Ьу zero
Результат выглядит логично
:
1 %0 вычисляет остаток от деления 1 на О. Но 1
на О делить нельзя, поэтому Python выдает ошибку ZeroDivisionError.
ПРИМЕЧАНИЕ
При работе в интерактивном окне IDLE такие ошибки, как ZeroDivisionError,
не создают особых проблем. На экране появляется сообщение об ошибке,
а затем открывается новое приглашение, и вы можете продолжить работу.
Но когда Pythoп обнаруживает ошибку в процессе работы программы, вы
полнение прерывается - другими словами, в вашей программе происходит
непредвиденный сбой. В главе 8 я покажу, как обрабатывать ошибки, чтобы
избежать преждевременного завершения программ.
При исп ользов ании операт ора% с от риц ате льн ыми чис лами ситуация не много
усложняется:
»>5%-3
-1
»>-5%3
1
»>-5%-3
-2
Хотя на первый взгляд эти результаты выглядят странно, они объясняются
четкой логикой выполнения оператора в Python. Для вычисления остатка r от
деления числах на число у в Python используется формула r =х - (у* (х //у)).
Например, чтобы определить результат 5 %-3, Python сначала вычисляет
(5 // -3). Так как результат 5 / -3 равен приблизительно -1.67, это означает,
что 5 // -3 дает -1 . Теперь 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,
потому что* обладает более высоким приоритетом, чем оператор-.
Возможно, вы заметили, что в выражениях этого примера не соблюдается
правило о включении пробелов по обе стороны от оператора. В РЕР 8 об этом
говорится следующее:
«Е сли ис польз уются опер аторы с р аз н ым и приоритетами, рассмотрите
возможность включения пробелов рядом с операторами, обладающими
самым низким приоритетом(-ами). Руководствуйтесь здравым смыслом;
тем не менее никогда не используйте более одного пробела и всегда
используйте одинаковое количество пробелов с двух сторон бинарного
оператора».
РЕР 8, «Other Recommendations» (https.j/pep8.org/)
Еще одна полезная практика - использование круглых скобок для обозначения
порядка выполнения операций, даже если эти скобки не являются необходи
мыми. Например, выражение (2 *3) - 1 более понятно, чем 2*3 - 1.
98 ГЛАВА 5 Числа и математические вычисления
5.3. ЗАДАЧА: ВЫПОЛНЕНИЕ ВЫЧИСЛЕНИЙ
С ПОЛЬЗОВАТЕЛЬСКИМ ВВОДОМ
Напишите программу exponent.py, которая получает от пользователя два числа
и выводит результат возведения первого числа встепень,заданную вторым числом.
Результат выполнения программы должен выглядеть примерно так (с вводом
от пользователя):
Enter а 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 . КОГДА РУТНОN ГОВОРИТ НЕПРАВДУ
Как вы думаете, сколько будет 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/1 0 выгл ядит к ак бесконечная д ро бь :
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) ab s( ) для пол учен ия абс олютног о значения (модул я) чи сл а ;
3) pow() для возведения числа в степень.
Также вы узнаете о методе, который применяют к числам с плавающей точкой
для проверки того, содержатли они целое значение.
Функция round()
Функцию round() применяют для округления числа до ближайшего целого:
>» r ound(2. 3)
2
»> r ound(2. 7)
3
Если дробная часть числа равна . 5, функция round() ведет себя довольно не
ожида нно:
»> round(2. 5)
2
>» round(З.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(lnstitute ofElectrical andElectronics Engineers - Институт
инженеров noэлектротехнике и электронике), потому что она помогает огра
ничить последствия округления для операций со многими числами.
IEEE поддерживает стандарт IEEE 754 для работы с числами с плавающей
точкой на компьютерах. Стандарт был опубликован в 1985году, в настоящее
время он широко используется производителями оборудования.
Число также можно округлить до заданного количества знаков в дробной части,
для чего round() следует передать второй аргумент:
>>> round(3~14159, 3)
3.142
>>> round(2.71828 , 2)
2. 72
Число 3.14159 округляется до трех знаков в дробной части, и вы получаете
результат з .142, а число 2.71828 округляется до двух знаков, и вы получае
те 2.72.
Второй аргумент round () должен быть целым числом. В противном случае
Python выдает ошибку TypeError:
>>> round(2.65 , 1.4)
Traceback (most recent call last):
File "<pyshell#0>", line 1, in cmodule>
round(2.65, 1.4)
TypeError: 'float' object cannot Ье 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,
если п отрицательно. Например, абсолютное значение 3 равно 3, а абсолютное
значение - 5 равно 5 .
Чтобы получить абсолютное значение числа в Python, используйте функцию
abs():
» >abs(3)
3
»> abs(-5.0)
5.0
Функция abs () всегда возвращает положительное число с таким же типом,
как у его аргумента. Другими словами, абсолютное значение целого числа
всегда является положительным целым числом, а абсолютное значение числа
с плавающей точкой всегда является положительным числом с плавающей
точкой.
Функция pow()
Из раздела 5.2 вы узнали, как возвести число в степень оператором **.Для
получения того же результата можно воспользоваться и функцией pow( ) .
Функция pow() получает два аргумента. Первый определяет основание (число,
возводимое в степень), а второй - показатель степени.
Например, следующая команда использует pow() для возведения 2 в сте
пень з:
>» pow(2, 3)
8
5.5 . Математические функции и числовые методы
103
Как и в случае с оператором**, показатель степени в pow() может быть отри
цательным:
»> pow(2, -2)
0.25
Итак, чем же pow() отличается от**?
Функция pow() получает необязательный третий аргумент, который вычисляет
результат возведения первого числа в степень второго числа, после чего вы
числяет остаток от деления на третье число. Иначе говоря, вызов pow(x, у, z)
эквивалентен (х **у)% z.
Вследующемпримерех=2,у =зиz=2:
»> pow(2, 3, 2)
0
Сначала 2 возводится в степень 3 и получается 8. Затем вычисляется выражение
8 %2, результат которого равен О, потому что 8делится на 2 без остатка.
Проверка числа с плавающей точкой
на целочисленность
В главе 3 я рассказывал о таких строковых методах, как .lower(), .upper()
и . find (). Для целых чисел и чисел с плавающей точкой тоже существуют
методы.
Числовые методы используются не так часто, но среди них есть один, ко
торый может вам пригодиться. У чисел с плавающей точкой имеется метод
. is_integer(), который возвращает True, если число является целым, то есть
не имеет дробной части. В противном случае возвращается False:
»>num=2.5
>> > num .is_i ntege r( )
False
»>num=2.0
> >> num .is _intege r( )
True
Метод . is_integer() может оказаться полезным при проверке пользователь
ского ввода. Например, если вы пишете приложение для заказа пиццы через
интернет, нужно проверить, является ли количество заказанных порций целым
104 ГЛАВА 5 Числа и математические вычисления
числом или нет. О том, как выполнять подобные проверки, мы расскажем
в главе 8.
Упражнения
1. Напишите программу, которая предлагает пользователю ввести число,
азатем выводитего округленным до двух цифр. Выполнение вашей про
граммы должно выглядетьпримерно так:
Enter а number: 5.432
5.432 rounded to 2 decimal places is 5.43
2. Напишите программу, которая предлагает пользователю ввести число,
а затем выводит абсолютное значение этого числа. Выполнение вашей
программы должно выглядеть примерно так:
Enter а number: -10
The absolute value of -10 is 10.0
3. Напишите программу, которая предлагает пользователю ввести два чис
ла, используя input() дважды, а затем сообщает, является ли разность
этих двух чисел целым числом. Выполнение вашей программы должно
выгляде ть при ме рн о так:
Enter а number: 1.5
Enter another number: .5
The difference between 1.5 and .5 is an integer? True!
4. Если пользователь вводит два числа, разность которых не является целым
числом, вывод должен выглядеть примерно так:
Enter а 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 nis 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 nis 7.13'
Чтобы округлить выводимое число до одного знака, замените . 2 на .1:
»>n=7.126
>>> f"The value of n is {n:.lf}"
'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 Числа и математические вычисления
Чтобы округлить число до заданного количества знаков, а также сгруппировать
разряды, включите , перед . в спецификацию:
»> п =1234.56
>>> f"The value of п is {n:,.2f}"
'The value of п is 1,234.56'
Спецификатор , . 2 f хорошо подходит для вывода денежных сумм:
>>> 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.j/docs.python.
org/3/library/string.html#f ormat-string-syntax_) .
Упражнения
1. Выведите результат вычисления з ** .125 в формате с фиксированной
точкой с тремя знаками в дробной части.
2. Выведите число 150000 как денежную сумму с разделением групп раз
рядов запятыми. Денежные суммы должны выводиться с двумя знаками
в дробной части.
5.7 . Комплексные числа 107
3. Выведите результат 2 / 10 в процентах без дробной части (то есть должно
выводиться значение 20%).
5.7 . КОМПЛЕКСНЫЕ ЧИСЛА
Python - один из немногих языков программирования, имеющий встроенную
поддержку комплексных чисел. Хотя комплексные числа не так часто встре
чаются вне сферы научных вычислений и компьютерной графики, поддержка
их в Python - одна из сильных сторон языка.
ПРИМЕЧАНИЕ
Если тема работыс комплексными числами в Pythoп вас не интересует, смело
пропускайте этот раздел. В других частях книги эта информация не исполь
зуетс я.
Из курсов алгебры и начал анализа вы, вероятно, помните, что комплексное
число состоит из двух частей: вещественной и мнимой.
Чтобы создать комплексное число в 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 не нужно ставить круглые скобки {в отличие от
.coпjugate{)).
Метод .coпjugate{) представляет собой функцию, которая выполняетдействие
с комплексным числом, тогда как .real и .imag никаких действий не выполня
ют - они просто возвращают некоторую информацию о числе.
Различия между методами и свойствами являются важным аспектом объ
ектно-ориентированноrо проrраммирования. Вы узнаете о них в главе 1О.
Кроме оператора целочисленного деления / / все арифметические операторы,
работающие с числами с плавающей точкой и целыми числами, также работают
с комплексными числами.
Эта книга - не учебник по математике, поэтому мы не будем обсуждать механи
ку вычислений с комплексными числами. Но мы познакомим вас с примерами
использования комплексных чисел с арифметическими операторами.
>»а 1+2j
>»ь з-4j
»>а+ь
(4-2j)
»>а-ь
( -2+бj)
»>а*ь
(11+2j)
»>а**Ь
(93 2.1 391 946 4322 12+ 95. 946 5ЗЗ6 603 415 j)
»>а/Ь
5.8. Итоги и дополнительные ресурсы
109
(-0.2+0.4j)
»>а/ /Ь
Traceback (most recent call last):
File "<stdin>", line 1, in cmodule>
TypeError: can't take floor of complex number.
Интересно (хотя и неудивительно с математической точки зрения), что объекты
int и float содержат свойства. real и .imag, как и метод . conjugate():
>»х=42
»> x.real
42
»> x.imag
0
»> х.conjugate()
42
>»у=3.14
»> y.real
3.14
»> у. imag
0.0
>>> y.conjugate()
3.14
Для чисел с плавающей точкой и целых чисел . real и . conjugate() всегда воз
вращают само число, а .imag всегда возвращаетО. Однако следует заметить, что
n. real и n. imag возвращают целое число, если nявляется целым числом, и число
с плавающей точкой, если n является числом с плавающей точкой.
После знакомства с основами комплексных вычислений может возникнуть во
прос: придется ли вам когда-нибудь использовать их? Если вы изучаете Python
для неб-программирования, обработк и д ан ны х или п рог рам ми ров ани я обще го
назначения, скажу честно: скорее всего, никогда не придется.
С другой стороны, без комплексных чисел не обойтись в научных вычислениях
и компьютерной графике. Если вы подвизаетесь в этих областях, встроенная
поддержка комплексных чисел в Python может вам пригодиться.
5.8 . ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе мы рассказали, как работать с числами в Python. Вы узнали, что
существуют два основных типа чисел - целые и числа с плавающей точкой,
а еще в Python реализована встроенная поддержка комплексных чисел.
11 О ГЛАВА 5 Числа и математические вычисления
Вы научились выполнять основные математические операции с числами
с и спольз ованием операторов+,-, *,/ и % . Та кж е в ы освои ли ар ифм етич еск ие
выражения и приемы форматирования арифметических выражений в коде
программы, определенные в РЕР 8.
Далее мы рассказали о числах с плавающей точкой и их представлении, ко
торое не всегда бывает точным на 100%. Данное ограничение действует не
только в Python. Это особенность современных вычислительных технологий,
обусловленная способом представления чисел с плавающей точкой в памяти
ком пьютера.
Затем вы узнали, как округ./Iять числа до заданной точности функцией round():
она округляет нейтральные числа до ближайшего четного, и такой способ округ
ления несколько отличается от того, что мы изучали в школе. Такжея показал
некоторые способы форматирования чисел для вывода.
В завершающей части главы была описана встроенная поддержка комплексных
чисел в Python.
ИНТЕ РАКТ ИВНЫ Й ТЕ СТ
Кэтой главе прилагается бесплатный интерактивный тест для проверки усво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/ pybasics-numbers
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
• «Basic Data Types inPython» (https//realpython.com/python-data-types/)
• «How to Round Numbeгs in Python» (https//realpython.com/python-
rounding/)
ГЛАВА6
Функции и циклы
Функции являются строительными блоками практически любой программы
Python. Обычно именно в них происходят основные события!
Вы уже видели, как использовать некоторые функции, например print( ),
len() и round (). Все эти функции называются встроенными, потому что они
реализованы непосредственно в языке Python. Вы также можете создавать
пользовательские функции для решения конкретных задач.
Функции разбивают код на меньшие блоки. Их используют для определения
действий, которые должны неоднократно выполняться в программном коде. Вам
не придется писать один и тот же код каждый раз, когда программе потребуется
выполнить эту задачу, достаточно вызвать функцию!
Но иногда некоторую часть кода требуется повторить несколько раз подряд.
Для этого используются циклы.
В этой главе вы:
• создадите пользовательские функции;
• научитесь работать с циклами forи while;
• узнаете, что такое область видимости и почему она важна.
Итак, за дело!
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 . Что же такое функция?
11З
Обычно после выполнения 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):
F ile "<p yshell #З>", li ne 1, in < modul e>
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?
>» r etur n_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, у): #Сигнатура функции
# Тело функции
product=х *у
return product
Создание функции для чего-то настолько простого, как оператор*, выглядит
странно. Пожалуй, multiply() - не та функция, которую вы будете использо
вать в реальной программе. Но это хороший первый пример, чтобы понять, как
создаются фу нкц ии!
ВАЖНО!
Когда вы определяете функцию в интерактивном окне IDLE, необходимо на
жать Enter дважды после строки, содержащей команду returп, чтобы функция
была зарегистрирована Python:
>>> def multiply(x, у):
product=х *у
return product
# <--- Здесь нужно нажать Enter во второй раз.
>»
Разобьем функцию на части и посмотрим, что в какой момент происходит.
Сигнатура функции
Первая строка кода функции называется сигнатурой функции. Она всегда на
чинается с ключевого слова def (сокращение от define - определить).
Рассмотрим повнимательнее сигнатуру multiply( ):
def multiply(x, у):
Сигнатура функции состоит из четырех частей.
1. Ключевое слово def.
2. Имя функции multiply.
3. Список параметров (х, у)
4. Двоеточие(:) в конце строки .
Когда Python читает строку, начинающуюся с ключевого слова def, он создает
новую функцию. Эта функция присваивается переменной, имя которой со
впадает с именем функции.
6.2 .Написание ваших собственных функций
117
ПРИМЕЧАНИЕ
Так как имена функций становятся переменными, они должны подчиняться
правилам имен переменных, о которых вы узнали в главе 3.Таким образом,
имя функции может содержать только цифры, буквы и подчеркивания и не
должно начинаться с цифры.
Список параметров представляет собой список имен, заключенный в круглые
с кобки . Он определяет ожидаемые входные значения функции; {х, у) - список
параметров для multiply{), который определяет два параметра - х и у.
Па ра ме тр являе тся раз нов идн ост ью переме нной, но н е име ет собственного
значения. Параметр замещает фактические значения, которые будут переданы
при вызове функции с одним или несколькими аргументами.
Код в теле функции использует параметры так, как если бы они являлись пере
менными с реальными значениями. Например, тело функции может содержать
строкус выражением х * у.
Так как х и у значений не имеют, произведение х * у тоже не имеет значения.
Python сохраняет выражение в виде шаблона и подставляет недостающие зна
чения при выполнении функции.
Функция может получать любое количество параметров, в том числе и ни одного .
Тело функции
Тело ф ун кц ии содержит код, кот орый выполн яется п ри и спользовании ф ун к ци и
в программе. Тело функции для multiply{) выглядит так:
def multiply(x, у):
# Тело функции
product=х *у
return product
multiply - совсем простая функция. Ее тело содержит всего две строки кода.
Первая строка тела функции создает переменную с именем product и при
сваивает ей значение х *у. Так как х и у еще не содержат значений, эта строка
в действительности определяет шаблон для значения, которое будет присвоено
product при выполнении функции.
Вторая строка тела функции называется командой return. Она начинается
с ключевогослова return,за которым следует переменная product.Достигнув
118 ГЛАВА 6 Функции и циклы
команды return, Python прекращает выполнение функции и возвращает зна
чение pr od uc t.
Обратите внимание: обе строки кода в теле функции снабжены отступами.
Это очень важно! Каждая строка с отступом, расположенная под сигнатурой
функции, считается принадлежащей телу функции.
Например, вызов функции print () в следующем примере не входит в тело
функции, потому что эта строка не имеет отступа:
def m ultiply( x, у) :
product=х *у
return product
print("Where am !?") # Не принадлежит телу функции
Если добавить отступ в строку с print (), эта строка станет частью тела функ
ции, даже несмотря на пустую строку между print() и предыдущей строкой:
def multiply(x, у):
product=х *у
return product
print("Where am !?") # В теле функции
При добавлении отступов в тело функции необходимо соблюдать одно правило:
отступы всех строк должны содержать одинаковое количество пробелов.
Попробуйте сохранить следующий код в файле с именем multiply.py и выпол
нить его из IDLE:
def multiply(x, у):
product=х*у
return product # С одним лишним пробелом
IDLE не будет выполнять этот код! На экране появится диалоговое окно с со
общением об ошибке «unexpected indent» («неожиданный отступ»). Python
ожидает, что команда return имеет такое же количество пробелов в отступе,
как и в строке над ней.
Другая ошибка возникает тогда, когда размер отступа в строке меньше, чем
у строки над ней и отступ не соответствует никакой из предшествующих строк.
Измените файл multiply.py , чтобы он выглядел так:
def multiply(x, у):
product =х *у
return product # Отступ меньше, чем в предыдущей строке
6.2. Написание ваших собственных функций 119
Сохраните и запустите файл. IDLE прерывает его выполнение с сообщением
об ошибке unindent does not match апу outer iпdeпtatioп level (отступ не со
ответствует ни одному внешнему уровню). Количество пробелов в отступе
команды returп отличается от любой строки в теле функции.
ПРИМЕЧАНИЕ
Хотя в Pythoп нет правил, определяющихколичество пробеловв отступах кода
в теле функции, РЕР 8 рекомендует использовать отступы из четырех пробелов.
В книге соблюдается именно это правило.
После выполнения команды returп функция останавливается и возвращает
значение. Если подкомандой returп располагается код, отступ которого указы
вает на то, что он является частью тела функции, этот код выполнен не будет.
Например, в следующей функции команда priпt () никогда не выполняется:
def multiply(x, у):
product =х *у
return product
print("You can't see me!")
Эта версия multiply() никогда не выведетстроку "You сап't see me!" .
Вызов функции, определенной пользователем
Пользовательская функция вызывается точно так же, как и любая другая: вы
вводите имя функции, за которым указываете список аргументов в круглых
скобках.
Например, чтобы вызвать multiply() с аргументами 2 и 4, просто введите сле
дующую команду:
multiply(2, 4)
В отличиеот встроенных, пользовательские функции становятся доступными
только после того, как они будут определены с ключевым словом def. Необхо
димо определить функцию, прежде чем вызывать ее.
Введите следующую программу в окне редактора IDLE:
num = multiply(2, 4)
print(num)
120 ГЛАВА 6 Функции и циклы
def mult iply(x , у ):
product =х *у
return product
Сохраните файл и нажмите FS. Так как функция multiply() вызывается до ее
определения, Python не распознает имя multiply и выдает ошибку NameError:
Traceback (most recent call last):
File "C:Usersdaveamultiply.py", line 1, in cmodule>
num = multiply{2, 4)
NameError: name 'multiply' is not defined
Чтобы исправить ошибку, переместите определение функции в начало файла:
def multiply(x, у):
product =х *у
return product
num = multiply(2, 4)
print(num)
Снова сохраните файл и нажмите FS, чтобы запустить программу. На этот раз
в интерактивном окне выводится значение 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
Строка "Неllo, 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 а container.
Функции help() передается имя переменной или функции, а она выводит по
лезную информацию об указанной переменной или функции. В данном случае
help() сообщает, что len() является встроенной функцией, которая возвращает
количество элементов в ко н т е йн е р е.
ПРИМЕЧАНИЕ
Контейнер - специальный термин для объекта, который содержит другие
объекты. Строка является контейнером, потому что она содержит символы.
О других типах контейнеров я расскажу в главе 9.
Посмотрим, что происходит при вызове help() для multiply():
>>> help(multiply)
Help оп function multiply in module ~main~
mu ltiply( x, у )
Функция h el p () вы вод ит сигнатуру фун кци и, но н е предоставляет ник ако й
информации о том, что эта функция делает. Чтобы лучше документировать
функцию multiply(), следует предоставить dос-строку - строковый литерал
в утроенных кавычках в начале тела функции.
122 ГЛАВА 6 Функции и циклы
Dос-строка описывает, что делает функция и какие виды параметровона ожи
дает получить:
def multiply(x, у):
"""Возвращает произведение двух чисел х и у."""
product=х *у
returп product
Перепишите функцию mul tiply() с dос-строкой. Теперь можно воспользоваться
функцией help() в интерактивном окне для просмотра dос-строки:
> > > help( m ultiply)
Help оп fuпctioп multiply iп module ~maiп~
mu ltiply( x, у )
Возвращает произведение двух чисел х и у.
В РЕР 8 о dос-строках не сказано почти ничего, кроме того, что они должны
определяться в каждой функции (https.j/pep8.org/#docuтentation-strings).
Существует ряд стандартизированных форматов dос-строк, но мы не будем
в них углубляться. Некоторые общие рекомендации по написанию dос-строк
можно найти в РЕР 257 (https.j/www.python.org/dev/peps/pep-0257/).
Упражнения
1. Напишите функцию cube (), которая получает один числовой параметр
и возвращает значение указанного числа в третьей степени. Протести
руйте функцию - вызовите cube() для нескольких разных чисел и вы
ведите результаты.
2. Напишите функцию greet( ), которая получает один строковый параметр
с именем name и выводит текст «Hello <пате>!», где <пате> заменяется
зн ачением параметра na m e .
6.3 . ЗАДАЧА: КОНВЕРТЕР ТЕМПЕРАТУР
Напишите программу teтperature.py, которая определяет две функции.
1. convert_cel_to_far() получает один параметр float, представляющий
температуру по шкале Цельсия, и возвращает значение float, пред
ставляющее ту же температуру по шкале Фаренгейта. Преобразование
выполняется последующей формуле:
6.4. Циклическое выполнение
123
F=С*9/5+32
2. convert_far_to_cel() получает один параметр float, представляющий
температуру по шкале Фаренгейта, и возвращает значение float, пред
ставляющее ту же температуру по шкале Цельсия. Преобразование вы
полняется по следующей формуле:
C=(F-32)*5/9
Программа должна делать следующее.
1. Запрашивать у пользователя температуру в градусах по шкале Фарен
гейта, а затем выводить температуру, преобразованную к шкале Цельсия.
2. Запрашивать у пользователя температуру в градусах по шкале Цельсия,
а затем выводить температуру, преобразованную к шкале Фаренгейта.
3. Выводить все преобразованные температуры, округленные до двух знаков
в дробной части.
Пример выполнения программы:
Enter а temperature in degrees F: 72
72 degrees F = 22.22 degrees С
Enter а temperature in degrees С: 37
37 degrees с = 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 выполняет код в теле
цикла и снова возвращается к проверке условия. В r:~ротивном случае код тела
пропускается и вып олня ется д ал ьн ей ш ий к о д прог раммы.
Рассмотрим пример. Введите следующий код в интерактивном окне:
>>>n=1
»> while n <5:
print(n)
n=n+1
1
2
з
4
Сначала целое число 1присваивается переменной n. Затем создается цикл while
с условием n < 5. Это условие проверяет, что значение n меньше 5.
Если n меньше 5, выполняется тело цикла - две строки кода. В первой строке
значение n выводится на экран. Во второй строке n увеличивается на 1.
Выполнение цикла состоит из пяти шагов (итераций).
НОМЕР
ЭНАЧЕ·
УСЛОВИЕ
ЧТО ПРОИСХОДИТ
WАГА
НИЕN
1<5(true)
Выводит 1; увеличивается до 2
2
2
2<5(true)
Выводит 2; увеличивается до З
з
з
З <5(true)
Выводит З; увеличивается до 4
4
4
4<5(true)
Выводит 4;увеличивается до 5
5
5
5 < 5(false)
Ничего; цикл завершается
Если действовать неосторожно, можно создать бесконечный цикл. Такая си
туация возникает, когда условие цикла всегда истинно. Бесконечный цикл не
завершается никогда, а его тело выполняется снова и снова.
Пример бесконечного цикла:
>>>n=1
>»whilen<5:
print(n)
6.4 . Циклическое выполнение
125
Единственное отличие этого цикла while от предыдущего в том, что nне увели
чивается в теле цикла. На каждом шаге цикла переменная n остается равной 1.
Таким образом, условиеп <5 всегда будет истинным, ачисло1будет выводиться
снова и снова.
ПРИМЕЧАНИЕ
Бесконечные циклы не всегда плохи. Иногда это именно то, что нужно. Напри
мер, код для работы с оборудованием может в бесконечном цикле проверять,
была ли активизирована некоторая кнопка или переключатель.
Если вы запустили программу, которая вошла в бесконечный цикл , прикажи
те Python немедленно завершить программу, нажав сочетание клавиш Ctrl+C.
Python прекращает выполнение программы и выдает ошибку Keyboardinterrupt:
Traceback (most recent call last):
File "<pyshell#8>", line 2, in <module>
print(n)
Keyboardlnterrupt
Рассмотрим пример практического использования цикла while. В одном из
возможных применений цикл while проверяет, удовлетворяет ли пользова
тельский ввод некоторому условию, и если нет - запрашивает у пользователя
новые данные, пока не будет получен корректный ввод.
Наприме р, с л едующая про гра мма мн огок ратн о за пра ши вае т у п ользователя
положительное число, пока не будет введено именно положительное число:
num = float(input("Enter а positive number: "))
while num <= 0:
print("That's not а positive number!")
num = float(input("Enter а positive number: "))
Сначала программа выводит сообщение, предлагающее ввести положительное
число. Условиеnum <= 0 проверяет, что значениеnum меньше либо равно О.
Если значение num положительно, условие нарушается. Тело цикла пропускается,
и программа завершается.
126 ГЛАВА 6 Функции и циклы
В противном случае, если значение num равно О или отрицательно, выполняется
тело цикла. Программа оповещает пользователя о том, что введенное значение
недопустимо, и предлагает повторить попытку.
Циклы w hi le идеально подходя т для повтор ения ч ас т и к ода, пока в ыполн яется
заданное условие. С другой стороны, они плохо работают при повторении части
кода фиксированное количество раз.
Цикл for
Цикл for выполняет часть кода по одному разу для каждого элемента в кол
лекции. Количество выполнений кода определяется количеством элементов
в коллекции.
Циклfor, как и while, состоит из двух частей.
1. Команда forначинается с ключевогослова for, за которым следует вы
ражение принадлежности, и завершается двоеточием:
2. Тело цикла for содержит код , выполняемый на каждом шаге цикла
.
Каждая
строка содержит отступ из четырех пробелов.
Рассмотрим пример. Следующий цикл for последовательно выводит каждую
букву строки "P ython ":
for letter in "Python":
pri n t(l et ter)
В этом примере команда for имеет вид for letter in "Python", а выражение
принадлежности - letter in "Python".
На каждом шаге цикла переменной letter присваивается очередная буква
строки "Python", после чего выводится значение letter.
Цикл выполняется по одному разу для каждого символа строки "Python", так
что тело будет выполнено шесть раз. В следующей таблице кратко описано
выполнение цикла.
НОМЕР
ЗНАЧЕНИЕ LEПER
ЧТО ПРОИСХОДИТ
ШАГА
11р11
Выводит Р
2
"у"
Выводит у
3
//tlf
Выводитt
6.4. Циклическое выполнение 127
НОМЕР
ЗНАЧЕНИЕ LEПER
ЧТО ПРОИСХОДИТ
ШАГА
4
"t"
Выводит h
5
"о"
Выводит о
6
lln"
Выводит п
Чтобы понять, почему циклы for лучше подходят для перебора коллекций
элементов, перепишем цикл for из предыдущего примера вформе цикла while.
Для этого можно сохранить индекс следующего символа строки в переменной.
На каждом шаге цикла выводится символ с текущим индексом, после чего
индекс у ве л ич ивается .
Цикл остановится, как только значение индексной переменной достигнет дли
ны строки. Помните, что индексы начинаются с 0, а следовательно, последний
индекс в строке "Python" равен 5.
Этот код можно было бы записать так :
word = "Python"
index = 0
while index < len(word):
pr int( word[ inde x])
index = index + 1
Эта версия намного сложнее версии с for!
Впрочем, цикл for не только проще - он выглядит более естественно. Он ближе
к тому, как бы вы описали цикл на человеческом языке.
ПРИМЕЧАНИЕ
.
Иногда приходится слышать, как люди называют какой- нибудь код питони
ческим. Обычно этим термином обозначается код понятный, компактный
и широко использующий встроенные возможности Pythoп.
Таким образом, использование циклаforдля перебора коллекции элементов
следует признать более питоническим, чем цикл while.
Иногдатребуется перебрать числав некоторомдиапазоне. В Python существует
удобная встроенная функция raпge( ), предназначенная именно для создания
диапазона чисел.
128 ГЛАВА 6 Функции и циклы
Например, range(З) возвращает диапазон целых чисел от 0 доз (не включая
последнее). Таким образом, range(З) создает диапазон чисел 0, 1 и 2.
Вызов range( n),где n- любоеположительноечисло, может использоваться для
выполнения цикла ровно n раз. Например, следующий цикл выводит строку
"P ython" т р и раза:
for n in range(3):
prin t("P ython ")
Также можно задать начальную точку диапазона. Например, вызов range ( 1, 5)
создает диапазон из чисел 1, 2, з и 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, з, 4 и 5 и выводит количество людей и сумму,
которую должен заплатить каждый участник. Форматный спецификатор , . 2 f
форматирует сумму как число с фиксированной точкой, округленное до двух
знаков и с разделением групп разрядов запятыми.
Если запустить программу и ввести значение 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{l, 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 присваивается значение з.
Окончательный вывод выглядит так:
n 1andj4
n 1andj5
n 1andj 6
n 2andj4
n 2andj5
n 2andj6
n 3andj4
n 3andj5
n 3andj6
Цикл, находящийся внутри другого цикла, называется вложенным циклом.
Вложенные циклы встречаются достаточно часто. Разрешается вкладывать
циклы while в циклы for и наоборот. Более того, возможно вложение циклов
на глубину более двух уровней.
Циклы - мощный инструмент в арсенале программиста. Они используют одно
из самых больших достоинств компьютеров: повторять одни и те же действия
огромное количество раз, не уставая и не жалуясь.
130 ГЛАВА 6 Функции и циклы
ВАЖНО!
Вложение циклов неизбежно повышаетсложность вашего кода. Обэтом сви
детельствует значительное увеличение числа шагов в приведенном примере
по сравнению с предыдущими примерами с одним циклом for.
Вложенные циклы - иногда единственный способ решения задачи, но слиш
ком большое количество вложенных циклов может отрицательно сказаться
н а быстр одействии пр огр ам мы .
Упражнения
1. Напишите цикл for, который выводит целые числа от 2 до 1О с исполь
зованием range( ). Каждое число должно выводиться в новой строке.
2. Напишите цикл while, который выводит целые числа от 2 до 10 . (Под
сказка: сначала необходимо создать новое целое число.)
3. Напишите функцию douЫes (),которая получает число и увеличивает его
вдвое. Используйте douЫes () в цикле, чтобы трижды увеличить вдвое
число 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 . ОБЛАСТЬ ВИДИМОСТИ В РУТНОN
Любое обсуждение функций и циклов в Python будет неполным без упоминания
областей видимости (scope).
Эта тема может оказаться одной из самых сложных для понимания в про
граммировании, поэтому здесь мы будем рассказывать о ней, не углубляясь
в детали. К концу этого раздела вы уясните, что такое область видимости
и почему она важна. Также вы узнаете о правиле LEGB для разрешения об
ластей видимости.
Что такое область видимости?
Присваиваязначение переменной, вы тем самым связываете значение с именем.
Имена уникальны. Например, невозможно присвоить одно имя двум разным
числам:
>>>х=2
>>> х
2
»>х
3
»>х
3
Когда вы присваиваете неременной х значение з, вы уже не сможете получить
значение 2 по имени х.
132 ГЛАВА 6 Функции и циклы
И такое поведение вполне логично. В конце концов, если бы переменная х имела
значения 2 и з одновременно, то как бы вы вычисляли х + 2? Сколько должно
по лучиться - 4 или 5?
Оказывается, присвоить одному имени два разных значения все же возможно.
Просто это потребует некоторых ухищрений.
Откройте новое окно редактора в IDLE и введите следующую программу:
х = "Hello, World"
def func():
х=2
print(f"Inside 'func', х has the value {х}")
func()
print(f"Outside 'func', х has the value {х}")
В этом примере переменной х присваиваются два разных значения: "Hello,
World" в начале и 2 внутри func().
Вывод программы выглядит немного странно:
Inside 'func', х has the value 2
Outside 'func', х has the value Hello, World
Как получилось, что х все еще содержит значение "Hello, World" после вызова
func(),изменяющего значение хна 2?
Дело в том, что func() имеет другую область видимости, нежели код за предела
ми этой функции. Другими словами, внутри func () можно присвоить объекту
такое же имя, как у объекта за пределами func(), и Python будет хранить эти
два объекта по отдельности.
Тело функции обладает так называемой локальной областью видимости с соб
ственным набором доступных имен. Код за пределами тела функции принад
лежит глобальной области видимости.
Область видимости можно рассматривать как набор имен, связанных с объ
ектами. Когда вы используете в своем коде конкретное имя (например, имя
переменной или функции), Python проверяет текущую область видимости,
чтобы определить, существует ли такое имя.
Разрешение областей видимости
Области видимости образуют иерархию. Например, возьмем следующий
фрагмент:
х=5
def outer_func():
у=3
def inner_func():
z=х+у
return z
return inner_func()
ПРИМЕЧАНИЕ
6.6 .Область видимости в Python 1ЗЗ
Функция inner_func() называется внутренней, потому что она определяется
внутри другой функции. Оказывается, как и в случае с вложенными циклами,
функциитакже могут определяться внутри других функций!
О внутренних функциях можно большеузнать из статьи «lnnerFunctions-What
Are Т h е у G ood Fo r?> > (ht tps:// realpyt hoп.co m/inner -funct ions-wh at-are -they-g ood
for/) на сайте Real Python.
Переменная z существует в локальной области видимости inner_func (). Когда
Pythonвыполняет строку z =х +у, сначала осуществляется поиск переменных х
и у в локальной области видимости. Ни одна из них в локальной области ви
димости не существует, поэтому Python переходит вверх к области видимости
outer_func().
Область видимости outer_func() является внешней по отношению к inner_
func (). Это все еще не глобальная область видимости, но и не локальная
inner_func( ). Она находится посередине между этими двумя областями.
Переменная у определена в области видимости outer_func( ), и ей присвое
но значение з . Тем не менее х в этой области видимости не существует, по
этому Python снова перемещается вверх к глобальной области видимости.
Здесь обнаруживается имя х со значением 5. Итак, именах и у успешно раз
решены, и Python может выполнить строку z = х +у, которая присваивает z
значен ие 8 .
Правило LEGB
Чтобы запомнить, как в Python происходит разрешение областей видимости,
можно воспользоваться правилом LEGB. Это сокращение (Locai, Enclosing,
Globa l, Built-in) описывает порядок, в котором Python производит поиск имен
по областям видимости.
134 ГЛАВА 6 Функции и циклы
Краткое описание поможет вам запомнить, как работает процесс поиска.
1. Локальная (Local): локальная, или текущая, область видимости может
быть телом функции или областью видимости верхнего уровня файла
с кодом. Она всегда представляет область видимости, в которой интер
претатор Python работает в настоящий момент.
2. Внешняя (Enclosing): внешняя область видимости находится на один
уровень выше локальной. Если локальная область видимости соответ
ствует внутренней функции, то внешней становится область видимости
внешней функции. Если область видимости соответствует функции
верхнего уровня, то внешняя область видимости соответствует гло
бальной.
3. Глобальная (Global): глобальная область видимости находится на самом
верхнем уровне иерархии. Она содержит все имена, определенные в коде,
которые не содержатся в теле функции.
4. Встроенная (Built-in): встроенная область видимости содержит все имена,
встроенные в Python (например, ключевые слова). Такие функции, как
round () и abs (), принадлежат встроенной области видимости. Все, что
вы можете использовать, не определяя самостоятельно, содержится во
встроенной области видимости.
Области видимости - тема, сложная для понимания. Чтобы усвоить эту кон
цепцию, вам потребуется некоторая практика. Не волнуйтесь, если с первого
раза вам не удалось в ней разобраться. Просто продолжайте практиковаться
и ис пользуйте пра вило LEG B.
Нарушение правил
Как вы думаете, что выведет следующий код?
total = 0
def add_to_total(n):
total = total + n
add _to_tota l(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_tota l(S )
F il e " C:/U ser s/da vea /stuf f /python/sc ope . py", lin e 4 , in add_to _total
total = total + n
UnboundLocalError: local variaЫe 'total' referenced before assignment
Минутку! Согласно правилу LEGB, Python должен был понять, что имя total
не существует в локальной области видимости add_to_total( ), и перейти к гло
бальной области видимости для разрешения имени.
Проблема в том, что код пытается выполнить присваивание переменной total,
что приводит к созданию нового имени в локальной области видимости. Затем,
когда Python выполнит правую часть присваивания, он найдет в локальной об
ласти видимости имя total, которому еще ничего не присвоено.
Подобные ошибки весьма коварны. Это одна из причин для использования
уникальных имен переменных и функций независимо от того, какой области
вид имо сти о ни принадлежат.
Проблему можно обойти при помощи ключевого слова global:
total = 0
def a dd_ to_tota l( n) :
global total
total = total + n
ad d_to_tota l(S )
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-funetions-loops
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
• «Python 'while' Loops (Indefinite Iteration)» (https.j/realpython.com/
p yth on- while - loop /)
• «Python 'for' Lo~ps (Definite Iteration)» (https.j/realpython.com/python-
for- loo p/)
ГЛАВА 7
Поиск и исправление
ошибок в коде
Все совершают ошибки - даже опытные профессиональные разработчики!
IDLE неплохо справляется с выявлением синтаксических ошибок и ошибок
време ни в ыполне ния, но существует т ре т ья разнов идность ошибо к, с кот орой
вы, возможно, уже сталкивались. Логические ошибки происходят тогда, когда
в целом правильная программа делает не то, что предполагал ее создатель.
Логические ошибки приводят к непредвиденному поведению программы.
Устранение ошибок называется отладкой, а специальный инструмент, который
помогает находить последствия логических ошибок и понять причины их воз
никн овения, - от ладчиком.
Умение находить и исправлять ошибки в коде - тот навык, которым вы будете
пользоваться на протяжении всей карьеры программиста!
В этой главе вы:
• научитесь использовать окно DebugControlв IDLE;
• отработаете навыки отладки на примере функции, содержащей ошибки.
Итак, за дело!
7.1. ИСПОЛЬЗОВАНИЕ ОКНА DEBUG CONTROL
Основным интерфейсом отладчика IDLE является окно Debug Control, которое
далее для краткости я буду называть окном Debug. Чтобы открыть окно Debug,
выберите команду Debug ~ Debugger в меню интерактивного окна.
Каждый раз, когда окно Debug открыто, в интерактивном окне рядом с пригла
шением выводится сообщение [DEBUG ON]. Оно показывает, что отладчик активен.
138 ГЛАВА 7 Поиск и исправление ошибок в коде
ВАЖ НО!
Если команда Debug отсутствует в строке меню, щелкните на интерактивном
окне, чтобы передать ему фокус ввода.
Теперь откройте новое окно редактора и разместите три окна на экране, чтобы
они бы л и ви дн ы одновременно.
В этом разделе вы узнаете, как устроено окно Debug, научитесь выполнять код
в отладчике по строкам, а также устанавливать точки останова для ускорения
процесса отладки.
Окно Debug Control: обзор
Чтобы понять, как работает отладчик, начнем с простой программы, не содер
жащей ошибок. Введите следующий код в окне редактора:
for i in range(l, 4):
j=i *2
print(f"i is {i} and j is {j}")
Сохраните файл, оставьте окно Debug открытым и нажмите FS. Выполнение
длится недолго.
Окно Debug выглядит примерно так:
[t.DebugControl
17Stack Г Source
Go Step Over Out Quit
t----'---'---'--~~г- Loca ls Г Globals
debug_example.py:1: <module>O
'Ьdb'.runQ
,
line585: exec(and, globals, locals)
!>·__111а111 •.'.<morJule>Q,lirie1.lor1in range{1.4)
Locals
_annotations_{)
_ builtins_
<module'Ьuiltins' {built·in)>
_ doc_
None
_file_
'
C:/Users/davea/Desktop/debug_example.py'
_ loader_
<dass '_frozen_importlib.Builtinlmporter'>
_ name_
'_ main_'
_package_
None
_ spec_
None
D
х
v
7.1 .Использование окна Debug Control 139
Обратите внимание: на панелиStack в верхней части окна выводится следующее
сообщение:
> ·~main~'.<module>(), line 1: for i in range(l, 4):
Оно указывает, что строка 1 (содержащая код for i iп range(l, 4):) готова
к выполнению, но еще не начала выполняться. Часть' _main_' . m odule() этого
сообщения указывает на тот факт, что вы сейчас находитесь в основном разделе
программы - в отличие от, допустим, определения функции перед основным
блоком кода.
Под панелью Stack располагается панель Locals, где перечислены всякие стран
ные слова вроде _annotations_, _builtins_, _doc_ и т. д. Это внутренние
системные переменные, на которые пока можно не обращать внимания . При
вы по лн ен ии п ро гр ам мы переменные, объ явл енн ые в код е, будут о тображаться
в этом окне, чтобы вы могли отслеживать их значения.
В левом верхнем углу окна Debug расположены пять кнопок: Go, Step, Over, Out
и Quit. Они управляют перемещением отладчика по вашему коду.
Сейчас мы исследуем назначение каждой из этих кнопок, начиная со Step.
Кнопка Step
Щелкните на кнопке Step в левом верхнем углу окна Debug. Окно Debug не
много изменится, теперь оно выглядит примерно так:
ШJ, DebugControl
Р" Stack Г Source
Go Step Over Out Quit
r-~-~-~-~~г- locals Г Globals
debug_example.py:2: <module>O
'Ьdb".runQ, lin e 585: exec(cmd, globals, locals)
!> '_ main_' <module>Q,
l
111e2.J =1•2
•
locals
_ annotations_ {}
_ builtins_
_ doc_
_file_
_ loader_
_ name_
_package_
_spec_
<module 'Ьuiltins' (built-in) >
None
'C:/Users/davea/Desktop/debug_example.py'
<class
'
_frozen_importlib.Builtinlmporter
'
>
_ main_
None
None
о
х
v
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 с утра до
ночи, можно установить точку останова. Тем самым вы приказываете отладчику
выполнять код, пока не будет достигнута точка останова.
Точки останова сообщают отладчику, когда следует приостановить выполне
ние, чтобы вы могли проанализировать текущее состояние программы. Так
что на самом деле выполнение программы не прерывается, а только приоста
навливается.
Чтобы установить точку останова, щелкните правой кнопкой мыши (Сtгl+щелчок
на Мае) на той строке кода в окне редактора, где вы хотите приостановить вы
полнение, и выберите команду Set Breakpoint. IDLE выделяет строку желтым
цветом - это признак установленной точки останова. Чтобы удалить точку
останова, щелкните правой кнопкой мыши на строке с точкой останова и вы
берите команду Clear Breakpoint.
Нажмите кнопку Quit в верхней части окна Debug, чтобы временно отключить
отладчик. Окно при этом не закроется, и вам стоит и далее держать его откры
тым, потому что вскоре мы к нему вернемся.
7.1 . Использование окна Debug Control 141
Установите точку останова в строке кода с командой print( ). Окно редактора
должно выглядеть примерно так:
!}debug_example.py - C:/Users/davea/Deskt."
Eile .Edit F2rmat Run Qp'tions Window tlelp
for i in range(l, 4):
j=i *2
print (f"i i3 {i} and j i3 {j}")1
о
х
"
Ln: 3 Col: 35
Сохраните и запустите файл. Как и прежде, панель Stack окна Debug показы
вает, что отладчикзапустился и ожидает выполнения строки 1. Щелкните на
кнопке Go и понаблюдайте за тем, что происходит в окне Debug.
etrDebugControl
о
х
r;; Stack Г Source
Go Step Over Out Quit
1--~-_....._ _....._..____,1;7 locals Г Globals
debug_example.py:З: <module>O
bdb'.runQ,line585: exec(cmd,globals, locals)
!> '_main_.' <nюdule'O. l111e3 rmnt(f"t is{t}a11dJisU}")
lo cals
_ annotations_ {}
_ builtins_
<module 'Ьuiltins' (built-in) >
_ doc_
No ne
_file_
'
C:/Users/davea/Desktop/debug_example.py'
_ load er_
<class 'Jrozen_importlib.Builtinlmporter'>
_name_
'_main_'
_package_
No ne
_ spec_
No ne
2
Напанели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 теперь выглядит так:
Gfr Debug Control
Р' Stack Г Source
Go Step Over Out Quit
1--~-~-~-~~Р' locals Г Globals
debug_example.py:З: <module>O
'Ьdb'.runQ, line 585: exec(cmd, globals, locals)
> '_mан1_'.<niodt1le>Q, line 3: print(f"i 1s [1) andj is{j\")
locals
_ annotations_ {}
_ builtins_
<module 'builtins' (built-in)>
_ doc_
None
_ file_
'
C:/Users/davea/Desktop/debug_example.py'
_ loader_
<class
'
_ frozen_importlib.Builtinlmporter'>
_пате_
'_main_'
_package_
Non e
_ spec_
Non e
2
4
о
х
Вы видите, что изменилось? На панели Stack выводится то же сообщение, что
и прежде: оно указывает, что отладчик собирается снова выполнить строку 3.
Однако переменные i и j теперь содержат 2 и 4. В интерактивном окне также
выводится результат выполнения строки с командой print() при первом про
ходе цикла.
Каждый раз, когда вы нажимаете кнопку Go, отладчик выполняет код в обычном
режиме, пока не достигнет следующей точки останова. Так как точка останова
была установлена в строке 3, находящейся внутри цикла for, отладчик оста
навливается в этой строке при каждом прохождении цикла.
Нажмите Go втретий раз. Теперь i и j содержатзначения з и 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"
prin t(a dd_unde rsc or es( phra se ))
Введите этот код в окне редактора, сохраните файл и запустите программу на
жатием FS. Программадолжна вывести _h _e _l_l_o _, но вместо этого выводится
о_, то есть буква о, за которой следует один символ подчеркивания.
Если вы уже видите, в чем проблема этого кода, пока не исправляйте ее. Этот
раздел должен научить вас пользоваться отладчиком IDLE для поиска ошибок.
ПРИМЕЧАНИЕ
Отладка может потребовать значительных усилий и времени, а ошибки бывают
ко вар ным и и т рудноуло вимыми.
Хотя в этом разделе рассматривается относительно простая ошибка, описан
ная схема анализа кода и поиска ошибок остается неизменной и для более
сложных случаев.
144 ГЛАВА 7 Поиск и исправление ошибок в коде
Если же вы не опознали проблему, не огорчайтесь! К концу этого раздела вы
найдете ее и научитесь отыскивать похожие проблемы в рабочем коде.
Отладка - практический навык, и по мере накопления опыта вы научитесь раз
рабатывать собственные приемы отладки. В этом разделе представлен простой
алгоритм из четырех шагов, который поможет вам на начальном этапе.
1. Предположите, в какой части кода может находиться ошибка.
2. Установите точку останова и проанализируйте код, выполняя проблемную
часть кода в пошаговом режиме с отслеживанием важных переменных.
3. Найдите строку кода с ошибкой (если она есть) и внесите изменения для
р еш ен ия проб лемы.
4. Повторяйте шаги 1 -3 , пока код не заработает так, как ожидалось.
Шаг 1. Предположите, где находится ошибка
Прежде всего следует определить часть кода, которая с большой вероятностью
содержит ошибку. Возможно, вам не удастся сразу найти точную позицию
ошибки, но обычно можно предположить, в какой части кода ее следует искать.
Обратите внимание: наша программа делится на две части - определение
функции (где определяется add_underscores()) и основной блок кода, в кото
ром определяется переменная phrase со значением "hello", а затем выводится
результат вызова add_underscores(phrase).
Взгляните на основную часть:
phrase = "hello"
prin t(a dd_unde rsc or es( phr ase ))
Как вы думаете, может ли проблема скрываться здесь? Маловероятно, не правда
ли? Все в этих двух строках кода выглядит нормально. А значит, проблема, воз
мож но, со держит ся в опреде лении функ ции :
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,чтоименно
происходит внутри кода.
(} squash_some_bugs.py - C:/Users/davea/Desktop/squash_so. ..
file fdit Format Bun Qptions Window J:!elp
def add_underscores(word):
newwot:d=""
:for-1 in rADqe(О, len(word)):I
new_word = word[i) +
return new word
phrase = "hello "
print(add_underscores(phrase))
о
х
v
Ln:3 Col:33
Откройте окно Debug и запустите файл. Выполнение прерывается в первой
строке, то есть в определении функции.
Нажмите кнопку Go, чтобы выполнить код вплоть до точки останова. Окно
Debug должно выглядеть примерно так:
(} DeЬug Conttol
Р" Stack Г Source
Go Step Over Out Quit
1--_.__....__....___,~_. Р" locals г; Globals
squash_some_bugs.py:З: add_underscoresQ
'ЬdЬ
'
.runQ,line585: exec(cmd,globals, locals)
'_main_'.<module>О, line8:print(add_underscores(phrase))
> '_ma1n_'.add_underscoresQ, line 3: for11n range(O, len(\•JOrd)):
locals
new_word•_•
word
'hello '
о
х
В этой точке код приостанавливается непосредственно перед входом в цикл
for в функции add_underscores() . Обратите внимание: на панели Locals
отображаются две локальные переменные, word и new_word. В настоящее
время word содержитзначение "hello", а new_word - значение"_",каки ожи
далось.
146 ГЛАВА 7 Поиск и исправление ошибок в коде
Щелкните на кнопке Step, чтобы войти в цикл for. Окно Debug изменяется, и на
панели Locals появляется новая переменная i со значением О.
[} Debug Control
ох
17 Stack Г Source
Go Step Over Out Quit
_ ......._ _.__ __._ __._ _,17 Locals Г Globals
squash_some_bugs.py.4: add_underscoresO
'bdb'.runO. line585: exec(cmd, globals, locals}
' _main_'.<module>О. line8: print(add_underscores(phrase)}
> '_main_'.add_underscoresQ,line4: ne.-1y1ord =\Vord[1) + "_'·
Local s
о
new_word '_'
word
'h ello '
i - счетчик, используемый вцикле for. Значение i показывает, какая итерация
цикла выполняется в настоящий момент.
Щелкните на кнопке Step еще раз. Взглянув на панель Locals, вы увидите, что
переме нная ne w _w or d п рин яла значение " h_".
i:. DeЬug Control
Гv Stack Г Source
Go Step Over Out Quit
!---'---'---'--'----' ГvLocals ГGlobals
squash_some_bugs.py:З: add_underscoresQ
'bdb'.runQ,line 585:exec(cmd, globals, locals)
'_main_'.<module>Q, line8: print(add_underscores(phrase))
> '_main_·.add_underscoresQ, l1ne 3: for1in range(O, len("ord)):
Locals
о
new_word 'h_'
word
'hello•
о
х
"
Что-то не так. Изначально new_word содержит значение "_",а при второй ите
рации цикла переменная должна содержать значение "_h_". Ес ли щ ел кн ут ь
на кнопке Step еще несколько раз, вы увидите, что new_word присваивается е_,
потом 1_ит.Д.
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 сап 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"
pr int(a dd_unde r scor es( phr ase ))
При выполнении файла в интерактивном окне выводится следующий результат:
i = 0; new_word
h-
i 1· new_word
е
,
-
i = 2· new_word
1
,
-
i = 3; new_word
1-
i = 4; new_word
о_
о-
Вывод показывает, какое значение принимает 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
Процесс переработки существующего кода для того, чтобы он был более на
глядным, удобочитаемым и понятным или лучше соответствовал стандар
там, установленным для команды, называется рефакторинzом. В этой книге
рефакторингу уделяется не много времени, но он очень важен для создания
проф ессио нальн ого ко да.
7.3 . ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы освоилиработу с окном Debug в IDLE. Я показал, как анализи
ровать значения переменных, вставлять точки останова и использовать кнопки
Step, Go, Over и Out.
Вы немного потренировались в отладке функции, содержащей ошибку, для
чего применили четырехшаговый процесс выявления и устранения ошибок.
1. В ыра ба ты ва ем п редположе ние, гд е находится ош ибка.
2. Устанавливаем точку останова и анализируем код.
3. Ищем ошибку и пытаемся ее исправить.
4. Повторяем шаги 1 -3 , пока ошибку не удастся исправить.
150 ГЛАВА 7 Поиск и исправление ошибок в коде
Отладка - не только наука, но и искусство. И освоить ее можно только одним
способом - побольше практиковаться!
Один из способов тренировки - открыть окно Debug Control и использовать
его для пошагового выполнения кода других упражнений и задач в книге.
ИНТЕРАКТИВНЫЙ ТЕСТ
Кэтой главе прилагается бесплатный интерактивный тестдля проверкиусво
енных вами знаний
.
Тест доступен на телефоне или компьютере:
realpython.com/quizzes!pybasics-debugging
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
• «Python Debugging With pdb1>, статья (https.j/realpython.com/python-
debugging-pdb/)
•
«PythonDebuggingWith pdb1>, видеокурс (https.j/realpython.com/courses/
python-debugging-pdb/)
ГЛАВА В
Условная логика
v
и управление программои
Почти весь код, встречавшийся вам в книге до сих пор, был безусловным. Дру
гими словами, в коде не было вариантов выбора. Все строки кода выполнялись
в том порядке, в котором они были записаны, или в порядке вызова функций
с возможными повторениями внутри циклов.
В этой главе я научу вас пользоваться условной логикой для написания про
грамм, выполняющих разные действия в зависимости от конкретных условий.
В сочетании с функциями и циклами условная логика позволяет писать сложные
программы, способные обрабатывать различные ситуации.
В этой главе вы научитесь:
• сравнивать значения двух и более переменных;
• использовать команды i f для управления последовательностью выпол
не ния ваш ей прог раммы ;
• обрабатывать ошибки конструкциями try и except;
• 11рименять условную логику для создания простых моделей.
Итак, за дело!
8.1 . СРАВНЕНИЕ ЗНАЧЕНИЙ
Условная логика основана на выполнении разных действий в зависимости от
того, истинно или ложно некоторое выражение, которое называется условием.
Эту идею используют не только компьютеры. Люди постоянно пользуются
у словно й логико й для п рин ятия решен ий.
Например, в США по закону алкогольные напитки можно покупать только
с 21 года. Утверждение «если вам не менее 21 года, вы можете купить пиво»
152 ГЛАВА 8 Условная логика и управление программой
может рассматриваться как пример условной логики. Часть «если вам не менее
21 года» определяет условие (возраст не менее 21 года), которое может быть
либо ИСТИННЫМ, либо ложным.
В программировании условия часто задаются в форме сравнения двух зна
чений - например, что одно значение больше другого или что два значения
равны. Для выполнения сравнения используется стандартный набор условных
знаков, называемых булевскими операторами сравнения; вероятно, многие из
ни х вам у же знакомы.
Булевские операторы сравнения перечислены в следующей таблице.
ОПЕРАТОР
ПРИМЕР
смысл
СРАВНЕНИЯ
>
а>Ь
а больше Ь
<
а<Ь
а меньше Ь
>=
а>= Ь
а больше или равно Ь
<=
а<= Ь
а меньше или равно Ь
!=
а!= Ь
анеравноЬ
а== Ь
а равно Ь
Термин «булевский» происходит от фамилии английского математика Джорджа
Буля, работы которого легли в основу современных вычислительных техно
логий. В честь Буля условная логика иногда называется булевской логикой.
Существует фундаментальный логический тип данных 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. Во втором
примере з не больше 5, поэтому результат равен False.
ВАЖ НО!
Распространенная ошибка при записи условий - использование оператора
присваивания = вместо == для проверкидвух значений на равенство.
К счастью, при обнаружениитакой ситуации Pythoп выдает ошибкуSyntaxError-
и вы узнаете об этом еще до запуска программы.
Булевские операторы сравнения удобно рассматривать как вопросы, относящи
еся кдвум значением: а == Ьспрашивает, содержатли аи Ь одинаковоезначение.
Аналогичным образом а !=Ьспрашивает, содержатли а и Ь разные значения.
Условные выражения не ограничиваются сравнением чисел. Их также можно
использовать для сравнения других значений - например, строк:
>>> "а" == "а'1
T rue
>>> "а"
"Ь"
False
>>> "а" < "Ь"
True
>>> "а" >"Ь"
Fa lse
Последние два примера могут показаться кому-то странными. Как одна строка
может быть больше или меньше другой?
С числами операторы сравнения <и > представляют понятия «больше» и «мень
ше», но в более общем смысле они представляют концепцию упорядочения.
В этом отношении "а" < "Ь" проверяет, что строка "а" предшествует строке "Ь".
Но как упорядочиваются строки?
В Python строки упорядочиваются лексикоrрафически - этот хитроумный
термин означает, что они следуют в том порядке, в котором идут в словаре. Та
ким образом, можно считать, что "а" < "Ь" проверяет, предшествует ли буква а
букве Ь в словаре.
Лексикографическое упорядочение также действует и для строк из двух и более
символов, для чего проверяется каждая порядковая буква строки:
154 ГЛАВА 8 Условная логика и управление программой
>>> "apple" < "astronaut"
T rue
>>> "beauty" > "truth"
False
Так как строки могут содержать и другие символы, кроме алфавитных, упорядо
чение также должно распространяться и на эти символы. Каждому символу со
ответствует число, называемое кодовым пунктом Юникода. Чтобы сравнить два
символа, Python берет кодовые пункты этих символов и сравнивает два числа.
Мы не будем углубляться в подробности работы кодовых пунктов Юникода.
На практике операторы сравнения < и > чаще всего используются с числами,
а не со строками.
Упражнения
1. Для каждого из следующих условных выражений предположите, какой
результат будет получен при их обработке - True или False. Затем введите
их в интерактивном окне, чтобы проверить свои ответы:
1<=1
1!=1
1!=2
"good" != "bad"
"good" != "Good"
123 == "123"
2. Для каждого из следующих выражений заполните пустые места (обозна
ченные_) соответствующим булевским оператором сравнения, чтобы
выражение давало результат True:
3
4
105
"jack" _ "jill"
42
" 42"
8.2 . ДОБАВИМ НЕМНОГО ЛОГИКИ
Кроме булевских операторов сравнения Python использует специальные клю
чевые слова (которые называются логическими операторами) для объединения
булевских выражений. Существуют три логических оператора - and, or и not.
Логические операторы используются для конструирования составных логиче
ских выражений. По большей части их смысл аналогичен смыслу в естественном
языке, хотя в Python действуют намного более точные правила их использования.
8.2. Добавим немного логики
155
Ключевое слово and
Рас смо три м с ле д ую щи е утверждения.
1. У кошек четыре ноги.
2. У кошек есть хвост.
В общем случае оба эти утверждения истинны.
Если объединить эти два утверждения связкой and, то полученное утвержде
ние «у кошек четыре ноги and у кошек есть хвост~> также является истинным.
Если же изменить смысл обоих утверждений на противоположный, составное
утверждение «у кошек не четыре ноги and у кошек нет хвоста~> будет ложным.
Даже если объединить истинное утверждение с ложным, составное утвержде
ние будет ложным. Оба утверждения - «у кошек четыре ноги and у кошек нет
хвоста» и «у кошек не четыре ноги and у кошек есть хвост» - будут ложными.
Когдадва утверждения, Ри Q, объединяются связкой and, составное утвержде
ние «Р and Q» будет истинным в том и только в том случае, если истинно как
Р,такиQ.
Оператор and в Python работает точно так же. Четыре примера составных вы
ражений с использованием and:
>>> 1 < 2 and 3 < 4 # Оба выражения истинны (True)
T rue
Оба выражения истинны, поэтому их комбинация также истинна (True).
>>> 2 < 1 and 4 < 3 # Оба выражения ложны (False)
False
Оба выражения ложны, поэтому их комбинация ложна (False).
>>> 1 < 2 and 4 < 3 # Второе выражение ложно (False)
False
Выражение 1 <2 истинно, но выражение 4 <З ложно, поэтому их комбинация
ложна (False).
>>> 2 < 1 and 3 < 4 # Первое выражение ложно (False)
False
Выражение 2 < 1 ложно, а выражение з <4 истинно, поэтому их комбинация
ложна (False).
156 ГЛАВА 8 Условная логика и управление программой
В следующей таблице приведена краткая сводка использования оператора and.
КОМ&ИНАЦИR С AND
True апd True
True апd False
False апd True
False апd False
РЕЗУЛЬТАТ
True
False
False
False
Каждое из этих правил можно проверить в интерактивном окне:
»> True and True
Tr ue
»> True and False
False
>» False and True
False
»> False and False
False
Ключевое слово or
Ко гда мы ис поль зуем слово «И ЛИ» (o r) в б ыт у, иногда подразумевается « ис
ключающее или» - то есть истинным может быть либо первый вариант, либо
второй, но не оба сразу.
Например, в утверждении «Я могу остаться or я могу уйти» используется «ис
ключающее или»: нельзя одновременно остаться и уйти. Истинным может быть
только что-то одно.
Однако в языке Python ключевое слово or исключающим не является. Иначе
говоря, если Р и Q - два выражения, то выражение Р or Q истинно в любой из
следующих комбинаций.
1. р истинно.
2. Q истинно.
3. Истинно как Р, так и Q.
Рассмотрим несколько примеров со сравнениями чисел:
>>> 1 с 2 or 3 с 4 # Оба выражения истинны (True)
True
8.2. Добавим немного логики
157
>>> 2<1or4<3#Обавыраженияложны(False)
False
»> 1 < 2 or 4 < 3 # Второе выражение ложно (False)
True
>>> 2 < 1 or 3 < 4 # Первое выражение ложно (False)
True
Обратите внимание: если хотя бы одна часть составного выражения равна True,
то, даже если другая часть равна False, результат все равно равен True.
В следующей таблице приведена краткая сводка использования оператора or.
КОМ&ИНАЦИЯ С OR
РЕЗУЛЬТАТ
True orTrue
True
True orFalse
True
False orTrue
True
False orFalse
False
И снова все результаты можно проверить в интерактивном окне:
»> True or True
True
»> True or False
True
»> False or True
True
>» False or False
False
Ключевое слово not
Ключевое слово not вычисляет логическое отрицание одного операнда:
ИСПОЛЬЗОВАНИЕ NОТ
РЕЗУЛЬТАТ
not True
False
not False
True
158 ГЛАВА 8 Условная логика и управление программой
В этом также можно убедиться в интерактивном окне:
»> not True
False
»> not False
True
Следует помнить, что в сочетании с операторами сравнения (такими, как==)
not не всегда ведет себя так, как вы ожидаете.
Например, not True == Falseвозвращает True, но False == not True выдаст
ош ибку:
>» not True
True
False
>>> False == not True
File "cstdin>", 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.
При работе со сложными выражениями лучше всего начать с самой сложной
части выражения и продвигаться к более простым.
На прим ер, поп робу ем вычи слит ь сл ед ую щее выра жение:
("А"!="А")ornot(2>=З)
Начните с двух выражений в круглых скобках."А" !="А" дает результат False,
потому что значение "А" равно самому себе. 2 >= 3 также дает False, потому
что 2 меньше з. В результате мы получим эквивалентное, но более простое
выр ажени е:
(False) or not (False)
Так как not обладает более высоким приоритетом, чем or, приведенное выше
вы ра же ни е экви валентно сл еду ющем у:
False or (not False)
160 ГЛАВА 8 Условная логика и управление программой
not Falseдает результат True, поэтому выражение можно упростить дополни
тельно:
False or True
Наконец, поскольку любое составное выражение с or истинно, если истинно
хотя быодноизвыражений сдвухсторон от or, можно заключить, что ("А" !=
"А") 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)
Tru e
Логические операторы и булевские операторы сравнения не очень понятны
с первого раза, так что, если материал этого раздела вам не удастся усвоить
с первого раза, - не беспокойтесь!
После небольшой практики вы научитесь разбираться, что происходит в таких
выражениях, и строить собственные составные условные команды, когда они
вам потребуются.
Упражнения
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 "А" == "В"
8.3 . УПРАВЛЕНИЕ ПОСЛЕДОВАТЕЛЬНОСТЬЮ
ВЫПОЛНЕНИЯ ПРОГРАММЫ
Вы узнали, как сравнивать значения булевскими операторами сравнения и стро
ить условные команды с логическими операторами. Теперь добавим логику
в код, чтобы он мог выполнять разные действия для разных условий.
Команда if
Команда i f приказывает Python выполнить блок кода только в том случае, если
выполняется некоторое условие.
Например, следующая командаi f выводит 2 and 2 is 4 втом случае, если условие
2 + 2 == 4 дает результат True:
if2+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 пропускает блок с отступом
и продолжает выполнение со следующей строки без отступа.
Так, следующая команда i f ничего не выводит:
if2+2==5:
print("Is this the mirror universe?")
162 ГЛАВА 8 Условная логика и управление программой
Вселенная, в которой условие 2 + 2 == 5 оказывается истинным, была бы до
вольно странной!
ПРИМЕЧАНИЕ
При отсутствии двоеточия после условия в команде if выдается ошибка
SyntaxError:
>>>if2+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 для выполнения кода
только в том случае, если условие команды i f ложно.
В следующем примере 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, вы
вести сообщение "Vou passed the class!" Иначе вывести сообщение "You did
not pass the class : (".
Обратите внимание: ключевое слово else не имеет условия и за ним следует
двоеточие. Условие не нужно, потому что команда elseвыполняет код при на
рушении условия команды i f.
164 ГЛАВА 8 Условная логика и управление программой
ВАЖ НО!
При отсутствии двоеточия после ключевого слова else Python выдаст ошибку
SyntaxError:
>>>if2+2==5:
print("Who broke my math?")
else
SyntaxError: invalid syntax
В этом примере выводится следующий результат:
Vou did not pass the class :(
Thank you for attending.
Даже при выполнении блока с отступом после else Python все равно выполнит
строку, которая выводит сообщение "Thank you for attending."
Ключевые слова i f и else хорошо работают совместно, если вы проверяете
условие, имеющее ровно два состояния. Однако иногда требуется проверить
три и более варианта. В таких случаях используется ключевое слово elif.
Ключевое слово elif
Ключевое слово elif (сокращение от else if) может использоваться для до
бавления дополнительных условий после команды i f.
Команды elif, как и if, состоят из трех частей.
1. Ключевое слово elif.
2. Условие, за которым следует двоеточие.
3. Блок с отступом, который выполняется в том случае, если условие ис
тинно.
ВАЖНО!
При отсутствии двоеточия в конце команды elif выдается ошибка SyntaxError:
>>>if2+2==5:
print("Who broke my math?")
elif2+2==4
SyntaxError: invalid syntax
8.3 .Управление последовательностью выполнения программы 165
Следующая программа объединяет i f, elif и else для вывода буквенной оценки
успеваемости ст уд ен та :
grade=85#1
ifgrade>=90:#2
print("You passed the class with an А.")
elif grade >= 80: # 3
print("You passed the class with а в.")
elif grade >= 70: # 4
prir1t( "You passed the class with а С.")
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 а В.
Thanks for attending.
Проанализируем работу кода поэтапно.
1. В строке 1grade присваивается значение 85.
2. Условие grade >= 90 ложно, поэтому команда i f в строке 2 пропускается.
3. Условие grade >= 80 истинно, поэтому выполняется блок под командой
elif в строке 3и выводится сообщение "You passed the class with а в.".
4. Команды elif и else в строках 4и 5 пропускаются,так как было выпол
нено условие команды elif в строке 3.
5. Наконец, выполняется строка 6 и выводится сообщение "Thanks for
atteпding. ".
Ключевые слова if, elif и else принадлежат к числу наиболее часто исполь
зуемых в Python. Они позволяют писать код, который по-разному реагирует
на различные условия.
Команда i f позволяет решать более сложные задачи, чем код без условной
логики. Команды i f даже можно вкладывать друг в друга для реализации не
имоверно сложной логики!
166 ГЛАВА 8 Условная логика и управление программой
Вложенные команды if
По аналогии с тем, как циклы while можно вкладывать друг в друга, также до
пустимо вложить команду i f внутрь другой команды i f, чтобы создать сложную
структуру принятия решений.
Рассмотрим следующий сценарий. Два человека играют в спортивную игру.
Требуется определить победителя на основании счета и игры.
•
Если они играют в баскетбол, то побеждает игрок с большим счетом.
• Если они играют в гольф, то побеждает игрок с меньшим счетом.
• В любом виде спорта при равенстве счетов игра заканчивается вничью.
Следующая программарешает этузадачу с использованием вложенных команд
i f, отражающих описанные выше условия:
sport = input("Enter а sport: ")
pl_score int(input("Enter player 1 score: "))
p2_score = int(input("Enter player 2 score: "))
#1
if sport.lower() == "basketball":
#2
if pl_score == p2_score:
print("The game is а draw.")
elif pl_score > p2_score:
print("Player 1 wins. ")
else:
print("Player 2 wins. ")
elif sport.lower()
" golf ":
#3
if pl_score == p2_score:
print("The game is а draw. ")
elif pl_score < p2_score:
print("Player 1 wins. ")
else:
print("Player 2 wins. ")
else:
print("Unknown sport")
Сначала программа предлагает пользователю ввести вид спорта и счета двух
игроков.
Команда i f в разделе #1 выполняется в том случае, если переменная sport
содержит значение "basketball" . М е то д .lower() гарантирует, что такие ва
рианты ввода, как "Basketball" или "BasketBall",будут интерпретироваться
одинаково.
8.3 .Управление последовательностью выполнения программы 167
Если счета обоих игроков равны, то игра завершается вничью. В противном
случае игрок с более высоким счетом побеждает.
Команда i f в разделе #2 выполняется в том случае, если переменная sport со
держитзначение"golf". Если счета равны, то игра завершается вничью. Впро
тивном случае игрок с меньшим счетом побеждает.
Команда i f в разделе #3 выполняется в том случае, если переменная sport со
держит какое-то другое значение, отличноеот "basketball" или "golf" . В этом
случае выводится сообщение "Unknown sport" (неизвестный вид спорта).
Результат выполнения программы зависит от входных значений. Пример вы
полнения со вводом "basketball":
Enter а sport: basketball
Player 1 score: 75
Player 2 score: 64
Player 1 wins.
А вот как выглядит результат с теми же счетами при вводе "golf":
Enter а sport: golf
Player 1 score: 75
Player 2 score: 64
Player 2 wins.
Если указать любой другой вид спорта, кроме баскетбола или гольфа, программа
выводит сообщение Unknown sport.
В итоге возможно семь вариантов выполнения программы.
ВИД СПОРТА
СЧЕТДВУХИГРОКОВ
'Ъasketball"
p1_score == p2_score
'Ъ asketba 11 "
р1_score > p2_score
'Ъ asketb a ll"
р1_score < p2_score
"golf"
р1_score == p2_score
"golf"
р1_score > p2_score
"golf"
р1_score < p2_score
Любые другие
Любые комбинации
Вложенные команды i f дают много возможных вариантов выполнения кода.
Если программа содержит более двух уровней вложенных команд i f, число
возможных вариантов стремительно возрастает.
168 ГЛАВА 8 Условная логика и управление программой
ПРИМЕЧАНИЕ
При слишком большой глубине вложения команд ifпрограмма значительно
усложняется и становится трудно предсказать поведение программы при
заданных условиях.
По этой причине использовать вложенные команды ifобычно не рекомен
дуе тся.
Давайте упростим приведенную выше программу, сократив число вложенных
команд if.
Прежде всего, независимо от вида спорта, игра завершается вничью, если
pl_score равно p2_score. Таким образом, проверку равенства можно вывести
из вложенных команд i f для каждого вида спорта и преобразовать в одну
кома нду i f:
if pl_score == p2_score:
print("The game is а draw. ")
elif sport. lower() == "basketball":
if pl_score > p2_score:
print("Player 1 wins. ")
else:
print("Player 2 wins. ")
elif sport.lower() == "golf":
if pl_score < p2_score:
print("Player 1 wins. ")
else:
print("Player 2 wins. ")
else:
print("Unknown sport.")
Ост алось только ше сть в оз м ож н ых вариантов вы полнен ия.
Впрочем , и это немало. Нельзя ли еще как-то упростить программу?
Одно из возможных решений: игрок 1 выигрывает при выполнении любого из
следующих условий.
1. sport содержит "basketball", а pl_scoreбольше p2_score.
2. sport содержит "golf", а pl_score меньше p2_score.
8.3 .Управление последовательностью выполнения программы 169
Это можно описать составными условными выражениями:
sport = sport.lower()
pl_wins_bball = (sport == "basketball") and (pl_score > p2_score)
pl_wins_golf = (sport == "golf") and (pl_score < p2_score)
pl_wins = playerl_wins_basketball or playerl_wins_golf
Код достаточно плотный, поэтому мы разберем его последовательно.
Сначаластрока, присвоенная sport, преобразуется к нижнему регистру, чтобы
значение можно было сравнивать с другими строками, не беспокоясь о возмож
ных ошибках, связанных с расхождениями в регистре символов.
В следующей строке находится структура, которая на первый взгляд кажется
немного странной. За оператором присваивания = следует выражение с опе
раторомпроверкиравенства ==. Эта строка вычисляет следующее составное
логическое выражение и присваивает результат переменной pl_wins_bball:
(sport == "basketball") and (pl_score > p2_score)
Если sport содержит "basketball ", а pl_score больше p2_score, значение
pl_wins_bballистинно.
Затем аналогичная операция выполняется с переменной pl_wins_golf. Если
sport содержит "golf", а pl_score меньше p2_score, значение pl_wins_golf
истинно.
Наконец,переменнаяpl_winsсодержит True,если игрок 1выиграл в баскетбол
или гольф, и False в противном случае.
С этими изменениями программу можно довольно серьезно упростить:
sport = sport.lower()
if pl_score == p2_score:
print("The game is а draw.")
elif (sport == "basketball") or (sport == "golf"):
pl_wins_bball = (sport == "basketball") and (pl_score > p2_score)
pl_wins_golf = (sport == "golf") and (pl_score < p2_score)
pl_wins = pl_wins_bball or pl_wins_golf
if pl_wins:
print("Player 1 wins. ")
else:
print("Player 2 wins. ")
else:
print("Unknown sport")
170 ГЛАВА 8 Условная логика и управление программой
В обновленной версии программы есть всего четыре варианта выполнения и код
получается более понятным.
Иногда без вложенных команд i f не обойтись. Но если вы заметите, что вам
приходится писать их слишком много, возможно, стоит остановиться и поду
мать, как упростить код.
Упражнения
1. Напишите программу, которая запрашивает у пользователя слово при
помощи функции iпput() и сравнивает длину слова с числом 5. Про
грамма должна вывести один из следующих результатов н зависимости
от длины пользовательского ввода:
"Длина ввода меньше 5 символов"
"Длина ввода больше 5 символов"
" Длина ввода сос тавл яет 5 симво лов"
2. Напишите программу, которая выводит сообщение "Я загадал(а) число
от 1до10. Угадайте его". Получите число от пользователя при помощи
функции iпput (). Если пользователь вводит число 3 , программа должна
вывести сообщение "Вы победили! ",а для любого другого ввода программа
выводит сообщение "Вы проиграли".
8.4. ЗАДАЧА: ПОИСК МНОЖИТЕЛЕЙ ЧИСЛА
Множителем положительного целого числа п называется любое положительное
целое число, меньшее либо равное п, на которое п делится без остатка.
Например, 3 является множителем 12, потому что при делении 12 на 3 полу
чается 4 без остатка. С другой стороны, 5 множителем 12 не является, потому
что 12 делится на 5 с остатком 2.
Напишите программуf actors.py, которая запрашивает у пользователя положи
тельное целое число, а затем выводит список множителей этого числа. Пример
запуска программы с выводом результата:
Enter а positive integer: 12
1isаfactorof12
2isаfactorof12
3isаfactorof12
4isаfactorof12
6isаfactorof12
12isаfactorof12
8.5. Управление циклами
171
Подсказка: вспомните, о чем мы говорили в главе 5, - оператор % может ис
пользоваться для получения остатка от деления двух чисел.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
8.5 . УПРАВЛЕНИЕ ЦИКЛАМИ
В главе 6 я рассказывал, как выполнять блок кода в циклах for или while. Циклы
удобны для выполнения повторяющихся операций и для обработки большого
количестваразных входных значений по одной схеме.
В этом разделе речь пойдето команде if, вложенной в циклы for. Также я по
знакомлю вас с двумя ключевыми словами, breakи continue, которые позволяют
управлять выполнением цикла.
Команды if и циклы for
Блок кода в цикле forне отличается от любого другого блока. Это означает, что
команда if может размещаться внутри цикла for точно так же, как и в любом
др угом фрагм енте к ода .
В следующем примере цикл for с командой i f используется для вычисления
и вывода суммы всех четных целых чисел, меньших 100:
sum_of_evens = 0
for n in range(101):
ifn%2==0:
sum_of_evens sum_of_evens + n
print(sum_of_evens)
Сначала переменная sum_of_evens инициализируется значением 0. Затем про
грамма в цикле перебирает числа от О до 100 и прибавляет четные значения
к sum_of_evens. Итоговое значение sum_of_evens равно 2550.
break
Ключевое слово break приказывает Python выйти из цикла. Другими словами,
цикл немедленно останавливается, а выполнение продолжается с кода, следу
ющего после цикла.
172 ГЛАВА 8 Условная логика и управление программой
Например, следующий код в цикле перебирает числа от 0 до з, но цикл пре
рывается пр и д ост иж ени и числа 2:
for n in range(4):
ifn==2:
b reak
print(n)
print(f"Finished with n = {n}")
В результате выводятся только первые два числа:
0
1
Finished with n 2
continue
Ключевое слово continue пропускает весь оставшийся код в теле цикла и пере
ходит к следующей итерации.
Например, следующий код перебирает числа от 0 до з и выводит каждое число,
кр оме 2:
for i in range(4):
ifi==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прерываетцикл привыполнении условия,
а ключевое слово coпtiпue пропускает оставшийся код текущей итерации цикла
при выполнении условия.
Циклы for ... else
Хотя эта структура используется не так часто, циклы Python тоже могут вклю
чать секцию else. Рассмотрим пример:
phrase = "it marks the spot"
for character in phrase:
if character == "Х":
br eak
else:
print("There was по 'Х' in the phrase")
Цикл forв этом примере перебирает символы строки "it marks the spot" и оста
навливается при обнаружении буквы "Х". Выполнив код примера, вы увидите,
что на консоль выводится сообщение There was по 'Х' iп the phrase.
Теперьизменитетекстна"Хmarks thespot". При выполнении того же кода
с новым текстом не будет выведено ничего. Что происходит?
Код в блоке else выполняется только в том случае, если предшествующий цикл
for завершился без выполнения команды break.
Таким образом, при выполнении кода с phrase = "it marks the spot" строка кода
с командой break выполнена не будет, потому что фраза не содержит символа Х.
Вместо этого выполняется блок else и выводится строка "There was по 'Х' iп
the phrase".
С другой стороны, при выполнении кода с phrase = "Х marks the spot" будет
выполнена строка с командой break; таким образом, блок else не будет вы
полняться и вывод отсутствует.
В следующем примере пользователю предоставляются три попытки для ввода
пароля:
for п in range(З):
password = input("Password: ")
if password == "I<ЗBieber":
b reak
print("Password is incorrect.")
else:
print("Suspicious activity. The authorities have Ьееп alerted." )
174 ГЛАВА 8 Условная логика и управление программой
Программа перебирает числа от 0 до 2. При каждой итерации пользователю
предлагается ввести пароль. Если введенный пароль правилен, команда break
используется для выхода из цикла. В противном случае программа сообщает,
что пароль неправильный, и пользователь повторяет попытку.
После трех безуспешных попыток цикл for завершается без выполнения строки
кода, содержащей break. В этом случае выполняется блок else и пользователь
получает сообщение о подозрительной активности.
ПРИ МЕ ЧА НИЕ
Этот раздел мы посвятили циклам for, потому что они используются чаще
других.
Тем не менее все упоминаемые возможности работают и в циклах while. Дру
гими словами, команды break и coпtinue можно выполнять и в циклах while.
Цикл while даже может иметь секцию else!
Условная логика в теле цикла открывает новые возможности управления
выполнением кода. Цикл можно остановить ключевым словом break или про
пустить текущую итерацию ключевым словом continue. Можно даже сделать
так, что некоторый код будет выполняться только в том случае, если цикл был
завершен без выполнения команды break.
Это чрезвычайно мощные инструменты, которые пригодятся любому про
гра ммис ту.
Упражнения
1. Используя команду break, напишите программу, которая многократно
запрашивает у пользователя данные и завершается только тогда, когда
пользователь введет "q" или "Q ".
2. Используя команду continue, напишите программу, которая перебирает
числа от 1до 50 и выводит все числа, не кратные3.
8.6 . ВОССТАНОВЛЕНИЕ ПОСЛЕ ОШИБОК
Ошибки в к о д е неприятны , но абс олютн о н ормальн ы! Ошибки случа ются д аже
у лучших программистов.
Программисты часто называют ошибки времени выполнения «исключениями».
Таким образом, когда вы сталкиваетесь с ошибкой, поздравьте себя! Вы заста
вили свой код сотворить нечто исключительное.
8.6. Восстановление после ошибок
175
Чтобы создавать надежные программы, необходимо иметь возможность обраба
тывать ошибки, вызванные некорректным пользовательским вводом или другим
непредсказуемым источником. В этом разделе я расскажу, как это делается.
Зоопарк исключений
Когда в ваш ей про грам ме пр оисходи т исключение, полезно зн ать, ч т о и ме нн о
пошло не так. В Python существует ряд встроенных типов исключений для
опи сани я раз ных видов ошиб ок.
Некоторые уже встречались вам в этой книге. Для удобства я собрал их в этом
разделе, а также добавил к ним ряд новых представителей.
ValueError
Ошибка ValueError происходит, когда операция обрабатывает недействитель
ное значение.
Например, попытка преобразования строки "not а number" в целое число при
водит к ошибке ValueError:
>>> int("not а number")
Traceback (most recent call last):
File "cpyshell#l>", line 1, in cmodule>
int("not а number")
ValueError: invalid literal for int() with base 10: 'not а number'
Последняя строка выводит имя исключения и описание проблемы. Этот общий
формат используется для всех исключений Python.
TypeError
Ошибка TypeError происходит при выполнении операции со значением не
правильного типа. Например, попытка суммирования строки и целого числа
приведет к ош иб ке Type Err or :
>>>"1"+2
Traceback (most recent call last):
File "cpyshell#l>", line 1, in cmodule>
"1"+2
TypeError: сап only concatenate str (not "int") to str
NameError
Ошибка NameErrorпроисходит при попытке использованияимени переменной,
которое не было определено в программе:
176 ГЛАВА 8 Условная логика и управление программой
>>> print(does_not_exist)
Traceback (most recent call last):
File "cpyshell#З>", line 1, in cmodule>
pri nt(d oe s_not_ ex ist)
NameError: name 'does_not_exist' is not defined
ZeroDivisionError
Ошибка ZeroDivisionError происходитв том случае, если делитель в операции
деления равен нулю:
»>1/0
Traceback (most recent call last):
File "cpyshell#4>", line 1, in cmodule>
1/0
ZeroDivisionError: division Ьу zero
OverflowError
ОшибкаOverflowError происходит, если результат арифметической операции
оказывается слишком большим. Например, при попытке возвести значение 2. 0
в степень 1_000 _000 вы получите ошибку OverflowError:
>>> pow(2.0 , 1 _000_000)
Traceback (most recent call last):
File "cpyshell#б>", line 1, in cmodule>
pow(2.0 , 1 _000_000)
OverflowError: (34, 'Result too large')
В главе 5 мы уже говорили, что целые числа в Python обладают неограниченной
точностью. Это означает, что ошибки OverflowError могут происходить только
с числами с плавающей точкой. Возведение целого числа 2 в степень 1_000 _000
не приведет к ошибке OverflowError!
Полный список встроенных исключений Python приведен в официальной до
кументации (https.j/docs.python.org/3/library/exceptions.html).
Ключевые слова try и except
Иногда можно предусмотреть возможностьтого или иного исключения. Вместо
того чтобы позволить программе аварийно завершиться, можно перехватить
ошибку в момент ее возникновения и попытаться скорректировать ее.
Например, можно предложить пользователю ввести целое число. Если он ука
жет значение, не являющееся целым числом (например, строку "а"), следует
сообщить ему, что введено недопустимое значение.
8.6. Восстановление после ошибок
177
Для предотвращения аварийного завершения программы можно воспользо
ваться ключевыми словами try и except. Рассмотрим пример:
try:
number = int(input("Enter ап integer: "))
except ValueError:
print("That was not ап integer")
Ключевое слово try обозначает начало блока try, и после него ставится двое
точие. Код с отступом, следующий за try, выполняется в программе. В данном
случае пользователю предлагается ввести целое число. Так как input() воз
вращает строку, пользовательский ввод преобразуется в целое число вызовом
int(), а результат присваивается переменной number.
Если введенное значение не является целым числом, операция int () выдает
ошибку ValueError. В таком случае будет выполнен код с отступом, располо
женный подстрокой except ValueError. Таким образом, вместо аварийного
завершения программы выводится строка "That was not an integer". Если поль
зователь введет допустимое целое значение, то код в блоке except ValueError
выполняться не будет.
Но при исключении другого типа (например, TypeError) программа аварийно
завершится. В приведенном выше примере обрабатывается только один тип
исключения - ValueError.
Однако секцияexcept можетобрабатывать сразу несколько типов исключений.
Для этого следует разделить имена исключений запятыми и заключить список
имен в круглые скобки:
def divide(numl, num2):
try:
print(numl / num2)
except (TypeError, ZeroDivisionError):
print("encountered а proЫem")
В этом примере divide() получает два параметра, numl и num2, и выводит ре
зультат деления numl на num2.
Если divide() вызывается со строковым аргументом, то операция деления вы
даетошибкуTypeError. Кроме того, если аргумент num2 равен нулю,появляется
сообщение об ошибке ZeroDivisionError.
Строка except (TypeError, ZeroDivisionError) обрабатываетоба исключения
и выводит строку "encountered а proЫem" при возникновении любого из двух
исключ ений.
178 ГЛАВА 8 Условная логика и управление программой
Впрочем, во многих случаях полезно перехватывать ошибки по отдельности; это
позволит вывести более осмысленный текст. Для этого после блока try следует
определить несколько блоко в e xc ep t:
def divide(numl, num2):
try:
print(numl / num2)
except TypeError:
print("Both arguments must Ье numbers")
except ZeroDivisionError:
print("num2 must not Ье 0")
В этомпримере ошибки TypeError и ZeroDivisionError обрабатываются по от
дельности. Это позволяет вывести более точное сообщение, если что-то пойдет
не так.
Если одноиз значений numl или num2 не является числом, то программа выдает
ошибку TypeError ивыводит сообщение "Both arguments must Ье numbers " . Е сли
значение num2 равно нулю, выдается ошибка ZeroDivisionError и выводится
сообщение"num2 must not Ье 0".
except без исключения
Ключевое слово exceptтакже может использоваться само по себе, без указания
ко нкр етн ых ис ключен ий:
try:
# Много потенциально опасных операций
exc ept :
print("Something bad happened!")
Если при выполнении кода в блоке tryпроизойдет любоеисключение, будет вы
полнен блок exceptи на экране появится сообщение "Somethingbad happened ! "
Казалось бы, это отличный способ предотвратить аварийное завершение ваших
программ. Но на самом деле поступать так не рекомендуется!
Для этого есть несколько причин, но начинающим программистам важно звать,
что перехват всех исключений скроет ошибки в вашем коде и создаст ложное
чувство уверенности в том, что он работает, как ожидалось.
Если вы будете перехватывать только конкретные исключения, то при обна
ружении непредвиденных ошибок Python выведет трассировку и подробности
ошибки - и вы получите больше информации для отладки вашего кода.
8.7. Моделирование событий и вычисление вероятностей
179
Упражнения
1. Напишите программу, которая предлагает пользователю ввести целое
число. Если пользователь ввел что-то другое, программа должна пере
хватить ошибку ValueError и вывести сообщение "Try again."
Когда пользователь задаст целое число, программа должна вывести это
число и завершить работу без сбоев.
2. Напишите программу, которая предлагает пользователю ввести строку
и целое число n, а затем выводит символ с индексом n во введенной строке.
Используйте механизм обработки ошибок для того, чтобы предотвратить
аварийное завершение программы, если пользователь задаст что-то кроме
целого числа или если индекс выходит за границы массива. Программа долж
на выводить разные сообщения об ошибках в зависимости от типа ошибки.
8.7 . МОДЕЛИРОВАНИЕ СОБЫТИЙ
И ВЫЧИСЛЕНИЕ ВЕРОЯТНОСТЕЙ
В этом разделе я покажу, как применить некоторые концепции циклов и услов
ной логики, о которых мы рассказали ранее, при решении реальной задачи -
моделировании событий и вычислении вероятностей.
Мы проведем простое моделирование, известное под названием эксперимента
Монте-Карло. Каждый эксперимент состоит из испытания - некоторого процесса,
который можно повторить (например, броска монеты). Каждое испытание генери
рует результат (например, падение монеты орлом или решкой вверх). Испытание
повторяется многократнодля вычисления вероятности того или иного исхода.
Для программирования этой схемы необходимо включить в код источник
случайности.
Модуль random
Py th on предоставляет набор ф у н кц и й для ге нерир овани я сл уч ай ных чисел
вмодуле random. Стандартная библиотека Python - организованная коллекция
модулей, которую можно импортировать вваш коддля решенияразличных задач.
Чтобы импортировать модуль random, введите следующую команду винтер
активном окне IDLE:
>>> import random
Теперь вы можете использовать функции из модуля random в своем коде.
180 ГЛАВА 8 Условная логика и управление программой
ПРИМЕЧАНИЕ
Модули и команды import более подробно рассматриваются в главе 11 «Мо
дули и пакеты».
Функция randint() модуля random имеет два обязательных параметра а и Ь, оба
должны быть целыми числами. Функция возвращает случайное целое число,
которое больше или равно а, но меньше или равно Ь. Например, следующий код
создает случайное целое число от 1до 10:
>>> random.randint(l, 10)
9
Так как функция возвращает случайный результат, скорее всего, вы получите
другой вывод вместо 9. Если снова выполнить тот же код, он с большой веро
ятностью вернет иное число.
Таккак функция randint() принадлежит модулю random, для ее использования
перед именем необходимо указать имя модуля random и точку.
При использовании randint() важно помнить, что оба параметра, а и Ь, должны
быть целыми числами, а вывод равен а, Ь или любому числу между ними. На
пример, random.randint(0, 1) возвращает одно из двух случайно выбранных
чисел- 0или1.
Все целые числа в диапазоне от а до Ь будут возвращаться randint() с равной
вероятностью. Таким образом, для randint(l, 10) все целые числа от 1до10
будут возвращаться с вероятностью 10%. Для randint(0, 1) вероятность воз
вращения О составляет 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, который десять тысяч раз моделирует бро
сок монеты и обновляет соответствующие счетчики падения монеты орлом
и решкой:
# С четч ики ин ициа лиз ирую тся нул ями
he ads_ tally = 0
tails_tally = 0
for trial in range(10_000):
if coin_flip() == "heads":
he ads _tally he ads _tally + 1
else:
tails_tally tails_tally + 1
Сначаласоздаются две переменные-счетчики heads_tally и tails_tally, кото
рые инициализируются целым числом О.
Затем цикл 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 в интервале от Одо 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" с вероятностью 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 ООО бросков игрального
кубика и выводит среднее значение из тех, что выпали.
8.8 . ЗАДАЧА: МОДЕЛИРОВАНИЕ ЭКСПЕРИМЕНТА
С БРОСКОМ МОНЕТЫ
Допустим, вы многократно бросаете симметричную монету, пока орел и решка
не выпадут хотя бы по одному разу. Другими словами, после первого броска вы
продолжаете бросать монету, пока она не упадет другой стороной вверх.
184 ГЛАВА 8 Условная логика и управление программой
Бросая монету, вы получаете последовательность орлов и решек. Например,
пр и перв ом пров едении экспе римента (в первой попыт ке) вы мож ете по лучить
последовательность «орел, орел, решка~>.
Сколько в среднем понадобится бросков, чтобы в последовательности оказались
как орел, так и решка?
Напишите программу для моделирования ситуации, когда эксперимент со
ставляют 10 ООО попыток и надо вывести среднее число подбрасываний монеты
в каждой попытке.
8.9 . ЗАДАЧА: МОДЕЛИРОВАНИЕ ВЫБОРОВ
При помощи модуля random ииспользования условнойлогики можно построить
модель выборов, в которых участвуют два кандидата.
Допустим, два кандидата, А и В, претендуют на должность мэра города, в кото
ром три избирательных участка. Последние опросы показывают, что кандидат А
имеет следующие шансы на победу в каждом участке.
• Участок 1: 87%
• Участ ок 2: 65%
• Участок 3: 17%
Напишите программу, которая моделирует выборы 1О ООО раз и выводит про
цент выборов, когда победил кандидат А.
Для простоты считайте, что кандидат одержал верх на выборах, если он победил
как минимум на двух из трех участков.
8.1 О. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы познакомились с условными командами и условной логикой.
Вы научились сравнивать значения с такими операторами, как <, >, < =, >=, ! =
и ==, и узнали, как строить сложные условные выражения с использованием
операторов and, or и not.
Затем вы освоили управление логикой выполнения программы при помощи
команд i f. Вы узнали, как создавать ветви выполнения в программе команда
ми if... else и if ... elif ... else, а также научились управлять выполнением кода
внутри блоков i f командами break и continue.
8.1О. Итоги идополнительные ресурсы 185
Далее мы изучили синтаксис try ... exceptдля обработки ошибок, которые могут
происходить во время выполнения. Это важная конструкция, позволяющая
программам корректно обрабатывать неожиданные ситуации и не огорчать
пользователей сбоями.
Наконец, мы применили инструменты, о которых шла речь в этой главе, и вос
пользовались модулем random для построения простых моделей.
ИНТЕРАКТИВНЫЙ ТЕСТ
Кэтой главе прилагается бесплатный интерактивный тестдля проверки усво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes!pybasics-conditiona/-logic
Дополнительные ресурсы
Один умный вулканец однажды сказал: «Логика - лишь начало мудрости, но
далеко не конец» ( Спок. «Звездный путы-).
Дополнительную информацию об условной логике вы найдете на следующих
ресурсах:
• «Operators and Expressions in Python» (https.j/realpython.com/python-
opera t ors-e xp ressi ons /)
• «Conditional Statements in Python» (https.j/realpython .com/python-
co ndition al-s tate me nts/)
ГЛАВА 9
Кортежи, списки и словари
До настоящего момента вы работали с фундаментальными типами данных -
такими, как str, int и float. Многие реальные задачи проще решаются при
объединении простых типов данных в более сложные структуры.
Структура данных моделирует такие наборы данных, как списки чисел, строки
электронной таблицы или записи в базе данных. Чтобы написать простой и эф
фективный код, выберите структуру данных, которая лучше всего моделирует
данные для вашей программы.
Python поддерживает три встроенные структуры данных, которым и посвящена
эта глава: кортежи, списки и словари.
В этой rлаве вы:
• научитесь работать с кортежами, списками и словарями;
• узнаете, что такое неизменяемость и почему она важна;
• узнаете, когда используются разные структуры данных.
Итак, за дело!
9.1 . КОРТЕЖИ КАК НЕИЗМЕНЯЕМЫЕ
ПОСЛЕДОВАТЕЛЬНОСТИ
Пожалуй, простейшей составной структурой данных является последователь
ность элементов.
Последовательность представляет собой упорядоченный список значений.
Каждому элементу последовательности присваивается индекс - целое число,
обозначающее позицию элемента в последовательности. Как и в случае символов
в строках, индекс первого значения в 11ослсдоватсльности раuен 0.
9.1. Кортежи как неизменяемые последовательности
187
Например, буквы латинского алфавита образуют последовательность, первым
элементом которой является буква А, а последним - буква Z. Строки также
являются последовательностями. Строка "Python" состоит из 6 элементов, на
чиная с "Р" (индекс 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 и з и присваивает его пере
менной с именем my_first_tuple.
Чтобы убедиться в том, что my_first_tuple содержит кортеж, можно восполь
зоваться функцией type( ):
>>> type(my_first_tuple)
<class 'tuple'>
В отличие от строк, которые состоят из последовательности символов, кортежи
могут содержать значения любых типов, в том числе и разных. Кортеж (1, 2. 0,
" three") вполне допустим.
Также существует специальный кортеж, не содержащий никаких значений. Он
называется пустым кортежем, а для его создания следует ввести две круглые
скобки, между которыми нет ни одного символа:
>>> empty_tuple = ()
На первый взгляд пустой кортеж может показаться странным и бесполезным,
но на самом деле он имеет практический смысл.
Допустим, вам поручено предоставить кортеж со всеми целыми числами, ко
торые одновременно являются четными и нечетными. Таких целых чисел не
существует, но пустой кортеж позволит вам выполнить требование.
А как создать кортеж, содержащий ровно один элемент?
Попробуйте выполнить следующий фрагмент в IDLE:
»>х=(1)
»> type (x)
<class 'int'>
Если заключить значение в круглые скобки, но не включить ни одной запятой,
Python интерпретирует его не как кортеж, а как значение в круглых скобках.
Таким образом, ( 1) становится всего лишь экзотическим способом записи
целого числа 1.
Чтобы создать кортеж, содержащий единственное значение 1, необходимо по
ставить после 1 запятую:
>» х=(1,)
»> type(x)
<class 'tuple'>
9.1 . Кортежи как неизменяемые последовательности
189
Кортеж из одного элемента может показаться таким же странным, как и пустой.
Почему бы не отказаться от возни с кортежами, а просто использовать само
значение?
Это зависит от задачи.
Если вам понадобится создать кортеж всех четных простых чисел, то это будет
(2,), потому что 2 - единственное четное простое число. Значение 2 не подой
дет, потому что это не кортеж.
Напервый взгляд может показаться, что это проявлениеизлишнего педантизма,
но программирование часто требует изрядной доли такого качества. В конце
концов, никто не сравнится с компьютером по части педантизма.
Встроен ная фун кци я tu pl e()
Для создания кортежа из последовательности другого типа - например, стро
ки - можно воспользоваться встроенной функцией tuple( ):
> > > tuple( "P ython")
('Р', 'у', 't', 'h', 'о', 'n')
Функция tuple() принимает только один параметр, поэтому вам не удастся
просто перечислить нужные значения как отдельные аргументы. Если вы по
пытаетесь это сделать, Python выдаст ошибку ТуреЕ rror:
>>> tuple(l, 2, 3)
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
tuple(l, 2 , 3)
TypeError: tuple expected at most 1 arguments, got 3
Ошибка TypeError выдается и в том случае, если переданный tuple() аргумент
не может быть интерпретирован как список значений:
»> tuple(l)
Traceback (most recent call last):
File "cpyshell#l>", line 1, in cmodule>
tuple(l)
TypeError: 'int' object is not iteraЫe
Слова not iteraЫe (не итерируемый) в сообщении об ошибке означают, что
одно целое число не может использоваться для перебора, то есть целый тип
данных не содержит нескольких значений, которые можно перебирать одно
за ОДНИМ.
190 ГЛАВА 9 Кортежи, списки и словари
Единственный параметр tuple() не является обязательным. Опустив его, вы
получите пустой кортеж:
>» tuple()
()
Но программисты Python обычно предпочитают более короткую запись () для
создания пустых кортежей.
Сходство между кортежами и строками
У кортежей и строк много общего. Как кортежи, так и строки являются типами
последовательностей конечной длины, поддерживают индексацию и работу со
срезами, являются неизменяемыми, а их содержимое можно перебирать в цикле.
Главное отличиестрок и кортежей заключается втом, что элементами кортежа
могут быть любые значения, а строки содержат только символы.
Рассмотрим сходства и различия строк и кортежей более подробно.
У кортежей есть длина
И строки, и кортежи обладают длиной. Длина строки равна количеству со
держащихся в ней символов. Длина кортежа равна количеству содержащихся
в нем элементов.
Как и в случае со строками, для определения длины кортежа можно восполь
зоваться функцией len():
>>> numbers = (1, 2, З)
»> len(numbers)
3
Кортежи поддерживают индексацию и срезы
Вспомните, о чем говорилось в главе 4: для обращения к отдельным символам
строки можно воспользоваться индексом:
>>> name = "David"
»> name(l]
,а,
Индекс [1] после имени переменной приказывает Pythoп получить символ
с индексом 1 в строке "David". Т ак к ак н уме рац ия индексов начинается с О,
символом с индексом 1 является буква "а".
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)
»> value s[ 2:4]
(5, 7)
Синтаксис values[2:4] создает новый кортеж, содержащий все целые числа из
values,начиная с позиции 2идопозиции 4(невключая ее).
Правила, относящиеся к срезам строк, также распространяются на срезы корте
жей . Возможно, вам стоит вернуться к примерам работы со срезами из главы 4
и попр обовать пр име ни ть их к кортежам.
Кортежи неизменяемы
Кортежи, как и строки, являются неизменяемыми, то есть значение элемента
кортежа невозможно изменить после его создания.
- ПРИМЕЧАНИЕ
Хотя кортежи являются неизменяемыми, в некоторых ситуациях значения
в кортеже все же могут изменяться.
Эти странности и аномалии подробно рассматриваются в видеокурсе
«lmmutaЬility in Python» (https://realpython. com/courses/immutaЬility- python/)
на сайте Real Python.
При попытке изменить в кортеже значение с заданным индексом Python вы
дает ошибку TypeError:
192 ГЛАВА 9 Кортежи, списки и словари
>>> values[0] = 2
Traceback (most recent call last):
File "cpyshell#l>", line 1, in cmodule>
values[0] = 2
TypeError: 'tuple' object does not support item assignment
Кортежи итерируемы
Кортежи, как и строки, итерируемы, то есть их содержимое можно перебирать
в цикле:
>>> vowels =("а", "е", "i" , "о", "u ")
>>> for vowel in vowels:
print(vowel.upper())
А
Е
I
о
u
Цикл for в этом примере работает точно так же, как и циклы for, которые мы
использовали в главе 6 для перебора в числовых диапазонах range( ).
На первой итерации цикла из кортежа vowels извлекается значение "а". Оно
преобразуется в букву верхнего регистра строковым методом. upper( ), описан
ным в главе 4, а затем выводится вызовом print().
На следующем шаге цикл извлекает значение "е", преобразует его к верхнему
регистру и выводит. То же самое происходит со значениями "i" , "о" и "u".
Итак, вы научились создавать кортежи и выполнять с ними некоторые базовые
операции. Рассмотрим несколько стандартных сценариев использования кортежей.
Упаковка и распаковка кортежей
Существует третий, менее распространенный способ создания кортежей. Можно
ввести список значений, разделенных запятыми, без круглых скобок:
>>> coordinates = 4.21 , 9 .29
>>> type(coordinates)
cclass 'tuple'>
Все выглядит так, словно одной переменной coordinates присваиваются два
значения. В каком-то смысле так и есть, хотя в результате два значения упако
вываются в один кортеж. Чтобы убедиться втом, что coordinates действительно
является кортежем, можно воспользоваться функцией type().
9.1 . Кортежи как неизменяемые последовательности 193
Если значения можно упаковать в кортеж, то вполне логично, что кортеж также
можно распаковать:
>>> х, у = coordinates
»>х
4.21
»>у
9.29
Значения, содержащиеся в одном кортеже coordinates, распаковываются в две
переменные - х и у.
Объединение упаковки с распаковкой кортежа позволяет выполнить несколько
присваиваний в одной строке:
>>> name, age, occupation = "David", 34, "programmer"
»> name
'David'
»> age
34
»> occupation
' pro g rammer'
Этот фрагмент работает, потому что сначала в правой части команды присва
ивания значения "David", 34 и "programmer" упаковываются в кортеж. Затем
значения распаковываются в три переменные, name, age и occupation, в ука
занном порядке.
ПРИМЕЧАНИЕ
Хотя присваивание значений нескольким переменным водной строке может
уменьшить число строк в программе, такделать не рекомендуется.
Если значения присваиваются более чем двум или трем переменным, вам
будет труднее определить, какое значение связывается с той или иной пере
мен ной .
Учтите, что количество имен переменных в левой части выражения присваива
ния должно соответствовать количеству значений в кортеже из правой части.
В противном случае Python выдает ошибку ValueError:
>>>а,Ь,с,d=1,2,3
Traceback (most recent call last):
File "<pyshell#0>", line 1, in cmodule>
а,Ь,с,d=1,2,3
ValueError: not enough values to unpack (expected 4, got 3)
194 ГЛАВА 9 Кортежи, списки и словари
Сообщение об ошибке указывает, что кортеж в правой части содержит слишком
мало значений для распаковки по четырем переменным в левой части.
Pythonтакжевыдаетошибку ValueError, если количество значений в кортеже
пр ев ыша ет кол ичество име н п еременных :
>>>а,Ь,с=1,2,3,4
Traceback (most recent call last):
File "cpyshell#l>", line 1, in <module>
а,Ь,с=1,2,3,4
ValueError: too many values to unpack (expected 3)
Теперь сообщение об ошибке указывает, что кортеж содержит слишком много
значен ий д ля распаковк и по трем п ерем енным .
Проверка существования значений
Чтобы проверить, содержится ли значение в кортеже, используйте ключевое
слово in:
>>> vowels = ("а", "е", "i", "о", "u")
>>> "о" in vowels
Tru e
>>> "х" in vowels
False
Если значение в левой части in присутствует в кортеже из правой части, то
результатравен True. В противном случае результат равен False.
Возвращение нескольких значений из функции
Кортежи часто используются для возвращения нескольких значений из одной
ф ункц ии:
>>> def adder_subtractor(numl, num2):
...
return (numl + num2, numl - num2)
>>> adder_subtractor(3, 2)
(5, 1)
У функции adder_subtractor() два параметра - numl и num2. Она возвращает
кортеж, где первый элемент содержит сумму двух чисел, а второй содержит их
разность.
Строки и кортежи - всего лишь два примера встроенных типов последо
вательностей Python. И строки, и кортежи являются неизменяемыми, они
9.2 . Списки: изменяемые последовательности
195
итерируемы (поддерживают перебор), их можно индексировать и создавать
из них срезы.
В следующем разделе я расскажу о третьем типепоследовате.llьностей, у кото
рого есть существенное отличие от строк и кортежей: он является изменяемым.
Упражнения
1. Создайтелитерал кортежа с именем cardinal_numbers, содержащий
строки "first", "second" и "third" в указанном порядке.
2. Используя индексирование и print (), выведите строку с индексом 1 из
cardinal_numbers.
3. В одной строке кода распакуйте значения из cardinal_numbers в три но
вые строки с именами positionl, position2 иpositionЗ. Затем выведите
кажд ое значение в отд ельной ст ро ке .
4. Используя функцию tuple() и строковый литерал, создайте кортеж
с именем my_name, содержащий буквы вашего имени.
5. Проверьте, входит ли символ "х" в my_name, при помощи ключевого
слова in.
6. Используя синтаксис сегментов, создайте новый кортеж, содержащий
всебуквыmy_name, кроме первой.
9.2. СПИСКИ: ИЗМЕНЯЕМЫЕ
ПОСЛЕДОВАТЕЛЬНОСТИ
Структура данных list (список) - еще один тип последовательностей в Python.
Как строки и кортежи, списки состоят из элементов, индексируемых целыми
числами (начиная с О).
На первый взгляд списки своим внешним видом и поведением сильно напо
минают кортежи. Списки можно индексировать, создавать из них срезы, про
верять существование элементапри помощи in, атакже перебирать элементы
в цикле for.
Однако, в отличие от кортежей, списки являются изменяемыми; это означает,
что вы можете изменить значение с заданным индексом даже после того, как
список будет создан.
В этом разделе я расскажу, как создавать списки. Затем мы сравним списки
с кортежами .
196 ГЛАВА 9 Кортежи, списки и словари
Создание списков
Литерал списка очень похож на литерал кортежа, но вместо круглых скобок он
закл ючается в кв адратны е скобки [ ] :
>>> colors = ["red", "yellow", "green", "Ыuе"]
»> type(colors)
<class 'list' >
При проверке списка Python выводит его в виде литерала:
>>> colors
['red', 'yellow', 'green', 'Ыuе']
Значения в списках, как и значения в кортежах, не обязаны иметь один тип.
Литерал [ "one ", 2, з. 0] вполне допустим.
Кроме литералов, можно воспользоваться встроенной функцией list() для
создания нового объекта списка на основе любой другой последовательности.
Например, list() можно передать кортеж (1, 2 , З) для создания списка [1,
2, 3]:
»> list((l, 2, З))
[1, 2, З]
Список даже можно создать на основе строки:
>>> list("Python")
['Р', 'у', 't', 'h', 'о', 'n']
Каждая буква строки становится элементом списка.
Существует еще один способ создания списка на основе строки. Если имеется
строка с перечнем элементов, разделенных запятыми, воспользуйтесь строко
вым методом . split( ):
>>> groceries = "eggs, milk, cheese"
>» grocery_list = groceries.split(", ")
>» grocery_list
['eggs', 'milk', 'cheese']
Строковый аргумент, передаваемый . split (), называется разделителем. Из
меняя разделитель, можно разбивать строку на списки разными способами:
>>> # Разбиение по точке с запятой
>>> "a;b;c".split(";")
9.2 . Списки: изменяемые последовательности 197
['а', ·ь·, 'с']
>>> # Разбиение по пробелам
>>> "The quick brown fox".split(" ")
['The', 'quick', 'brown', 'fox']
>>> # Разбиение по нескольким символам
>>> "abbaabba". split("ba")
['аЬ', 'аЬ', '']
В последнем примере строка разбивается по вхождениям подстроки "Ьа", кото
рая встречается сначала в позиции с индексом 2, а затем с индексом 6. Так как
разделитель состоит из двух символов, элементами спискастановятся только
символыс индексами 0,1,4 и 5.
. split () всегда возвращает список, длина которого на 1 превышает количество
разделителей в строке. Разделитель "Ьа" встречается в "аЬЬааЬЬа" дважды,так
что список, возвращаемый split(), состоит из трех элементов.
Обратите внимание: последним элементом списка является пустая строка. Это
происходит из-за того, что за последним "Ьа" никакиедругие символы не следу
ют. Если разделитель вообще не входит в строку, . split () возвращает список,
единственным элементом которого является исходная строка:
>>> "abbaabba".split("c")
['аЬЬа аЬЬа']
И так, существуют три способа создания списков.
1. Литерал списка.
2. Встроенная функция list().
3. Строковый метод . split ().
Списки поддерживают те же операции, что и кортежи.
Основные операции списков
Операции индексирования и сегментации работают со списками так же, как
и с корт ежами.
К элементам списков можно обращаться по индексам:
>>> numbers =[1, 2, з, 4]
» > numbers[1]
2
198 ГЛАВА 9 Кортежи, списки и словари
Также можно создать новый список на основе существующего, используя син
таксис сегментации:
»> пum bers[ l:З]
[2, 3]
Для проверки существования элементов списков используется оператор iп:
>>> # Проверка существования элемента
>>> "ВоЬ" in numbers
False
Списки итерируемы; это означает, что их содержимое можно перебрать в цик
ле for:
>» # Выводит только четные числа в списке
>>> for number in numbers:
if number %2 == 0:
2
4
print(number)
Главное отличие списка от кортежа заключается в том, что элементы списка
могут изменяться, тогда как элементы кортежа изменяться не могут.
Изменение элементов списка
Список можно рассматривать как последовательность пронумерованных ячеек.
Каждая ячейка содержит значение, и каждая ячейка в любой момент времени
должна быть заполнена. Однако значение в заданной ячейке всегда можно за
мен ить но вым.
Возможность замены значений в списке другими значениями называется из
меняемостью. Списки являются изменяемыми. Элементы кортежей не могут
заменяться новыми значениями, поэтомукортежи называются неизменяемыми.
Чтобы заменить одно значение в списке другим, присвойте новое значение
в позиции, заданной индексом:
>>> colors = ["red", "yellow", "green", "Ыuе"]
>> > co lors [0] = "burg uпdy"
Значение с индексом 0 изменяется с "red" на "burgundy":
»> col ors
['burgundy', 'yellow', 'green', 'Ыuе']
9.2 . Списки: изменяемые последовательности
199
Вы можете изменить сразу несколько значений в списке, используя срезы:
>>> colors[l:З] = ["orange", "magenta"]
»> colors
['burgundy', 'orange', 'magenta', 'Ыuе']
colors[1:з] выбирает ячейки с индексами 1 и 2. Значениям в этих позициях
присваиваются строки "orange" и "magenta" соответственно.
Список, присваиваемый срезу, не обязан иметь такую же длину, как и срез. На
пример, можно присвоить список из трех элементов срезу из двух элементов:
>>> colors = ["red", "yellow", "green", "Ыuе"]
»> colors[l:З] = ["orange", "magenta", "aqua"]
»> colors
['red', 'orange', 'magenta', 'aqua', 'Ыuе' ]
Значения "orange" и "magenta" заменяют исходные значения "yellow" и "green"
в списке colors с индексами 1 и 2. Затем в позиции с индексом 4 создается
новая яч е й ка , котор ой присваиваетс я зн ачение "Ыuе" . Нако нец, в п оз иц ии
с индексом з присваивается строка "aqua".
Если длина списка, присвоенного срезу, меньше длины среза, то общая длина
исходного списка уменьшается:
>>> colors
['red', 'orange', 'magenta', 'aqua', 'Ыuе' ]
>>> colors[1:4] = ["yellow", "green"]
»> colo rs
['red', 'yellow', 'green', 'Ыuе' ]
Значения "yellow" и "green" заменяют значения "orange" и "magenta" в списке
colors с индексами 1 и 2. Затем значение "Ыuе" заполняет позицию с индек
сом 3, а индекс 4 полностью удаляется из colors. Этот пример показывает, как
изме нять списки путем и ндексации и срез ов. Кр ом е того, для изм енения с п и с к а
можно использовать методы списков.
Методы списков для добавления
и удаления элементов
Конечно же, вы можете добавлять и удалять элементы с использованием ин
дексации и срезов, но методы списков предлагают более естественный и удобо
читаемый механизм изменения.
Мы рассмотрим несколько методов списков для выполнения разных операций,
начиная со вставки одного значения в позицию с заданным индексом.
200 ГЛАВА 9 Кортежи, списки и словари
list.insert()
Метод list.insert () используется для вставки нового значения в список. Он
получает два параметра - индекс i изначение х - и вставляет значение х в по
зицию с индексом i:
>>> colors = ["red", "yellow", "green", "Ыuе"]
»> # Вставляет "orange" во вторую позицию
>>> colors.insert(l, "orange")
>>> colors
['red', 'orange', 'yellow', 'green', 'Ыuе']
В этом примере стоит обратить внимание на пару важных моментов.
Первый применим ко всем методам. Чтобы использовать методы, вы сначала
указываете имя списка, с которым хотите выполнить операцию, затем точку
и имя метода списка.
Таким образом, чтобы вызвать insert() для списка colors, используйте запись
colors. insert( ). Такой синтаксис уже знаком вам по строковым и числовым
методам.
Также обратите внимание на то, что при вставке значения "orange" в позицию
с индексом 1 значение "yellow" и все следующие значения сдвигаются вправо.
Если значение параметра индекса . insert () превышает наибольший индекс
списка, значение вставляется в конец списка:
>>> colors.insert(10, "violet")
»> colors
['red', 'orange', 'yellow', 'green', 'Ыuе',
'violet' ]
Здесь значение "violet" вставляется в позицию с индексом 5 несмотря на то,
что метод .insert () был вызван с индексом 10.
Также методу .insert () могут передаваться отрицательные индексы:
>>> colors.insert(-1, "indigo")
»> co lors
['red', 'orange', 'yellow', 'green', 'Ыuе', 'indigo', 'violet' ]
Фрагмент вставляет строку "indigo" в позицию с индексом -1 , что соответ
ствует последнему элементу списка. Значение "violet" сдвигается вправо на
од ну позиц ию.
Если вы можете вставить значение по заданному индексу, вполне логично, что
также существует возможность удаления элемента с заданным индексом.
9.2 . Списки: изменяемые последовательности 201
ВАЖ НО!
При вставке значения в список методом .iпsert() результат не нужно присва
ив ат ь исходном у списк у.
Например, следующий код фактически стирает содержимое списка colors:
>>> colors = colors.insert(-1, "indigo")
>>> print(colors)
None
Метод .iпsert() изменяет colors на месте. Это справедливо для всех методов
списков, которые не возвращают значения.
list.pop()
Метод list. рор() получает один параметр - индекс i - иудаляет из списказна
чение в позиции с заданным индексом. Удаляемое значение возвращается методом:
>>> color = colors.pop(З)
»> color
'gr ee n'
>» colors
['red', 'orange', 'yellow', 'Ыuе', 'indigo', 'violet']
Значение "greeп" с индексом З удаляется и присваивается переменной color.
При проверке переменной colors можно убедиться в том, что строка "green"
действите льно бы ла у да л ен а.
В отличие от. insert(), Python выдаетошибку IndexErrorпри передаче. рор()
аргумента, превышающего последний индекс:
>>> colors.pop(10)
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
c ol ors. pop( 10)
IndexError: рор index out of range
Отрицательные индексы также работают с . рор ():
>>> colors.pop(-1)
'violet'
»> colors
['red', 'orange', 'yellow', 'Ыuе ', 'indigo']
Если при вызове .рор() значение не передается, то метод удаляет из списка
последний элемент:
202 ГЛАВА 9 Кортежи, списки и словари
» > colors. рор()
' indigo'
>» colors
['red', 'orange', 'yellow', 'Ыuе']
Удаление последнего элемента вызовом . рор() без указания индекса обычно
считается более питоническим подходом.
list.append()
Метод list. append() используется для добавления нового элемента в конец
списка:
> > > c olor s.a ppe nd("i ndigo")
>>> colors
['red', 'orange', 'yellow', 'Ыuе', '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', 'Ыuе ', 'indigo', 'violet', 'ultraviolet']
Метод . extend() получает один параметр, который должен быть итерируемым
типом. Элементы этого объекта присоединяются в конец списка в том же по
рядке, в котором они указываются в аргументе, передаваемом . extend().
Как и .insert() и . append(), . extend() изменяет список «на месте».
9.2. Списки: изменяемые последовательности
203
Как правило, передаваемый . extend () аргумент содержит другой список, но
это также может быть кортеж. Например, приведенный выше пример можно
записать в следующем виде:
>>> colors.exteпd(("violet", "ultraviolet"))
Четыре метода списков, о которых мы рассказали в этом разделе, используются
чаще других. В следующей таблице приведено краткое описание этих методов.
МЕТОДСПИСКА
ОПИСАНИЕ
. insert(i, х)
Вставляет значение х в позицию с индексом i
. append(х)
Вставляет значение х в конец списка
. e xtend(iteraЬle) Вставляет все значения из iteraЬle в конец списка с сохра
нением порядка
.pop(i)
Удаляет и возвращает элемент с индексом i
Кроме методов списков Python также содержит несколько полезных встроенных
функций для работы со списками чисел.
Списки чисел
Одна из самых распространенных операций, используемых со списками чисел,-
суммирование всех значений. Для этой цели можно воспользоваться циклом for:
»> nums =[1, 2, 3, 4,
>» total =0
>» for number in nums:
total =total +
>» total
15
5]
numbe r
Сначала переменная total инициируется значением 0. Затем программа в ци
кле перебирает все числа в nums и прибавляет их к total. В итоге мы получаем
значение 15.
Хотя решение с циклом вполне прямолинейно, в Python существует другой,
куда более компактный вариант:
»> sum([l, 2, 3, 4, 5])
15
Встроенная функция sum() получает список в аргументе и возвращает сумму
всех значений в списке.
204 ГЛАВА 9 Кортежи, списки и словари
Если список, переданный sum( ), содержит значения, которые не являются
числовыми, выдается ошибка TypeError:
»> sum([l, 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([l,2,3,4,5])
1
»> max([l, 2, 3, 4, 5))
5
Обратите внимание: функции sum( ), min() и max() также работают с кортежами:
>>> sum((l, 2 , 3, 4, 5))
15
»> min((l, 2, 3, 4, 5))
1
»> max((l, 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, используя функцию p ri nt () и си н
таксис сегментов.
6. Создайте список breakfast из строки "eggs, fruit, orangejuice" при
помощи строкового метода. split( ).
7. Убедитесь в том, что breakfast содержит три элемента, при помощи
функции len ().
206 ГЛАВА 9 Кортежи, списки и словари
8. Создайте генератором списка новый список lengths, содержащий длины
всех строк из списка breakfast.
9.3 . ВЛОЖЕНИЕ, КОПИРОВАНИЕ И СОРТИРОВКА
КОРТЕЖЕЙ И СПИСКОВ
Итак, вы узнали, что собой представляют кортежи и списки, научились создавать
их и выполнять базовые операции. Рассмотрим еще три концепции:
1) вложение;
2) копирование;
3) сортировка.
Вложенные списки и кортежи
Списки и кортежи могут содержать значения произвольных типов. Это озна
чает, что списки и кортежи могут содержать списки и кортежи как значения.
Вложенный список или кортеж представляет собой список или кортеж, который
содержится как значение в другом списке или кортеже.
Например, следующий списоксодержитдва значения, каждое из которых само
по себе является списком:
>» two_Ьу_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[l]
[3, 4]
Так как two_by_two[l] возвращает список [З, 4], для обращения к элементу
вложенного списка можно воспользоваться записью с двумя индексами:
>>> two_by_two[1][0]
3
Сначала Python вычисляет two_by _two [1] и возвращает [3, 4]. Затем Python
вычисляет [З, 4] [0] и возвращает первый элемент з.
9.3. Вложение, копирование и сортировка кортежей и списков
207
Очень упрощенно список списков или кортеж кортежей можно представить
себе как таблицу со строками и столбцами.
Список two_by _two содержит две строки: [1, 2] и [З, 4]. Столбцы состоят из
соответствующих элементов каждой строки. Таким образом, первый столбец
содержит элементы 1 и з, а второй - элементы 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 добавляется новая строка
"Тigger". Но при выводе содержимого 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.
Если вы захотите создать копию списка списков, можно воспользоваться
записью ( : ], как было показано выше:
>>> matrixl = [[1, 2], [З, 4]]
>>> matrix2 = matrixl[:]
>>> matrix2[0] = [5, 6]
»> matrix2
[[5, 6], [З, 4]]
»> matrixl
[[1, 2], [З, 4]]
Посмотрим, что происходит при изменении первого элемента второго списка
в matrix2:
>>> matrix2[1][0] 1
»> matrix2
[[5, 6], [1, 4]]
»> matrixl
[[1, 2], [1, 4]]
Обратите внимание: второй список в matrixl тоже изменился!
Это происходит из-за того, что список на самом деле содержит не сами объ
екты, а ссылки на эти объекты в памяти. Срез [ :) возвращает новый список,
содержащий теже ссылки, что и исходный список. Нажаргоне программистов
этот способ копирования списка называется поверхностным копированием.
Чтобы скопировать список и все его элементы, необходимо создать глубокую
копию. Она является действительно независимой копией объекта. Для созда
ния глубокой копии списка можно воспользоваться функцией deepcopy() из
модуля Pythoп сору:
» > import сору
>>> matrixЗ = copy.deepcopy(matrixl)
>>> matrix3[1][0] = З
>» matrixЗ
9.3 . Вложение, копирование и сортировка кортежей и списков 209
[[5, 6] , [З, 4]]
»> matrixl
[[5, б], [1, 4]]
matrixЗ создается как глубокая копия matrixl. При изменении элемента matrixЗ
соответствующий элемент matrixl не изменяется.
ПРИМЕЧАНИЕ
За дополнительной информацией о поверхностном и глубоком копирова
нии обратитесь к статье «Shallow vs Deep Copyiпg ofPythoп Objects» (https://
realpythoп.com/copyiпg-pythoп-objects/) на сайте Real Pythoп.
Сортировка списков
У списков есть метод . sort(), который сортирует элементы по возрастанию.
По умолчанию список сортируется в алфавитном или числовом порядке в за
висимости от типа элементов в списке:
>>> # Списки строк сортируются по алфавиту
>>> colors = ["red", "yellow", "green", "Ыuе"]
>>> colors.sort()
»> colors
['Ыuе', 'green', 'red', 'yellow']
>>> # Списки чисел сортируются по порядку
>>> numbers = [1, 10, 5, З]
>>> numbers.sort()
>>> numbers
[1, 3, 5, 10]
Обратите внимание: . sort () сортирует список ~на месте», так что присваивать
результат вызова переменной необязательно .
. sort() также поддерживает необязательный параметр key, который может ис
пользоваться для настройки сортировки списка. Параметр key получает функ
цию, а список сортируется на основании возвращаемого значения этой функции.
Например, чтобы отсортировать список строк по длине, можно передать в key
функцию le n :
>>> colors = ["red", "yellow", "green", "Ыuе"]
>>> colors.sort(key=len)
»> colors
['red', 'Ыuе ', 'green', 'yellow']
21 О ГЛАВА 9 Кортежи, списки и словари
Вызывать функцию, передаваемую в параметре key, не нужно. Просто укажите
имя функции без круглых скобок. Например, в предыдущем примере key пере
дается имя len, а не len().
Функция, передаваемая key, должна получать только один аргумент.
Также key можно передавать функции, определяемые пользователем. В следу
ющем примере функция get_second_elemeпt() используется для сортировки
списка корт ежей по их в то ры м эле ментам :
>>> def get_second_element(item):
return item[l)
»> 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), а вторым - кортеж (З, 4).
2. Напишите циклfor, который перебирает dataи выводит сумму каждого
вложенного кортежа. Результат должен выглядеть примерно так:
Row1sum:3
Row2sum:7
3. Создайте список [4, З, 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",
"ex uberant", "glistening"]
4. Предлоги: ["against", "after", "into", "beneath", "upon", "for", "in",
"like", "over", "within"]
5. Наречия ["curiously", "extravagantly", "tantalizingly", "furiously",
"sensuously"]
Случайным образом выберите следующее количество элементов из каждого
списка:
• три су ществительны х;
• три глагола;
• три прилагательных;
• два предлога;
• одно наречие.
Для этого можно воспользоваться функцией choice() из модуля random. Она
получает список и возвращает случайно выбранный элемент списка.
Пример использования random. choice() для получения случайного элемента
из списка [..а .., "Ь•1, ••с'']:
import random
random_element = random.choice(["a", "Ь", "с"])
Используя случайно выбранные слова, сгенерируйте и выведите стихотво
рение, структура которого аналогична стилю Клиффорда Пиковера (https:j/
en.wikipedia.org/wiki/Clifford_A ._ Pickover):
9.6. Храните отношения в словарях
21З
{A/An} {прилl} {сущl}
{A/An} {прилl} {сущl} {глl} {предлl} the {прил2} {сущ2}
{наре чl}, th e {су щl} {гл2}
the {сущ2} {глЗ} {предл2} а {прилЗ} {сущЗ}
Пример стихотворения, которое могла бы сгенерировать ваша программа:
А furry horse
А furry horse curdles within the fragrant mango
extravagantly, the horse slurps
the mango meows beneath а balding extrovert
При каждом запуске программыдолжно генерироваться новое стихотворение.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
9.6. ХРАНИТЕ ОТНОШЕНИЯ В СЛОВАРЯХ
Одной из самых полезных структур данных Python является словарь.
В этом разделе вы узнаете,что такое словарь, чемсловари отличаютсяот списков
и кортежей и как создавать и использовать словари в вашем коде.
Что такое словарь?
Обычно словарем называется текст, содержащий определения слов. Каждая
запись в словаре состоит из двух частей: определяемого слова и его определения.
Словари Python, как и списки и кортежи, предназначены для хранения коллек
ций объектов. Но вместо того, чтобы хранить объекты в последовательности,
словари хранят информацию в виде пар данных «ключ - значение». Другими
словами, каждый объект в словаре состоит из двух частей: ключа и значения.
Ключ в паре «ключ - значение» представляет собой уникальное имя, по кото
рому можно идентифицировать значение. Если провести параллель с обычным
словарем, ключ можно сравнить с определяемым словом, а значение - с его
определением.
Например, словарь можно использовать для хранения названий штатов и их
столиц.
214 ГЛАВА 9 Кортежи, списки и словари
КЛЮЧ
ЗНАЧЕНИЕ
" California"
"Sacramento"
"NewYork"
"Albany"
"T e xas"
"Austin"
В этой таблице ключами словаря являются названия штатов, а значениями -
названия их столиц.
Обычный словарь отличается от словаря Python тем, что в Python отношение
между ключом и его значением полностью произвольно. С любым ключом
можно связать любое значение.
Например, следующая таблица пар «ключ - значение» вполне допустима:
ключ
1'red11
17
ЗНАЧЕНИЕ
"Sunday"
12:45pm
True
Ключи в этой таблице логически не связаны со значениями . Единствен
ная связь заключается в том, что с каждым ключом связано то или иное
значение.
В этом смысле словарь Python больше похож на отображение, чем на обычный
словарь. Термином «отображение» в математике называется отношение (соот
ветствие) между двумя множествами.
Представление словарей как отображений особенно полезно. С этой точки
зрения, обычный словарь представляет собой отображение, связывающее слова
с их определениями.
Словарь Python представляет собой структуру данных, которая связывает мно
жество ключей с множеством значений. С каждым ключом связывается одно
значение, которое определяет отношения между двумя множествами.
Создание СflОварей
Следующий фрагмент создает литерал словаря, содержащий названия штатов
и их столиц:
»> capitals = {
"California": "Sacramento",
"New York" : "Albany",
"Texas": "Austin",
}
9.6. Храните отношения в словарях
215
Обратите внимание: каждый ключ отделяется от своего значения двоеточием,
пары «ключ - значение» разделяются запятыми, а весь словарь заключается
в фигурные скобки.
Словарь можно также создать из последовательности кортежей при помощи
встроенной функции dict( ):
>>> key_value_pairs = (
("California", "Sacramento"),
("New York", "Albany"),
( "Te x as ", "Austin") ,
>>> capitals = dict(key_value_pairs)
При проверке переменной, содержащей словарь, ее содержимое выводится
в форме литерала словаря независимо от того, как она была создана:
»> capitals
{'California': 'Sacramento', 'New York': 'Albany', 'Texas': 'Austin'}
ПРИМЕЧАНИЕ
Если вы воспроизводите наши примеры в версии Python до 3.6 , вы заметите,
что порядок вывода словарей в интерактивном окне отличается от приве
денного в примерах.
До выхода Pythoп 3.6 порядок пар «ключ - значение» в словаре Pythoп был
случайным. В более поздних версиях этот порядокгарантированно совпадает
с порядком их вставки.
Пустой словарь создается в форме литерала или вызовом dict( ):
»> {}
{}
»> dict()
{}
Итак, словарь создан. Посмотрим, как обратиться к хранящимся в нем значе
ниям.
216 ГЛАВА 9 Кортежи, списки и словари
Обращение к значениям словаря
Чтобы обратиться к значению в словаре, укажите ключ в квадратных скоб
ках ( []) после словаря или имени переменной, который был присвоен словарь:
>>> capitals["Texas"]
'Austin '
Запись с квадратными скобами, используемая для обращения к значению в сло
варе, похожа на ту, которая использовалась для получения значений из строк,
списков и кортежей. Тем не менее словарь - это структура данных, принципиаль
но отличающаяся от структуры типа последовательность (списка или кортежа).
Чтобы увидеть различия, сделаем шаг назад и заметим, что словарь capitals
можно было бы определить в виде списка:
>>> capitals_list = ["Sacramento", "Albany", "Austin"]
Индексная запись позволяет получить названия столиц всех трех штатов из
словаря capi t al s:
>>> capitals_list[0] # Столица Калифорнии
'Sacramento'
>>> capitals_list[2] # Столица Техаса
'Aust in'
Важная особенность словарей заключается в том, что они могут наделять кон
текстом содержащиеся в них значения. Смысл выражения capitals[ "Texas"]
более понятен, чем смысл capitals_list[2], и вам не придется запоминать
порядок следования значений в длинном списке или кортеже.
Концепция упорядочения составляет главное различие между обращением
к э лемент ам и з последовательности и элеме нтам и з сл о ва р я.
Для обращения к значениям в последовательности используются индексы -
целые числа, отражающие порядок элементов в последовательности.
Обращение к элементам словаря производится по ключу. Ключи не определяют
порядок элементов словаря. Они только предоставляют условную метку, которая
может использоваться для обращения к значению.
Добавление и удаление значений в словарях
Словари, как и списки, являются изменяемыми структурами данных. Это
означает, что элементы можно добавлять и удалять из словаря.
9.6 . Храните отношения в словарях
217
Добавим столицу Колорадо в словарь capitals:
>>> capitals("Colorado"] = "Denver"
Привводе "Colorado" в качестве ключа мы применим квадратные скобки, как
при получении значения. Затем оператор присваивания =мы используем для
присваивания значения "Denver" по новому ключу.
При проверке capitals мы видим, что всловаре появился новыйключ "Colorado"
с о зна чением "De nve r":
»> 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#l>", line 1, in cmodule>
ca pita ls[ "Ar iz ona "]
KeyError: 'Arizona'
KeyError - самая распространенная ошибка при работе со словарями. Каждый
раз, когда вы ее встречаете, это означает, что программа попыталась обратиться
к значению по несуществующему ключу.
218 ГЛАВА 9 Кортежи, списки и словари
Чтобы проверить, существует ли ключ в словаре, воспользуйтесь ключевым
словом in:
>>> "Arizona" in capitals
False
>>> "California" in capitals
T rue
Ключевое слово 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:
pr int( ke y)
California
New York
Colorado
Таким образом, если вы хотите перебрать словарь capitals и вывести "The
capital ofХ is У", где Х - название штата, а У - его столица, это можно сде
лать так:
>>> 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
Впрочем, существует и более компактная запись, использующая метод словаря
. i tems ();она возвращает объект, похожий на список и содержащий кортежи пар
«ключ - значение». Например, capitals. i tems () возвращает список кортежей
штатов и их столиц:
>>> capitals.items()
dict_items([('California', 'Sacramento'), ( 'New York', 'Albany'),
('Colorado', 'Denver')])
Объект, возвращаемый . i tems (), в действительности списком не является.
Он относится к специальному типу dict_items:
>>> type(capitals.items())
<class 'dict_items'>
Не беспокойтесь о том, что собой представляет тип dict_items. Обычно вам
не придется работать с ним напрямую. Важно знать, что метод . i tems() может
использоваться для перебора ключей словаря одновременно со значениями.
Перепишем предыдущий цикл с .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,с которым мы работали в этом разделе, каждый ключ был
строкой. Тем не менее ю1ючи словаря вовсе не обязаны относиться к одному типу.
Например, можно добавить в capitals ключ, который является целым числом:
>>> capitals[50] = "Honolulu"
»> capitals
{'California' : 'Sacramento', 'New York' : 'Albany',
'Colorado': 'Denver', 50: 'Honolulu'}
Существует только одно ограничение на то, какие значения являются валид
ными ключами словаря. Допустимы только неизменяемые типы. В частности,
это означает, что список не может быть ключом словаря.
220 ГЛАВА 9 Кортежи, списки и словари
Подумайте: что должно произойти, если список используется в качестве ключа
словаря, а потом в коде этот список изменится? Должен ли новый список быть
связан с тем же значением, что и старый список в словаре? Или значение, свя
занное со старым ключом, должно быть полностью удалено из словаря? Python
н е ст р о и т п редпол ожения, он выдает исключение:
>>> capitals[[l, 2, З]] = " Bad"
Traceback (most recent call last):
File "cstdin>", line 1, in cmodule>
TypeError: unhashaЫe type: 'list'
Может показаться несправедливым, что одни типы могут быть ключами,
а другие - нет, но в языке программирования очень важно четко определенное
поведение. Программа не должна угадывать, что имел в виду программист!
Для справки ниже мы перечислили все типы данных, упоминавшиеся ранее,
которые являются валидными для ключей словарей.
ВАllИДНЬIЕ ТИПЫ кnlОЧЕЙ СЛОВАРЕЙ
Целые числа
Числа с плавающей точкой
Строки
Логические
Кортежи
В отличие от ключей, значения словарей могут относиться к любому типу
Python, в том числе быть другими словарями.
Вложенные словари
По ана логии с о спи скам и вн утри други х списков и ко рте жам и внутри других
кортежей допустимы и вложенные словари. Изменим словарь capitals, чтобы
продемонстрировать эту возможность:
»> states = {
"California": {
},
"capital": "Sacramento",
"f lower": "California Рорру"
"New York": {
"capital": "Albany",
"flower": "Rose"
},
"Texas": {
},
"capital": "Austin",
"flower": "Bluebonnet"
9.6. Храните отношения в словарях
221
Здесь мы не связывали названия штатов со столицами, мы создали словарь,
который связывает название штата со словарем, содержащим название столицы
и цветок - символ штата. Значением для каждого ключа является словарь:
>>> 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 Ьу 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',
'Co lora do': 'De nver ',
'Connecticut': 'Hartford',
'Del a war e': 'Do ver ',
'Florida': 'Tallahassee',
'Georgia': 'Atlanta',
Затем выберите из словаря случайное название штата и присвойте название
штата и его столицы двум переменным. Первой строкой кода своей программы
импортируйте модуль random.
Затем выведите название штата и предложите пользователю указать столицу.
Если пользователь задал неправильный ответ, повторяйте вопрос, пока поль
зователь не ответит правильно или не введет команду "exit" для завершения.
Если пользователь ответил правильно, выведите сообщение "Correct" и за
вершите программу. Но если пользователь завершает программу без указания
правильного ответа, выведите правильный ответ и слово "Goodbye" .
ПРИМЕЧАНИЕ
Проследите затем,чтобы пользователь не пострадал из-за регистра символов.
Другими словами, ответ «Denver» считается идентичным «deпver». Сделайте
то же самоедля команды выхода - «EXIT» и «Exit» должны работать точно так
же,как и«exit». -
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
9.9. Задача: коты в шляпах
223
9.8 . КАК ВЫБРАТЬ СТРУКТУРУ ДАННЫХ
Итак, мы рассказали вам о трех структурах данных, реализованных в Python:
списках, кортежах и словарях.
Возникает вопрос: как понять, когда использовать ту или иную структуру
данных? Отличный вопрос, который возникает у многих новичков в Python.
Тип структуры данных зависит от решаемой задачи, и не существует никаких
однозначных правил, которые регламентировали бы выбор. Всегда необходимо
хорошенько обдумать задачу и сознательно выбрать структуру, которая лучше
всего подойдет для конкретного случая.
К счастью, все-таки есть некоторые рекомендации, которые помогут вам:
• Используйте список, если выполняются следующие условия:
• у данных имеется естественный порядок;
• данные будут обновляться или изменяться во время выполнения;
• структура данных будет использоваться в основном для перебора.
• Используйте кортеж, есливыполняются следующие условия:
• у данных имеется естественный порядок;
• данные не будут обновляться или изменяться во время выполнения;
• структура данных будет использоваться в основном для перебора.
• Используйте словарь, если выполняются следующие условия:
• данные не упорядочены или их порядок роли не играет;
• данные будут обновляться или изменяться во время выполнения;
• структура данных будет использоваться в основном для выборки
значений.
9.9 . ЗАДАЧА: КОТЫ В ШЛЯПАХ
У вас сто котов. Однажды вы решаете рассадить всех своих котов в большой
круг. Изначально ни один из них не носит шляпы. Вы сто раз обходите круг,
всегда начиная с первого кота (кот No 1). Каждый раз, когда вы останавливае
тесь рядом с котом, вы проверяете, есть ли на нем шляпа. Если шляпы нет, то
вы надеваете шляпу на кота, а если есть - снимаетеее.
1. На первом круге вы останавливаетесь у каждогокота и надеваете на него
шляпу.
224 ГЛАВА 9 Кортежи, списки и словари
2. На втором круге вы останавливаетесь у каждого второго кота (No 2, No 4,
No6,No8ит.д.).
3. На третьем круге вы останавливаетесь у каждого третьего кота (No 3,
No6,No9,No12ит.д.).
4. Процесс продолжается до тех пор, пока вы не обойдете круг сто раз. При
последнем обходе вы останавливаетесь только у кота No 100.
Напишите программу, которая выводит, на каких котах будут шляпы после
всех обходов.
ПРИМЕЧАНИЕ
Эта задача не очень простая, но и не настолько сложная, как может показаться
на первый взгляд. Подобная задача часто встречается на собеседованиях при
приеме на работу, так как она показывает вашу сообразительность.
Сохраняйте спокойствие. Начните с диаграммы и псевдокода. Найдите за
кономерность, а потом переходите к программированию!
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
9.10. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе я рассказал о трех структурах данных: списках, кортежах и словарях.
Списки являются изменяемыми последовательностями объектов. Для взаимо
действия со списками используются различные методы, включая . аррепd ()
и . exteпd (). Сортировка списков осуществляется методом . sort (). Для об
ращения к отдельным элементам списков используется запись, сходная с ана
логичной записью строк. К спискам можно применять срезы.
Кортежи, как и списки, являются последовательностями объектов. Главное
отл ичие списков о т кор теже й з аключается в то м, чт о кор теж и неи зменя емы.
Созданный кортеж модифицировать невозможно. К элементам кортежей, как
и к элементам списков, можно обращаться по индексам и применять срезы.
Словари хранят данные в виде пар «ключ - значение~.>. Они не являются
последо вательностями, а з на чи т , к эл емен там словаря н е л ь з я о братиться по
порядковому номеру. Для обращения к элементу указывают ключ. Словари
прекрасно подходят для хранения отношений и для быстрого доступа к данным.
Словари, как и списки, являются изменяемыми.
9.1 О. Итоги и дополнительные ресурсы
225
Списки, кортежи и словари являются итерируемыми, то есть их элементы
можно перебирать в цикле. Мы рассмотрели примеры циклического перебора
всех трех структур в циклах for.
ИНТЕРАКТИВНЫЙ ТЕСТ
Кэтой главе прилагается бесплатный интерактивный тестдля проверкиусво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/ pybasics-tuples-lists-dicts
Дополнительные ресурсы
Дополнительную информацию о списках, кортежах и словарях вы найдете на
ресурсах:
• «Lists and Tuples in Python» (https:j/realpython.com/python-lists-tuples/)
• «Dictionaries in Python» (https:j/realpython.com/python-dicts/)
ГЛАВА 10
Объектно-ориентированное
программирование (ООП)
Объ ект но- ори ент иро ван ное п рог ра мми ро ван ие (ОО П) представляет собой
мет одол огию структу рирования пр ог рам мы з а с че т упаковки вза имосвя занных
свойств и поведения в отдельные объекты.
Представьте программу как своего рода сборочную линию: на каждом этапе
сборки отдельный узел системы (объект) обрабатывает материал, а в конечном
итоге из сырья получается конечный продукт.
Объект содержит данные (аналог сырья или предварительно обработанных
материалов на каждом этапе сборочной линии) и задает поведение (действие,
выполняемое каждым компонентом сборочной линии).
В этой главе вы научитесь:
• создавать класс - этоаналогчертежадлясозданияобъектов;
• использовать классы для создания новых объектов;
• мо делиров ать сис темы с наслед ованием кл ас со в.
Итак, за дело!
10.1 . ОПРЕДЕЛЕНИЕ КЛАССА
Примитивные структуры данных - такие, как числа, строки, списки, - созда
вались для представления простой информации: стоимости килограмма яблок,
названия стихотворения, ваши любимые цвета... А если надо представить что-то
более сложное?
Допустим, вы хотите создать базу данных сотрудников в организации. Требуется
хранить о каждом из них основную информацию: имя, возраст, должность и год
поступления на работу.
1 0.1 . Определе ние кл асса 2 27
Одно из возможных решений - представить информацию о каждом сотруднике
в виде списка:
kirk = ["James Kirk", 34, "Captain", 2265)
spock ["Spock", 35, "Science Officer", 2254)
mccoy = ["Leonard МсСоу", "Chief Medical Officer", 2266)
У такого подхода масса недостатков.
Во-первых, усложняется сопровождение файлов с большим количеством кода.
Если вы обратитесь к kirk[0] спустя какое-то время после объявления списка
kirk, будете ли вы помнить, что элемент с индексом О содержитимя работника?
Во-вторых, возможно возникновение ошибки, если для разных работников
списки содержат разное количество элементов. В спискеmccoy из приведенно
го выше примера возраст не указан, поэтому mccoy[l] вместо возраста вернет
строку с должностью "ChiefMedicalOfficer".
Если вы хотите сделать такой код более управляемым и удобным в сопрово
ждении, используйте классы - это отличный выход.
Классы и экземпляры
Классы применяются для создания пользовательских структур данных. Клас
сы определяют функции, называемые методами; в них задаются поведение
и действия, которые объект, созданный на основе класса, сможет выполнять
со своими данными.
В этой главе мы создадим класс Dog для хранения информации о свойствах
и пове дении соб ак .
Класс представляет собой «чертеж», то есть прототип для определения объектов.
Он не содержит никаких данных. Класс Dog указывает, что кличка и возраст
необходимы для определения собаки, но он не содержит клички и возраста
ник акой кон кретной со ба к и.
Если класс можно сравнить с чертежом, то экземпляр представляет собой объ
ект, построенный на основе класса; он содержит реальные данные. Экземпляр
класса Dog уже не является чертежом. Он представляет конкретную собаку -
например, это может быть Майлз четырех лет от роду.
Иначе говоря, класс можно сравнить с бланком или анкетой, а экземпляр - это
анкета, заполненная информацией. Подобно тому как один вид анкеты разные
люди могут заполнить данными, уникальными для каждого из них, на основе
одного класса можно создать множество экземпляров.
228 ГЛАВА 10 Объектно-ориентированное программирование (ООП)
Как определить класс
Все определения классов начинаются с ключевого слова class, за которым
следует имя класса и двоеточие. Любой код с отступом, расположенный под
определением класса, считается частью тела класса.
Пример класса Dog:
class Dog:
pas s
Тело класса 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):
self.name
= name
self.age = age
1 0.1 . Определение клас са 22 9
Обратите внимание: сигнатура метода . _ init_() имеет отступ из четырех
пробелов, а тело метода - из восьми пробелов. Эти отступы очень важны. Они
сообщают Python, что метод ._ init_() принадлежит классу Dog.
В теле . _init_() содержатся две команды, в которых используется пере
менная self.
1. self. name =name создает атрибут с именем name иприсваиваетемузна
че ние параметра n am e .
2. self. age =age создает атрибут с именем age и присваиваетему значение
параметра a ge .
Атрибуты, созданные в ._ init_() , называются атрибутами экземпляров.
Значение атрибута экземпляра относится к конкретному экземпляру класса.
ВсеобъектыDog обладаютатрибутами name иage, но значения nameи age зависят
от конкретного экзе мпляра D o g .
С другой стороны, атрибуты класса имеют одно и то же значение для всех
экземпляров класса. Атрибут класса можно определить, присваивая значение
переменной name за пределами ._ init_().
Например, следующий класс Dog содержит атрибут класса с именем species
и значением "Canis familiaris":
class Dog:
# Атрибут класса
species = "Canis familiaris"
def ~init~(self, name, age):
self.name = name
self.age = age
Атрибуты класса определяются непосредственно под первой строкой имени
класса с отступом в четыре пробела. Им всегда должно присваиваться исходное
значение. При создании экземпляра класса атрибуты класса формируются ав
томатически, и им присваиваются исходные значения. Используйте атрибуты
класса для определения свойств, которые должны иметь одно и то же значение
для в се х экземпляров к лас са. Исполь зуйте атриб уты экземпля ров для с во йс тв ,
которые отличаются в разных экземплярах.
Итак, класс Dog готов. Переходим к созданию экземпляров!
230 ГЛАВА 1О Объектно-ориентированное программирование (ООП)
10.2. СОЗДАНИЕ ЭКЗЕМПЛЯРОВ
(ИНСТАНЦИРОВАНИЕ)
Откройте интерактивное окно в IDLE и введите следующий фрагмент:
»> class Dog:
pass
Тем самым вы создаете новый класс Dog, не содержащий ни атрибутов, ни
методов.
Создание нового объекта класса называется инстанцированием.
Чтобы создать новый объект Dog, введите имя класса, за которым следует пара
круглых скобок:
»> Dog()
<~main~.Dog object at 0xl06702d30>
Теперь новый объект Dog существует по адресу Ox106702d30. Загадочная стро
ка из букв и цифр - адрес памяти, указывающий, где объект Dog хранится
в памяти компьютера. Учтите, что адрес, который вы увидите в своей системе,
будет другим.
Теперь создайте второй объект Dog:
»> Dog()
<~main~. Dog obje c t a t 0х00 04с сс90 >
Новый экземпляр Dog располагается по другомуадресу памяти. Дело втом, что
это совершенно новый экземпляр, который никак не связан с первым объектом
Dog, созданным ранее.
Можно посмотреть на это иначе. Введите следующий фрагмент:
»> а =Dog()
»> Ь=Dog()
>>>а==Ь
False
В этом коде создаются два объекта Dog, которые присваиваются переменным а
и Ь. При сравнении а и Ь оператором== будет получен результат False. Хотя
как а, так и Ь являются экземплярами класса 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#б>", 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 ГЛАВА 1 О Объектно-ориентированное программирование (ООП)
'M iles'
>>> miles.age
4
Аналогичным образом можно обращаться к атрибутам класса:
>>> buddy.species
'Canis familiaris'
Одно из самых больших преимуществ использования классов для организа
ции данных заключается в том, что экземпляры гарантированно содержат все
заданные атрибуты. Все экземпляры Dog содержат атрибуты . species, . na m e
и .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 и запустите программу кла
вишей FS. Затем откройте интерактивное окно и введите следующие команды,
чтобы посмотреть на методы экземпляров в действии:
>>> 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 ГЛАВА 1О Объектно-ориентированное программирование (ООП)
А теперь посмотрим, что происходит при выводе объекта miles функцией
pri nt( ):
»> print(miles)
<~main~.Dog object at 0x00aeff70>
При вызове print(miles) вы получаете загадочное сообщение, из которого мож
но узнать, что объект Dog находится в памяти по адресу Ox00aeft70. Пользы от
такого сообщения немного. Чтобы изменить выводимую информацию, следует
определить специальный метод экземпляра с именем ._ str_( ).
В окне редактора измените имя метода. description() класса Dog на ._ str_( ):
class Dog:
# Оста льн ые части кла сса D og оста ются без изме нений
#Замените .description() на ~str~()
def ~str~(self):
return f"{self.name} is {self.age} years old"
Сохраните файл и нажмите FS. Теперь при вызове print(miles) вы получите
намного более понятный вывод:
>>> miles - Dog("Miles", 4)
»> print(miles)
'Miles is 4 years old'
Такие методы, как . _init_() и ._str_(),называются duпdеr-методами
(dunder - сокращение от douЫe underscores, то есть двойное подчеркива
ние). Существует много dunder-мeтoдoв, которые могут использоваться для
настройки поведения классов в Python. И хотя эта тема слишком сложна для
книги начального уровня, понимание dunder-мeтoдoв очень важно для освоения
ООП на языке 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 ООО и красную с пробегом 30 ООО. Выведите данные на каждую
машину. Результат должен выглядеть так:
The Ыuе car has 20,000 miles.
The red car has 30,000 miles.
3. Измените класс Carи добавьте в него метод экземпляра .drive(),который
получает числовой аргумент и прибавляет его к атрибуту . mileage. Убе
дитесь в том, что ваше решение работает; создайте экземпляр с нулевым
пробегом, вызовите .drive(100) и выведите атрибут .mileage, чтобы
убедиться в том, что он принял значение 100.
10.3 . НАСЛЕДОВАНИЕ ОТ ДРУГИХ КЛАССОВ
Наследованием называется процесс, в котором один класс получает атрибуты
и методы другого класса. Создаваемый класс называется производным, или
дочерним, классом, а классы, от которых наследуют производные классы, на
зыва ютс я родит ельскими.
Дочерние классы могут переопределять или расширять атрибуты и методы
родительских классов. Другими словами, они наследуют все атрибуты и мето
ды родительских классов, но также могут задавать свои уникальные атрибуты
и методы.
Хотя аналогия не идеальна, но наследование объектов можно рассматривать
как некое подобие наследования генетического. Возможно, вы унаследовали
цвет волос от своей матери. Это атрибут, с которым вы родились. Допустим,
вы решили окрасить волосы в фиолетовый цвет. Вы успешно переопределили
атрибут цвета волос, унаследованный от матери (предполагается, что ее волосы
не были фиолетовыми).
Также вы в каком-то смысле наследуете от родителей язык общения. Если ваши
родители говорят на английском, то и вы будете говорить на английском. Те
перь представьте, что вы решили выучить второй язык - допустим, немецкий.
236 ГЛАВА 1О Объектно-ориентированное программирование (ООП)
В таком случае набор атрибутов расширяется, потому что вы добавили атрибут,
которого не было у ваших родителей.
Пример с собачьей площадкой
Вообразите, что вы находитесь на собачьей площадке. На ней много собак раз
ных пород, и все они заняты своими собачьими делами.
Вы хотите смоделировать площадку при помощи классов Python. Класс Dog,
написанный в предыдущем разделе, позволяет различать собак по кличке и воз
расту, но не по породе.
Мы можем изменитькласс Dog вокне редактора идобавить в него атрибут .breed:
class Dog:
species = "Canis familiaris"
def ~init~(self, name, age, breed):
self.name
= name
self.age = age
self.breed = breed
Методы экземпляров, определенные ранее, здесь не указаны, потому что они
для данного обсуждения не важны.
Нажмите FS, чтобы сохранить файл. Теперь вы можете смоделировать собачью
площадку, задав параметры нескольких разных собак в интерактивном окне:
>>> miles = Dog("Miles", 4, "Jack Russell Terrier")
>>> buddy = Dog("Buddy", 9, "Dachshund")
»> jack = Dog("Jack", З, "Bulldog")
»> jim = Dog("Jim", 5, "Bulldog")
Разные породы собак имеют разные отличительные особенности. Например,
у бульдогов лай низкий, а у такс - высокий и пронзительный.
Если использовать только класс Dog, можно передавать методу . speak строку
с транскрипциейлая каждый раз, когдаэтотметод вызывается дляэкземпляра Dog:
>>> buddy.speak("Vap")
'Buddy says Уар'
>>> jim.speak("Woof")
'Jim says Woof'
>>> jack.speak("Woof")
'Jack says Woof'
10.3. Наследование отдругих классов 237
Впрочем, решение с передачей строки при каждом вызове . speak() однообраз
но и неудобно. Более того, строка, представляющая звук, издаваемый каждым
экземпляром Dog, должна определяться его атрибутом . breed, а нам приходится
вручную передавать правильную строку . speak() при каждом вызове.
Чтобы с классомDog было удобнее работать, мы создадимотдельныйдочерний
класс для каждой породы собак. Это позволит расширить функциональность,
наследуемую каждым производным классом, включая определение аргумента
поумолчаниюдля .sреаk().
Родительские классы и дочерние классы
Создадим дочерний класс для каждой из трех пород, упоминавшихся ра
нее: джек-рассел-терьер Uack Russell Terrier), такса (Dachshund) и бульдог
(Bu lld og) .
Для удобства приведу полное определение класса 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 ГЛАВА 1О Объектно-ориентированное программирование (ООП)
>>> miles = JackRussellTerrier("Miles", 4)
>>> buddy = Dachshund("Buddy", 9)
>>> jack = Bulldog("Jack", З)
>>> jim = Bulldog("Jim", 5)
Экземпляры дочерних классов наследуют все атрибуты и методы родительского
клас са:
>>> miles.species
'Canis familiaris'
>>> buddy.name
' Buddy'
»> print(jack)
Jack is З 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() в определении класса для каждой
породы.
Чтобы переопределить метод, заданный в родительском классе, вы опреде
ляете одноименный метод в дочернем классе. Вот как это делается в классе
J ackRu s sell T erri er:
class JackRussellTerrier(Dog):
def speak(self, sound="Arf"):
return f"{self.name} says {sound}"
Теперь метод . speak() определен в классе JackRussellТerrier с аргументом
по умолчанию для sound, которому присвоено значение "Arf" .
Обновите файл dog.py новой версией класса JackRussellTerrierи нажмите FS,
чтобы сохранить и запустить файл. Теперь можно вызвать метод . speak() для
экземпляра JackRussellTerrier без передачи аргумента для sound:
>>> miles = JackRussellTerrier("Miles", 4)
>>> miles.speak()
'Miles says Arf'
В некоторых ситуациях одна и та же собака издает разные звуки, так что, если
Майлз рассердится и зарычит, вы все еще сможете вызвать .speak() с другим
звуком:
>>> miles.speak("Grrr")
'Miles says Grrr'
Имея дело с наследованием классов, важно помнить, что изменения в роди
тельском к л ас с е авт оматически р аспростра няются н а д очерни е классы - при
условии, что изменяемый атрибут или метод не был переопределен в дочернем
к лас се.
Например, измените в окне редактора строку, возвращаемую методом . speak()
из класса Dog:
240 ГЛАВА 1 О Объектно-ориентированное программирование (ООП)
class Dog:
# Другие атрибуты и методы остаются неизменными
# Изменяется строка, возвращаемая .speak()
def speak(self, sound):
return f"{self.name} barks: {sound}"
Сохраните файл и нажмите FS. Теперь при создании нового экземпляра 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"):
r et ur n super () . spea k(sound)
Когда вы вызываете super(). speak (sound) внутри JackRussellTerrier, Pythoп
ищет в родительском классе Dog метод . speak() и вызывает его с переменной
sound.
Обновите файл dog.py обновленной версией класса JackRussellTerrier. Со
храните файл и нажмите FS, чтобы протестировать его в интерактивном окне:
>>> 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.
Добавьте в класс метод . а rea(), который возвращает площадь прямо
угольника(length * width).
ЗатемнапишитеклассSquare, представляющий квадрат. Этот класс дол
жен наследовать от класса Rectangle и создаваться с одним атрибутом
. side_length. Протестируйте класс Square: создайте экземпляр Square
сатрибутом . side_length, равным 4. Вызов .area () должен возвращать 16.
242 ГЛАВА 1 О Объектно-ориентированное программирование (ООП)
Задайте свойству . width вашего экземпляра Square значение 5. Затем
снова вызовите . area(). Метод должен вернуть значение 20.
Этот пример показывает, что наследование классов не всегда наилучшим
образом модели рует о тн ош ен ия ме ж ду по дмнож ества ми. В матем атике
любой квадрат является прямоугольником, но в компьютерных про
граммах это не всегда так.
Хорошо продумывайте варианты поведения, чтобы они выполняли
именно то, что вы задумали, и используйте иерархии классов с осто
рож ность ю.
10.4 . ЗАДАЧА: МОДЕЛЬ ФЕРМЫ
В этой задаче вы создадите упрощенную модель фермы. Не упускайте из виду,
что правильных ответов может быть несколько.
В этой задаче на первый план выходит не столько синтаксис классов Python,
сколько проектирование программных структур вообще, а это в высшей степени
субъективно. Задачу мы намеренно сформулировали открытой, чтобы вы по
думали над тем, как организовать ваш код по классам.
Прежде чем начинать программирование, возьмите бумагу и ручку и набросай
те модель своей фермы, включая классы, атрибуты и методы. Подумайте над
наследованием. Как предотвратить дублирование кода? Не жалейте времени
и проработайте столько вариантов модели, сколько посчитаете нужным.
Требования задачи открыты для интерпретации, но постарайтесь придержи
ваться следующих рекомендаций.
1 . В пр огра мме до лж но быть по крайне й мере четыре к лас са: родительск ий
класс Animal и не менее трех производных классов животных, наследу
ющих от Animal.
2. Каждый класс должен содержать несколько атрибутов и по крайней
мере один метод, моделирующий поведение, присущее конкретному
животному или всем животным, - они должны ходить, бегать, есть,
спать и т. д.
3. Не усложняйте. Используйте наследование. Предусмотрите воэможность
вывода подробной информации о животных и их поведении.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
10.5. Итоги и дополнительные ресурсы 243
10.5 . ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы узнали об объектно-ориентированном программировании
(ОО П) - парадигме программирования , исп ользуемо й в о м ноги х со вре менн ых
языках, включаяjаvа, С#, С++ и Python.
Вы узнали, как определить класс - своего рода чертеж для строительства
объектов, а также создавать экземпляры на основе класса. Также вы узнали
об атрибутах, соответствующих свойствам объекта, и методах, определяющих
особенности пов едения и де йствия об ъе кт а .
Наконец, вы узнали, как работает механизм наследования, создающий производ
ные классы на основе родительского класса. Далее я показал, как обращаться
к методу родительского класса при помощи вызова super() и как проверить,
наследует ли объект от другого класса, при помощи isinstance().
ИНТЕРАКТИВНЫЙ ТЕСТ
Кэтой главе прилагается бесплатный интерактивный тестдля проверки усво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpythoп.com/quizzes/pybasics-oop
Дополнительные ресурсы
В этой главе вы познакомились с основами ООП, но вам предстоит еще очень
много узнать по этой теме! За дополнительной информацией обращайтесь
к следующим ресурсам:
• официальная документация Python (https.j/docs.python.org/3/tutorial/
classes.html)
• статьи об ООП на сайте Real Python(https.j/realpython.com/search?q=oop)
ГЛАВА 11
Модули и пакеты
По мере накопления опыта вы начнете браться за программирование больших
проектов, и в какой-то момент вам станет ясно, что хранить весь код проекта
в одном файле очень неудобно.
Но есть выход: использовать не один файл, а распределить код по нескольким
файлам - модулям. Далее вы в любой момент можете собрать отдельные
модули вместе и построить из них большое приложение, как строят дом из
кирпичей.
В этой главе вы научитесь:
• создавать собственный модуль;
• использовать модуль в других файлах командой import;
• объедин ять несколько м одул ей в пак ет.
Итак, за дело!
11.1 . РАБОТА С МОДУЛЯМИ
Модуль - это файл, содержащий код Python, который может повторно ис
пользоваться в других файлах с кодом Python.
Собственноговоря, каждый файл с кодом Python, который вы создали при изу
чении этой книги, был модулем, но вы пока не знаете, как использовать код из
одного модуля в другом модуле.
Разбиение программы на модули имеет четыре важных достоинства.
1. Простота: модуль решает однузадачу.
2. Удобство сопровождения: маленькие файлы лучше больших.
11.1 . Работа с модулями
245
3. Удобство повторноrо использования: модули сокращают дублирование
код а.
4. Определение областей видимости: модули имеют собственные про
странства имен.
Этот раздел посвящен модулям. Вы научитесь создавать их в IDLE, импортиро
вать один модуль в другой, а также узнаете, как модули создают пространства
имен.
Создание модулей
Запустите IDLE и откройте новое окно редактора командой File ~ New File или
нажатием клавиш Ctrl+N. В окне редактора определите функцию add(),которая
возвращает сумму двух своих параметров:
# adder.py
def add(x, у):
returnх+у
Выберите команду File ~ Save или нажмите клавиши Ctrl+S, чтобы сохранить
файл с именем adder.py в новом каталоге myproject/. Файл adder.py-это модуль
Python! Он не является законченной программой, но от модулей этого и не
требуется.
Теперь откройте другое окно редактора клавишами Ctrl+N и введите следующий
код :
# main.py
value = add(2, 2)
print(value)
Сохраните файл с именем main.py в толькочто созданнойпапкеmyproject/. На
жмите FS, чтобы выполнить модуль.
При выполнении модуля в интерактивном окне IDLE выводится ошибка
NameError:
Traceback (most recent call last):
File "//Documents/myproject/main.py", line 1, in cmodule>
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 , а затем запустите модуль клавишей
FS. И на этот раз появляется ошибка NameError! Дело в том, что обращение
к add() возможно только из пространства имен adder.
Пространство имен представляет собой набор имен - переменных, функций
и классов. Каждый модуль Python имеет собственное пространство имен.
Чтобы обратиться к переменным, функциям и классам модуля из этого же
модуля, достаточно указать их имя. Именно так мы поступали в примерах
до настоящего момента . Но для импортированных модулей этот способ не
ГОДИТСЯ.
Чтобы обратиться из вызывающего модуля к имени, содержащемся в импорти
рованном модуле, укажите имя импортированного модуля, точку и нужное имя:
<модуль >. <имя>
Например, чтобы вызвать add() в модуле adder, необходимо использовать
запись adder.add ().
ВАЖНО!
Имя, используемоедля импортирования модуля, совпадает с именем файла
модуля.
По этой причи~е имена файлов модулей должны быть допустимыми иден
тификаторами Pythoп. Это означает, что они могут содержать только буквы
верхнего и нижнего регистра, цифры и символы подчеркивания и не могут
начинаться с цифры.
Обновите код main.py:
# main.py
import adder
value = adder.add(2, 2) # <-- Измените эту строку
print(value)
11.1. Работа с модулями
247
Сохраните файл и запустите модуль. В интерактивном окне выводится значение 4.
При выполнении команды import <модуль> в начале файла импортируется все
пространство имен модуля. Все новые переменные и функции, добавленные
в adder.py, становятся доступными в main.py , дополнительно ничего импорти
ровать не придется.
Откройте окно редактора для adder.py и добавьте следующую функцию перед
add():
# adder.py
# Этот код оста етс я без изме нени й
def add(x, у):
returnх+у
def douЫe(x): #<--Добавьте эту функцию
returnх+х
Сохраните файл. Откройте окно редактора для main.py и добавьте следующий код:
# main.py
import adder
value = adder.add(2, 2)
douЫe_value = adder.douЫe(value) # <-- Добавьте эту строку
print(douЫe_value) # <-- Измените эту строку
Сохраните и запустите main.py. При выполнении модуля винтерактивном окне
выводится значение 8. Так как douЫe() уже существует в пространстве имен
adder, ошибка NameError не выдается.
Разновидности команды import
Команда import весьма гибкая. Существуют две ее разновидности, о которых
необходимо :шать.
1. import <модуль> as <другое_имя>
2. from <модуль> import <имя>
Рассмотрим каждую из этих разновидностей более подробно.
248 ГЛАВА 11 Модули и пакеты
import <модуль> as <другое_имя>
Ключевое слово as позволяет изменить имя импортирования:
import <модуль> as <другое_имя>
Когда вы импортируете модуль этим способом, для обращения к пространству
имен модуля используется <друюе_uмя> вместо имени <модуль>.
Например, модифицируйте команду import в main.py следующим образом:
import adder as а # <-- Измените эту строку
# Последующий код остается без изменений
value = adder.add(2, 2)
douЫe_value = adder.douЫe(value)
print(douЫe_value)
Сохраните файл и нажмите FS. 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 теперь не распознается, потому что модуль был импортирован
с именем а вместо adder.
Чтобы программа main.py снова заработала, необходимо заменить adder.add ()
иa dde r. dou Ыe( ) нaa.a dd() иа. dоu Ые( ):
import adder as а
value = a .add(2, 2) # <-- Измените эту строку
douЫe_value = a.douЬle(value) # <-- И эту строку тоже!
print(douЫe_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)
douЫe_value = adder.douЫe(2, 2)
print(douЫe_value)
Сохраните файл и нажмите FS. Происходит исключение NameError:
Traceback (most recent call last):
File "//Documents/myproject/main.py", line З, 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.douЫe()в main.py на add () иdouЫe():
# main.py
from adder import add
value = add(2, 2) # <-- Измените эту строку
douЫe_value = douЫe(value) # <-- И эту строку тоже!
print(douЫe_value)
Сохраните файл и запустите модуль. Как вы думаете, что произойдет?
Возникает еще одна ошибка NameError:
Traceback (most recent call last):
File "//Documents/myproject/main.py", line 4, in <module>
douЫe_value = douЫe(value)
NameError: name 'douЫe' is not defined
На этот раз NameErrorсообщает, что имя douЫe не определено; это доказывает,
что из модуля adder было импортировано только имя add.
Чтобы импортировать имя douЫe, включите его в команду import в main.py:
# main.py
from adder import add, douЫe # <- - Измените эту строку
# Дальнейший код остается без изменений
value = add(2, 2)
douЫe_value = douЬle(value)
print(douЫe_value)
250 ГЛАВА 11 Модули и пакеты
Сохраните и запустите модуль. Теперь он выполняется без ошибки NameError.
В интерактивном окне выводится значение 8.
Сводка команд impo rt
В следующей таблице суммируется то, что вы узнали об импортировании
модулей.
КОМАНДАIMPORT
import <модуль>
import <модуль> as
<другое_имя>
from <модуль> import
<имяl>, <имя2> ...
РЕЗУЛЬТАТ
Импортирует все пространство имен модуля <модуль>
под именем <модуль>. Для обращения к именам из вы
зывающего модуля используется запись <модуль>. <имя>
Импортирует все пространство имен модуля <модуль>
под именем <другое_имя>. Для обращения к именам
из вызывающего модуля используется запись <дру
гое_имя>.<имя>
Импортирует из модуля <модуль> только имена <имяl>,
<имя2> и т. д. Имена добавляются в локальное про
странство имен вызывающего модуля, и к ним можно
обращаться напрямую
Разделение пространств имен - одно из главных преимуществ разбиения кода
на модули. Давайте разберемся, чем же так важны пространства имен и почему
вам стоит о них знать.
Для чего нужны пространства имен
Допустим, каждому человеку на нашей планете присвоен идентификатор. Чтобы
людей можно было отличать друг от друга, все идентификаторы должны быть
уникальными. Да, идентификаторов понадобится довольно много!
Мир разделен на страны, поэтому мы можем сгруппировать людей по стране
рождения. Если присвоить каждой стране уникальный код, его можно при
соединить к идентификатору. Например, тому, кто родился в Соединенных
Штатах, может быть присвоен идентификатор US-357, а тому, кто родился
в В еликобр итании, - и ден тифи кат ор G B- 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 {пате}!, где {пате} заменяется аргу
ментом функции.
2. Создайте модуль таiп.ру, который импортирует функцию greet () из
greeter.py и вызывает функцию с аргументом "Real Python".
11.2 . РАБОТА С ПАКЕТАМИ
Модули позволяют разделить программу на отдельныефайлы, которые можно
повторно использовать по мере надобности. Взаимосвязанный код объединяют
в один модуль и хранят отдельно.
Пакеты позволяют более широко использовать эту организационную структуру,
давая возможность группировать взаимосвязанные модули в одном простран
стве имен.
Сейчас вы научитесь создавать собственные пакеты Python и импортировать
код из этих пакетов в другие модули.
Создание пакетов
Пакет - это папка, содержащая один или несколько модулей Python. В пакет
дол же н так же входить спе циа льн ый мо дул ь с и ме не м _iпit_.p y. Следующий
пример пакета демонстрирует эту структуру.
mypackage/
E_init_.py
modute1.py
modute2.py
Модуль_iпit_.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_ .p y
Сохраните файл с именем _init_ . p y во вложенной папке mypackage/ папки
packages_example.
Наконец, создайте еще два окна редактора. Сохраните эти файлы с именами
module1.py и module2.py соответственно в папкеmypackage/ и вставьте в начало
каждого файла комментарий с именем файла. Когда вы все завершите, у вас
должны быть открыты пять окон IDLE: интерактивное окно и четыре окна
редактора.
Итак, структура пакета создана; теперь можно добавить код. Добавьте в файл
module1.py следующую функцию:
# modulel.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.modulel.greet("Pythonista")
mypackage.module2.depart("Pythonista")
11.2 . Работа с пакетами
255
Сохраните main.py и нажмите FS , чтобы запустить модуль. В интерактивном
окне выдается ошибка AttributeError:
Traceback (most receпt call last):
File "\MacHomeDocumeпtspackages_examplemaiп.py", liпe 5, iп <module>
my pac kag e.m odul el. gre et( "Py tho пis ta" )
AttributeError: module 'mypackage' has по attribute 'modulel'
Когдавы импортируетемодульmypackage, пространства имен modulel и module2
не и мп ор тир ую тс я автоматиче ски - их тоже не обхо димо импортировать. Из
мените команду import в начале main.py:
# maiп.py
import mypackage.modulel # <-- Измените эту строку
# Дальнейший код остается без изменений
myp ack age .mo dul el.g ree t(" Pyt hoп ist a")
myp ack age .mo dul e2. dep art ("P yth oпi sta ")
Теперь сохраните и запустите модуль main.py. В интерактивном окне должен
появиться следующий результат:
Hello, Pythoпista!
Traceback (most receпt call last):
File "\MacHomeDocumeпtspackages_examplemaiп.py", liпe 6, iп <module>
m ypa cka ge. mod ule 2.d epa rt(" Pyt hoп ist a")
AttributeError: module 'mypackage' has по attribute 'module2'
Функция mypackage. modulel. greet() была вызвана, потому что в интерактивном
окнебыло выведеносообщение "Hello, Pythoпista !".
Однако функция mypackage.module2.depart() вызвана не была. Эта строка
создала ошибку атрибутов, потому что на данный момент из mypackage был
импортирован только модуль modulel.
Чтобы импортировать module2, добавьте следующую команду import в начало
файла main.py:
# maiп.py
import mypackage.modulel
import mypackage.module2 # <-- Add this liпe
# Да льн ейши й код оста ется без изме нений
m ypa cka ge. mod ule l.g reet ("P yth oпi sta ")
myp ack age .mo dule 2.d epa rt( "Py tho пis ta" )
Если теперь сохранить и выполнить main.py, будут вызваны обе функции,
greet() и depart():
256 ГЛАВА 11 Модули и пакеты
Hello, Pythonista!
Goodbye, Pythonista!
В общем случае при импортировании модулей из пакетов вводят полное имя
в следующем формате:
import <имя_пакета>.<имя_модуля>
Сначала указывают имя пакета, затем точку, а после нее имя импортируемого
модуля.
ВАЖ НО!
Имена папок пакетов, каки имена файлов модулей, должны быть допустимы
ми идентификаторами Pythoп. Они могут содержать только буквы верхнего
и нижнего регистра, цифры и символы подчеркивания и не могут начинаться
с циф ры.
Как и в случае с модулями, существует несколько разновидностей команды
import, которые используютдля импортирования пакетов.
Разновидности команды import для пакетов
Когда я рассказывал об импортировании имен из модулей, то показал вам три
разновидности команды import. Для импортирования модулей из пакетов их
четыре:
1. import <пакет>
2. import <пакет> as <другое_имя>
3. from <пакет> import <модуль>
4. from <пакет> import <модуль> as <другое_имя>
Они работают почти так же, как их аналоги . Например, вместо импортирования
mypackage.modulel и mypackage.module2 в отдельных строках можно импорти
ровать оба модуля в одной строке. Внесите изменения в файл таiп.ру:
# main.py
from mypackage import modulel, module2
m odule l. gr ee t(" Pyt honista ")
module2.depart("Pythonista")
Если сохранить и запустить модуль, в интерактивном окне будут выведены те
же результаты.
11.2. Работа с пакетами
257
Имя импортируемого модуля можно изменить при помощи ключевого слова as:
# main.py
from mypackage import modulel as ml, module2 as m2
ml.greet("Pythonista")
m 2. dep ar t("P ythonista ")
Так же из моду ля п ак е та доп устимо импор тировать отдельные и м е на . Наприм ер,
если вы внесете следующие изменения в файл main.py , результат, выводимый
при сохранении и запуске модуля, останется неизменным:
# main.py
from mypackage.modulel import greet
from mypackage.module2 import depart
greet("Pythonista")
dep ar t( "P ythonis ta" )
Поскольку есть несколько способов импортирования пакета, то естественно
задаться вопросом: какой из них лучше?
Рекомендации по импортированию пакетов
Рекомендации по импортированию имен из модулей действуют и при импор
тировании модулей из пакетов. Старайтесь сделать импортирование как можно
более явным и однозначным, чтобы модули и имена, импортированные в вы
зывающий модуль, имели соответствующий контекст.
В общем случае наиболее однозначным является следующий формат:
i mp or t <пакет>. <мод уль>
Для обращения к именам из модуля вводится команда вида:
<паке т>. <мод уль>. <и мя>
При таком подходе у вас не возникнут вопросы, когда вам встретятся имена из
импортируемых модулей. Но иногда имена пакетов и модулей оказываются
слишком длинными, и вам приходится снова и снова вводить конструкцию
<пакет>. <модуль> в своем коде.
Следующий формат позволяет пропустить имя пакета и импортировать в про
странство имен вызывающего модуля только имя модуля:
from <пакет> import <модуль>
258 ГЛАВА 11 Модули и пакеты
Теперь для обращения к имени из модуля достаточно ввести <модуль>. <имя>.
Хотя такая конструкция уже не показывает, из какого пакета происходит имя,
контекст модуля остается очевидным.
А вот следующий формат обычно считается неоднозначным, и его стоит ис
пользовать только в том случае, если риск импортирования из модуля имени,
конфликтующего с именем в вызывающем модуле, полностью отсутствует:
from <nакет>.<модуль> import <имя>
Вы узнали об импортировании модулей из пакетов. Теперь давайте посмотрим,
как вкладывать пакеты в другие пакеты.
Импортирование модулей из подпакетов
Пакет - всего лишь обычная папка, содержащая один или несколько модулей
Python, одному из которых должно быть присвоено имя _init_ . p y. Таким
образом, следующая структура пакетов вполне допустима.
mypackage/
mysubpackage/
t.: -_init_.ру
L moduleЗ.py
_ ini t_.py
modulel.py
module2.py
Пакет, вложенный в другой пакет, называется подпакетом. Например, папка
mysubpackage является подпакетом mypackage, потому что она содержит модуль
_init_ .p y , а также второй модуль с именем moduleЗ.py.
Запустите на своем компьютере файловый менеджер и создайте новую пап
ку с именем mysubpackage/. Удостоверьтесь, что она находится внутри папки
mypackage/, созданной ранее.
Откройте в IDLE два новых окна редактора. Создайте файлы _init_. py
и moduleЗ.py, сохраните оба модуля в папке mysubpackage/. Включите в файл
moduleЗ.py следующий код:
# mod ule З.py
people = ["John", "Paul", "George", "Ringo"]
Теперь откройте файл main.py из корневой папки проекта packages_examples/.
Удалите существующий код и замените его следующим:
# main.py
from mypackage.modulel import greet
from mypackage.mysubpackage.moduleЗ import people
for person in people:
gre e t(pe r son)
11.2 . Работа с пакетами
259
Список people из модуля moduleЗ в пакете mysubpackage импортируется через
имя модуля в точечной нотации mypackage. mysubpackage. moduleЗ.
Сохраните и запустите main.py . В интерактивном окне появится результат:
Hello, John!
Hello, Paul!
Hello, George !
Hello, Ringo!
Подпакеты удобны для организации кода очень больших пакетов. Они помогают
поддерживать в пакете четкую и упорядоченную структуру папок.
Тем не менее глубокое вложение подпакетов приводит к появлению длинных
имен в точечной нотации. Только представьте, сколько символов вам придется
ввести, чтобы импортировать модуль из подпакета, находящегося в подпакете
входящего в подпакет пакета.
Желательно ограничить подпакеты одним, максимум двумя уровнями вложен
ности.
Упражнения
1. В новой папке проекта с именем package_exercises/ создайте пакет helpers,
состоящий из трех модулей: _init_ .p y , string.py и math.py.
Добавьте в мoдyльstring.py функцию с именем shout( ), которая получает
один строковый параметри возвращаетновую строку с символами, пре
образованными к верхнему регистру.
Добавьте в модуль math.py функцию с именем area(), которая получает
два параметра, length и width, и возвращает их произведение length *
wid th.
2. В корневой папке проекта создайте модуль main.py, который импортирует
функции shout() и area(). Используйте shout() и area() для вывода
следующего результата:
ТНЕ AREA OF А 5-ВУ-8 RECTANGLE IS 40
260 ГЛАВА 11 Модули и пакеты
11.З. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе я показал, как создавать модули и пакеты Python, а также импор
тировать объекты из других модулей . Вы узнали, что деление кода на модули
и пакеты обладает рядом достоинств.
•
Маленькие файлы с кодом проще больших.
•
Маленькие файлы с кодом создают меньше проблем при сопровождении.
•
Модули можно повторно использовать в проекте .
•
Модули группируют взаимосвязанные объекты в изолированных про
странствах имен.
ИН ТЕР АКТИ ВНЫЙ ТЕ СТ
К этой главе прилагается бесплатный интерактивный тест для проверки усво
енных вами знаний. Тестдоступен на телефоне или компьютере:
reaf py tho n.c oml quiz zes /p ybas ic s-m odules -pac k ages
Дополнительные ресурсы
За дополнительной информацией о модулях и пакетах обращайтесь к следу
ющ и м ресурсам :
• «Python Modules and Packages» (https.j/realpython.com/courses/python-
modules-packages/)
• «Absolute vs Relative Imports» (https://realpython.com/ courses/absolute-
vs-relative-imports-python/ )
ГЛАВА 12
Операции ввода и вывода
с файлами
До сих пор вы писали программы, получающие входные данные от одного из
двух источников: от пользователя или самой программы. Вывод ограничивался
отображением текста в интерактивном окне IDLE.
Эти методы ввода и вывода бесполезны во многих ситуациях - вот несколько
примеров:
• вх одн ые значения неизвестн ы в о вр емя н аписа ния прогр аммы;
• программе требуется больше данных,чем пользователь способен ввести
вручную ;
• результаты после запуска программы необходимо передать другим раз
работчикам.
На помощь приходят файлы.
В этой главе вы научитесь:
• работать с путями и метаданными файлов;
• читать и записыватьтекстовыефайлы;
• читать и записывать файлы в формате CSV;
• создавать, удалять, копировать и перемещать файлы и папки.
Итак, за дело!
12.1. ФАЙЛЫ И ФАЙЛОВАЯ СИСТЕМА
Скорее всего, у вас уже есть опыт работы с файлами. Впрочем, даже в этом
случае программисту необходимо знать о файлах то, чего не знают обычные
пользователи.
262 ГЛАВА 12 Операции ввода и вывода с файлами
Сейчас мы познакомим вас с концепциями, необходимыми для работы с фай
лами в Python.
Анатомия файла
Существует много типов файлов: текстовые, графические, аудиофайлы, файлы
в формате PDF и т. д. Впрочем, независимо от типа файл представляет собой
последовательность байтов, называемую содержимым файла.
Байт - целое число со значением в диапазоне от О до 255 . Байты записываются
на физический носитель при сохранении файла. Когда вы обращаетесь к файлу
н а своем компьютере, ба йты фа йл а последовательно сч иты вают ся с д ис ка .
В файле нет ничего, что указывало бы, как интерпретировать его содержимое. Вы
как программист отвечаете за преобразование байтов в нужный формат, когда
ваша программа открывает файл. Может показаться, что это достаточно сложно,
но Python выполняет большую часть тяжелой работы за вас. Например, Python
может преобразовать числовые байты текстового файла в текстовые символы. Вам
не нужно знать, как выполняется это преобразование. В стандартной библиотеке
имеются инструменты для работы с разными типами файлов, включая графиче
ские и аудиофайлы. Чтобы обратиться к файлу на устройстве хранения данных,
необходимо знать, где хранится файл и как взаимодействовать с этим устройством.
Эта грандиозная работа выполняется файловой системой вашего компьютера.
Файловая система
Файловая система компьютера решает две задачи.
1. Она выдает абстрактное представление файлов, хранящихся на компью
тере и любых устройствах, подключенных к нему.
2. Она взаимодействует с устройствами для управления записью и чтением
данных файлов.
Python взаимодействует с файловой системой на вашем компьютере. Возмож
н о с т и т а к и х в заимодей ствий ограни чиваю тся операциями, кот орые под держ и
ваются файловой системой.
ВАЖНО!
В разныхоперационныхсистемах (ОС) используются разные файловые систе
мы. Важно помн.ить об этом при написании кода, который будет выполняться
в различных ОС.
12.1 .Файлы и файловая система 263
Файловая система управляет взаимодействиями между компьютером и физиче
ским устройством хранения данных. И это очень хорошо. Этоозначает, чтовам-
программисту Python - не придется беспокоиться о таких подробностях, как
обращение к физическому носителю или управление вращением жесткого диска.
Иерархия файловых систем
Файловые системы организуют файлы в иерархию каталогов, также называемых
папками. На верху иерархии находится корневой каталог. В нем хранятся все
остальные файлы и каталоги файловой системы.
ВАЖНО!
В Windows каждый диск имеет собственную иерархию файлов с корневым
каталогом, представленным именем диска.
В macOS и Linux каждый диск представляет собой подкаталог единого кор
невого каталога.
Каждый файл обладает именем, которое должно отличаться от имен всех осталь
ных файлов в той же папке. Каталоги также могут содержать другие каталоги,
называемые подкаталогами, или вложенными папками.
Следующее дерево каталогов наглядно представляет иерархию файлов и папок
в некоторой файловой системе.
rtoot~pp/i . :. .: . . progr am .py
L data.txt
[
to s/
cats/
lion. j pg
С siamese.png
dog s/
i.......: . dachshound.jpg
L jack_russel.gif
В этой файловой системе корневому каталогу присвоено имя root/. Он содер
жит два подкаталога с именами арр/ и photos/. Подкаталог арр/ содержит файл
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 единого корневого каталога не существует. Каждое устрой
ство (диск) обладает отдельной файловой системой с собственным корневым
каталогом, имя которого состоит из буквенного обозначения диска, за которым
следует двоеточие и обратная косая черта \.
Как правило, жесткому диску, на котором установлена операционная система,
назначается буква С, поэтому корневой каталог файловой системы для этого
диска имеет имя с: .
Другое принципиальное отличие файловых путей Windows, macOS и Ubuntu
заключается в том, что каталоги в путях Windows разделяются обратной косой
чертой \,тогда как разделителем каталогов в macOS и Ubuntu является обычная
косая черта /.
Когда вы пишете программы, которые должны выполняться в разных опера
ционных системах, очень важно правильно указывать путь. В версиях Python
до 3.4 стандартная библиотека содержит модуль pathlib, упрощающий работу
с путем в разных операционных системах.
12.2 . Работа с путями к файлам в Python
265
12.2 . РАБОТА С ПУТЯМИ К ФАЙЛАМ В РУТНОN
Модуль 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 интерпретирует обратную косую черту
как начало служебной последовательности ( еsсаре-последовательности), пред
ставляющей специальный символ в строке - например, символ новой строки \n.
При попытке создать объект Pathдля пути к файлу в Windows "С: \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( ).
У каждой операционной системы имеется специальный каталог для хранения
данных текущего пользователя. Он называется домашним каталогом пользо
вателя. Его местонахождение зависит от ОС:
•
W in d ows: С :\Usе rs\<u мя_пол ьзова теля>
• ma cO S: /U sers/ <uмя пользо вателя>
•
U bu n tu Li nux : / hom e/<u мя пользователя>
Метод класса 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 Woгking Diгectoгy, 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")
Обратите внимание: строка пути не начинается с с: \ или /. Чтобы проверить, яв
ляется ли путь абсолютным, можно воспользоваться функцией. 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, представляющий абсолютный путь:
»> re la tiv e_ pa th path lib. Pa th( "/Use r s/Da vid")
>>> absolute_path relative_path.resolve()
>>> absolute_path
WindowsPath('C:/Users/David')
Path. resolve() пытается проложить наиболее длинный абсолютный путь.
12.2 . Работа с путями к файлам в Python
269
Иногда относительный путь оказывается неоднозначным. В таких случаях
. re solve() возвращает относительный путь. Другими словами, нет гарантий
того, что . 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
С:\
Атрибут .parent возвращает имя первого родительского каталога в пути к файлу
в видестроки:
» > path. parent
WindowsPath('C:/Users/David')
. parent - это сокращенная запись для .parents (0).
Если путь к файлу является абсолютным, вы можете обратиться к корневому
каталогу пути при помощи атрибута . anchor:
»> path. anchor
'С:\'
Стоит заметить, что . 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
'h ell o'
»> path.suffix
'.txt'
Наверное, вас интересует, как сделать что-нибудь полезное с файлом hello.txt.
В следующем разделе я расскажу, как читать и записывать файлы. Но прежде
чем открыть файл для чтения, желательно проверить, существует ли этот файл.
Проверка существования пути к файлу
Объект Path для пути к файлу можно создать даже в том случае, если путь не
существует. Конечно, пути, которые не ведут к реальным файлам или каталогам,
особой пользы не принесут, если только вы не создадите их в какой-то момент.
У объектов Path имеется метод . exists (),который возвращаетTrue или False
в зависимости от того, существует ли путь на компьютере, на котором вьшол
няется программа.
Например, если в вашем домашнем каталоге отсутствует файл hello.txt, вызов
. e xists () для объекта Path, представляющего этот файл, вернет False:
>>> path = pathlib.Path.home() / "hello.txt"
>>> path.exists()
False
12.2. Работа с путями к файлам в Python 271
Используя текстовый редактор или другой инструмент, создайте пустой
текстовый файл с именем hello.txt в своем домашнем каталоге. Затем снова
выполните приведенный выше пример и убедитесь в том, что path.exists()
возвращает Tr ue.
Также можно проверить, ведет ли путь к файлу или к каталогу. В первом случае
используется метод .is_file():
>>> path.is_file()
True
Если файл не существует, то .is_file() возвращает False.
Чтобы проверить, ведет ли путь к каталогу, используйте метод. is_dir( ):
>>> # "hello.txt" не является каталогом
>>> path.is_dir()
False
>>> # home является каталогом
>>> home.is_dir()
Tr ue
Работа с путями - неотъемлемая часть любого программного проекта, где
выполняется чтение или запись данных на жесткий диск или другой носитель
информации. Понимание различий между путями к файлам в разных опера
ционных системах и умение работать с объектами pathlib. Path для любой
ОС - исключительно важный и полезный навык.
Упражнения
1. Создайте новый объект Path для файла с именем myJile.txt в папке
my_folder/, находящейся в домашнем каталоге вашего компьютера. При
свойте этот объект Path переменной с именем file_path.
2. Проверьте, существует ли файл/каталог для пути, присвоенного file_
pa th.
3. Выведите имя для пути, присвоенного file_path. Программа должна
вывести my_file.txt.
4. Выведите имя родительского каталога для пути, присвоенного file_path.
Программа должна вывести my_folder.
272 ГЛАВА 12 Операции ввода и вывода с файлами
12.З. ОСНОВНЫЕ ОПЕРАЦИИ
ФАЙЛОВОЙ СИСТЕМЫ
Теперь, когда вы умеете работать с путями к файлам при помощи модуля 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()
Tru e
Если вы попытаетесь создать каталог, который уже существует, происходит
ошибка:
>>> new_dir.mkdir()
Traceback (most recent call last):
File "cpyshell#32>", line 1, in cmodule>
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 а 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 cmodule>
ne sted_ dir. m kdir( )
File "C:\Users\David\AppData\Local\Programs\Python\
Python\lib\pathlib.py" , line 1266, in mkdir
self._accessor.mkdir(self, mode)
FileNotFoundError: [WinError З] The system cannot findthe path
specified: 'C:\\Users\\David\\new_directory\\folder_a\\folder_b'
Проблема в том, что родительский каталог folder _а/ не существует. Как правило,
для создания каталога все родительские папки целевого каталога (в данном
случае folder_Ь/) уже должны существовать.
Чтобы сформировать все родительские папки, необходимые для создания целе
во г о к ата ло га , присв ойте необязатель ному параметру p ar e nt s метода. mk di r( )
значение T r u e:
>>> nested_dir.mkdir(parents=True)
В этом случае . mkd ir( ) создает родительский каталог folder _a/, что необходимо
для создания целевого каталога folder_b/.
Объединяя все сказанное, мы получаем следующую типичную схему создания
каталогов:
path.mkdir(parents=True, exist_ok=True)
274 ГЛАВА 12 Операции ввода и вывода с файлами
Когда параметрам parents и exist_okприсвоено значение True, весь путь будет
создан в случае необходимости, а если путь уже существует, то исключение не
выдается.
Эта схема полезна, но такой подход не универсален. Например, если пользо
ватель введет несуществующий путь, лучше перехватить исключение и пред
ложить проверить введенный путь. Возможно, пользователь просто сделал
опечаткув имени существующего каталога!
Теперь посмотрим, как создавать файлы. Создайте новый объект Path с именем
file_path для пути new_directory/filel.txt:
»> file_path = new_dir / «filel.txt»
В каталоге new_directory/ файл с имeнeмfile1.txt отсутствует, так что путь еще
не существует:
>>> file_path.exists()
False
Файл можно создать методом Path.touch( ):
>>> file_path.touch()
Метод создает новый файл с имeнeмfile1.txt в папке new_directory/. Он еще не
сод ерж ит ни каки х данных:
>>> file_path.exists()
Tru e
>>> file_path.is_file()
Tru e
В отличие от .mkdir( ), метод. touch() не выдает исключение, если создаваемый
путь уже существует:
»> #При повторном вызове .touch() исключение не выдается
>>> file_path.touch()
Файл, созданный вызовом . touch (), не содержит никаких данных. Я покажу,
как записывать данные в файлы в разделе 12.5 «Чтение и запись файлов».
Создать файл в несуществующем каталоге невозможно:
»> file_path = new_dir / "folder_с" / "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 для получения пути
к родительской папке для фaйлafile2.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():
p rint (pa th}
C:\Users\David\new_directory\filel.txt
C:\Users\David\new_directory\folder_a
C:\Users\David\new_directory\folder_c
В данный момент папка new_directory/ содержит три элемента:
1) файл с имeнeмfile1.txt;
2) каталог с именем folder_c/;
3) каталог с именем folder_a/.
Метод . i terdir () возвращает итерируемый объект, который можно преоб
разовать в список:
>>> list(new_dir.iterdir())
[ Wind owsP at h('C :/U ser s/Da vid/n ew _dire c tor y/fil el. txt' ),
WindowsPath('C:/Users/David/new_directory/folder_a'),
WindowsPath('C:/Users/David/new_directory/folder_c')]
Преобразовывать этот объект в список требуется достаточно редко. Как правило,
вызов .iterdir()используется вциклеfor, как было сделано в первом примере.
Учтите, что .i terdir() возвращает только элементы, непосредственно со
держащиеся в папке new_directory/. Иначе говоря, вы не увидите путь к файлу,
хранящемуся в каталоге folder_c/.
Способ перебора содержимого каталога и всех его подкаталогов существует,
но легко сделать это с. iterdir() не удастся. Вскоре мы доберемся до решения
этой задачи, но сначала я покажу, как искать файлы в каталогах.
Поиск файлов в каталоге
Иногда требуется перебрать только файлы определенного типа или файлы
с определенными правилами выбора имен. Вы можете вызвать метод Path. glob()
12.3. Основные операции файловой системы
277
для пути, представляющего каталог, чтобы получить итерируемый объект для
соде ржи мог о ка та ло га , уд овл етв оряю щег о неко торо му критерию.
Может показаться странным, что метод для поиска файлов называется .glob (),
но здесь есть исторические причины. В ранних версиях операционной системы
Unix программа сименем globиспользовалась для расширения шаблонов путей
к файлам до полных путей.
Метод .glob() делает нечто похожее. Методу передается строка, содержащая
шаблон с подстановочным символом, а . glob () возвращает список путей, со
ответствующих этому шаблону.
Подстановочный символ - специальный символ, который используется
в шаблонах. При создании конкретного пути к файлу он заменяется другими
символами. Например, в шаблоне "* . txt" звездочка * является подстановочным
символом, который может быть заменен любым количеством других символов.
Шаблон "*. txt" совпадает с любым путем к файлу, который завершается рас
ширением .txt. Другими словами, если при замене *в шаблоне всемисимволами
в пути к файлу, кроме четырех последних, будет получен исходный путь, то он
совпадет с шаблоном "* .txt".
Рассмотрим пример использования папки new_directory/, ранее присвоенной
пер емен ной new_di r :
»> for path in new_dir.glob("*.txt"):
print(path)
C:\Users\David\new_directory\filel.txt
Метод .glob (),как и . i terdir(), возвращает итерируемый объект, содержащий
список путей, но в этом случае возвращаются только пути, соответствующие
шаблону "* .txt".
Следует заметить, ч:го метод . glob() возвращает только пути файлов, содержа
щиеся в папке, для которой он вызывается.
Возвращаемое значение . glob() можно преобразовать в список:
»> list(new_dir.glob("* .txt"))
[W indowsP a th(' C:/U ser s/Da vid/ne w_d ire cto ry/f ile l. txt') ]
Чаще всего .glob() используется в циклах for.
В следующей таблице описаны наиболее популярные подстановочные символы.
278 ГЛАВА 12 Операции ввода и вывода с файлами
ПОДСТАНОВОЧ· ОПИСАНИЕ
ПРИМЕР СОВПАДАЕТ НЕСОВПА·
НЫЙСИМВОЛ
ДАЕТ
*
Любое количество "*Ь*"
Ь,аЬ, Ьс,аЬс а, с, ас
символов
Один символ
" ?Ьс"
аЬс,ЬЬс , сЬс Ьс, ааЬс,
ab cd
[аЬс)
Один из символов, (CB)at
Cat,Bat
at,cat,bat
перечисленных
в квадратных скоб-
ках
Примеры использования каждого подстановочного символа я покажу позднее.
А сейчас мы создадим еще несколько файлов в папке new_directory/, чтобы у нас
было больше возможностей для экспериментов.
Введите следующий код:
>»paths=[
пew_dir / "programl.py ",
пew_dir / "program2.py",
new_dir / "folder_a" / "programЗ.py",
new_dir / "folder_a" / "folder_b " / "imagel.jpg",
new_dir / "folder_a" / "folder_b" / "image2.png",
>>> for path in paths:
path.touch()
»>
После выполнения этого фрагмента структура папки new_directory/ будет такой,
как показано ниже.
new_directory/
folder_a/
t~d~a~~1.jpg
L image2.png
programЗ.py
folder с/
L file2.txt
filel.txt
programl. ру
program2.py
12.3. Основные операции файловой системы
279
Итак, у нас появилась более интересная структура, с которой можно работать.
Давайте посмотрим, как .glob() работает с каждым из подстановочных сим
волов.
Подстановочный символ *
Подстановочный символ * заменяет любое количество символов в шаблоне.
Например, шаблон "*. ру" соответствует всем путям, заканчивающимся суф
фиксом .ру:
>» list(new_dir.glob("*.py "))
[WindowsPath('C:/Users/David/new_directory/programl.py'),
WindowsPath('C:/Users/David/new_directory/program2.py')]
Символ * может многократно использоваться в одном шаблоне:
»> list(new_dir.glob("*l*"))
[ Windo wsP ath( 'C :/Use r s/Da vid/ne w_di re cto ry/f ile l. txt') ,
WindowsPath('C:/Users/David/new_directory/programl.py')]
Шаблон "*1*" соответствует любому пути, содержащему цифру 1,до ипосле
которой может следовать любое количество символов. В папке new_directory/
цифра 1 встречается только в file 1.txt и program 1.ру.
Если опустить первое вхождение* в шаблоне "*1*" (получим шаблон "1*") ,
то сов падений не бу дет :
>» list(new_dir.glob("l*"))
[]
Шаблон"1*" совпадает с путями к файлам, которые начинаются с цифры 1,за
которыми следует любое количество символов. В папке new_directory/ нет ни
одного файла, путь к которому соответствует этому шаблону, поэтому вызов
. g lob() ничего не возвращает.
Подстановочный символ ?
Подстановочный символ ?заменяет одинсимвол в шаблоне. Например, шаблон
" program?.ру" соответствует любому пути, начинающемуся со слова program,
за которым следует один символ и суффикс .ру:
>» list(new_dir.glob("program?. ру"))
[WindowsPath('C:/Users/David/new_directory/programl.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("*l. ?? "))
[WindowsPath('C:/Users/David/new_directory/programl.py')]
Шаблон "*1. ??"соответствует любому пути, который состоит из цифры 1,
за которой следует точка и еще два символа. В каталоге new_directory/ этому
шаблону соответствует только program 1.ру. Обратите внимание: file 1.txt этому
шаблону не соответствует, потому что за точкой следуют три символа.
Подстановочный символ []
Подстановочный символ [] отчасти напоминает ?, потому что он заменяет
только один символ. Отличие в том, что вместо одного произвольного симво
ла, как в случае с ?, [ ] должен совпадать только с одним символом из набора,
заключенного в квадратные скобки.
Например, шаблон "р rogram [ 13] . ру" соответствует любому пути, содержаще
му слово program, за которым следует цифра 1 или 3, а потом расширение .ру.
В новой папке new_directory/folder этому шаблону соответствует только файл
program1.ру:
»> lis t(n ew_ dir .gl ob( "pr ogr am[ lЗ] .py "))
[WindowsPath('C:/Users/David/new_directory/programl.py')]
Как и в случае с другими подстановочными символами, вы можете многократно
использовать [] в одном шаблоне, а также комбинировать его с другими.
Рекурсивный поиск совпадений
и подстановочный символ **
Как вы уже видели, . iterdir() и .glob() имеют одно серьезное ограничение:
они возвращают пути только для той папки, для которой они были вызваны.
12.3 . Основные операции файловой системы
281
Например, new_dir.glob("*.txt") возвращает только пyтьfile1.txt из new_
directory/. Путь file2.txt из подкаталога folder_c/ не возвращается, хотя он и со
впадает с шаблоном "*. txt".
Существует специальный подстановочный символ**, с помощью которого
можно применять шаблон рекурсивно. Для этого в начале шаблона надо поста
вить префикс "* */ ". Тем самым вы приказываете .glob() искать соответствия
шаблону как в текущем каталоге, так и во всех его подкаталогах.
Например, шаблон "* */ *. txt" соответствует как filel.txt, так и folder_c/file2.txt:
>» list(new_dir.glob("**/*.txt"))
[Wi ndowsP a th( 'C: /Use rs/D av id/ne w_dir e ctor y/f ile l. txt') ,
Windo wsP ath( 'C :/Use rs /Dav id/ne w_dir ec tor y/f older _c /fil e2. txt' )]
Аналогичным образом шаблон "**/ *.ру" соответствует любым файлам .ру
в каталоге new_directory/ и всех его подкаталогах:
>» list(new_dir.glob("**/*.py"))
[WindowsPath('C:/Users/David/new_directory/programl.py'),
WindowsPath('C:/Users/David/new_directory/program2.py'),
WindowsPath('C:/Users/David/new_directory/folder_a/programЗ.py')]
Также существует сокращенный метод рекурсивного поискас именем . rglob().
При использовании этого метода передается шаблон без префикса"**/":
»> list(new_dir.rglob("*.py "))
[WindowsPath('C:/Users/David/new_directory/programl.py'),
WindowsPath('C:/Users/David/new_directory/program2.py'),
WindowsPath('C:/Users/David/new_directory/folder_a/programЗ.py')]
Буква r в . rglob() означает recursive, то есть рекурсивный. Некоторые люди
предпочитают использовать этот метод вместо того, чтобы включать в шаблоны
префикс"**/", потому что эта запись немного короче. Обе версии абсолютно
допустимы. В этой книге мы будем использовать . rglob(), а не префикс.
Перемещение и удаление файлов и папок
Иногда требуется переместить файл или каталог в новое место либо полностью
удалить. Это можно сделать средствами модуля pathlib,но учтите, что иногда
такой способ приводит к потере данных, поэтому такие операции следует вы
полнят ь с иск люч ите льн ой осторожностью.
Для перемещения файла или каталога используется метод . replace().
282 ГЛАВА 12 Операции ввода и вывода с файлами
Например, следующий фрагмент перемещает файл file1.txt из папки new_
directory/ во вложенную папкуfolder_a/:
»> source = new_dir / "filel.txt"
»> destination = new_dir / "folder_a" / "filel.txt"
>>> source.replace(destination)
Win dowsP a th(' C:/U ser s/Da vid/n ew_ dire c tor y/fol der _a /fil el. 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 / "programl.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')]
Если путь, для которого вызывается . uпliпk(), не существует, выдается ис
ключение 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
spe c if ie d: 'C :\\Use rs \\Davi d\\new _dire c tory\ \progr a ml . p y'
Если вы хотите проигнорировать исключение, присвойте нео бязательному
параметру missing_ok значение True:
>>> file_path.unlink(missing_ok=True)
В данном случае ничего не происходит, потому что файл, представляемый
file_path, не существует.
ВАЖНО!
Удаленный файл пропадает навсегда. Прежде чем удалять его, убедитесь, что
вы действительно этого хотите!
Использ уйте . uпlink() только с путями, представляющими файлы. Чтобы уда
лить катало г, используйте м етод . rmdir(). Помните, что каталог должен быть
пустым. В противном случае операция выдает исключение OSError:
>» folder_d = new_dir / "folder_d"
>>> folder_d.rmdir()
Traceback (most recent call last):
File "<pyshell#97>", line 1, in c 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 :\\U ser s\\Da vid\\ne w_di re ctor y \\ fo lde r _d '
284 ГЛАВА 12 Операции ввода и вывода с файлами
В папке folder_d/ находится только один фaйл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_Ь/, в котором хранятся два
файла с именами image 1jpg и image2.png.
При в ызове rm tre e() с переда чей о бъекта fo l de r_ a удал яется папка fol der _a/
вместе со всем содержимым:
>>> # The folder_a/ directory по longer exists
>>> folder_a.exists()
False
>>> # Searching for 'image*.*' files returns nothing
»> list(new_dir. rglob("image*. * "))
[]
Раздел получился достаточно объемным. Вы узнали, как выполняются неко
торые стандартные операции файловых систем:
• создание файлов и каталогов;
• перебор содержимого каталогов;
• поиск файлов и папок по шаблону;
• перемещение и удаление файлов и папок.
Все эти операции требуются достаточно часто. Однако очень важно помнить,
что ваши программы - всего лишь «гости» на компьютерах других людей.
12.4 . Задача: перемещение всех графических файлов в новый каталог
285
Неосторожность может привести к непреднамеренному повреждению чужой
информации, потере важных документов и других данных.
Всегда будьте внимательны при работе с файловыми системами. Если у вас
возникнут сомнения, перед выполнением операции удостоверьтесь, что путь
существует, и всегда предлагайте пользователю подтвердить предстоящую
оп ерацию!
Упражнения
1. Создайте в домашней папке новый каталог с именем my_folder/.
2. Создайте в my_folder/ три файла:
• file1.txt
• file2.txt
• image1.png
3. Переместите файл image1.png в новый каталог images/, находящийся
в каталоге my_folder/.
4 . Удали те фaйл file 1.tx t.
5. Удалите каталог my_folder/.
12.4. ЗАДАЧА: ПЕРЕМЕЩЕНИЕ ВСЕХ
ГРАФИЧЕСКИХ ФАЙЛОВ В НОВЫЙ КАТАЛОГ
В папке practice_files располагается вложенная папка с именем documents/. Она
содержит несколько файлов и вложенных папок. Некоторые файлы хранят
графические изображения - они имеют расширения .png, .g if ил и .jpg.
Создайте в папке practice_files подпапку с именем images/, переместите в нее все
графические файлы. Когда это будет сделано, в новой папке должны находиться
четыре файла:
• image1.png
• image2.gif
• imageЗ.png
• image4jpg
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
286 ГЛАВА 12 Операции ввода и вывода с файлами
12.5 . ЧТЕНИЕ И ЗАПИСЬ ФАЙЛОВ
В современном мире мы имеем дело с файлами на каждом шагу. Они являются ос
новнымсредством хранения и передачи данных вцифровомформате. Скорее всего,
только за сегодняшний день вы уже открывали десятки, если не сотни файлов.
В этом разделе я расскажу, как читать и записывать файлы в Python.
Что такое файл?
Файл представляет собой последовательность байтов, а байт содержит число от
О до 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 не позволяет кодировать такие символы, как
fi или ii, а в UTF-8 это возможно. Это означает, что текст в кодировке ASCII
можно декодировать в UTF-8 , но декодирование текста в кодировке UTF-8
в ASCII возможно не всегда.
ВАЖНО!
Если для кодирования и декодирования текста используются разные коди
ровки, могут возникнуть серьезные проблемы.
Например, текст, закодированный вUTF-8 идекодированный в UTF-16 , может
быть интерпретирован как текст на совершенно другом языке, а вовсе не на
том, который предполагался изначально!
Более подробно о кодировке символов рассказано в статье «Uпicode &
Character Eпcodiпgs iп Pythoп: АPainless Guide» (https://realpython.com/python-
encodings-guide/) на сайте Real Python.
Знать, какая кодировка используется в файле, важно, но это не всегда очевидно.
На современных компьютерах с системой Windows текстовые файлы обычно
кодируются в UTF-16 или UTF -8 . В macOS и Ubuntu Linux по умолчанию
обычно используется UTF-8 .
Далее в этом разделе мы будем работать с файлами, для которых используется
кодировка UTF-8. Если при этом у вас возникнут проблемы, возможно, вам
следует изменить кодировку примеров.
288 ГЛАВА 12 Операции ввода и вывода с файлами
Завершители строк
Каждая строка текстового файла завершается одним или двумя символами,
которые интерпретируются как метка конца строки. Эти символы обычно не
отображаются в текстовом редакторе, но существуют в данных файла в виде
байтов.
Два символа, используемых для представления конца строки, - возврата
курсора и переноса строки. В строках Python это еsсаре-последовательности
\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.ореп();
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 '
en codin g=' utf- 8'>
Объекты текстовых файлов являются экземплярами класса TextIOWrapper. Вам
никогда не придется создавать экземпляры этого класса напрямую, потому что
их можно создавать методом Path. open().
Существует несколько режимов открытия файлов. Они описаны в следующей
таблице.
РЕЖИМ
"r"
"а"
''rb "
ОПИСАНИЕ
Создает объект текстового файла для чтения и выдает ошибку, если
файл не удалось открыть
Создает объект текстового файла для записи и перезаписывает все
существующие данные в файле
Создает объект текстового файла для присоединения данных в ко
нец файла
Создает объект двоичного файла для чтения и выдает ошибку, если
файл не удалось открыть
"wb "
Создает объект двоичного файла для записи и перезаписывает все
существующие данные в файле
"аЬ"
Создает объект двоичного файла для присоединения данных в ко
нец файла
В следующей таблице приведены строки для самых распространенных коди
ровок символов.
СТРОКА
· ·a scii"
"utf-8"
"utf-16"
"utf-32"
КОДИРОВКА
ASCll
UTF-8
UTF-1 6
UTF-32
Когда вы создаете объект файла вызовом . open(), Python хранит ссылку на
файл, пока вы не прикажете Python закрыть файл или программа не завершится.
12.5. Чтение и запись файлов 291
ВАЖНО!
Всегда явно приказывайте Python закрыть файл.
Не закрывать за собой файлы - все равно что не убирать за собой грязь. Когда
программа завершает работу,в системе недолжно оставаться лишнего мусора.
Чтобы закрыть файл, вызовите метод . close() для объекта файла:
»> file.close()
Если у вас имеетсясуществующий объект Path, для открытия файлов рекомен
дуется использовать Path. open (). Тем не менее для открытия файлов можно
воспользоваться и встроенной функцией open().
Встроенн ая фун кци я op en ()
Встроенная функция 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 '
e nc oding= 'utf - 8'>
Чтобы закрыть файл, вызовите метод. close() для файлового объекта:
»> file.close()
292 ГЛАВА 12 Операции ввода и вывода с файлами
Чтобы открыть файлы на основе существующего объекта pathlib. Path, в боль
шинстве случаев используется метод Path. open().Тем не менее, если вся функ
циональность модуля pathlib вам не нужна, функция open() отлично подойдет
дл я быстрого создания ф айло вого о бъ е кт а.
Команда with
Когда вы открываете файл, ваша программа получает доступ к данным, внешним
по отношению к самой программе. Операционная система должна управлять
связью вашей программы с физическим файлом. Когда вы вызываете метод
файлового объекта . с lose( ), операционная система разрывает эту связь.
Если ваша программа аварийно завершится после того, как файл будет открыт,
но до его закрытия, то системные ресурсы, задействованные в связи с файлом,
так и останутся зарезервированными, пока операционная система не поймет,
что они более не используются. Чтобы гарантировать, что ресурсы файловой
системы будут освобождены даже в случае сбоя программы, файл можно от
крыть командой 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:
# Работа с файлом
На самом деле нет никаких причин не открывать файлы командой wi th. Этот
синтаксис работы с файлами считается питоническим. Далее в книге мы будем
использовать эту схему при каждом открытии файла.
Чтение данных из файла
Запустите текстовый редактор, откройте файл 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.
Внутри блока wi th метод файлового объекта. read () читает текст из файла
и присваивает результат переменной text.
З начение, воз вра щае мое . r ead (), представля ет соб ой ст рок ов ый об ъект со
значением "Hello, World":
»> type(text)
<class 'str'>
»> text
' Hello, World'
Метод . read() читает текст из файла и возвращает его в виде строки (string).
Если файл содержит несколько строк (lines)1 текста, то они разделяются сим
волом новой строки \n. Снова откройте файл hello.txt в текстовом редакторе
и разместите текст "Hello again"вo второй строке файла. Сохраните файл.
В интерактивном окне 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():
Hello, World
Hello again
print(line)
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():
Hello, World
Hello again
print(line, end="")
Часто бывает удобнее использовать . 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. Это
происходит из-за того, что метод . wr i te( ) возвращает количество записанных
символов. Строка "Hi there!" состоит из девяти символов, поэтому .write()
возвращает 9 .
Когда текст "Hi there!" записывается в файл hello.txt, все существующее со
держимое перезаписывается. Все выглядит так, как если бы вы удалили старый
файл hello.txt и создали новый.
ВАЖНО!
Когда вы передаете параметр mode="w" при вызове .ореп{), содержимое
исходного файла перезаписывается. Это приводит к потере всех исходных
данных в файле!
Чтобы убедиться втом,что файл содержит только текст "Hi there!",прочитаем
и выведем содержимое файла:
>>> with path.open(mode="r", eпcoding="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 l\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 () существует, новый файл newJile.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#l72>", line 1, in cmodule>
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
Из этого раздела вы узнали много нового. Вы узнали, что все файлы являются
последовательностями байтов - целых чисел со значениями от О до 255 .
Вы изучили кодировки символов, используемые для преобразования между
байтами и текстом, а также освоили отличия завершителей строк в разных
операционных системах. Наконец, вы научились читать изаписывать текстовые
файлы с использованием метода Path. open () и встроенной функции open().
Упражнения
1. Запишите следующий текст в файл starships.txt, хранящийся в вашем
домашнем каталоге:
Discovery
E nte rpr ise
Defiant
Voyager
Каждое слово должно располагаться в отдельной строке.
298 ГЛАВА 12 Операции ввода и вывода с файлами
2. Прочитайте файл starhips.txt, созданный в упражнении 1, и выведите
каждую строку текста в файле. В выводе не должно быть лишних пустых
строк между словами.
3. Прочитайтефайл starhips.txt и выведите слова, начинающиеся с буквы О.
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]))
2
3
3
3
3
3
for temp in temperature_readings[l:]:
file.write(f",{temp}")
Фрагмент создает в домашнем каталоге файл с именем temperatures.csv и от
крывает его в режиме присоединения. В новой строке от конца файла первое
значение списка temperature_readings записывается в файл. Затем в той же
строке записываются все остальные значения в списке, перед каждым из кото
рых вставляется запятая.
В итоге в файл будет записана строка текста "68,65 ,68, 70,74,72 ". Чт о бы у бе
диться в этом, прочитайте текст из файла:
>>> with file_path.open(mode="r", encoding="utf-8") as file:
text = file.read()
»> text
'68, 65, 68, 70 ,7 4, 72'
12.6 . Чтение и запись данных CSV 299
Такой формат называется 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_te m per atur es =
[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:
>>> for temp_list in daily_temperatures:
writer.writerow(temp_list)
19
19
19
12.6 . Чтение и заnись данных CSV 301
Как и метод . write() объекта файла, . writerow() возвращает количество симво
лов, записанных в файл. Каждый списокиз daily_temperatures преобразуется
в строку, содержащую значения температуры, разделенные запятыми, и каждая
из этих строк состоит из 19 символов.
Теперь закройте файл:
»> file.close()
Открыв файл temperatures.csv в текстовом редакторе, вы увидите в нем следу
ющ ий тек ст:
68, 65, 68, 70, 74, 72
67,6 7,70, 72, 72, 70
68,70, 74, 76, 74, 73
В приведенных примерах командаwith не использовалась для записи в файл,
чтобы вы могли проанализировать каждую операцию в интерактивном окне
IDLE. На практике гораздо лучше использовать with.
Вот как выглядит код с использованием команды wi th:
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.opeп(mode="r", eпcodiпg="utf-8", пewliпe="")
>>> reader = csv.reader(file)
Вызов csv. reader() возвращает объект чтения CSV, который может использо
ваться для перебора строк в файле CSV:
>>> for row iп reader:
p riпt( row)
['68', '65'' '68'' '70''
['67'' '67'' '70'' '72',
['68'' '70'' '74'' '76''
»> file.close()
'74', '72']
'72'' '70']
'74'' '73']
Каждая строка файла CSV возвращается в виде списка строк. Чтобы восстано
вить список списков daily_temperatures, необходимо преобразовать каждый
список строк в список целых чисел при помощи генератора списка.
Ниже приведен пример, в котором мы открываем файл CSV командой wi th,
читаем каждую строку в файле CSV, преобразуем список строк в список целых
чисел, азатем сохраняемкаждый списокцелых чисел в списке списков с именем
daily_temperatures:
>>> # Создание пустого списка
>>> daily_temperatures = []
>» with file_path.opeп(mode="r", eпcodiпg="utf-8", пewliпe="") as file:
reader = csv.reader(file)
for row iп reader:
# Преобразование строки в список целых чисел
iпt_row = [iпt(value) for value iп row]
# Присоединение списка целых чисел к списку daily_temperatures
dai ly_ tem per atur es. app eпd (iп t_r ow)
> >> daily_te mpe ra ture s
[[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.DictWriter, который записывает словари с совместно используемыми
ключами в строки файла 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[O].keys(),так как реорlе[О]
словарь, ключами которого являются имена полей. Это полезно, если имена
полей неизвестны или же их столько, что списковый литерал становится не
удобным.
Как и объекты csv.writer, объекты DictWriter содержат методы .writerow()
и .writerows() для записи строк данных в файл. Объекты DictWriter также
содержат третий метод . writeheader(), который записывает строку заголовка
в файл CSV:
>>> writer.writeheader()
10
Метод . wri teheader() возвращает количество символов, записанных
в файл, - в данном случае 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,З,4,5],
[б, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
2. Напишите программу, которая читает числа в файле numbers.csv из
упражнения 1 в список списков целых чисел с именем numbers. Выведите
прочитанный списоксписков. Результат должен выглядеть так:
[[1, 2, З, 4, 5], [б, 7, 8, 9, 10], [11, 12, 13, 14, 15]]
3. Напишите программу, которая записывает следующий список словарей
в фaйлfavonte_colors.csv в вашем домашнем каталоге:
favorite_colors = [
{"name": "Joe", "favorite_color": "Ыuе"},
{"name": "Anne", "favorite_color": "green"},
{"name": "Bailey", "favorite_color": "red"},
4. Выходной файл CSVдолжен иметь следующий формат:
name,favorite color
Joe,Ыue
Anne,green
Bailey, red
5. Напишите программу, которая читает данные из фaйлafavonte_colors.csv
(см. упражнение 3) в список cлoвapeйfavonte_colors. Выведите список
словарей. Результат должен выглядеть примерно так:
12.8 . Итоги и дополнительные ресурсы
307
[{"name": "Joe", "favorite_color": "Ыuе"},
{"name": "Anne", "favorite_color": "green"},
{"name": "Bailey", "favorite_color": "red"}]
12.7. ЗАДАЧА: СОЗДАНИЕ СПИСКА РЕКОРДОВ
В папкеpractice_files находитсяфайл CSVscores.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
О_ О,22
Misha46,25
Empiro,23
Ма ххТ, 25
L33 tH 4x ,42
johnsmith,30
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
12.8. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе я рассказал о файловой системе и о путях к файлам, а также пока
зал, как работать с ними при помощи модуля pathlib стандартной библиотеки
Python. Вы научились создавать новые объекты Path, обращаться к компонентам
путей, а также создавать, перемещать и удалять файлы и папки.
Также вы узнали, как читать и записывать обычные текстовые файлы методом
Path. open () и встроенной функцией open () и как работать с файлами CSV
308 ГЛАВА 12
·
Операции ввода и вывода с файлами
(значения, разделенные запятыми) с использованием модуля csv из стандарт
ной библиотеки Python.
ИНТЕРАКТИВНЫЙТЕСТ
К этой главе прилагается бесплатный интерактивный тестдля проверкиусво
енных вами знаний. Тестдоступен на телефоне или компьютере:
rea/ py thon .c oml quizz es !py bas ic s-f i/ es
Дополнительные ресурсы
За дополнительной информацией о модулях и пакетах обращайтесь к следу
ющим ресурсам:
• «Reading andWriting Files in Python» (https.j/realpython.com/read-write-
files-python/)
• «Working With Files in Python» (https.j/realpython.com/working-with-files-
in-python/)
ГЛАВА 13
Установка пакетов
•
с помощью р1р
До сих пор мы работали в рамках стандартной библиотеки Python. Изучая
остальной материал нашего учебного курса, вы будете работать с различными
пакетами, не включенными в Python по умолчанию.
Для многих языков программирования существует менеджер пакетов, авто
матизирующий процесс установки, обновления и удаления сторонних пакетов.
И Python - не исключение.
Менеджер пакетов, ставший де-факто стандартным для Python, называется pip.
Традиционноего приходилось загружать иустанавливать отдельноот Python.
Начиная с версии 3.4 он включен в большинство дистрибутивов языка.
В этой главе вы узнаете:
• как установить и управлять сторонними пакетами в pip;
• как овы пр еиму щес тва и риски использ ования с торон них п ак ет ов .
Итак, за дело!
13.1. УСТАНОВКА СТОРОННИХ ПАКЕТОВ
С ПОМОЩЬЮ PIP
Менеджер пакетов Python - pip - используется для установки и управления
сторонними пакетами. Эта программа существует отдельно от Python, хотя
весьма вероятно, что она была установлена на вашем компьютере, когда вы
загру жали и уст анавливали P ytho n.
pip работает через командную строку. Это означает, что программа должна за
пускаться из командной строки или программы-терминала. Способ открытия
терминала зависит от операционной системы.
31 О ГЛАВА 1 З Установка пакетов с помощью pip
Windows
Нажмите клавишу Windows, затем введите cmd и нажмите Enter, чтобы открыть
приложение Командная строка (Command Prompt). На экране появляется окно,
которое выглядит примерно так, как показано ниже:
Также можно воспользоваться приложением PowerShell: нажмите клавишу
Windows, введите powershellи нажмите Enter, появится окно PowerShell.
13.1 . Установка сторонних пакетов с помощью pip 311
mac OS
Н аж м и т е Сmd+пробел, что бы отк рыть окн о п оиска S potlig ht. Введите кома нду
terminal и нажмите Enter, чтобы запустить приложение Terminal. Открывшееся
ок но в ыгля дит так:
Ubuntu Linux
Щелкните на кнопке Show Applications в нижней части панели инструментов
и проведите поискпо строке terminal. Щелкните на значке приложенияTerminal,
чтобы открыть терминал. Открывшееся окно терминала выглядит примерно так:
312 ГЛАВА 1 З Установка пакетов с помощью pip
Проверка установки pip
Открыв окно терминала, убедимся в том, что программа pip установлена на
вашем компьютере. Конкретный способ проверки зависит от операционной
системы.
В Windows введите следующую команду для проверки pip:
$ python -m pip --version
В macOS и Linux наличие установленной копии pip проверяется следующей
командой:
$ pythonЗ -m pip --version
Если программа pip установлена, то в окне терминала должна появиться ин
формация, которая выглядит примерно так:
p i p 20.2. З f ro m c :\use rs\D avid \app data \loc al \prog ra m s\python\
python\lib\site-packages\pip (python 3.9)
Из вывода следует, что в настоящее время в системе установлена версия
pip 20.2 .3 и она связана с установкой Python 3.9 .
ВАЖНО!
Если вы не получили никакого результата при проверке pip или было вы
ведено сообщение об ошибке, попробуйте выполнить команду pythonЗ. 9
-m pip --version. Возможно, все последующие команды pythonЗ придется
заменить на pythonЗ. 9.
Обновление pip до последней версии
Прежде чем двигаться дальше, убедитесь в том, что у вас установлена последняя
версия pip. Чтобы обновитьpip, введите следующую команду вокне терминала
и нажмите Enter:
$ pythonЗ -m pip install --upgrade pip
Если доступна б~лее новая версия pip, она будет загружена и установлена ав
томатически. В противном случае появится сообщение о том, что в системе уже
установленасамая последняя версия: «Requirement already satisfied» - или
что-нибудь в этом роде.
13.1. Установка сторонних пакетов с помощью pip 31З
ВАЖНО!
В этом разделе все приводимые команды начинаются с pythonЗ -m pip. Это
важно для пользователей macOS и Liпux, потому что команда python -m pip
может установить команды для неправильной версии Python.
Но если вы являетесь пользователем Windows, вам придется использовать
python -m pip, потомучто команда pythonЗ -m pipне будет работать на вашем
ко мпью тере .
Итак, программа pip обновлена до последней версии - давайте посмотрим, что
она может делать!
Вывод списка всех установленных пакетов
Вы можете воспользоваться pip для вывода списка установленных пакетов.
Посмотрим, какие пакеты доступны в настоящий момент. Введите следующую
команду в окне терминала:
$ руthопЗ -m pip list
Если никакие пакеты еще не установлены (например, если вы начали этот учеб
ный курс с установки Python 3.9), pip выведет примерно такую информацию:
Package
Ve rsio п
pip
19.3 .1
se tup tools 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)
1•••••••••••••••••••••••••••••••• 1 бlkB 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)
1 ••••••••• •••••••••••••••••••••••1 133kB 3.3MB/s
Co llec ting ce r tifi> = 2017. 4. 17
Downloading https:// ... certifi-2019 .11 .28.py3-none-any.whl (156kB)
1••••••••••••••••• •••••• •••••••••1 163kB . ..
Collecting chardet<3.1.0 ,> =3.0.2
Downloading https://...chardet-3.0 .4-py2.py3-none-any.whl (133kB)
1••••••••••••••••••••••••• ••••••• 1 143kB 6.8MB/s
Collecting idna<2.9 ,> =2 .5
Downloading https://...idna-2.8-py2.py3-none-any.whl (58kB)
1 • •••••• • •••••••••••••• •• •••••••• 1 бlkB 3.8MB/s
Installing collected packages: urllibЗ, certifi, chardet, idna,
re que sts
Successfully installed certifi-2019 .11.28 chardet-3.0.4 idna-2.8
requests-2.22 .0 urllib3-1.25 .7
ПРИМЕЧАНИЕ
Форматирование было слегка изменено, чтобы вывод нормально помещался
на странице. Результат, который вы увидите на своем компьютере, может не
сколько отличаться от приведенного.
Обратите внимание: сначала pipсообщаето загрузке requests. Вы видите URL -
aдpec,с которого pipустанавливает пакет, атакже индикатор, демонстрирующий
ход загрузки.
После этого мы замечаем, что pip устанавливает еще четыре пакета: chardet,
certifi, idпa и urllibЗ. Они называются зависимостями пакета 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 и urllibЗ.
По умолчанию pip устанавливает последнюю версию пакета. Вы также можете
управлять тем, какая версия пакета будет установлена, при помощи необяза
тельных спецификаторов версии.
Установка конкретных версий пакетов
Существует несколько способов, как установить именно ту версию, которую
вы хотите. Например:
1) последнюю версию с номером, большим заданного номера;
2) последнюю версию с номером, меньшим заданного номера;
3) последнюю версию с конкретным номером .
Чтобы установить последнюю версию requests с номером версии 2 и выше,
выполните следующую команду:
$ python3 -m pip install requests>=2.0
Обратите внимание на выражение >=2. 0 после имени пакета requests. Оно при
казывает pipустановить версию requests, номер которой больше или равен 2 .0 .
Выражение >= называется спецификатором версии; оно указывает, какая
версия пакета должна быть установлена . Есть несколько разных специфика
торов, которые вы можете использовать в командах. Чаще всего встречаются
следующие спецификаторы.
---------------------- ----- -~ -. .. - .- :~ ~ - - -- ~ -~ -
СПЕЦИФИКАТОР ВЕРСИИ
<=, >=
<,>
ОПИСАНИЕ
Меньше или больше спецификатора (включительно)
Меньше или больше спецификатора (не включая)
В точности равно спецификатору
316 ГЛАВА 1 З Установка пакетов с помощью pip
Рассмотрим несколько примеров.
Чтобы установить последнюю версию, номер которой меньше либо равен за
данной, используйте спецификатор версии<=:
$ pythonЗ -m pip install requests<=З.0
Команда установит последнюю версию requests, номер которой меньше либо
равен 3.0.
Спецификаторы версии<= и >= инклюзивны, то есть они включают номер, сле
дую щий за спецификатором. Так же сущест вуют экс кл юзи вн ые спецификато ры,
не включающие номер: спецификаторы< и>.
Следующая команда устанавливает последнюю версию requests, номер которой
строго меньше 3.0:
$ pythonЗ -m pip install requests<З.0
Спецификаторы можно объединять, чтобы программа pip всегда устанавли
вала версию в заданном диапазоне номеров. Например, следующая команда
устанавливает последнюю версию requests из серии 1.0:
$ pythonЗ -m pip install requests>=l.0,<2.0
Такие команды стоит использовать в том случае, если ваш проект был совместим
только с версией 1.0 пакета и вы хотите установить последние обновления для
этой серии.
Наконец, зависимости можно привязать к конкретной версии спецификатором
версии==:
$ pythonЗ -m pip install requests==2.22 .0
Эта команда устанавливает фиксированную версию 2.22 .0 пакета request.
Вывод подробной информации о пакетах
Теперь, когда пакет requests установлен, вы можете воспользоваться pip для
просмотра подробной информации о пакете:
$ pythonЗ -m pip show requests
Name: requests
Version: 2.22.0
Summary: Python НТТР 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, urllibЗ
Required-by:
Команда pythonЗ -m pip show выводит информацию об установленном пакете,
включая имя автора, адрес электроннойпочты и домашнюю страницу в интер
нете, на которой вы сможете больше узнать о том, что делает пакет.
Пакет requests используется для выдачи запросов НТТР из программы Python.
Этот пакет, чрезвычайно полезный во многих областях, стал зависимостью для
большого числа других пакетов Python.
Удаление пакета
Если пакет можно установить припомощи pip, то вполнелогично, что еготакже
можно удалить. Давайте удалим пакет requests.
Чтобы удалить его, введите следующую команду в окне терминала:
$ pythonЗ -m pip uninstall requests
ВАЖНО!
Если у вас уже имеются проекты, использующие requests или одну из его
зависимостей, возможно, вам не стоит выполнять команды, о которых мы
расскажем далее в этом разделе.
Не ме дл ен но появитс я сл ед ующ ее приглашение:
Unin stal ling re qu est s-2 .2 2. 0:
Would remove:
c:\users\damos\...\requests-2.22.0 .dist-info\*
c:\users\damos\a...\requests\*
Pro ceed ( y/n) ?
Прежде чем удалять что-нибудь с вашего компьютера, pip сначала спрашивает
у вас разрешение. Как предусмотрительно!
Введите у и нажмите Enter, чтобы продолжить. Появится сообщение, которое
подтверждает, что пакет requests был удален:
Successfully uninstalled requests-2.22 .0
318 ГЛАВА 1 З Установка пакетов с помощью pip
Сно ва просмо трите спи сок па ке то в:
$ python3 -m pip list
Package Version
cer ti fi
2018.4 .16
cha rde t
3.0.4
idna
2.7
pip
10.0.1
set uptoo ls 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
se tupt ools 39. 0. 1
Экосистема сторонних пакетов - одна из самых сильных сторон Python. Эти
пакеты позволяют программистам Python работать чрезвычайно эффективно
и создавать полнофункциональные программные продукты намного быстрее,
чем это можно сделать па таком языке, как, допустим, С++. Тем не менее ис
пользование сторонних пакетов имеет некоторые особенности, к которым сле
дует относиться с осторожностью. Об этом я расскажу в следующем разделе.
13.2 . ПОДВОДНЫЕ КАМНИ СТОРОННИХ ПАКЕТОВ
Элегантность сторонних пакетов заключается в том, что они позволяют легко
добавить в проект н ов ую функцио нальност ь, и вам н е прих одитс я кодировать
13.2. Подводные камни сторонних пакетов
319
все с нуля. Тем самым достигается значительное повышение производитель
ности.
Однако чем больше мощность, тем выше ответственность. Как только вы
включаете в свой проект чей-то пакет, вы оказываете огромное доверие тем,
кто занимался его разработкой и сопровождением.
Используя пакет, который разрабатывал кто-то другой, вы отчасти теряете
контроль над своим проектом. Разработчики, занимающиеся сопровождением
пакета, могут выпустить новую версию с изменениями, несовместимыми с той
версией, которая используется в вашем проекте.
По умолчанию pip устанавливает новейшую версию пакета. Если вы передадите
свой код другим разработчикам, которые установят обновленную версию исполь
зуемого в вашей программе пакета, возможно, они не смогут запустить ваш код.
Это создает значительную проблему как для вас, так и для конечного пользо
вателя. К счастью, Python предоставляет решение для этой достаточно частой
проблемы: виртуальные среды.
Виртуальная среда создает изолированную и, что самое важное, - воспроиз
водимую среду, которая может использоваться для разработки проекта. Среда
содержит конкретную версию Python, а также конкретные версии зависимостей
вашего проекта.
Когда вы передаете свой код кому-то другому, у получателя остается возмож
ность воспроизвести среду и быть уверенным в том, что ему удастся выполнить
код без ошибок.
Виртуальные среды - достаточно сложная тема, рассказ о которой выходит за
рамки нашей книги. Чтобы больше узнать о виртуальных средах и о том, как их
использовать, обращайтесь к учебному курсу «Managing Python Dependencies
With Pip and Viгtual Environments» (https//realpython.com/products/managing-
python-dependencies/) на сайте Real Python. В этом курсе вы узнаете, как:
• устанавливать сторонние пакеты Pythonсиспользованиемменеджера
пакетов pip в Windows, macOS и Liпux на более высоком уровне, чем
описано здесь, а также как их использовать и управлять ими;
• изолировать эависимости проектов в виртуальных средах для предот
вращения конфликтов версий в ваших проектах Python;
• применять полный процесс поиска и идентификации качественных
сторонних 11акетов, состоящий из семи этапов, в ваших проектах Python
(и для обоснования ваших решений у коллег и руководства);
320 ГЛАВА 1 З Установка пакетов с помощью pip
• создавать воспроизводимые среды разработки и развертывания прило
жений с использованием менеджера пакетов pip и файлов требований.
13.З. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы научились устанавливать сторонние пакеты с помощью pip -
менеджера пакетов языка Python. Вы узнали несколько полезных команд pip,
включая pip install, pip list, pip show и pip uninstall.
Также вы узнали о некоторых проблемах, которые иногда возникают при ис
пользовании сторонних пакетов. Не каждый пакет, который может быть загру
жен в pip, подойдетдля вашего проекта. Так как код установленного пакета вы
не контролируете, вам приходится верить, что пакет безопасен и будет хорошо
работать у пользователей вашей программы.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com!quizzeslpybasics-installing-packages
Дополнительные ресурсы
За дополнительной информацией об управлении сторонними пакетами об
ращайтесь к следующим ресурсам:
• «Managing Python Dependencies~ (https:j/realpython.com/products/
managing-python-dependencies/)
• «PythonVirtualEnvironments: А Primer~ (https:j/realpython.com/python-
virtua/-environments-a-primer/)
ГЛАВА 14
Создание и изменение
файлов PDF
PDF (PortaЫe Document Format) - один из самых известных форматов для
распрост ранения докум ентов в и нт е рн е т е. Фа йлы PDF моrу т содер жать те кст,
rрафику, таблицы, формы, мультимедийный контент (например, видео или
анимации) - и все это в одном файле.
Разнообразие типов контента усложняет работу с PDF. Столько разных видов
данных, которые необходимо декодировать при открытии файла PDF! К сча
стью, в экосистеме Python существуют отличные пакеты для чтения, обработки
и создания фа йл ов PD F.
В этой rлаве вы научитесь:
• читать текст из файла PDF;
• разбивать файл PDFна несколькофайлов;
• объеди нять несколько ф айл ов P DF ;
• поворачивать и обрезать страницы в файлах PDF;
• шифровать и дешифровать файлы PDF с паролем;
• создавать файл PDF с нуля.
Итак, за дело!
14.1. ИЗВЛЕЧЕНИЕ ТЕКСТА ИЗ ФАЙЛА PDF
В этом разделе вы научитесь читать файлы PDF и извлекать из них текст при
помощи пакета PyPDF2. Но прежде чем пользоваться пакетом, необходимо
установить его с помощью pip:
$ pythonЗ -m pip install PyPDF2
322 ГЛАВА 14 Создание и изменение файлов PDF
Проверьте установку, выполнив следующую команду в терминале:
$ pythonЗ -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 и чтения информации об этом файле. В примерах
мы используем файл Pri.de _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() /
"p ython- basic s- exe r cise s" /
" ch14-interact-with-pdf -files" /
"practice_files" /
14.1 . Извлечение текста из файла PDF 323
"Pride_and_Prejudice.pdf"
Переменная pdf_pathтеперь содержит путь к РDF-версии романа Джейн Остин
«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() записано в смешанном реги
стре, а не в нижнем с подчеркиваниями, как рекомендовалось в РЕР 8. Пом
ните: в РЕР 8 содержатся рекомендации, а не правила. С точки зрения Python
смешанный регистр абсолютно законен.
ПРИМЕЧАНИЕ
Пакет PyPDF2 происходитот пакета pyPdf. Он был написан в 2005 году, всего
через 4 года после публикации РЕР 8.
В это время многие программисты переходили на Python с языков, в которых
смешанный регистр был более распространенным.
324 ГЛАВА 14 Создание и изменение файлов PDF
Также к информации документа можно обратиться через атрибут .documentinfo:
>>> pdf.documentlnfo
{'/Title': 'Pride and Prejudice, Ьу Jane Austen', '/Author': 'Chuck',
'/Creator': 'MicrosoftФ Office Word 2007',
'/CreationDate': 'D:20110812174208', '/ModDate': 'D:20110812174208',
'/Producer': 'MicrosoftФ Office Word 2007'}
Объект, возвращаемый .documentinfo, выглядит как словарь, нов действитель
ности им не является. К каждому элементу в .documentinfoможно обращаться
как к атрибуту.
Например, для получения названия используется атрибут. title:
>>> pdf.documentlnfo.title
'Pride and Prejudice, Ьу Jane Austen'
Объект . documentinfo содержит метаданные PD F, указанные при создании PDF .
Класс PdfFileReader предоставляет все методы и атрибуты, необходимые для
обращения к данным в файлах PDF. Давайте посмотрим, что можно сделать
с файлом PDF и как это делается.
Извлечение текста из страницы
Страницы PDF представлены в PyPDF2 классом PageObject. Экземпляры
PageObject используются для взаимодействия со страницами в файлах PDF.
Создавать экземпляры PageObject напрямую не нужно. Вместо этого к ним
можно обращаться при помощи метода .getPage() объекта PdfFileReader.
Извлечение текста из одной страницы PDF выполняется в два этапа.
1. Получение PageObject вызовом PdfFileReader.getPage().
2. Извлечение текста в виде строки методом
.extractтext()экземпляра
PageObject.
Файл Pride_and_Prejudice.pdfсостоит из 234 страниц. Каждой странице при
своен индексот 0 до233. Чтобы получить объектPageObject, представляющий
конкретную страницу, передайте индекс страницы при вызове PdfFileRea-
der.getPage():
>>> first_page = pdf .getPage(0}
. getPage() returns а PageObject:
>>> type(first_page)
<class 'PyPDF2 .pdf.PageObject'>
14.1 .Извлечение текста из файла PDF 325
Текст страницы извлекается методом PageObj ect. extractText():
>>> first_page.extractText()
'
\п \пТhе Project Guteпberg EBook of Pride апd Prejudice, Ьу Jапе
Austeп\п \п\пThis eBook is for the use of апуопе aпywhere at по cost
апd with\п \пalmost по restrictioпs whatsoever. You may сору it,
give it away оr\п \пrе\п-\пusе it uпder the terms of the Project
Guteпberg Liceпse iпcluded\п \пwith this eBook or опliпе at
www.guteпberg.org\п \п \п \пTitle: Pride апd Prejudice\п \n
\пAuthor: Jane Austen\п \n \nRelease Date: August 26, 2008
[EBook #1342]\п\n[Last updated : August 11, 2011]\n \п \nlaпguage:
Eng\nlish\n \п \nCharacter set eпcodiпg: ASCII\п \n \п***
START OF THIS PROJECT GUTENBERG ЕВООК PRIDE AND PREJUDICE ***\п \п
\n \n \n \nProduced Ьу Aпonymous Volunteers, and David Widger\п
\п \п \п \п \п \п \nPRIDE AND PREJUDICE \п \n \nBy Jane
Austeп \n \n\n \n \nCoпtents\п \n'
Результат был дополнительно отформатирован, чтобы он лучше выглядел на
странице. Вывод, который вы получите на своем компьютере, может быть от
форматирован иначе.
ПРИМЕЧАНИЕ
В зависимости отспособа кодированияфайла PDF текст, прочитанный из PDF,
может содержать странные символы или же в нем могут пропасть разрывы
строк. Это оборотная сторона чтения текста из PDF. В реальных приложениях
текст, прочитанный из PDF, иногда приходится чистить вручную.
КаждыйобъектPdfFileReaderсодержитатрибут .pages, который можетисполь
зоваться для последовательного перебора всех страниц файла PDF. Например,
следующий цикл for выводитсодержимоевсех страниц PDFстекстом романа
«Pride and Prejudice~:
>>> for page iп 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() /
"pyt hon-b asic s- exe r cise s" /
"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:
#з
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 для файла Pnde_and_
Prejudice.txt в вашем домашнем каталоге; он присваивается переменной
output_file_path.
2. Затем output_file_path открывается в режиме записи, а объект файла,
возвращенный вызовом . open (), присваивается переменной output_
file. Команда with, о которой вы узнали в главе 12 «Операции ввода
и вывода с файлами>->, обеспечивает закрытие файла при выходе из
блок а wit h.
3. Далее в блоке with название PDF и количество страниц записываются
в текстовый файл вызовом output_file. write ().
4. Наконец, программав циклеfor перебирает всестраницы PDF. Накаждом
шаге цикла следующий объект PageObject присваивается переменной
page. Текст каждой страницы извлекается вызовом page.extractText()
и записывается в output_file.
Сохраните и запустите программу. Она создает в вашем домашнем каталоге
новый файл с именем Pnde_and_Prejudice.txt и полным текстом документа
Pnde_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 с ис пользо ванием Py PDF 2.
Использование класса 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("Ыank.pdf").open(mode="wb") as output_file :
pdf_writer.write(output_file)
»>
Фрагмент создает в текущем рабочем каталоге новый файл с именем Ыank.pdf.
Открыв этот файл в программе просмотра PDF (например, в Adobe Acrobat),
вы увидите документ с одной пустой квадратной страницей с размером стороны
1 дюйм.
ВАЖНО!
Обратите внимание: при сохранении файла PDF файловый объект передается
методу .write() объекта PdfFileWriter, а не методу .write() файлового объекта.
В частности, следующий код работать не будет:
»> with Path("Ыank.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с книгой «Pгide 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() /
" pyth oп- basi cs- exe rcis es" /
" ch14-interact-with-pdf -files" /
"practice_files" /
"Pride_and_Prejudice.pdf"
>>> input_pdf = PdfFileReader(str(pdf_path))
Передайте индекс 0 методу . getPage (), чтобы получить объект PageObj ect,
представляющий первую страницу 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- ba sics- e xer cis es" /
" 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 п in range(l, 4):
»>
page = input_pdf.getPage(n)
pdf_writer.addPage(page)
14.2. Извлечение страниц из файлов PDF 331
Цикл перебирает числа1, 2 и 3, потому что range(l, 4) не включает последнее
число (4). На каждом шагецикла страница с текущим индексом извлекается ме
тодом .getPage(), после чего она добавляется в pdf_writer методом . addPage().
Теперь pdf_writer содержит три страницы, в чем можно убедиться при помощи
. getNumPages():
>>> pdf_writer.getNumPages()
3
:Наконец, извлеченные страницы записываются в новый файл PDF:
»> with Path("chapterl.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("chapterl_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.
Чтобы использовать . appendPagesF romReader (), передайте экземпляр
PdfFileReader в параметре reader метода. Например, следующий код копиру
ет каждую страницу из файла PDF с книгой «Pгide and Prejudice» в экземпляр
PdfFileWriter:
>>> pdf_writer = PdfFileWriter()
>>> pdf_writer.appendPagesfromReader(pdf_reader)
Экземпляр pdf_writer содержит каждую страницу из pdf_reader!
Упражнения
1. Извлеките последнюю страницу из файла Pride_and_Prejudice.pdf и со
храните ее в файле last_yage.pdf, находящемся в домашнем каталоге.
2. Извлеките из файла Pride_and_Prejudice.pdf все страницы с четными
индексами (не номерами страниц!) и сохраните их в новом файле every_
other_yage.pdf в домашнем каталоге.
3. Разбейте файл Pride_and_Prejudice.pdf на два новых файла PDF. Пер
вый должен содержать первые 150 страниц, а второй - все оставшиеся
страницы. Сохраните оба файла в домашнем каталоге с именами part_ 1.
pdf и part_2.pdf.
14.З. ЗАДАЧА: КЛАСС PDFFILESPLIПER
Создайте класс с именем PdfFileSplitter, который читает данные файла
PDF из существующего экземпляра PdfFileReader и разбивает их на два
новых объекта PDF. При создании экземпляра класса должна передаваться
строка пути.
14.4 . Конкатенация и слияние файлов PDF ЗЗЗ
Например, следующая команда создает экземпляр PdfFileSplitter на основе
файла PDF mydoc.pdf в текущем рабочем каталоге:
pdf_splitter = PdfFileSplitter("mydoc.pdf")
Класс PdfFileSplitter должен содержать два метода.
1. Метод . split () получает один параметр breakpoint, в котором передается
целое число - номер страницы, которая становится последней в первой
части разбиваемого файла PDF.
2. Метод .write() получает одинпараметрfilename, в котором передается
строка пути.
После вызова . split() класс PdfFileSplitter должен содержать атрибут
.w riterl, которому присваивается экземпляр 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 означает, что файлы объединяются
последовательно друг за другом, образуя один документ. Например, компания
в конце месяцаможет провести конкатенацию нескольких ежедневных отчетов
в один ежемесячный отчет.
Слияние двух объектов PD F также объединяет их в один файл. Но эта операция
не присоединяет второй файл PDF к концу первого файла. Слиянием можно
вставить файл после заданной страницы первого файла PDF. Затем все страницы
первого файла PDF, следующие после точки вставки, сдвигаются в позицию
после вставленного файла PDF.
Главное отличие между этими двумя операциями заключается в том, что
Pd f File Wr iter м ож ет только п рисоедин ять стр ани цы в конец списка стран иц
(конкатенация), тогда как PdfFileMerger поддерживает вставку в произвольной
позиции (слияние).
Создайте свой первый экземпляр PdfFileMerger. В интерактивном окне IDLE
введите следующий код, чтобы импортировать класс PdfFileMerger и создать
новый экземпляр:
>>> from PyPDF2 import PdfFileMerger
>>> pdf_merger = PdfFileMerger()
Только что созданные объекты PdfFileMerger пусты. Прежде чем вы сможете
с ними что-то сделать, в них необходимо добавить страницы.
Добавитьстраницыв объект pdf_mergerможно двумя способами. Выбор зависит
от того, что именно нужно сделать.
•
. append() выполняет конкатенацию, то есть присоединяет все страницы
существующего документа PDF в конец набора страниц, содержащихся
в PdfFileMerger.
•
. me rge() вставляет все страницы существующего документа РDF после
заданной страницы PdfFileMerger.
В этом разделе будут рассмотрены оба способа. Начнем с . append ().
Конкатенация файлов PDF вызовом .append()
В папке practice_files главы 14 находится подкаталог expense_reports, в котором
содержатся три отчета о расходах сотрудника компании.
Вы хотите выполнить конкатенацию трех файлов PDF и передать их работо
дателю в одном файле PDF, чтобы сотрудник получил компенсацию эа затраты,
связанные с работой.
14.4 . Конкатенация и слияние файлов PDF ЗЗS
Для начала можно воспользоваться модулем pathlib и получить список объ
ектов Path для всех трех отчетов в папке expense_reports/:
>>> from pathlib import Path
>>> reports_dir = (
Path. home() /
"p ython- basic s- exe r cise s" /
"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"):
pr int(pa th. nam e)
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:
pr int( path. na me )
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_m e rge r. appe nd( 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 _m er ge r. wr ite (ou tput_f ile)
>»
Теперь в вашем текущем рабочем каталоге появился файл 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() /
"pyth on-b asic s- exe rc ise s" /
"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,
но перед страницей с индексом 1.
338 ГЛАВА 14 Создание и изменение файлов PDF
Для этого следует вызвать pdf_merger. merge() с двумя аргументами.
1. Первый аргумент - целое число 1 , обозначающее индекс страницы для
вставки оглавления.
2. Второй - строка пути к файлу PDF , содержащему оглавление.
Вот как это выглядит:
>>> pdf_merger.merge(l, str(toc_path))
Все страницы из файла PDF с оглавлением будут вставлены перед страницей
с индексом 1. Так как файл PDF с оглавлением состоит только из одной стра
ницы, он вставляется в позицию с индексом 1. Страница, которой изначально
был присвоен индекс 1, сдвигается в позицию с индексом 2. Страница, которой
изначально был присвоен индекс 2, сдвигается в позицию с индексом з, и т. д.
Теперь запишите файл PDF, полученный в результате слияния, в выходной файл:
»> with Path("full_report.pdf") .open(mode="wb") as output_file:
pdf _me r ger . wr ite( output_ file )
»>
В текущем рабочем каталоге есть фaйлfull_report.pdf. Откройте его в програм
ме просмотра PDF и убедитесь, что оглавление было вставлено в правильной
позиции.
Конкатенация и слияние PDF - чрезвычайно распространенные операции.
Хотя примеры в этом разделе выглядят несколько искусственно, только пред
став ьте, насколько полез ной окажется пр огра мма в сл у ч а е сл иян ия тыся ч PD F
или при автоматизации рутинных задач, ручное выполнение которых заняло
бы слишком много времени.
Упражнения
1. Папка practice_filesглaвы 14 содержит три файла PDF с именами merge1.pdf,
merge2.pdf и mergeЗ.pdf. Используя экземпляр PdfFileMerger, выполните
слияние двух файлов merge1.pdf и merge2.pdf методом .append(). Со
храните результат в файле с именем concatenated.pdf в вашем домашнем
каталоге.
2. Создайте новый экземпляр PdfFileMerger и испольэуйте .merge() для
слияния файла mergeЗ.pdf и файла concatenated.pdf из упражнения 1,
вставив mergeЗ.pdf между двумя страницами concatenated.pdf. Сохраните
новый файл в домашнем каталоге с именем merged.pdf.
14.5. Поворот и обрезка страниц PDF 339
Результатом должен стать файл PDF из трех страниц. На первой странице
должно выводиться число 1, на второй - 2и на третьей - 3.
14.5 . ПОВОРОТ И ОБРЕЗКА СТРАНИЦ PDF
Вы уже узнали, как извлекать текст и страницы из файлов PDF и как выпол-
1111ть конкатенацию и слияние двух и более файлов. Это чрезвычайно распро
страненные операции с 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- bas ics- exe r cise s" /
" ch14-interact-with-pdf -files" /
"practice_files" /
"u gly .pdf "
Теперь создайте экземпляры PdfFileReader и PdfFileWriter:
» > pdf_reader
>» pdf_wr ite r
Pdf Fi leR ea de r( str( pdf _pa th))
PdfFilewriter()
!lаша цель - иснользовать pdf_writer для создания нового файла PDF, в ко
торо:v1 все страню1ы имеют правильную ориентацию. Четные страницы в PDr'
340 ГЛАВА 14 Создание и изменение файлов PDF
ориентированы правильно, но нечетные страницы повернуты на 90° против
часовой стрелки.
Для решения проблемы мы воспользуемся методом PageObject.rota-
teClockwise() . Метод получает целочисленный аргумент (в градусах) и по
ворачивает страницу по часовой стрелке на заданный угол. Например,
. r otateClockwise(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)
»>
ifn%2==0:
page.rotateClockwise(90)
pdf_writer.addPage(page)
ПРИ МЕ ЧА НИЕ
При выполнении приведенного выше цикла в интерактивном окне IDLE вы
увидите довольно обширный вывод. Дело в том, что .rotateClockwise() воз
вращ ает экземпляр P a geO bject .
Пока не обращайте внимания на этот вывод. При выполнении программ из
окна редактора 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,
отмечая, какие из страниц ориентированы неправильно.
На самом деле определить, какие страницы необходимо повернуть, можно на
месте ... Вернее, почти всегда можно.
Посмотрим, как это делается, начиная с создания нового экземпляра
PdfFileReader:
>>> pdf_reader = PdfFileReader(str(pdf_path))
Это необходимо, потому что вы изменили страницы в старом экземпляре
PdfFileReader, повернув их.
Таким образом, создавая новый экземпляр, вы начинаете все с чистого листа.
Экземпляры PageObject поддерживают словарь значений, содержащих инфор
мацию о странице:
>>> pdf_reader.getPage(0)
{'/Contents': [IndirectObject(ll, 0), Indirect0bject(12, 0),
IndirectObject(lЗ, 0), Indirect0bject(14, 0), Indirect0bject(15, 0),
Indirect0bject(16, 0), Indirect0bject(17, 0), Indirect0bject(18, 0)],
'/Rotate': -90, '/Resources': {'/ColorSpace': {'/CSl':
Indirect0bject(19, 0), '/С50': Indirect0bject(19, 0)}, '/XObject':
{'/Im0': Indirect0bject(21, 0)}, '/Font': {'/TTl':
IndirectObject(23, 0), '/ТТ0': Indirect0bject(25, 0)}, '/ExtGState':
{'/GS0': Indirect0bject(27, 0)}}, '/CropBox': [0, 0 , 612 , 792],
'/Parent': IndirectObject(l, 0), '/MediaBox': [0, 0, 612, 792],
'/Туре': '/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(l)
>>> page["/Rotate"]
0
Это означает, что странице с индексом О назначен поворот на -90°. Ин аче г ово ря,
она поворачивается против часовой стрелки на 90°. У с тра ниц ы с и ндек сом 1
угол поворота равен 0, то есть она вообще не поворачивается.
Если повернуть первую страницу вызовом . rotateClockwise(), то значение
/Rotate изменится с -90 на 0:
>>> page = pdf_reader.getPage(0)
>» pa ge[ "/R otate "]
-90
>>> page.rotateClockwise(90)
>>> page["/Rotate"]
0
Теперьвы знаете, как проверитьключ /Rotate,и сможете использовать его для
поворота страниц в фа йле ugl y. pdf .
Прежде всего необходимо заново инициализировать объекты pdf_reader
и pdf_writer,чтобы начать с чистого листа:
>» pdf_reader
»> pdf_writer
PdfFileReader(str(pdf_path))
PdfFileWriter()
Напишите цикл, который перебирает страницы из итерируемого объекта
pdf_reader.pages, проверяет значение /Rotate и поворачивает страницу, если
эт о значение равно -9 0 :
>>> 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 не содержит ключа/Rоtаtе, то при попытке обращения по
этому ключу произойдет исключение KeyError.Для его перехвата можно вос
пользоваться блоком try ... except.
Значение /Rotate не всегда соответствует вашим ожиданиям. Например, если
вы отсканируете бумажный документ, в котором страница была повернута на
90° против часовой стрелки, будет казаться, что содержимое файла PDF по
вернуто. При этом ключ /Rotate может содержать значение 0.
Это одна из многих странностей, которые усложняют работу с файлами PDF.
Иногда достаточно просто открыть PDF в программе просмотра PDF и вручную
выяснить, что здесь происходит.
Обрезка страниц
Другая распространенная операция с файлами PDF - обрезка страниц. На
пример, это нужно для разбиения одной страницы на несколько частей или для
извлечения небольшого фрагмента страницы (подписи, иллюстрации и т. д.).
344 ГЛАВА 14 Создание и изменение файлов PDF
В папке practice_files главы 14 хранится файл с именем half_and_half.pdf. В нем
содержится часть текста сказки Ганса Христиана Андерсена«The Little Meгmaid»
(«Русалочка»).
Каждая страница файла PDF состоит из двух столбцов. Разобьем каждую стра
ницу на две, чтобы на ней был опубликован только один столбец.
Начнем с импортирования классов PdfFileReader и PdfFileWriter из модуля
PyPDF2, а также класса Path из модуля pathlib:
>>> from pathlib import Path
>>> from PyPDF2 import PdfFileReader, PdfFileWriter
Теперь создайте объект Path для файла half_and_half.pdf:
»> pdf_path = (
Path.home() /
"pyth on-ba sic s-e xe rc ise s" /
"ch14-interact-with-pdf -files" /
"practice_files" /
"ha lf_a nd_ha lf. pdf "
Затем создайте новый объект PdfFileReader и получите первую страницу PDF:
>>> pdf_reader = PdfFileReader(str(pdf_path))
>>> first_page = pdf_reader.getPage(0)
Чтобы выполнить обрезку страницы, давайте поближе познакомимся со струк
турой страниц. Экземпляры PageObject (такие, как first_page) имеют атрибут
. mediaBox, который представляет прямоугольную область, определяющую
границы страницы.
Атрибут .mediaBox можно исследовать в интерактивном окне IDLE, прежде
чем использовать его для обрезки страницы:
>>> first_page.mediaBox
Rectangle0bject([0, 0 , 792 , 612])
Атрибут .mediaBox возвращаетобъект RectangleObject. Он определяется в па
кете PyPDF2 и представляет прямоугольную область страницы.
Список [0, 0 , 792, 612] в выходных данных задает прямоугольную область.
Первые два числа показывают координаты х и у левого нижнего угла прямо
угольника. Третье и четвертое числа представляют ширину и высоту прямо-
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[l]
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_yag-e.pdf в текущем рабочем каталоге, вы
увидите, что верхняя часть стран11цы удале11а. Как обре:~ап, страницу так,
чтобы оставался видимым только текст в лсвоii части страницы? Необходимо
уменьшить горизонтальный размер страницы вдвое. Для этого достаточно
изменить координату . upperRight объекта . mediaBox. Давайте посмотрим,
как это делается.
Прежде всего необходимо создать новые объекты PdfFileReader и PdfFileWriter,
потому что вы только что измениm1 нервую страшщу в pdf_reader, азатем до
бавили ее в pdf_writer:
»> pdf_reader
» > pdf_writer
PdfFileReader(str(pdf_path))
PdfFileWriter()
Теперь получим первую страницу PDF:
>>> first_page = pdf_reader.getPage(0)
На этот раз будем работать с кo1111cii псрвоii стра111щы. чтобы только что из
влеченная страница не изменялас1" Для :пого мож110 импортировать модуль
сору из стандартной библиотеки Pytl1011 11 созлап, копию страницы вызовом
deepcopy():
»> import сору
>>> left_side = copy.deepcopy(first_page)
Теперь скорректируем left_sideGc:J 11:шс11с1111я каюrх-л11бо своiiств first_page.
Это позволяет нам позднее исно.•1ь:ювап, first_page ;~ля r1:шлечения текста
правой части страницы.
Пришло время немного посчитат~" Мы уж<' выяс1111ли, •по 11равый верхний
угол . mediaBox необходимо перещ·r·пп1, в нr•11тр верхнего края странины. Для
14.5. Поворот и обрезка страниц PDF 347
этого мы создали новый кортеж, первый компонент которого равен половине
исходного значения, и присвоили его свойству . upperRight.
Начнем с получения текущих координат правого верхнего угла .mediaBox:
>>> current_coords = left_side.mediaBox.upperRight
Затем создадим новый кортеж, первая координата в котором равна половине
значения текущей координаты, а вторая равна исходной:
>>> new_coords = (current_coords(0] / 2, current_coords[l])
Наконец, кортеж с новыми координатами присваивается атрибуту . 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)
»>
Откройте файл croppedyages.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 . ШИФРОВАНИЕ И ДЕШИФРОВАНИЕ
ФАЙЛОВРDF
Иногда файлы PDF защищаются паролями. Пакет PyPDF2 позволяет работать
с зашифрованными файлами PDF, а также добавлять парольную защиту к су
ществующим файлам PDF.
Шифрование файлов PDF
Для добавления парольной защиты к файлам PDF используется метод
. e nc rypt() экземпляра 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- bas ics- ex er cis es" /
" ch14-interact-with-pdf -files" /
"practice_files" /
" new sletter.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)
В этом п ри мер е используется пар оль пользователя " Su p er Secr et " и па роль
владельца "ReallySuperSecret".
Если файл PDF зашифрован паролем, то при попытке открыть его вы должны
ввести пароль для просмотра его содержимого. Защита распространяется и на
чтение из PDFв программе Python. А теперь посмотрим, как дешифровать PDF
средствами м оду ля Py PDF 2.
Дешифрование файлов PDF
Чтобы дешифровать зашифрованный файл PDF, воспользуйтесь методом
.decrypt() экземпляра PdfFileReader.
Метод .decrypt() получает один параметр password, в котором передается па
роль длядешифрования. Уровеньдоступа, который вы получаете при открытии
PDF, зависит от аргумента, передаваемого в параметре password.
350 ГЛАВА 14 Создание и изменение файлов PDF
Сейчас мы откроем зашифрованный файл newsletteryrotected.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):
F il e " c:\r ea lpytho n\ven v\lib\site -pa c kage s\P yPD F2\p df. 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() возвращает целое число - признак успеха дешифрования:
•
0 - пароль указан неверно;
•
1 - правильный пароль пользователя;
•
2 - правильный пароль владельца.
14.7. Задача: восстановление порядка страниц 351
После того как файл был дешифрован, вы можете обратиться к содержимому
файла PDF:
>>> pdf_reader.getPage(0)
{'/Contents': Indirect0bject(7, 0), '/ CropBox': [0, 0, 612, 792],
'/ MediaBox': [0, 0 , 612, 792], '/ Parent': IndirectObject(l, 0),
' / Resources ' : Indirect0bject(8, 0), '/Rotate' : 0, ' /Туре': '/Page'}
Итак, вы научились цзвлекать текст, а также обрезать и поворачивать страницы
файлов так, как сч11таете нужным.
Упражнения
1. Папка practice_files главы 14 содержит файл PDF с именем top_secret.pdf.
Зашифруйте файл с паролем пользователя UnguessaЫe, используя метод
Pd fFi leW rit er.en cry p t().
Сохран11те зашифрованны й файл с именем top_secret_encrypted.pdf
в своем домашнем каталоге.
2. Откройте файл top_secгet_enc1ypted.pdf, созданный в упражнении 1,
расшифруйте его и выведите текст, содержащийся на первой странице
PDF.
14.7. ЗАДАЧА: ВОССТАНОВЛЕНИЕ
ПОРЯДКА СТРАНИЦ
В папке practice_files главы 14 содержится файл PDF с именем scramhled.pdf,
состоя щий из семи страниц. На каждой странице находится число от 1до7 , но
порядок чисел н;:~рушен.
Кроме того, некоторы е страниц "' повер нут ы на 90, 180 или 270 градусов по
часовой стрелке или против нее.
Напишите програм,,1у, которая восста 11авл11вает файл PDF. Для этого она со
ртирует страницы по номеру в тексте и 11ри необ ходимости поворачивает их
в вертикальное положение.
Предполагается, что каждый объект PageObject, прочитанный из scramЫed.pdf,
содержит ключ«/Rotate».
352 ГЛАВА 14 Создание и изменение файлов PDF
Сохраните восстановленный файл PDF в файле с именем unscramhled.pdfв до
машнем каталоге.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
14.8 . СОЗДАНИЕ ФАЙЛА PDF С НУЛЯ
Пакет PyPDF2 отлично подходитдля чтения и изменениясуществующих файлов
PDF, но у него есть принципиальное ограничение: он не может использоваться
для создания новых файлов PDF. В этом разделе мы воспользуемся инструмен
тарием ReportLab Toolkit для генерирования файлов PDF с нуля.
ReportLab представляет собой полнофункциональное решение для создания
PDF. Существует платная коммерческая версия, но также доступна версия
с открытым исходным кодом и ограниченной функциональностью.
Установка repor11ab
Прежде всего следует установить reportlab при помощи pip:
$ python3 -m pip install reportlab
Прове рьте установку ком анд ой pi p sho w:
$ 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 (с) 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.draw5tring(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. По умолчанию используется размер страницы А4, отличный от стандарт
ного для США размера 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, импортируйте объект LЕТТЕR из модуля
pagesize и передайте его в параметре pagesizeпри созданииэкземпляраCanvas:
>>> from reportlab.lib .pagesizes import LETTER
>>> canvas = Canvas("hello.pdf", pagesize=LETTER)
Проверив объект LЕТТЕR, вы увидите, что он содержит кортеж чисел с плава
ющей точкой:
»> LЕТТЕR
(612.0, 792 .0)
Модуль reportlab.lib. pagesize определяет много стандартных размеров
страниц. В таблице перечислены некоторые варианты с указанием размеров.
РАЗМЕРСТРАНИЦЫ
А4
LETTER
LE GAL
TABLOID
РАЗМЕРЫ
210ммх297мм
8,5 дюймов х 11 дюймов
8,5 дюймов х 14дюймов
11 дюймов х 17дюймов
Кроме этих размеров модуль содержит определения всех стандартных размеров
листов ISO 216 (https:j/ru.wikipedia.org/ wiki//S0_216).
Настройка свойств шрифта
Такж е при выводе текста на о бъект Canvas можно изменить шрифт, его размер
и цвет.
Для изменения шрифта и его разм ера используется метод. setFont(). Сначала
создайте новый экземпляр Canvas с именем ф айла /опt-ехатрlе.рd/ и размером
листа Letter:
>>> canvas = Canvas("font-example.pdf", pagesize=LETTER)
Затем выберите шрифтTimesNew Roman, 18 пунктов:
>>> canvas.setFont("Times-Roman", 18)
356 ГЛАВА 14 Создание и изменение файлов PDF
Наконец, выведите строку "Times New Roman (18 pt)" на объект Canvas и со
храните его:
>>> canvas.drawString(l * inch, 10 * inch, "Times New Roman (18 pt)")
>>> canvas.save()
С этими настройками текст будет выводиться на расстоянии 1дюйм от левого
края страницы и 10 дюймов от нижнего края. Откройте фaйлfont-example.pdf
в текущем рабочем каталоге и удостоверьтесь в этом!
По умолчанию доступны три шрифта:
1. "Courier"
2. " Helvetica"
3. "Times-Roman"
Каждый шрифт существует в полужирном и курсивном начертании. Список
всех начертаний шрифтов, доступных в reportlab:
• "Courier"
• "Courier-Bold
• "Co urier-BoldOЫiqu e"
•
"Courier-OЬlique"
• "Helvetica"
•
"Helvetica-Bold"
•
"Helvetica-BoldOЬlique"
•
"Helvetica-OЬlique"
•
"Times-Bold"
•
"T imes-Bolditalic
•
"Times-Italic"
•
"Times-Roman"
Также можно задать цвет шрифта методом . setFillColor( ). В следующем
примере мы создадим файл PDF с именем font-colors.pdf, содержащий текст
синего цвета:
from reportlab.lib.colors import Ыuе
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("Ыue")
canvas.drawString(l*inch, 10*inch, "Blue text")
# Сохранить файл PDF
canvas.save()
Ыuе - объект, импортированный из модуля reportlab.lib . colors module. Этот
модуль содержитнесколькораспространенных цветов. Полныйсписокцветов
хранится в исходном коде reportlab.
Примерами этого раздела мы хотели показать, как работает объект Canvas.
Но это знакомство - поверхностное. С модулем reportlab вы можете создавать
таблицы, формы и даже высококачественную графику с нуля!
В руководстве пользователя Reportlab вы найдете многочисленные примеры
построения документов PDF. Руководство может стать отличной отправной
точкой, если вы захотите больше узнать о создании PDF в Python.
14.9 . ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы научились создавать и изменять файлы PDF средствами пакетов
PyPDF2 и reportlab.
Вы узнали, как средствами пакета PyPDF2:
• читать файлы PDF и извлекать из них текст при помощи класса
PdfFileReader;
• выполнять конкатенацию и слияние файлов PDF при помощи класса
PdfFileMerger;
• поворачивать и обрезать страницы файлов PDF;
• шифровать и дешифровать файлы PDF с паролем.
Также в этой главе я кратко познакомил вас с тем, как создавать файлы PDF
с нуля средствами пакета reportlab. Вы научились:
358 ГЛАВА 14 Создание и изменение файлов PDF
• пользоваться классом Canvas;
• записывать текст в Canvas вызовом .drawString();
• задавать гарнитуру и размер шрифта методом . setFont();
• изменять цвет шрифта вызовом .setFillColor().
Пакет reportlab - мощный инструментдля создания файлов PDF , и вы полу
чили лишь первое представление о его возможностях.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тест для проверки усво
енных вами знаний.Тест доступен на телефоне или компьютере:
realpython.com!quizzes!pybasics-pdf
Дополнительные ресурсы
За дополнительной информацией о работе с файлами PDF в Python обращай
тесь к следующим ресурсам:
• «How toWoгk With а PDF in Python» (https.j/realpython.com/pdf-python/)
• «ReportLab PDF Library User Guide» (https.j/www.reportlab.com/docs/
reportlab-userguide .pdf)
ГЛАВА 15
Базы данных
В главе 12 мы показали, как читать и сохранять данные из файлов средствами
Python. Также для хранения информации часто используются базы данных (БД).
База данных представляет собой структурированную систему хранения данных.
Иногда она состоит из нескольких файлов CSV, упорядоченных в каталогах,
а иногда оказывается гораздо сложнее. Система управления базами данных
(СУБД) - программа, управляющая доступом к БД и взаимодействием с ней.
В комплект поставки Python включена упрощенная система управления база
ми данных SQLite. Она идеально подходит для изучения азов работы с базами
данных.
В этой главе вынаучитесь:
• создавать базу данных SQLite;
• сохранять и читать данные из базы SQLite;
• использовать пакеты для работы сдругими базами данных.
ВАЖНО!
SQLite использует язык SQL(Structured Ouery Language - язык структурирован
ных запросов) для взаимодействия со своей базой данных. Некоторый опыт
работы сSQL пригодится вам при изучении материала этой главы.
Итак, задело!
15.1. ЗНАКОМСТВО С SQLITE
Существует много разных ядер баз данных SQL, и некоторые из них лучше
подходят для каких-то конкретных целей, чем други е. Одно из самых простых
и облегченных ядер баз данных SQL - SQLite - входит в стандартную уста
новку Python, а значит, уже работает на вашем компьютере.
360 ГЛАВА 15 Базы данных
В этом разделе вы научитесь пользоваться пакетом sqlitеЗ для создания новых
баз данных SQLite, а также для хранения и чтения данных.
Основы SQLite
Вот основные этапы работы с SQLite.
1. Импортирование пакета sqliteЗ.
2. Подключение к существующей БД или создание новой.
3. Выполнение команд SQL.
4. Закрытие подключения к БД.
Начнем знакомство с ними в интерактивном окне IDLE. Откройте IDLE и вве
дите следующие команды:
>>> import sqliteЗ
»> connection = sqliteЗ.connect("test_database.db")
Функция sqliteЗ. connect() используется для подключения или создания
ново й базы данных.
При выполнении команды . connect("test_database.db") Python ищет су
ществующую БД с именем "test_database.db". Если БД с таким именем не
найдена, в текущем рабочем каталоге создается новая.
Чтобы создать БД в другом каталоге, необходимо указать полный путь в аргу
менте .connect().
ПРИМЕЧАНИЕ
Также возможно создать базу данных в памяти, передав .conпect () строку
" :memory: ":
connection = sqliteЗ.connect(":memory:")
Этот способ хорошо подходитдля храненияданных, которыедолжны суще
ствовать только во время работы программы.
Аргумент .conпect () возвращает объект sqlitеЗ. Connection. В этом можно
убедиться при помощи type( ):
>>> type(connection)
<class 'sqliteЗ.Connection'>
15.1 .Знакомство с SQLite 361
Объект Connection осуществляет соединение между программой и базой дан
ных. Он содержит набор атрибутов и методов, которые могут использоваться
для взаимод ействия с БД.
Для хранения данных понадобится объект Cursor, который можно получить
вызовом connection.cursor():
>>> cursor = connection.cursor()
>» type(cursor)
< c lass 's qlit eЗ.C ursor '>
Объект sqliteЗ.Cursor становится ~окном~ для взаимодействия с базой дан
ных. При помощи Cursor можно создавать таблицы базы данных, выполнять
команды SQL и получать результаты запроса.
ПРИМЕЧАНИЕ
В терминологии баз данных курсором называется объект, предназначенный
для выборки результатов запроса к базе данных - по одной строке данных
за раз.
Восполь зуемся фу нк ци ей S Q Lit e d a te time ( ) для получ ения значения те кущего
местного времени:
»> query = "SELECT datetime('now ', 'localtime');"
>>> results = cursor .execute(query)
»> results
<sqliteЗ.Cursor object at 0х000001А27ЕВ85Е30>
"SELECТ 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()
»> time
query = "S E LE CT datetime('now', 'localtime');"
results = cursor.execute(query)
row = results.fetchone()
time = row[0]
'2018-11-20 23:14:37'
В этом примере объект Connection, возвращенный sqlitеЗ. 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 ТЕХТ, LastName ТЕХТ, Age INT);
Обратите внимание: после FirstNameи LastName следует слово ТЕХТ, а после 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 sqliteЗ
create_taЫe
CREATE TABLE People(
364 ГЛАВА 15 Базы данных
FirstName ТЕХТ,
La stNa me ТЕХТ,
Age INT
); '""'
inse r t_va lue s
INSERT INTO People VALUES(
'R on',
'Obvious ',
42
);"""
connection = sqliteЗ.connect("test_database.db")
cursor = connection.cursor()
cursor.execute(crate_taЫe)
cursor.execute(insert_values)
connection.commit()
connection.close()
Сначала создаются две строки с командами SQL, которые создают таблицу
People и вставляют в нее данные. Эти строки присваиваются переменным
create_taЫe и insert_values.
Обе команды записываются в синтаксисе с утроенными кавычками, чтобы мы
могли отформатировать код. SQL игнорирует отступы, что позволяет исполь
зовать пробелы в строке для улучшения удобочитаемости кода Python.
Затем мы создаем объект Connection вызовом sqliteЗ. connect() и присваи
ваемего переменной connection. Также можно создать объектCursorвызовом
connection. cursor() и использовать его для выполнения двух команд SQL.
Наконец, метод connection. commit () сохраняет информацию в базе данных.
Этот метод сохраняет внесенные изменения. Если не выполнить connection.
commit (),то таблица People создана не будет.
Сохраните файл и нажмите FS, чтобы запустить программу. База данных test_
database.db содержит таблицу People с одной строкой данных. В этом можно
убедиться в интерактивном окне:
>» connection = sqliteЗ.connect("test_database.db")
>>> cursor = connection.cursor()
>>> query = "SELECT * FROM People;"
>>> results = cursor.execute(query)
>>> results.fetchone()
( 'R on', 'Obvi ous', 4 2)
А теперь перепишем программу с использованием команды with для управления
подключением к базе данных.
15.1 . Знакомство с SQLite
365
Прежде чем что-либо делать, необходимо удалить таблицу People, чтобы со
здать ее заново. Введите следующий код в интерактивном окне, чтобы удалить
таблиц у P eo ple из базы данных:
>>> cursor.execute("DROP TABLE People;")
<sq lit eЗ. Cur sor o bje ct a t 0x000 001F739DB 6650>
>>> connection.commit()
>>> connection.close()
Вернитесь к окну редактора и измените программу следующим образом:
import sqliteЗ
create_taЫe =
CREATE TABLE People(
Fi rs tN ame ТЕХТ,
LastName ТЕХТ,
Age INT
);"""
inse r t_va lue s
INSERT INTO People VALUES(
'R on',
'Ob vi ous',
42
); """
with sqliteЗ.connect("test_database.db") as connection:
cursor = connection.cursor()
cursor.execute(create_taЫe)
cursor.execute(insert_values)
Ни вызов connection.close(),ни вызов connection.commit() не обязательны.
Любые изменения, вносимые в базу данных, будут автоматически сохранены
при завершении выполнения блока with. Это еще одно преимущество исполь
зования команды with для управления подключением к БД.
Выполнение нескольких команд SQL
Сценарий SQL представляет собой набор разделенных точкой с запятой команд
SQL, которые могут выполняться одновременно. Объекты Cursor содержат
метод . executescript () для выполнения сценариев SQL.
Следующая программа выполняет сценарий SQL, который создает таблицу
People и вставляет в нее несколько значений:
import sqliteЗ
sql = """
DROP TABLE IF EXISTS People;
366 ГЛАВА 15 Базы данных
CREATE TABLE People(
FirstName ТЕХТ,
LastName ТЕХТ,
Age INT
)j
INSERT INTO People VALUES(
'R on',
'Obvi ous',
'42'
)j"""
with sqliteЗ.connect("test_database.db") as connection:
cursor = connection.cursor()
cursor.executescript(sql)
Также возможно выполнить несколько сходных команд, вызвав метод
. ex ec utemany() и передав кортеж кортежей, в котором каждый внутренний
кортеж предоставляет информацию для одной команды.
Например, если у вас имеется большой набор записей о людях, которые нужно
вставитьвтаблицуPeople, вы можете сохранить эту информацию в следующем
кортеже кортежей:
people_values = (
("Ron", "Obvious", 42),
("Luigi", "Vercotti", 43),
(" Art hur ", "B elli ng", 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 sqliteЗ
# Получить данные людей от пользователя
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 sqliteЗ.connect("test_database.db") as connection:
c urso r = coпn ect ion. curs or()
cursor.execute(query)
А если имя пользователя содержит апостроф? Попробуйте добавить в таблицу
имя Flannery О'Connor - и вы увидите, что программаперестает работать. Дело
в том, что апостроф - то же самое, что одинарная кавычка, и для программы
все выглядит так, словно код SQL завершается раньше, чем вы предполагали.
В данном случае код только порождает ошибку, что уже достаточно плохо. Од
нако внекоторыхслучаях некорректный ввод может привести к повреждению
всей таблицы. Многие другие трудно прогнозируемые случаи могут нарушить
структуру таблицы SQL и даже удалить части базы данных. Чтобы этого не
произошло, всегда используйте параметризованные команды.
В следующем коде параметризованная команда используется для безопасной
вставки пользовательского ввода в базу данных:
i mpo rt sqlit eЗ
first_name = input("Enter your first name: ")
last_name = input("Enter your last name: ")
age = int(input("Enter your age: "))
data = (first_пame, last_name, age)
with sqliteЗ.connect("test_database.db") as connection:
cursor = connection.cursor()
cursor.execute("INSERT INTO People VALUES(?, ?, ?);", data)
368 ГЛАВА 15 Базы данных
Параметризация также пригодится для обновления строки в базе данных
командой SQL UPDAТE:
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 ТЕХТ,
LastName ТЕХТ,
Age INT
);"""
cursor. executemany("INSERT INTO People VALUES(?, ? , ?) ; " , values)
# Выбр ать все име на и фами лии людей, возр аст к отор ых
# превышает 30 лет
cursor.execute(
" S EL EC T FirstName, LastName FROM People WHERE Age > 30;"
for row in cursor.fetchall():
print( row )
15.2 .Библиотеки для работы с другими базами данных SQL 369
В этой программе мы сначала удаляем таблицу People, чтобы уничтожить из
менения, внесенные в предыдущих примерах этого раздела. Затем мы заново
создаемтаблицу People и вставляем в нее несколько значений. Далее вызовом
. ex ecute() выполняется команда SELECT, которая возвращаетимена ифамилии
всех людей, возраст которых превышает 30.
Наконец, .fetchall () возвращает результаты запроса в виде списка кортежей,
в которомкаждый кортежсодержитодну строку данных изрезультатов запроса.
Если ввести программу в новом окне редактора, а затем сохранить и запустить
файл, в интерактивном окне появится следующий вывод:
('Ron', 'Obvious')
( 'Luigi', 'Vercotti')
Действительно, это единственные люди в базе данных, чей возраст более 30 лет.
Упражнения
1. Создайте новую базу данных, содержащую таблицу Roster. Таблица со
стоит изтрехполей:Name, Species иAge. Столбцы Name и Speciesдолжны
быть текстовыми, а столбец Age должен быть целочисленным полем.
2. Заполните созданную таблицу следующими значениями:
NАМЕ
Benjamin Sisko
Jadzia Dax
Kira Nerys
SPECIES
Human
Trill
Bajoran
40
300
29
3. Обновите поле Name записи Jadzia Dax, чтобы оно содержало значение
Ezri Dax.
4. Выведите значения Name и Age для всех строк данных, у которых поле
Speciesсодержит значение Bajoran.
15.2. БИБЛИОТЕКИ ДЛЯ РАБОТЫ С ДРУГИМИ
БАЗАМИ ДАННЫХ SQL
Если у вас имеется другая база данных SQL, с которой вы бы хотели работать
из кода Python, синтаксис в основном будет идентичен тому, с которым мы вас
только что познакомили для SQLite. Тем не менее вам придется установить
370 ГЛАВА 15 Базы данных
дополнительные пакетыдля взаимодействия с БД, потому что SQLite - един
ственное встроенное решение.
Сущ еству ет много разновидностей SQ L с соо твет ств ующ ими пакет ами P ytho n.
Несколько наиболее распространенных и надежных альтернатив SQLite с от
крытым исходным кодом:
•
pyodbc для работы с базами данных ODBC(OpenDatabase Connectivity),
такими как Microsoft SQL Server;
•
psycopg2для работы с базами данных PostgreSQL;
•
PyMySQL для работы с базами данных MySQL.
Одно из отличий SQLite от других ядер баз данных (не считая синтаксиса кода
SQL, который незначительно различается в разных «диалектах» SQL) заключа
ется в том, что многие ядра баз данных требуют имени пользователя и пароля
для подключения. За информацией о синтаксисе подключения к базе данных
обраща йтесь к док умен тац ии конкретного па кет а.
Пакет SQLAlchemy - еще один популярный инструмент для работы с базами
данных.SQLAlchemy является системой объектно-реляционного отображения
(Object-Relational Mapper - ORM); такие системы используют объектно-ори
ентированную парадигму для построения запросов к базам данных. Их можно
настроить для подключения к разным базам данных. Объектно-ориентиро
ванный подход позволяет создавать запросы без написания низкоуровневых
команд SQL.
15.З. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе я показал, как взаимодействовать с базами данных ядра SQLite,
которое входит в поставку Python. SQLite - компактная и облегченная система
управления базами данных SQL, которая предназначена для хранения и вы
борки данных в программах Python. Для взаимодействия с SQLite в Python
необходимо импортировать модуль sqliteЗ.
Чтобы работать с базой данных SQLite, следует сначала подключиться к суще
ствующей БД или создать новую БД функцией sqlitеЗ. 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
Дополнительные ресурсы
За дополнительной информацией о работе с базами данных обращайтесь к сле
дующим ресурсам:
• «lntroduction to Python SQL Libraries» (https.j/realpython.com/python-
sq l-lib ran es /)
• «PreventingSQL Injection Attacks With Python» (https.j/realpython.com/
prevent-python -sq l-injection/)
ГЛАВА 16
Веб-программирование
Пожалуй , интернет стал самым большим источником информации (и дезин
формации) на планете.
Многие дисциплины: теория обработки данных, бизнес-аналитика, рассле
довательская журналистика - могут извлечь колоссальную пользу из сбора
и анализа данных с веб-сайтов.
Веб-скрапинrом (web scraping) называется процесс сбора и обработки низко
уровневых данных с веб-сайтов. В сообществе Python были разработаны до
статочно мощные средства извлечения веб-данных.
В этой главе вы научитесь:
• парсить данные с веб-сайтов спомощью строковыхметодов ирегулярных
выр ажен ий;
• парситьданные с веб-сайтов с помощью парсера HTML;
• взаимодействовать с формами и другими компонентами веб-сайтов.
При чтении этой главы желательно иметь некоторый опыт работы с HTML.
Итак, за дело!
16.1 . СКРАПИНГ И ПАРСИНГТЕКСТА
С ВЕБ-САЙТОВ
Автоматизированный сбор данных с веб-сайтов называется веб-скрапингом.
Некоторые веб-сайты явно запрещают пользователям извлекать данные с при
менением автоматизированных средств вроде тех, которые мы будем создавать
в этой главе. Нато есть две возможные причины:
·
16.1 .Скрапинг и парсинг текста с веб-сайтов 373
1. У сайта есть веские основания для защиты данных. Например, Google
Maps не позволяет слишком быстро запрашивать слишком много ре
зультатов.
2. Лавина повторных запросов к веб-серверу может забивать полосу про
пускания, замедляя работу сайта для других пользователей, возможно -
с созданием избыточной нагрузки на сервер, так что сайт полностью
перестает реагировать на запросы.
ВАЖНО!
Прежде чемзаниматься скрапингом, всегда проверяйте правила и условия ис
пользования веб-сайта, чтобы узнать,не нарушаетли этих правил обращение
к сайту с помощью автоматизированныхинструментов. Сюридическойточки
зрения веб-скрапинг не имеет однозначной трактовки. Но, пожалуйста,учтите,
что описанные ниже методы могут оказаться незаконными применительно
к веб-сайтам, запрещающим скрапинг.
Попробуем извлечь НТМL-код с одной веб-страницы. Мы будем использовать
страницуна сайте Real Python, созданную специально для этой главы.
Ваша первая программа для веб-скрапинга
В стандартную библиотеку Python входит пакет urllib, содержащий сред
ства для работы с URL-адресами. В частности, urllib. requestmodule со
держит функцию urlopen(), которая служит для открытия URL-aдpeca
в програ мме.
В интерактивном окне IDLE введите следующую команду, чтобы импортиро
вать urlopen():
>>> from urllib.request import urlopen
Веб- страница, которую мы будем открывать, доступна по URL-aдpecy:
>>> url = " http://olympus.realpython.org/profiles/aphrodite"
Чтобы открыть веб-страницу, передайте url функции urlopen():
>>> page = urlopen(url)
urlopen() возвращает объект HПPResponse:
»>page
<http.client.HTTPResponse object at 0xl05fef820>
374 ГЛАВА 16 Веб-программирование
Чтобы извлечь НТМ L-код страницы, сначала вызовите метод . read() объекта
HTTPResponse,которыйвозвращаетпоследовательность байтов. Затемвызовите
метод . decode() для декодирования байтов в строку в кодировке UTF-8:
>>> html_bytes = page.read()
»> html = html_bytes.decode("utf-8")
Далее выведите НТМL-код, чтобы просмотреть содержимое неб-страницы:
>» 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>
< /bo dy>
</h t ml>
После получения НТМL-кода в текстовом виде вы сможете извлечь из него
информацию несколькими способами.
Извлечение текста из HTML строковыми методами
Один из способов получения информации из НТМL-кода неб-страницы основан
на использовании строковых методов. Например, можно при помощи метода
. 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 = htm l[sta rt_in dex:e nd_index]
» > title
'Profile: Aphrodite'
Реальная разметка HTML может быть куда более сложной и куда менее предска
зуемой, чем разметка страницы профиля Aphrodite. Другая страница профиля
с менее тр ивиа льно й разметкой H T ML доступн а для в аш и х экспе рименто в по
адресу http.j/ olympus.realpython.org/profiles/poseidon.
Попробуйте извлечь заголовок по этому новому URL-aдpecy таким же способом,
как в предыдущем примере:
>>> url = "http://olympus.realpython.org/profiles/poseidon"
>>> page = urlopen(url)
>>> html = page.read().decode("utf-8")
>>> start_index = html.find("<title>") + len("ctitle>")
>>> end_index = html.find("</title>")
>>> title = html[start_index:end_index]
»> title
'\n<head>\n<title >Profile: Poseidon'
Сюрприз! С заголовком выдается некоторое количество НТМL-кода. Почему?
НТМL-разметка страницы /profiles/poseidon похожа на /profiles/aphrodite,
но есть небольшое различие. В открывающем теге <title> перед закрывающей
угловой скобкой > стоит дополнительный пробел, в результате чего тег ото
бражается в виде <title >.
376 ГЛАВА 16 Веб-nрограммирование
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.
Знакомство с регулярными выражениями
Реrулярные выражения - шаблоны, которые применяются для поиска текста
в строках. Поддержка регулярных выражений в Python осуществляется через
модуль re стандартной библиотеки.
ПРИМЕЧАНИЕ
Регулярные выражения поддерживаются не только в Pythoп. Это общая кон
цепция программирования, которая может использоваться в любом языке.
Чтобы работать с регулярными выражениями, прежде всего необходимо им
портировать мо дул ь r e:
import re
В регу лярн ых вы ра же ни ях для обозначения элементов ш аб лон а испо льзуют ся
специальные символы, называемые метасимволами. Например, звездочка *
обозначает нуль или более вхождений символа, находящегося непосредственно
перед звездочкой.
В следующем примере функция findall() используется для поиска в строке
люб ого текс та, с оответст вующего за данно му регу лярн ому в ыр аж ени ю:
>>> re.findall("ab*c", "ас")
['ас']
В первом аргументе re. findall () передается регулярное выражение, для ко
торого ищется совпадение, а во втором - проверяемая строка. В приведенном
примере ищется совпадение для шаблона "аЬ*с" в строке "ас".
16.1 . Скрапинг и парсинг текста с веб-сайтов
377
Регулярное выражение "аЬ*с" совпадает с любой частью строки, которая на
чинается с "а" и завершается "с", амежду ними содержится нуль или несколько
вхождений "Ь". re.findall() возвращает список всех совпадений. Строка "ас"
совпадает с этим шаблоном, поэтому она возвращается в списке.
Несколько примеров применения того же шаблона к разным строкам:
>>> re.findall("ab*c", "abcd")
[ 'аЬс']
>>> re.findall("ab*c", "асс")
['ас']
>>> re.findall("ab*c", "аЬсас")
[ 'аЬс', 'ас' ]
>>> re.findall("ab*c", "abdc")
[]
Обратите внимание: если ни одного совпадения не найдено, то findall() воз
вращает пустой с пи со к .
Поиск по шаблону производится с учетом регистра. Если вы хотите искать
совпадение независимо от регистра, передайте третий аргумент со значением
re. IGNORECASE:
>>> re.findall("ab*c", "АВС")
[]
>>> re.findall("ab*c", "АВС", re.IGNORECASE)
[ 'АВС']
Точка обозначает один произвольный символ в регулярном выражении. Напри
мер, поиск всех строк, которые содержат буквы "а" и "с", разделенные одним
символом, выпо лняе тся т а к :
>>> re.findall("a.c", "аЬс")
['аЬс']
>>> re.findall("a.c", "аЬЬс")
[]
»> re.findall("a.c", "ас")
[]
>>> re.findall("a.c", "асс")
['асс']
Шаблон .* в регулярном выражении обозначает произвольный символ, повто
ренный любое количество раз. Например," а. *с" можно использовать для поиска
378 ГЛАВА 16 Веб-программирование
любой подстроки, начинающейся с "а" и заканчивающейся "с", независимо от
того, какая буква - или какие буквы - находятся между ними:
>>> re.findall("a.*c", "аЬс")
[ 'аЬс']
>>> re.findall("a.*c" , "аЬЬс")
[ 'аЬЬс']
>>> re.findall("a.*c", "ас")
['ас']
>>> re.findall("a.*c", "асс")
['асс']
Обычно для поиска совпадения конкретного шаблона в строке используется
функция re.search(). Она несколько сложнее re.findall(), потому что воз
вращает объект MatchObject, в котором хранятся разные группы данных. Дело
в том, что совпадения могут быть найдены внутри других совпадений, и re.
search() возвращает все возможные результаты.
Подробности строения MatchObject сейчас несущественны. Пока вам доста
точно знать, что вызов .group() для MatchObject возвращает первый результат
с наибольшим охватом; в большинстве случаев это именно то, что вам нужно:
»> match_results = re.search("ab*c", "АВС", re.IGNORECASE)
>>> match_results.group()
'АВС'
В модуле 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
»> string
»> string
"Everything is creplaced> if it's in ctags>."
re.sub("<.*?>", "ELEPHANTS" , string)
"Everything is ELEPHANTS if it's in ELEPHANTS."
Извлечение текста из HTML с использованием
регулярных выражений
Вооружившись этой информацией, попробуем извлечь заголовок из разметки
http .j/o ly mpu s. rea lpy thon .o rg/pr ofile s/dion ys us, к о т о р а я вкл ючает н еаккуратно
записанную строку HTML:
<TITLE >Profile: Dionysus</title / >
У метода .find () здесь возникли бы сложности, но при умном использовании
регулярных выражений можно обработать такую разметку быстро и эффективно:
import re
from urllib.request import urlopen
ur l = "http ://oly mp us. r ea lpy thon. o rg/ pro fil es/ diony sus"
page = urlopen(url)
html = page.read().decode("utf-8 ")
pattern = " ctitle.*?>.*?</title.*?>"
match_results = re.search(pattern, html, re.IGNORECASE)
title = match_results.group()
title = re.sub("< .*?>", "", title) #Удалить теги HTML
print(title)
Давайте детальнее рассмотрим первое регулярное выражение в строке шаблона
и разобьем его на три части.
1. <title. *?>совпадает с открывающим тегом <ПТLЕ >в html. Часть <title
совпадает с <TITLE, потому что re. search() вызывается со значением
re. IGNORECASE , а *? > совпадает со всем текстом после <ПТLЕ вплоть до
первого вхождения>.
2. .*? минимально совпадает со всем текстом после открывающего тега
<ПТLЕ >,останавливаясь на первом совпадении </title. *?>.
380 ГЛАВА 16 Веб-программирование
3. </title. *?>отличается от первого шаблона только использованием
символа/,так что он совпадает с закрывающим тегом </title />в html.
Второерегулярноевыражение,строка"< •*?>",также использует минимальное
выражение .*? для поискавсехтегов HTMLв строке заголовка. Заменяя любые
совпаденияпустой строкой "", re. sub() удаляет теги и возвращает только текст.
При правильном использовании регулярные выражения становятся очень
мощным инструментом . Та информация, с которой мы познакомили вас сейчас,
едва ли дает даже первое представление об их возможностях. За дополнитель
ными сведениями о регулярных выражениях и об их использовании обращай
тесь к курсу из двух частей «Regulaг Expressions: Regexes in Python~ (https:j/
realpython.com/regex-python/) на сайте Real Python.
ПРИМЕЧАНИЕ
Веб-скрапинг можетоказаться утомительной и монотонной работой. Каждый
сайт не похожнадругие, и разметка HTML часто неидеальна. Болеетого, сайты
изменяются со времене,.;. Если программа веб-скрапинга работает сегодня,
это еще не значит, что она будет работать через год - или даже через неделю,
если на то пошло!
Упражнения
1. Напишите программу, которая извлекает весь код HTML веб-страницы
по а д ре с у h ttp: j/olym pus .re alp yth on. org/p rofile s/dion ysu s.
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, выполните следующую команду в своем
терминале:
$ pythonЗ -m pip install beautifulsoup4
Выполните команду pip show, чтобы просмотреть подробную информацию
о только чт о уст ановл енном па кет е:
$ pythonЗ -m pip show beautifulsoup4
Nam e: be autif ulsoup4
Version: 4.9.1
Summary: Screen-scraping library
Home-page: http://www.crummy.com/software/Beautifu1Soup/bs4/
Author: Leonard Richardson
Author-email: leonardr@segfault.org
license: МП
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
url ope n(u rl)
html page.read().decode("utf-8 ")
so up
BeautifulSoup(html, "html.parser")
Программа выполняет три операции.
1. Открывает URL http.j/olympus.realpython.org/profiles/dionysus при по
мощи функции urlopen() из модуля urllib. request.
2. Читает НТМL-код страницы в виде строки и присваивает ее переменной
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-
aдpeca всех изображений на странице. Эти ссылки содержатся в атрибуте src
тегов HTML <img>.
16.2 . Использование парсера HTMLдля извлечения веб-данных 383
В таком случае можно воспользоваться функцией find_all (), возвращающей
список всех экземпляров этого конкретного тега:
> » soup. fin d_all( "im g")
[<img src="/static/dionysus.jpg"/>, <img src="/static/grapes.png"/>)
Функция возвращает список всех тегов <img> в НТМL-документе. Может по
казаться, что объекты в списке являются строками, представляющими теги, но
в действительности это экземпляры класса Tag из библиотеки Beautiful Soup.
Объекты Tag предлагают простой интерфейс для работы с содержащейся в них
инф орма цие й.
Чтобы немного исследовать этот способ, для начала распакуем объекты Tag из
списка:
»> imagel, image2 = soup.find_all("img")
Каждый объект Tag содержит свойство . name , которое возвращает строку с ти
пом тега HTML:
»> imagel.name
'img'
Чтобы обратиться к атрибутам HTML объекта Tag, укажите имя атрибута в ква
дратных скобках (так, как если бы атрибуты были ключами словаря).
Например, тег <img src="/static/dionysus .jpg"/> содержит единственный атри
бут srcсо значением"/static/dionysus. jpg". А тег ссылки <а href="https://
realpython.com" target="_Ыank" >содержит два атрибута, href и target.
Чтобы получить источники изображений на странице профиля Dionysus, вы
обращаетесь к атрибуту src в синтаксисе словарей, упоминавшемся выше:
>>> imagel["src "]
' /static/dionysus.jpg'
>>> image2["src")
' /static/grapes.png'
К некоторым тегам в документах HTML можно обращаться по именам свойств
объектов Tag. Например, для получения тега <title> в документе можно вос
пользоваться свойством . title:
»> soup.title
ct itl e>Pr of i le: Diony susc /title >
384 ГЛАВА 16 Веб-nрограммирование
Обратившись к исходному коду профиля Dionysus (перейдите по адресу http.j/
olympus.realpython.org/profiles/dionysus, щелкните правой кнопкой мыши на
странице и выберите команду просмотра исходного кода страницы), вы увидите,
что тег <title> записан в документе в следующем виде:
<title >Profile: Dionysus</title/>
Beautiful Soup автоматически чистит теги, удаляя лишние пробелы в открыва
ющем теге и лишнюю косую черту / в закрывающем теге.
Также можно получить только строку, заключенную между тегами заголовка,
из свойства . stringобъекта Tag:
>>> soup.title.string
'Profile: Dionysus'
Одна из самых полезных возможностей Beautiful Soup - поиск конкретных
разновидностей тегов, атрибуты которых соответствуют определенным значе
ниям. Например, если вы хотите найти все теги <img>, у которых атрибут src
равен значению /static/dionysusjpg, передайте .find_all () дополнительный
аргумент:
>>> soup.find_all("img", src = "/static/dionysus.jpg")
[<img src="/static/dionysus.jpg"/>]
Пример выглядит искусственно, и, возможно, полезность этого приема сразу
не очевидна. Если вы потратите некоторое время, чтобы помониторить веб
сайты и их исходный код, вы заметите, что многие из них имеют чрезвычайно
сложную структуру HTML.
При веб-скрапинге нас часто интересуют конкретные фрагменты страницы.
Потратив немного времени на просмотр документа HTML, вы сможете иденти
фицировать теги с уникальными атрибутами, которые годятся для извлечения
нужных данных.
И тогда вместо того, чтобы полагаться на сложные регулярные выражения или
проводить поиск по документу с .find (), вы сможете напрямую обращаться
к конкретному интересующему вас тегу и извлекать нужные данные.
В некоторых случаях Beautiful Soup не предоставляет нужной функциональ
ности. Библиотекой lxml (https.j/lxml.de) труднее пользоваться на первых по
рах, но она обладает большей гибкостью при разборе документов HTML, чем
Beautiful Soup. Возможно, вам стоит ознакомиться с ее возможностями, когда
вы будете уверенно чувствовать себя с Beautiful Soup.
16.3 .Работа с НТМL-формами 385
ПРИМЕЧАНИЕ
Парсеры HTML- такие, как Beautiful Soup, - сэкономят вам нем ало врем ени
и усилий при поиске конкретныхданных на веб-страницах.Тем не менее ино
гда НТМL-код написан настолько плохо, чтодаже сложный парсер(например,
Beautiful Soup) не может правильно интерпретировать теги HTML.
В таких случаях обычно приходится извлекать необходимую информацию
самостоятельно (то есть с использованием.find()и регулярных выражений).
Упражнения
1. Напишите программу, которая извлекает весь НТМ L-код веб-страницы
http.j/olympus.realpython.org/profiles.
2. Используя Beautiful Soup, выделите список всех ссылок на странице -
проведите поиск тегов HTML с именем а и получите значение атрибута
hrefдля каждого тега.
3. Извлеките НТМL-код каждой страницы в списке - добавьте полный
путь к файлу и выведите текст (без тегов HTML) на каждой странице,
используя метод . get_text () из библиотеки Beautiful Soup.
16.З. РАБОТА С НТМL-ФОРМАМИ
Модуль urllib,с которым вы работали ранее в этойглаве, хорош длязапроса со
держимого веб-страницы. Однако в некоторых случаях получение нужного кон
тента требует взаимодействия с веб-страницей. Например, пользователь должен
отправить форму или щелкнуть на кнопке, чтобы отобразить скрытое содержимое.
В стандартной библиотеке Python нет встроенных средств для интерактивной
работы с веб-страницами, но в PyPI доступны многочисленные сторонние паке
ты . К их числу принадлежит пакет MechanicalSoup (https.j/github.com/hickford/
Mechanica/Soup) - популярный и достаточно простой в использовании.
В сущн ости, MechaпicalSoup устанавливает консольный браузер, то есть браузер
без графического интерфейса. Работой браузера можно управлять на программ
ном уровне из программы Python.
Установка MechanicalSoup
MechanicalSoup можно установить при помощи pipв терминале:
$ pythonЗ -m pip install MechanicalSoup
386 ГЛАВА 16 Веб-программирование
Команда pip show выводит более подробную информацию о пакете:
$ pythonЗ -m pip show mechanicalsoup
Nam e: M echan ical Sou p
Version: 0.12.0
Summary: А Python library for automating interaction with websites
Home-page: https://mechanicalsoup.readthedocs.io/
Author: UNKNOWN
Author-email: UNKNOWN
License: МП
Location: c:\realpython\venv\lib\site-packages
Requires: requests, beautifulsoup4, six, lxml
Required-by:
В частности, обратите внимание, что на момент написания книги последняя
версия - 0 .12 .0 . Чтобы библиотека Mechanica!Soup была загружена и опознана
после установки, необходимо закрыть и перезапустить сеанс IDLE.
Создание объекта Browser
Введите следующий фрагмент в интерактивном окне IDLE:
>>> import mechanicalsoup
>>> browser = mechanicalsoup.Browser()
Объекты Browser представляют консольный браузер. Их используют для за
проса страницы из интернета, для чего URL-aдpec передается методу . get ( ):
>>> url = " http://olympus.realpython.org/login"
>>> page = browser.get(url)
Переменной page присваивается объект Response, в котором хранится ответ,
полученный от браузера на запрос URL-aдpeca:
»> page
<Response [200]>
Число 200 представляет код состояния, возвращенный запросом. Код состояния
200 означает, что запрос был успешным. Неуспешный запрос может вернуть
код состояния 404, если URL не существует, или код состояния 500, если при
обработке запроса произошла ошибка на сервере.
Mechanica!Soup использует Beautiful Soup для парсинга НТМL-кода из за
проса. Переменная page содержит атрибут . soup, представляющий объект
BeautifulSoup:
16.3 . Работа с НТМL-формами 387
>>> type(page.soup)
<class 'bs4.Beautifu1Soup'>
Чтобы просмотреть НТМL-код, проверьте атрибут . 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.j/olympus.realpython.org/login в браузере и просмотрите
ее, прежде чем двигаться дальше. Попробуйте ввести случайное имя пользовате
ля и пароль. Если данные были указаны неправильно, в нижней части страницы
выводится сообщение «Wгong username or password!».
Но если задать правильные данные (имя пользователя zе u s и пароль
ThunderDude), то вы будете перенаправлены на страницу /profiles.
Следующий пример показывает, как использовать MechanicalSoup для запол
нения и отправки этой формы средствами Python.
Важной частью НТМL-кода является форма ввода регистрационных дан
ных - то есть все, что находится внутри тегов <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")[l]["value"] = "ThunderDude"
#3
profiles_page = browser.submit(form, login_page.url)
Сохраните файл и нажмите FS, чтобы запустить его. Чтобы убедиться в том, что
данные были успешно приняты, введите следующую команду в интерактивном
окн е:
>>> profiles_page.url
'http://olympus.realpython.org/profiles'
Разобьем приведенный выше пример на несколько этапов.
1. Создается экземпляр Browser, который далее используется для запроса
страницы http.jJolympus.realpython.org!login. НТМL-код страницы при
сваивается переменной login_html с использованием свойства . soup.
2. login_html. select ( "form") возвращаетсписок всех элементов <form> на
странице. Так как страница содержит только один элемент <form>, к форме
можно обратиться через элемент списка с индексом 0. Следующие две
строки выбирают поля для имени пользователя и пароля, а также задают
им значения "zeus" и "ThunderDude" соответственно.
3. Форма отправляется вызовом browser. submit (). Методу передаются два
аргумента: объект формы и URL-aдpec страницы login_page, к которому
вы обращаетесь через login_page. url.
В интерактивном окне убедитесь, что при отправке данных успешно происходит
перенаправлениенастраницу/profiles. Если что-то пошло не так, то значение
profiles_page.url останется прежним - "http://olympus.realpython.org/login".
16.3. Работа с НТМL-формами 389
ПРИМЕЧАНИЕ
Хакеры могут использовать подобные автоматизированные программы для
подбора паролей методом полного перебора {брутфорса, brute force): про
граммабыстро проверяет разные комбинации имени и пароля, пока не найдет
работающий вариант.
Имейте в виду, что это считается серьезным правонарушением- обнаружив,
что от вас поступает слишком много запросов, почти все современные сайты
блокируют вам доступ и сообщают ваш IР-адрес надзорным органам. Даже
не пытайтесь!
Итак, значение переменной profiles_page задано. Посмотрим, как на про
граммном уровне получить URL-aдpec каждой ссылки на странице /profiles.
Для этого снова используем метод . select( ), но на этот раз передадим строку
"а" для выбора всех якорных элементов <а> на странице:
>>> 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-aдpeca, содержащиеся в атрибутах href, являются относительными; от
них не будет пользы, если вы захотите перейти к ним позднее при помощи
MechanicalSoup.
Если вам известен полный URL-aдpec , то из него можно выделить часть,
необходимую для построения абсолютного адреса. В нашем случае базо
вый URL-aдpec - http:j/olympus.realpython.org. После этого можно выпол
нить конкатенацию базового URL-aдpeca с относительным 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.j/mechanicalsoup.readthedocs.io/en/staЬle/).
Упражнения
1. Используйте MechanicalSoup для предоставления правильного имени
пользователя ("zeus ") и пароля ("ThunderDude") форме авторизации,
находящейся по адресу http.j/olympus.realpython.org/login.
2. Выведите заголовок текущей страницы, чтобы убедиться, что вы были
перенаправлены настраницу /profiles.
3. Используйте MechanicalSoupдлявозвращения к страницевхода (верни
тесь к предыдущей странице).
4. Введите в форме авторизации неправильное имя пользователя и пароль,
а затем найдите в НТМL-коде возвращенной веб-страницы текст «Wгong
username or password!~, чтобы убедиться в том, что попытка входа за
вершилась неудачей.
16.4. ВЗАИМОДЕЙСТВИЕ С ВЕБ-САЙТАМИ
В РЕАЛЬНОМ ВРЕМЕНИ
Иногда возникает необходимость получить данные в реальном времени с сайта,
где информация постоянно обновляется.
В те времена, когда вы еще не начинали изучать программирование на языке
Python, вам пришлось бы сидеть перед браузером и постоянно обновлять стра
ницу, чтобы узнать, не появился ли обновленный контент. Но теперь процесс
можно автоматизировать методом .get() объекта MechanicalSoup Browser.
Откройте браузер и перейдите на http.j/olympus.realpython.org/dice. Страница
моделирует бросок шестигранного кубика, результат обновляетсяпри каждом
обновлении браузера. Мы напишем программу, которая непрерывно занимается
скрапингом страницы, чтобы извлечь новый результат.
16.4 . Взаимодействие с веб-сайтами в реальном времени
391
Прежде всего необходимо определить, какой элемент страницы содержит
результат броска. Щелкните правой кнопкой мыши в любой точке страницы
и выберите команду просмотра исходного кода страницы. [де-то в середине
НТМL-кода располагается тег <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 10 для обозначения того, что 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(l0)
При запуске программы на консоли немедленно появляетсяпервый результат.
Через 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 секунд, если это н е по след ний запро с
ifi<З:
time . sle ep( 10)
16.5 . Итоги и дополнительные ресурсы 393
Применяя подобные приемы, можно извлекать информацию с веб-сайтов, пери
одически обновляющих свои данные. Тем не менее следует помнить, что запрос
страницы несколько раз подряд за короткий промежуток времени может рас
сматриваться к а к по дозрительное и да же злона меренно е испо льзование сай та.
ВАЖНО!
Многие сайты публикуютдокумент с условиями использования {ТermsofUse).
Часто ссылка на него размещается в подвале главной страницы сайта.
Всегда читайте этотдокумент, прежде чем пытаться извлекать данные. Если вы
не нашли условий использования, попробуйте связаться с владельцем сайта
и узнать, нет ли у него правил, касающихся частых запросов.
Несоблюдение условий использования может привести к блокировке вашего
IР-адреса, так что будьте внимательны и уважайте права других!
Чрезмерное количество запросов иногда вызывает даже сбой сервера. Понятно,
почему так много сайтов обращает внимание на массовые запросы к своему
серверу! Всегда проверяйте условия использования и уважительно относитесь
к ним, если решите бомбардировать сайт запросами.
Упражнение
Повторите пример из этого раздела с извлечением результата броска, но также
включите текущее время его получения с веб-страницы. Это время можно по
лучить из части строки внутри тега <р>, следующего после результата броска
в НТМL-коде страницы.
16.5 . ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
Хотя данные с веб-серверов можно парсить средствами стандартной библиотеки
Python, в РуPI существует множество инструментов, упрощающих этот процесс.
В этой главе я показал, как:
• запрашивать веб-страницы при помощи встроенного модуля Python
url l ib;
• парсить HTML средствами BeautifulSoup;
• взаимодействовать с веб-формами с использованиемMechanicalSoup;
• многократно запрашивать данные с сайта для проверки обновлений.
394 ГЛАВА 16 Веб-программирование
Писать программы автоматизированного извле'-lения веб-данных интересно,
и в интернете хватает разных любопытных проектов.
Но помните: не все хотят, '-lтобы вы извлекали данные с их неб-серверов. Всегда
проверяйте условия использования (Terms of Use) сайта, прежде чем браться
за такую задачу, и сознательно выбирайте частоту неб-запросов, чтобы не пере
гружать с ер в е р лиш ним траф иком.
ИНТЕРАКТИВНЫЙ ТЕСТ
К этой главе прилагается бесплатный интерактивный тестдля проверкиусво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpythoп.com!quizzes/pybasics-web
Дополнительные ресурсы
За дополнительной информацией о взаимодействии с веб-сайтами средствами
Python обращайтесь к следующим ресурсам:
• «Beautiful Soup: Buildа Web ScraperWithPython» (https:j/realpython.com/
beautiful-soup-web-scraper-python/)
• «API Integration in Python» (https:j/realpython.com/api-integration-in-
python/)
ГЛАВА 17
Научные вычисления
и построение графиков
Python- один из ведущихязыковпрограммирования для научныхвычислений
и теории обработки данных (datascience).
Популярность Python в этой области частично обусловлена обилием сторонних
пакетов для обработки и визуализации данных, доступных в РуPI.
В экосистеме Python можно найти инструменты на любой случай - от работы
с большими массивами данных до их визуализации награфиках и диаграммах.
В этой главе вы научитесь:
• работать с массивами данных, используяNumPy;
•
строить диаграммы и графики с помощью Matplotlib.
ПРИМЕЧАНИЕ
Предполагается, что у вас есть некоторый опыт работы сматрицами. Если вы
не знакомы с матрицами или не интересуетесь научными вычислениями, эту
главу можно смело пропустить.
Итак, за дело!
17.1. ИСПОЛЬЗОВАНИЕ NUMPY ДЛЯ МАТРИЧНЫХ
ВЫЧИСЛЕНИЙ
В этом разделе вы научите сь хранить и обрабатыватьданные в матричном виде
при помощи пакета NumPy (http.j/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 написан на языке С и использует сложные алгоритмы для эффективного
выполнения вычислений.
ПРИМЕЧАНИЕ
Область применения NumPy не ограничивается научными вычислениями.
Допустим, вы проектируете игру и вам нужны простые средства для мани
пуляций со строками и столбцами. Массивы NumPy идеально подходят для
хранения двумерных данных.
Установка NumPy
Работу с пакетом NumPy начнем с его установки при помощи pip:
$ pythonЗ -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 Е. 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-мерным массивом называется массив с п измерениями. Например, одно
мерный массив представляет собой список, адвумерный - матрицу. Массивы
могут иметьтри, четыре и более измерений.
В этом разделе мы рассмотрим массивы содним или двумя измерениями.
Для созданияобъектаndarrayможно воспользоваться псевдонимом array. Объ
екты array инициализируются списками списков, так что для создания матрицы
из первого примера в виде массиваNumPyможно поступить так:
>>> import numpy as пр
»> matrix = np.array([[l, 2, 3], [4, 5, б], [7, 8, 9]])
»> matrix
array([[l, 2 , 3],
[4, 5, 6]'
[7, 8, 9]])
Обратите внимание: NumPy выводит матрицу в удобочитаемом формате. Это
происходитдаже при выводе матрицы вызовом print():
398 ГЛАВА 17 Научные вычисления и построение графиков
>>> print(matrix)
[[123]
[456]
[789]]
К отдельным элементам массива можно обращаться так же, как и к элементам
списка списков:
»> matrix[0] [1]
2
Кроме того, можно обращаться к элементам всего с одной парой квадратных
скобок, с разделением индексов запятой:
»> ma trix[ 0, 1]
2
Чем же массивы NumPy принципиально отличаются от списков Python?
Прежде всего массивы NumPy могут хранить только объекты одного типа - на
пример, числа в матрице из рассмотренного примера, тогда как списки Python
могут хранить объекты смешанных типов.
Посмотрим, что произойдет при попытке создания массива с разнотипными
элементами:
>» np.array([[l, 2, 3], ["а", "Ь", "с"]])
array([['l', '2', '3'],
['а', 'Ь', 'с']], dtype=' <Ull')
NumPy не выдает ошибки. Вместо этого каждый элемент преобразуется в строку.
Выражение dtype=' <Ull' вэтом выводеозначает, что массив позволяет хранить
только строки в формате Юникод длиной не более 11 байт.
Автоматические преобразования типов данных иногда помогают, но они также
могут стать потенциальным источником проблем - типы данных преобразуются
не так, как вы ожидали.
Обычно рекомендуется выполнять любые преобразования типов до инициа
лизации объекта array. Так вы можете быть уверены в том, что тип данных,
хранящихся в массиве, соответствует ожиданиям.
В NumPy каждое измерение массива называется осью (axis). Матрицы, о ко
торых мы говорили ранее, имели две оси. Массивы сдвумя осями называются
двумерными массивами.
17.1. Использование NumPyдля матричных вычислений 399
Пример трехмерного массива:
>>> matrix = np.array([
])
[[1, 2, З], [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:
»>А= np.array([[l, 2, З], [4, 5, 6], [7, 8, 9]])
»>2*А
array([[ 2, 4, 6],
[ 8, 10, 12],
[14, 16 , 18]])
Операции между двумя матрицами выполняются поэлементно, то есть оператор
при меняе тся к со отв етс тву ющим элем ентам матрицы :
>» в np.array([[5, 4, З], [7, 6, 5]J [9, 8, 7]])
»>с
В-А
»>с
array([[ 4, 2, 0],
[З,1,-1],
[ 2, 0, -2]])
Обратите внимание: С[0] [0] равно В[0] [0] - А[0] [0]. То же относится ко всем
остальным парам индексов. Все базовые арифметические операторы - +, -, *,
/ - в отношении массивов выполняются поэлементно.
400 ГЛАВА 17 Научные вычисления и построение графиков
Например, при умножении двух массивов оператором * произведение двух
матриц не вычисляется:
»> А= np.array([[l, 1, 1], [1, 1, 1], [1, 1, 1]])
»>А*А
array([[1, 1, 1],
[1, 1, 1],
[1, 1, 1]])
Для вычисления настоящего произведения матриц (https.j/rn.wikipedia.org/
wiki/Умножение_матриц) используется оператор@:
»>А@А
array([ [З, З, З],
[З, З, З],
[З, З, З]])
Оператор @появился в Python 3.5 , и, если вы используете более старую версию
Python, матрицы придется умножать другим способом. NumPy предоставляет
функцию matmul() для умножения двух матриц:
>>> np.matmul(matrix, matrix)
array([[З, З, З],
[З, З, З],
[З, З, З]])
Оператор@ использует функцию np.matmul() во внутренней реализации, по
этому принципиальных различий между этими двумя способами нет.
Ниже перечислены другие распространенные операции с массивами:
»> matrix = np.array([[l, 2, З], [4, 5, б], [7, 8, 9]])
>>> # Получение кортежа длин осей
»> matrix.shape
(3, 3)
>>> # Получение массива диагональных элементов
>>> matrix.diagonal()
array([1, 5, 9])
>>> # Получение одномерного массива всех записей
>>> matrix.flatten()
array([1, 2, З, 4, 5, 6, 7, 8, 9])
>>> # Транспонирование
>>> matrix.transpose()
array([[l, 4, 7],
[2, 5, 8],
[З, б, 9]])
17.1 .Использование NumPyдля матричных вычислений
401
»> # Вычисление минимума
»> matrix.min()
1
»> # Вычисление максимума
»> matrix.max()
9
»> # Вычисление среднего значения всех элементов
>>> matrix.mean()
5.0
»> # Вычисление суммы всех элементов
»> matrix.sum()
45
А теперь рассмотрим некоторые способы создания новых массивов на базе
старых.
Стыковка и изменение формы массивов
Если размеры осей совпадают, два массива можно состыковать по вертикали
методом пр.vstack() или по горизонтали методом пр. hstack():
»>А np.array([[l, 2, З], [4, 5, 6]])
»> В np.array([[7, 8, 9], [10, 11, 12]])
>» np.vstack([A, В])
array([[ 1, 2, З],
[4,5,б],
[7,8,9],
[10, 11 , 12]])
>>> np.hstack([A, В])
array([[ 1, 2, 3, 7, 8, 9],
[4,5,б,10,11,12]])
Также можно изменить форму массивов методом пр. reshape():
>>> A.reshape(б, 1)
array([ [1],
[2],
[З],
[4],
[5 ],
[б]])
402 ГЛАВА 17 Научные вычисления и построение графиков
Следует заметить, что . reshape() возвращает новый массив, а не изменяет ис
ходный массив на месте.
Общий размер массива после изменения формы должен соответствовать размеру
исходного массива. Например, выполнить matrix.reshape( 2, 5) невозможно:
>>> A.reshape(2, 5)
Traceback (most recent call last):
File "cstdin>", line 1, in cmodule>
ValueError: cannot reshape array of size 6 into shape (2, 5)
В этом случае мы пытаемся изменить форму массива с девятью элементами
и превратить его в массив с двумя столбцами и пятью строками. Такой массив
содержитдесять элементов.
Метод np. reshape() особенно полезен в сочетании с np. arange() - эквивален
том функции Python range() в NumPy. Главное различие заключается в том,
что np. arange() возвращает объект array:
>>> nums = np.arange(l, 10)
»> nums
array([l, 2, 3, 4, 5, 6, 7, 8, 9])
Диапазон np. arange() начинается с первого аргумента и завершается непосред
ственно перед вторым аргументом. Таким образом, np. а range(1, 10) возвращает
массив, содержащий числаот 1до9.
Совместно np. arange() и np. reshape() предоставляют полезный способ со
здания матрицы:
>>> matrix = nums.reshape(3, 3)
»> matrix
array([[l, 2, 3],
[4, 5, 6],
[7, 8, 9]])
Это даже можно сделать в одной строке, объединив вызовы np. arange ()
и np. reshape() в цепочку:
>>> np.arange(l, 10).reshape(3, 3)
array([[l, 2, 3],
[4, 5, 6],
[7, 8, 9]])
Такой способ создания матриц особенно полезен для создания массивов высо
кой размерности. Пример построения трехмерного массива с использованием
np.array() и np.reshape():
17.1. Использование NumPy для матричных вычислений
403
>>> np.arange(l, 13).reshape(3, 2, 2)
array([[[ 1, 2],
[ з, 4]],
[[ 5, 6],
[ 7, 8]],
[[ 9, 10],
[11, 12]]])
Конечно, не каждый многомерный массив может быть построен из последова
тельного списка чисел. В таких случаях часто проще создать одномерный список
элементов, а затем привести массив к нужной форме вызовом пр. reshape ():
»> arr = np.array([l, 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]]])
В списке, передаваемом пр. array() в приведенном примере, разность в любой
паре равна 2. Для упрощения создания массивов такого рода можно передать
пр. araпge () необязательный третий аргумент с именем stride:
>>> np.arange(l, 24, 2)
array([ 1, З, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23])
Перепишем предыдущий пример так, чтобы пр. araпge() передавался аргумент
stride:
>>> np.arange(l, 24, 2).reshape(3, 2, 2)
array([[[ 1, 3],
[ 5, 7]],
[[ 9, 11],
[13, 15]],
[ [17, 19],
[21, 23]]])
В этом разделе я познакомил вас с несколькими способами создания и об
работки м но гом ер ны х массивов с исп ользов анием струк туры да нн ых N um Py
array. Однако вы получили лишь очень поверхностное представление о том,
404 ГЛАВА 17 Научные вычисления и построение графиков
что можно сделать с NumPy! В конце главы вы найдете ссылки на материалы,
которые помогут вам углубить ваши знания о NumPy.
Упражнения
1. Используйте np.arange() и np.reshape() для создания массива NumPy
3 х 3 с именем А, содержащего числа от 3 до 11.
2. Выведите минимальное, максимальное и среднее значение по всем эле
ментам А.
3. Возведите в квадрат каждый элемент А, используя оператор **.Сохраните
результаты в массиве с именем В.
4. Пристыкуйте А к В вызовом np. vstack( ), после чего сохраните результаты
в массивес именем С.
5. Используйте оператор @для вычисления произведения матриц С и А.
6. Изменитеформусдоразмеров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
Nam e: m atp lotlib
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. Именно этот
инт ерфе йс с р аз у кажется польз ователям M A TLAB зна комым .
Второй способ построения графиков в Matplotlib основан на объектно-ориен
тированном API. Объектно-ориентированный подход предоставляет больше
воз можн осте й для упр авлени я граф иками, чем инт ерфе йс py plo t. Те м не менее
его концепции обычно более абстрактны.
В этом разделе вы освоите интерфейс pyplot и научитесь строить эффектные
графики с минимальными затратами времени.
Начнем с создания простого графика. Введите следующий фрагмент в новом
окне редактора IDLE:
from matplotlib import pyplot as plt
plt.plot([1, 2, 3, 4, 5))
plt. sho w()
406 ГЛАВА 17 Научные вычисления и построение графиков
Сохраните программу и запустите ее клавишей FS. На экране появится новое
окно со следующим графиком:
5.0
4.5
4.0
3.5
3.0
2.5
2.0
1.5
1.0
о.о
0.5 1.0 1.5 2.0 2..5 3.0 3.5 4.0
Вызов plt. plot( [1, 2, 3, 4 , 5]) строит график с линией, проходящей через
точки (О, 1), (1, 2), (2, 3), (3, 4) и (4, 5).
Список [1, 2, З, 4, 5], переданный plt. plot(), представляет координаты точек
на графике. Так как значения хне заданы, Matplotlib автоматически использует
индексы элементов списка - в данном случае О, 1 , 2, 3 и 4, так как в Python
нумерация индексов начинается с О.
Функция plt. plot () строит график, но на экране при этом никакая информа
ция не появляется. Чтобы отобразить график на экране, необходимо вызвать
plt.show().
Также можно задать значения х для точек вашего графика, для чего следует
передать plt. plot() два списка. Когда plt. plot () передаются два аргумента,
первый список задает значения х, а второй - значения у.
В окне редактора приведите программу к следующему виду:
from matplotlib import pyplot as plt
XS=[1,2,3,4,5)
ys=[2,4,6,8,10]
plt.plot(xs, ys)
plt. show()
17.2 . Построение графиков с помощью Matplotlib 407
Сохраните обновленную программу и нажмите FS. На экране должен появиться
следующий график:
10
9
8
7
б
s
4
3
2
1.0 l.S 2.0 2.S 3.0 3.S
4.0
4.S s.o
Обратите внимание: метки на осях теперь представляют новые координаты
точек графика.
Возможности plot () не ограничиваются рисованием линий. На приведен
ных выше графиках получилось, что все точки находятся на прямой линии.
По умолчанию при нанесении точек вызовом plot () каждая пара соседних
точек соединяется отрезком прямой.
Обновите значения х и у так, чтобы точки не располагались на прямой:
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 Научные вычисления и построение графиков
Сохраните файл и нажмите клавишу FS. На экране появится следующий график.
1.0 1.5 2.0 2.5 3.0
3.5
4.0
4.5
5.0
У plot() имеется необязательный параметр форматирования, который исполь
зуется для задания цвета и стиля линий или точек.
Например, следующая программапередает в параметре форматирования строку
"g-o":
from matplotlib import pyplot as plt
plt.plot([2, 4, 6, 8, 10], "g-o")
plt. show()
Буква g в "g-o" задает зеленый цвет (green), знак - задает сплошную линию,
а буква о - пометку точек на линии кругами.
о.о
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0
17.2. Построение графиков с помощью Matplotlib 409
Полный список всех комбинаций форматирования вы найдете в документации
Ma tp lotli b ( ht tp s.j/ma tplotli b. org/2 .0. 2/a pi/ py plo t_a pi. ht ml ).
Рисование нескольких графиков в одном окне
Если вам нужно вывести несколько графиков в одном окне, вызовите plot ()
несколько р а з .
Например, следующая программа рисует два графика:
from matplotlib import pyplot as plt
plt.plot([l, 2, 3, 4, 5])
plt.plot([l, 2 , 4, 8 , 16])
pl t.sh ow()
Сохраните программу в новом окне редактора и нажмите FS, чтобы увидеть
результат.
lб
14
12
10
в
б
4
2
о.о
o.s
1.0
l.S
2.0 2.S 3.0 3.S 4.0
Обратите внимание: графики выводятся разными цветами. Если вы хотите
управлять внешним видом каждого графика, передайте строки форматирования
plot () в дополнение к значениям х и у:
from matplotlib import pyplot as plt
plt.plot([l, 2, 3, 4, 5], "g-o")
plt.plot([l, 2, 4 , 8 , 16), "Ь-л")
plt.show()
41 О ГЛАВА 17 Научные вычисления и построение графиков
Теперь графики выводятся синим и зеленым цветом с выделением точек.
lб
14
12
10
8
6
4
2
о.о o.s 1.0 L5 2.0 2.S 3.0
3.5
4.0
А теперь рассмотрим некоторые возможности построения графиков по другим
ти пам источ ников данных.
Вывод на графике данных из массивов NumPy
До настоящего момента мы хранили данные для графика в классических спис
ках Python. В реальном мире для хранения данных, вероятно, будет исполь
зоваться что-то вроде массива NumPy. К счастью, Matplotlib хорошо работает
с объектами array.
Например, вместо списка можно воспользоваться функцией NumPy arange( ),
чтобы определить данные для графика и передать полученный объект array
п ри вызове plot ():
from matplotlib import pyplot as plt
import numpy as np
array = np.arange(l, 6)
plt.plot(array)
plt. show ()
17.2 . Построение графиков с помощью Matplotlib 411
Этот фрагмент строит следующий график:
5.0
4.5
4.0
3.5
3.0
2.5
2.0
1.5
1.0
о.о 0.5
1.0
1.5 2.0 2.5
3.0 3.5
4.0
При передаче двумерного массива каждый столбец массива интерпретирует
ся как значения оси ординат у. Например, следующий фрагмент кода рисует
четыре линии:
from matplotlib import pyplot as plt
import numpy as np
data = np.arange(l, 21).reshape(S, 4)
# d ata соде ржит сле дующ ий мас сив:
# array([[ 1,
#
[5'
#
[9,
#
[13,
#
[17,
plt.plot(data)
plt .sh ow()
2,
6,
10,
14,
18,
3' 4]'
7' 8]'
11, 12],
15, 16]'
19, 20]])
Вот результат:
20 .О
17.5
15 .О
12.5
1 0.О
7.5
5.0
2.5
о.о 0.5 LO 1.5 2.0 2.5 3.0 3.5
4.0
Если же вы хотите нанести на график матрицу по строкам, то выводить следует
результат транспонирования массива:
from matplotlib import pyplot as plt
import numpy as пр
data = np.arange(l, 21).reshape(S, 4)
plt.plot(data.transpose())
plt. show( )
На графике, сгенерированном этим фрагментом кода, линии построены по
строкам матрицы (вместо столбцов).
20. О
17.S
15 .О
12.5
10 .О
7.5
5.0
2.5
о.о
0.5
LO
L5
2.0
2.5
3.0
17.2 . Построение графиков с помощью Matplotlib 41З
До настоящего момента на графиках, которые мы строили, не было никакой
информации о том, что же этот график показывает. Сейчас мы расскажем, как
отф ормат иров ать г раф ик и добавить текст .
Форматирование графиков
Попробуем показать на графике, сколько материала о языке Python было изу
чено за первые 20 дней в результате чтения Real Python по сравнению с другим
веб-сайтом:
from matplotlib import pyplot as plt
import numpy as пр
days = np.arange(0, 21)
other_site, real_python = days, days ** 2
plt.plot(days, other_site)
plt.plot(days, real_python)
p lt. show( )
График, выводимый этим кодом, выглядит так:
400
350
300
250
200
150
100
so
о
о.о
2.5 5.0
7.5 10.О U.5 15.О 17 .5 20 .0
График далеко не идеален. На оси х отображаются половины дней, у графика
нет заголовка и названия осей.
Начнем с настройки оси х. Для определения местонахождения меток на осях
используется функция plt. xticks():
414 ГЛАВА 17 Научные вычисления и построение графиков
from matplotlib import pyplot as plt
import numpy as пр
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( )
Теперь на графике появляются метки дней: О, 5, 10 , 15 и 20.
400
350
300
250
200
150
100
50
о
о
5
10
15
20
Такой график лучше воспринимается, но и сейчас неясно, что же показывает
каждая ось. Для добавления названия осей используются функции plt.xlabel()
и plt. ylabel(). Обе функции имеют один параметр, в котором должна пере
даваться строка с названием оси.
Для добавления заголовка к графику исполюуется функция plt. title( ). Как
и plt. xlabel() и plt .ylabel(),функция plt. title() получает один строковый
аргумент с заголовком графика.
Обновите приведенный выше код, чтобы к оси х добавлялось название "Days of
Reading" (Время чтения в днях), к оси у - "Amount of Python Learned" (Объем
изученного P yt hon ) , а сам гра фик выв одил ся под заголовком " Pytho n Le ar ned
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()
График с заголовком и названиями осей показан ниже.
Python LearnedReading Real Python vs. OtherSite
400
350
~ 300
Е, ,,
~ 250
с
о
.&:
~ 200
Q.
"о
~ 150
:
:
;
J
о
Е 100
<
50
о
о
5
10
15
Days of Reading
Становится на что-то похоже!
20
Но проблемы все еще остались: непонятно, какой график относится к 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 пр
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( )
Вот как выглядит итоговый вариант:
Python Learned Reading Real Python vs. Other Site
400 - OtherSite
-
Real Python
350
so
о
о
5
10
1S
Days of Reading
20
plt. legend() также поддерживает ряд необязательных параметров, предназна
ченных для настройки условных обозначений. За дополнительной информацией
17.2. Построение графиков с помощью Matplotlib 417
обращайтесь к описанию условных обозначений в документации Matplotlib
(ht tp s .j/ma tplotlib .o rglu ser s/le ge nd_gui de. ht ml ) .
Другие разновидности графиков
До сих порвыстроили только линейныеграфики, но Matplotlibпозволяет строить
многие другие разновидности, включая гистограммы и столбчатые диаграммы.
Столбчатые диаграммы
Столбчатые диаграммы строятся функцией plt.bar(), которая получает два
обязательных параметра:
1) список значе ний х для централ ьной точки каждо го ст о лб ц а;
2) список значений у для высоты каждого столбца.
Например, следующий код строит столбчатую диаграмму, у которой центры
столбцовнаходятсявточках1,2,3,4и5поосихсвысотами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()
Столбчат ая диа грам ма выг ляди т так:
10
8
б
4
2
о
1
2
3
4
s
418 ГЛАВА 17 Научные вычисления и построение графиков
Для определения центральных точек и высот столбцов вместо списка можно
использовать массив NumPy. Следующий фрагмент строит диаграмму, анало
гичную предыдущей, но вместо списков используются массивы NumPy:
from matplotlib import pyplot as plt
import numpy as np
centers = np.arange(l, 6)
tops = np.arange(2, 12, 2)
plt.bar(centers, tops)
plt. show( )
Функция plt.bar() весьма гибкая. Первый аргумент не обязан быть списком
чисел - это может быть список строк, представляющих категории данных.
Допустим, вы хотите построить столбчатую диаграмму, на которой выводятся
данные из словаря:
fruits = {
"ap ples": 1 0,
"oranges": 16,
"bananas": 9,
"pe ar s": 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,
"pea rs ": 4,
}
plt.bar(fruits.keys(), fruits.values())
plt.show()
17.2 . Построение графиков с помощью Matplotlib 419
Столбцы разделены одинаковыми расстояниями, а названия фруктов обозна
чены метками на оси х.
16
14
и
10
8
б
4
2
о
apples
o r anges
Ьa nana s
pear s
Гис тог рам мы
Другая популярная разновидность диаграмм - гистограмма - показывает
ра спределен ие данны х. Д л я создания гист ограмм в Ma tp lot lib используется
функция 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(r an dom. r andn (10000) , 20)
plt. show( )
Matplotlib автоматически создает 20 категорий одинаковой ширины 0,5 .
1400
иоо
1000
800
600
400
200
о
--4
-3
-2
-1
о
1
2
3
4
Гистограммы легко настраиваются. Подробнее о построении гистограмм на
языке Pythonрассказано встатье <1Python Histogram Plotting: NumPy, Matplotlib,
Pandas & Seaborn~ (https://realpython.com/python-histogramsj) на сайте Real
Python.
Сохранение рисунков в графических файлах
Возможно, вы заметили, что в нижней части окна с графиками расположена
панель инструментов. Она позволяет сохранить построеннуюдиаграмму в гра
фическом файле.
Как правило, никому не хочется сидеть у компьютера и нажимать кнопку Save
для сохранения каждогографика. К счастью, Matplotlibпозволяет леrко сделать
это программными средствами.
Для сохранения графиков используется функция plt. savefig(). Путь к фай
лу, в котором вы хотите сохранить свой график, передается в виде строки.
17.2 . Построение графиков с помощью Matplotlib 421
Следующие примеры сохраняют простую столбчатую диаграмму с именем
bar.png в текущем рабочем каталоге.
Если вы решите сохранить его в другом месте, передайте при вызове абсолют
ный путь.
from matplotlib import pyplot as plt
import numpy as np
xs = np.arange(l, 6)
tops = np.arange(2, 12 , 2)
plt.bar(xs, tops)
plt.savefig("bar.png")
ПРИМЕЧАНИЕ
Чтобы одновременно и сохранить диаграмму, и вывести ее на экране, обяза
тельно сохраните ее перед выводом!
Функция show() приостанавливает выполнение кода, а при закрытии окна
график уничтожается, так что при попытке сохранения диаграммы после вы
зова show() вы получите пустой файл.
Работа с графиками в интерактивном режиме
Когда мы начинаем форматировать график, хочется настраивать его внешний
вид и оценивать результат, не тратя время на перезапуск программы.
Проще всего сделать это при помощи оболочки Jupyteг Notebook (https.j/
jupyter.org/), которая создает интерактивный сеанс интерпретатора Python,
выполняемого в браузере.
Блокноты Jupyteг стали главным средством взаимодействия с данными и их
анализа, и они отлично работают как с NumPy, так и с Matplotlib. Интерактив
ный учебник по использованию Jupyteг Notebook представлен в руководстве
«IPython in Depth>.> (https.j/realpython.com/pybasics-ipython-in-depth) на сайте
Real Python.
Упражнения
1. Постройте как можно больше графиков, приведенных в этом разде
ле, - напишите собственные программы, не подглядывая в приведен
ный ко д.
422 ГЛАВА 17 Научные вычисления и построение графиков
2. С посо бст вуют ли п ира ты глоб альному пот епл ению ? В папке prac ti ce_ fil es
главы 17 находится файл CSV с данными о количестве пиратов и глобаль
ной температуре. Напишите программу для анализа связи между ними;
программадолжначитать файлpirates.csv и строить график, на котором
количество пиратов указано на оси х и температура - на оси у. Добавьте
заголовок и названия осей графиков, затем сохраните полученный график
в виде файла в формате PNG.
17.З. ИТОГИ И ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ
В этой главе вы узнали о научных вычислениях и визуализации данных в Python.
В частности, вы научились:
• работать с массивами и матрицами средствами NumPy;
• строить графики при помощи Matplotlib.
Чтобы на должном уровне рассказать про научные вычисления, анализ и ви
зуализацию данных, не хватит и отдельной книги. Однако понимания базовых
понятий, с которыми вы познакомились в этой главе, достаточно для начала
самост оятельной р а б о ты .
ИН ТЕР АКТИ ВНЫЙ ТЕ СТ
К этой главе прилагается бесплатный интерактивный тест для проверки усво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpython.com/quizzes/pybasics-scientific-computing
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
• «Look Ма, No For-Loops: Array ProgrammingWith NumPy>.> (https.j/
realpython.com/numpy-array-programming/)
• «Data ScienceWith Python Core Skills (Learning Path)»(https.j/realpython.
com/leaming-paths/data-science-python-core -skills/)
ГЛАВА 18
Графические интерфейсы
До сих пор в этой книге я рассказывал о приложениях командной строки -
программах, которые запускаются и выводят результаты в окне терминала.
Приложения командной строкихорошо подходятдлясоздания инструментов,
которыми пользуются разработчики, но большинство пользователей программ
никогда не открывают окно терминала!
Графический интерфейс пользователя, или GUI (Graphical User Interface), - э то
окна с элементами (кнопками, текстовыми полями и т. д.), которые предоставля
ют пользовател ю зн ак омы й и нагл ядны й с по с о б взаимодействия с программой.
В этой главе вы научитесь:
• добавлять простой графический интерфейс к приложениям командной
строки с помощью EasyGUI;
• создавать полнофункциональные GUI-приложения с использованием
Tkinter.
Итак, за дело!
18.1 . ДОБАВЛЕНИЕ ЭЛЕМЕНТОВ GUI
С ПОМОЩЬЮ EASYGUI
Библиотека EasyGUI позволяет быстро добавить в программу графический
интерфейс. Возможности EasyGUI несколько ограниченны, но библиотеку
удобно применять для создания простых инструментов, которым нужен не
большой объем входных данных от пользователя.
В этом разделе мы используем EasyGUI для создания короткой программы,
которая позволяет выбрать файл PDF на жестком диске и повернуть его стра
ницы на заданный угол.
424 ГЛАВА 18 Графические интерфейсы
Установка EasyGUI
Прежде всего необходимо установить EasyGUI при помощи pip:
$ pythonЗ -m pip install easygui
Когда библиотека EasyGUI будет установлена, можно вывести расширенную
информацию о пакете командой pip show:
$ pythonЗ -m pip show easygui
Name: easygui
Version: 0.98.1
Summary: EasyGUI is а 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 Ьу simple
func tion ca lls .
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(),
которые ранее мы использовали для ввода и вывода.
Логика ЕаsуGUl-программ обычно работает по следующей схеме.
1. В какой-то момент на экране пользователя появляется визуальный
элемент.
2. Выполнение кода приостанавливается, пока пользователь не введет
данные в визуальном элементе.
3. Пользовательский ввод возвращается в ви;1е объекта, а вы1ю1шение кода
возобновляется.
18.1.Добавление элементов GUI с помощью EasyGUI 425
Чтобы получить представление о том, как работает EasyGUI, откройте новое
интерактивное окно в IDLE и выполните следующие команды:
>>> import easygui as gui
»> gui.msgbox(msg="Hello!", title="My first message Ьох")
При выполнении кода в Windows на экране появляется окно, которое выглядит
примерно так:
1Муfirstmessage Ьох
о
х
Rello!
Внешний вид окна зависит от операционной системы, в которой выполняется
код. В macOS окно выглядит так:
• 1"'18
Му first message Ьох
Hellol
То же окно в Ubuntu:
Му flrst message Ьох
~-~
Hellot
426 ГЛАВА 18 Графические интерфейсы
В примерах этого раздела мы используем скриншоты для Windows.
ВАЖНО!
Как EasyGUI,так и IDLE написаны с использованием библиотеки Tkiпter, о кото
рой речь пойдет вследующем разделе. Такое использование одного ресурса
иногда создает проблемы с выполнением - например, диалоговые окна могут
застывать на месте или блокироваться.
Если вам кажется, что вы тоже с этим столкнулись, попробуйте выполнить свой
код в окне терминала. Интерактивный сеанс Python запускается из терминала
командой python в Windows или командой руthопЗ в macOS/Ubuntu.
Вотчто вы увидите в диалоговом окне, сгенерированном приведенным кодом:
1. Строка "Hello! ",переданная в параметре msg функции msgbox(), выво
дится как текст в окне сообщения.
2. Строка "Му first message Ьох", переданная в параметре title, выводится
в заголовке окна сообщения.
3. Окно сообщения содержит кнопку с надписью "ОК".
Закройте диалоговое окно кнопкой ОК и загляните в интерактивное окно IDLE.
Ст рок а " ОК" выво дится под посл едней введен ной ва ми строк ой код а:
»> gui.msgbox(msg="Hello! ", title="My first message Ьох")
'ОК'
При закрытии диалогового окна msgbox() возвращает название кнопки. Если
пользователь закрывает диалоговое окно без нажатия кнопки ОК, то msgbox()
возвра щает значение N опе.
Название кнопки можно настроить при помощи третьего необязательного
параметра ok_buttoп. Например, следующая команда создает окно сообщения
с кнопкой "Click me":
»> gui.msgbox(msg="Hello!", title="Greeting", ok_button="Click me")
Функция msgbox() хорошо подходит для вывода сообщений, но она не предо
ставляет пользователю достаточно средств для взаимодействия с программой.
EasyGUI содержит несколько функций для вывода различных типов диалоговых
окон. Давайте рассмотрим некоторые из них!
18.1 . Добавление элементов GUI с помощью EasyGUI 427
Элементы графического интерфейса в EasyGUI
Кроме msgbox() EasyGUI содержит еще несколько функцийдля выводадругих
видов диалоговых окон. Описание этих функций приведено в следующей таблице.
ФУНКЦИЯ
msgbox()
button box( )
i ndex box()
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 Графические интерфейсы
Диал огово е окно в ыгля дит так:
1Choosewisely-.
о
х
ИЬ.аt iз your :favorite color?
Когда вы нажимаете одну из кнопок, ее метка возвращается в виде строки. На
пример, нажатиекнопкиYellowзаставляет buttonbox() вернуть строку "Yellow":
> > > gui. buttonbox(
'Yellow'
msg="What is your favorite color?",
title="Choose wisely... ",
choices=("Red", "Yellow", "Blue"),
Как и msgbox(), buttonbox() возвращает значение None, если пользователь за
крывает диалоговое окно без нажатия одной из кнопок.
indexbox()
indexbox() выводит диалоговое окно, идентичное тому, которое выводит
buttonbox() .Собственно, indexbox() создается точнотак же, как иbuttonbox():
>>> gui.indexbox(
msg="What's your favorite color?",
title="Choose wisely... ",
choices=("Red", "Yellow", "Blue"),
А вот как выглядит диалоговое окно:
1Choose wisely...
What iз your ravorite color?
о
х
18.1 . Добавление элементов GUI с помощью EasyGUI 429
Различие между indexbox() и buttonbox() заключается в том, что indexbox()
возвращает индекс названия кнопки (вместо самого названия) в списке или
кортеже, передаваемом choices.
Например, при щелчке на кнопкеYellow возвращается целое число 1:
>>> gui.indexbox(
1
msg="What's your favorite color?",
title="Favorite color",
choices=( "Red", "Yellow", "Blue"),
Так как 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 Графические интерфейсы
f Favorite color
D
х
Wha.t isyourfa.vorite color?
Введите название цвета (например, Yellow) и нажмите кнопку ОК. Введенный
вами текст возвращается в виде строки:
>>> gui.enterbox(
'Yellow'
msg="What is your favorite color?",
title="Favorite color",
Диалоговые окна очень часто применяются для выбора пользователем файла
или папки. В EasyGUI существуют специальные функции, разработанные
именно для таких операций.
fileopenbox()
Функция fileopenbox() выводит диалоговое окно для выбора открываемого
файла:
>>> gui.fileopenbox(title="Select а file")
Это диалоговое окно очень похоже на стандартное системное диалоговое окно,
в котором пользователь открывает файл (см. рис. на с. 439).
Выберитефайл и щелкнитена кнопке Open. Функция возвращает строку с пол
ным путем к выбранному файлу.
ВАЖ НО!
Функция fileopenbox() не открывает файл, а только предоставляет воз
можность выбрать его! Чтобы открыть файл, необходимо воспользоваться
встроенной функцией open (),о которой я рассказывал в главе 12 .
Как и msgbox() и buttonbox(), fileopenbox() возвращает значение None, если
пользователь нажмет кнопку Cancel или закроет диалоговое окно, не выбрав
файл.
18.1 .Добавление элементов GUI с помощью EasyGUI
431
1Selectа file
Х
v 1'1 >2!1isРС>
-
-=-
_
VJ~i 3'~chThis РС
Рj
Organize •
~~·[18
"
Desktop
1
v k Quickaccess
Desktop
;
~ Downloads
;
lfil Documents ;
v Folders (7)
ЗDObjects
11!;1 Pictures
;
•
Movies
}' Music
~ Documents
~
>Q ThisPC
Downloads
>
Net wo rk
Movies
F
i
lena me:
v 1Allfiles
vj
~~~~~~~~ ~~
Ореп j 1 Cancel j
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 а file")
»> open_file = open(path, "r")
Traceback (most recent call last):
File "cstdin>", 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 а file")
if path is None:
exit()
Если пользователь закрывает диалоговое окно, не нажав кнопкуОК, программа
закроется, а выполнение будет остановлено, потому что переменная path со
держитNone, а программа вызывает exit ()в блокеif.
18.1. Добавление элементов GUI с помощью EasyGUI 433
ПРИМЕЧАНИЕ
Если программа выполняется в IDLE, функция exit{}также з.акроет текущее
интерактивное окно.
Ключевое слово is из предыдущего примера вам еще не встречалось. Оно срав
нивает два объекта и определяет, не являются ли они одним и тем же объектом,
проверяя, имеют ли они одинаковые адреса памяти.
Как мы говорили ранее, адрес памяти объекта можно получить функцией id ( ) .
Таким образом, если значения id (а) и id ( Ь) для двух объектов одинаковы, то
выражение аisЬ даетрезультат True. Вэтомслучае а и Ь не являются разными
объектами. Они представляют один объект с двумя разными именами.
Почему же is используется для сравнения значения с None? Потому что объект
None может быть только один. Каждый раз, когда функция возвращает None, она
возвращает тот же объект в памяти, на который указывает ключевое слово None.
Теперь вы умеете создавать диалоговые окна вызовом EasyGUI, и мы можем
применить все ваши приобретенные навыки в реальном приложении.
Упражнение
1. Создайте следующее диалоговое окно.
, Watch out!
о
х
Warning!
2. Соз дайте сл ед ующ ее диалоговое о кн о.
о
х
Whe.t is your ne.me?
434 ГЛАВА 18 Графические интерфейсы
18.2 . ПРИМЕР: ПРОГРАММА ДЛЯ ПОВОРОТА
СТРАНИЦ PDF
EasyGUI очень хорошо подходит для вспомогательных приложений, авто
матизирующих простые, но повторяющиеся операции. Если вам частенько
приходится выполнять рутинные операции, например в офисе, то программы
EasyGUI, упрощающие выполнение повседневных задач, могут значительно
пов ыси ть производ ительность в аше й р аботы .
В этом разделе мы воспользуемся некоторыми диалоговыми окнами EasyGUI,
о которых вы узнали в прошлом разделе, чтобы разработать приложение для
поворота стра ниц P DF .
Дизайн приложения
Прежде чем браться за написание кода, давайте немного поразмыслим над тем,
как должна работать программа.
Она должна сначала спросить у пользователя, какой файл PD F следует открыть,
на сколько градусов должна быть повернута каждая страница и где пользователь
хотел бы сохранить новый файл 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 а PDF to rotate...·,
de fa ult= "*. pdf"
Здесь параметру по умолчанию задается значение"*. pdf", которое настраивает
диалоговое окно так, чтобы в нем выводились только файлы с расширением
.p df. Тем самым мы предотвращаем случайный выбор пользователем файла,
формат которого отличается от PDF.
Путь, выбранный rюль:ювателем, присваивается переменной iпput_path. Если
нользователь закрыл диалоговое окно, не выбрав файл (шаг 2), переменная
input_path сrщержит None.
Чтобы завершить программу, если пользователь закрыл диалоговое окно, не
выбрав значение, проверьте, равна ли None переменная input_path, и если рав
на - вызовите exit ():
436 ГЛАВА 18 Графические интерфейсы
# 2. Если пользователь отменяет диалоговое окно, завершить работу программы.
if iпp ut_pa th is N one:
exit()
На третьем шаге следует спросить пользователя, на какой угол он хотел бы по
вернуть страницы PDF. Пользователь может выбрать 90 , 180 или 270 градусов.
Для получения этой информации можно воспользоваться функцией buttonbox():
# 3. Предложить пользователю выбрать угол поворота -
#
90, 180 или 270 градусов.
choices = ("90", "180", "270")
degrees = gui.buttonbox(
msg="Rotate the PDF clockwise Ьу how many degrees?",
title="Choose rotation ... ",
choices=choices,
Созданное диалоговое окно содержит три кнопки с метками "90 ", "180 " и "270 ".
Когда пользователь щелкает на одной из этих кнопок, метка присваивается
переменной degrees в виде строки. Чтобы повернуть страницы в файле PDF
на выбранный угол, необходимо иметь это значение в виде целого числа, а не
строки. Преобразуйте его в целое число:
degrees = int(degrees)
Затем запросите у пользователя путь к выходному файлу функцией filesa-
vebo x( ):
# 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 а PDF to rotate... ",
de fa ult= "*. pdf"
438 ГЛАВА 18 Графические интерфейсы
# 2. Если пользователь отменяет диалоговое окно, завершить работу программы.
if input_path is None:
exit()
# 3. Предложить пользователю выбрать угол поворота -
#
9 0, 180 или 270 градусов.
choices = ("90", "180", "270")
degrees = gui.buttonbox(
msg="Rotate the PDF clockwise Ьу 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 а message Ьох that this is not allowed.
gui.msgbox(msg="Caпnot overwrite original file!")
# - Returп 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:
outpu t_pd f. wr ite ( outp ut_f ile )
EasyGUI отлично шщходит для быстрого создания пользовательских шперфей
сов небольших приложений и утилит. Для более крупных проектов возможно
стей EasyGUI может быть недостаточно. В таких случаях на помощь приходит
встроенная библиотека Python Tkinter (https//wiki.python.org/moin/Tklnter).
Tkinter - GUl-фреймворк, работающий на более низком уровне, чем EasyGUI.
Это означает, что у вас больше возможностей для унравления виэуальными
18.3. Задача: приложение для извлечения страницы PDF 439
аспектами GU1: размером окна, размером шрифта, цветом шрифта и элементами
графического интерфейса, присутствующими в диалоговом или обычном окне.
Далее в этой главе я расскажу о разработке GUI-приложений со встроенной
библи отек ой Pyth on Tk inte r .
Упражнение
В GUI-приложении для поворота страниц файла PDF существует проблема.
В программе происходит сбой, если пользователь закрывает окно buttonbox (),
используемое для выбора угла, не выбрав значение.
Исправьте этот недостаток: в цикле while продолжайте выводить диалоговое
окно выбора, пока переменная degrees остается равной None.
18.З. ЗАДАЧА: ПРИЛОЖЕНИЕ ДЛЯ ИЗВЛЕЧЕНИЯ
СТРАНИЦЫ PDF
Сейч ас в ы пр именит е Ea syGU I для напи сания GUI -при лож ения , извле кающе го
страницы из файла PDF.
Подробный план:
1. Предложить пользователю выбрать файл PDF .
2. Если файл PDF не выбран, завершить программу.
3. Запросить начальный номер страницы.
4. Если пользователь не ввел начальный номер страницы, завершить про
грамму.
5. Допустимыми номерами страниц являются положительные целые числа.
Если пользователь ввел недопустимый номер страницы:
• предупредить пользователя о том, что введенное значение недопустимо;
• вернуться к шагу 3.
6. Запросить конечный номер страницы.
7. Если пользователь не ввел конечный номер страницы, завершить про
грамму.
8. Если пользователь ввел недопустимый номер страницы:
• предупредить пользователя о том, что введенное значение недопустимо;
• вернуться к шагу 6.
440 ГЛАВА 18 Графические интерфейсы
9. Запросить место (путь) для сохранения извлеченных страниц.
10. Если пользователь не выбрал место для сохранения, :Jавершить про
грамму.
11. Если выбранное место совпадает с нутем входного файла:
• предупредить пользователя о том, что перезапись входного файла
недопустима;
• вернуться к шагу 9.
12. Выполнить извлечение страниц:
• открыть входной файл PDF;
• записать новый файл PDF,которыйсодержиттолькостраницыиз
выбранного диапазона.
Решение задачи, а также многие другие дополнительные ресурсы доступны
в интернете по адресу realpython.com/python-basics/resources.
18.4 . ЗНАКОМСТВО С TKINTER
Для Python существует много GUI-фреймворков, 110 Tkinter - единственный
фрейм ворк, вст роен ный в ст андарт ную биб лиотек у P yth on .
У Tkinter несколько сильныхсторон. Прежде всегоэто кросс-платформенность,
то есть одини тотже кодбудет работать вWindows, macOS и Linux. Визуаль
н ые э леме нты с тр о я т ся с исп ользов анием со бственн ых элементов и нтерф ейса
операционной системы, так что приложения, построенные на базе Tkinter,
выглядят так, словно они были написаны для той конкретной платформы, H<J.
которо й он и вып олняю тся.
Хотя Tkinter считается стандартным фреймворком Python GUI, он не лишен
недостатков. Один из наиболее заметных заключается в том, что графические
инте рфейсы , постро енные с исполь зовани ем T kinte r , выгля дят у старевш ими.
Если вам нужен стильный, современный интерфейс, воэможно, Tkinter - не
то, что вам подойдет.
Тем не менее Tkinter относительно компактен и прост в исполь:ювании 110
сравнению с другими фреймворками. Это делает его привлекательным для
построения GUl-приложений на языке Python, особенно если современный
лоск вас не привлекает, а на первый план выходит быстрое построение функ
ц иональ ного и к росс-пла тформен ного решения.
Посмотрим, как строятся приложения на базе Tkinter.
18.4 . Знакомство с Tkinter 441
ПРИМЕЧАНИЕ
Как я говорил в предыдущем разделе, среда IDLE построена на базе Tkiпter.
Из-за этого у вас могут возникнуть трудности при запуске ваших собственных
GUl -программ в IDL E.
Если вы обнаружите, что окно GUI, которое вы пытаетесь создать, внезапно
зависает или IDLE начинает вести себя непредсказуемо, попробуйте запустить
программу из командной строки или терминала.
Ваше первое приложение на базе Tkinter
Основополагающим элементом графического интерфейса Tkinter является
окно. Окна - это контейнеры, в которыхразмещаются все остальные элементы
GUI. Другие элементы - текстовые поля, надписи, кнопки и т. д. - называются
виджетами. Виджеты размещаются внутри окон.
Создадим окно, содержащееодин виджет. Откройте новое интерактивное окно
в IDLE.
Первое, что необходимо сделать, - импортировать модуль Tkinter:
>>> import tkinter as tk
Окно является экземпляром класса Tkinter Tk. Создайте новое окно и присвойте
его переменной window:
>>> window = tk.Tk()
При выполнении этого кода на экране появится новое окно. Его внешний вид
зависит от операционной системы.
1tk
ох
tk
""""'0
(а) Windows
(b)macOS
(с) Ubuntu
Далее в этой главе я буду использовать скриншоты для Windows.
442 ГЛАВА 18 Графические интерфейсы
Теперь, когда у вас имеется окно, добавим в него виджет. Применим класс
tk. Label для добавления в окно текста.
Создайте виджет Labelс текстом "Hello, Tkinter" и присвойте его переменной
с именем greeting:
>>> greeting = tk.Label(text="Hello, Tkinter")
Окно, созданное ранее, не изменяется. Вы только что создали виджет Label, но
он еще не был добавлен в окно.
Есть несколько способов добавления виджетов в окно. Сейчас мы воспользуемся
методом .pack() виджета Label:
>>> greeting.pack()
Окно принимает следующий вид:
о
х
Hell:o, Тklnter
Когда вы вызываете .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 aregreat !" .
2. Повторите упражнение 1с текстом "Python rocks! ".
3. Повторите упражнение 1с текстом "Engage! " .
18.5 . РАБОТА С ВИДЖЕТАМИ
Виджеты - подлинная суть Tkinter. Они создают элементы, посредством ко
торых пользователь взаимодействует с вашей программой.
Каждый виджет в Tkinterопределяется классом. Несколько примеров доступ
ных виджетов показаны ниже.
КЛАСС ВИДЖЕТА
Label
Button
Entr y
Te xt
Fra me
ОПИСАНИЕ
Виджет для вывода текста на экран
Кнопка, которая может содержать текст и выполняет дей
ствие при нажатии
Виджет для ввода одной строки текста
Виджет для ввода многострочного текста
Прямоугольная область, используемая для группировки
взаимосвязанных виджетов или для создания отступов
между виджетами
.ПРИМЕЧАНИЕ
·
·
.
Tkinter содержит намного больше виджетов, чем перечислено в таблице. Пол
ный список вы найдете в статьях «Basic Widgets» (https://tkdocs.com/tutorial/
widgets.html) и «MoreWidgets» (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", #Выбрать белый цвет текста
back gro und = "Ьl ack" # Вы брать черн ый цв ет фона
Tkinterподдерживает много разных названий цветов, в том числе:
•
"red"
•
"orange"
•
"yellow"
•
"green"
•
"Ыu е"
•
"purple"
ПРИМЕЧАНИЕ
Полный список цветов, включаясистемные цвета macOSи Windows, опреде
ляемые текущейтемой ОС,доступен на сайтеTkDocs (https://www.tcl.tk/man/
tc l8.6/ ТkCmd /colo rs.ht ml).
18.5 . Работа с виджетами 445
Многие названия цветов HTML (https.j/htmlcolorcodes.com/color-names/) также
подходят для Tkinter.
Цвета также возможно задавать шестнадцатеричными кодами RGB:
label = tk.Label(text="Hello, Tkinter", background="#34A2FE")
Эта команда назначает label приятный светло-синий цвет фона.
Шестнадцатеричные коды RGB менее понятны, чем текстовые названия
цветов, но открывают доступ к более широкой цветовой палитре. К счастью,
существуют средства (https.j/htmlcolorcodes.com/), значительно упрощающие
работу с шестнадцатеричными кодами цветов .
Если вы не хотите постоянно вводить названия foreground и background, ис
пользуйте сокращенные имена fg и bg для назначения цветов текста и фона:
label = tk.Label(text="Hello, Tkinter", fg = "white", bg="Ьlack")
Также можно управлять шириной и высотой надписи при помощи параметров
width и height:
label = tk.Label(
text= "H ello, Tkinte r ",
fg="white",
bg= "Ьla ck" ,
width=10,
heig ht=1 0
Вот как виджет Label будет выглядеть в окне:
_,
D
х
446 ГЛАВА 18 Графические интерфейсы
Может показаться странным, что виджет в окне не квадратный, хотя и width,
и height присвоено значение 10. Это объясняется тем, что значения height
и width измеряются в текстовых единицах.
Одна горизонтальная текстовая единица определяется шириной символа 0
(цифра ноль) системного шрифта по умолчанию. Аналогичным образом одна
вертикальная текстовая единица определяется высотой этого же символа 0.
ПРИМЕЧАНИЕ
Для обеспечения последовательного поведения приложения на разныхплат
формахTkiпterизмеряет высоту и ширину в текстовых единицах вместо дюй
мов, сантиметров или пикселей.
Определение единиц по ширине символа означает, что размер виджета опре
деляется шрифтом по умолчанию на машине пользователя.Это гарантирует,
что текст будет правильно размещаться в надписях и на кнопках независимо
от того, где выполняется приложение.
Виджеты Label отлично подходят для вывода текста, но они не годятся для
получения ввода от пользователя. Следующие три виджета, которые мы рас
смотрим, предназначены для решения именно этой задачи.
Виджеты Button
Виджеты Buttoп применяют для отображе ния кнопок, которые пользователь
может нажимать. Их можно настроить так, чтобы при щелчке на кнопке вызы
валась заданная функция
.
О том, как вы зывать функции при нажатии кнопки,
я расскажу в следующем разд еле. А пока я покажу, как создать виджет Button
и применить к нему стилевое оформление
.
Междувиджетами Button и Label существует определенное сходство. Во многих
отношениях виджет Button - это всего лишь виджет Label, на котором можно
щелкать мышью! Ключевые аргументы, используемые при создании и стилевом
оформлении Label, работают и с виджетами Button. Например, следующий
фрагмент создает виджет Button с синим фоном, желтым текстом и шириной
и высотой 25 и 5 текстовых единиц соответственно:
button = tk.Button(
te xt= "C lick me!" ,
width=25,
height=S,
bg="Ь lue",
fg="yellow",
18.5 .Работа с виджетами 447
А вот как виджет Button выглядит в окне:
Непло хо!
ПРИМЕЧАНИЕ
Фоны кнопок не работаютвmacOS.Этоограничение операционной системы,
а не ошибка вTkinter.
Следующие два виджета, о которых речь пойдет ниже, предназначены для
получениятекстового ввода от пользователя.
Виджеты Entry
Если вам нужно получить от пользователя небольшой фрагмент текста (напри
мер, имя или адрес электронной почты), используйте виджет Entry. Он выводит
поле, в котором пользоват ель может печатать текст.
Виджеты Entryсоздаются практически так же, как Label и Button. Например,
следующая команда создает виджет с синим фоном, желтым текстом и шириной
50 текстовых единиц:
entry = tk.Entry(fg="yellow", bg="Ьlue", 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()
Результат выглядит так:
D
х
Name
Tkinterавтоматически выравниваетLabelпоцентру над виджетом Entry вокне.
Это особенность метода . pack( ), о которой вы узнаете позднее.
Щелкнитевнутри виджета Entryи введите "RealPython".
D
х
Name
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" .
о
х
Nam e
eal Python
ПРИМЕЧАНИЕ
Символы текста в виджетахEntry, как и символы строковых объектов Python,
индексируются с нул я.
Если потребуется удалить из Entry сразу несколько символов, передайте
. delete () второй целочисленный аргумент с индексом символа, на котором
удаление должно остановиться.
Например, следующая команда удаляет первые четыре буквы измененного
текста в виджете Entry:
>>> entry.delete(0, 4)
Команда удаляет символы "е", "а" и "1 " с последующим пробелом. В виджете
остается текст "Python".
о
х
N<3m e
Python
ПРИМЕЧАНИЕ
Entry.delete()работает также, как и срезы строк. Первый аргумент определяет
начальный индекс, а символы удаляются до индекса, переданного во втором
аргументе, - не включая его.
450 ГЛАВА 18 Графические интерфейсы
Чтобы удалить весь текст в Entry, передайте специальную константуtk.END во
втором аргументе .delete():
>>> entry.delete(0, tk.END)
Текстовое поле остается пустым:
о
х
Na me
Вставка текста в виджет Entry выполняется методом .insert():
>>> entry.insert(0, "Python")
Окно выглядит так:
о
х
Na me
Python
Первый аргумент сообщает . insert (), в какой позиции должен быть вставлен
текст. Если в виджете Entryнет текста, новый текст всегдавставляется в начале
виджета независимо от того, какое значение передается во втором аргументе.
Например, если в приведенном выше вызове . insert() передать в первом ар
гументе100 вместо 0, результат окажетсятемже самым.
Если виджет Entryуже содержит текст, .insert() вставляет новый текст в за
данной позиции и сдвигает весь существующий текст вправо:
>>> entry.insert(0, "Real ")
Теперь виджет содержит текст "Real Python".
о
х
Nam e
Real Python
18.5. Работа с виджетами
451
Виджеты Entry хорошо подходятдля получения небольших фрагментовтекста
от пользователя, но так как этот текст всегда отображается в одной строке, они
хуже подходят для получения больших объемов текста. На помощь приходят
виджеты Text.
Виджеты Text
Виджеты Text используются для ввода текста, как и виджеты Entry. Различия
в том, что первые могут содержать несколько строк текста. С виджетом Text
пользователь может вводить целые абзацы - и даже целые страницы!
Виджеты Text, как и виджеты Entry, поддерживают три основные операции:
1) получение текста методом . get( );
2) удаление текста методом .delete();
3) вставка текста методом . insert( ).
И хотя имена методов совпадают с именами методов Entry, работают они не
много иначе. Проверим их на практике - создадим виджет Text и посмотрим,
на что он способен.
ПРИМЕЧАНИЕ
Если окно из предыдущего раздела все еще открыто, вы можете закрыть его
следующей командой в интерактивном окне IDLE:
>>> window.destroy()
Также окно можно закрыть вручную, щелкнув на кнопке Х в заголовке окна.
В интерактивном окне IDLE создайте новое пустое окно и включите в него
виджет Text вызовом .pack():
>>> window = tk.Tk()
>>> text_box = tk.Text()
>>> text_box.pack()
На экране должно появиться окно с текстовым полем. Щелкните в любой точке
окна, чтобы активизировать текстовое поле. Введите слово "Hello", нажмите
Enter и введите во второй строке слово "World" .
Окно должно выглядеть примерно так:
452 ГЛАВА 18 Графические интерфейсы
Hello
1if or ld
о
х
Текст виджета 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: 'indexl'
Метод Text. get ( ) получает как минимум один аргумент. Вызов .get () с одним
индексом возвращает один символ. Для получения нескольких символов не
обходимо передать два аргумента: начальный и конечный индекс.
Индексы виджетов Text отличаются от индексов виджетов Entry. Так как вид
жеты Text могут содержать многострочный текст, индекс должен содержать
два значения:
1) номер строки;
2) позицию символа в этой строке.
Номера строк начинаются с 1, а позиции символов - с 0.
Индекс строится в виде строки вида "<строка>. <символ>" (<строка> заменяется
номером строки, а <символ> - номером символа).
Например, индекс "1.0 " представляет первый символпервой строки, а "2. з" -
четвертый символ второй строки.
18.5. Работа с виджетами
453
Используем индекс "1 .0" для получения первой буквы первой строки тексто
вого поля, созданного ранее:
>>> text_box.get("l.0 ")
'Н'
Таккакиндексысимволовначинаютсяс О,аслово "Hello" начинается в первой
позиции текстового поля, индекс буквы о равен 4. Как и в случае со срезами
строк Python, для извлечения всего слова Hello из текстового поля конечный
индекс должен быть на 1 больше индекса последнего читаемого символа.
Таким образом, чтобы получить из текстового поля все слово "Hello", следует
использовать первый индекс "1 .0 " и второй индекс "1 .5":
>>> text_box.get("l.0" , "1.5")
'He llo'
Чтобы получить слово "World" во второй строке текстового поля, измените
номер строки в каждом индексе на 2:
>>> text_box.get("2.0" , "2.5")
'World'
Чтобы получить весь текст из текстового поля, передайте начальный индекс
" 1 .0 " и специальную константу tk.END вместо второго индекса:
>>> text_box.get("l.0" , tk.END)
'Hello\nWorld\n'
Обратите внимание: текст, возвращаемый . get (), включает символы новой
строки. Пример также показывает, что каждая строка в виджете Text, включая
последнюю, завершается символом новой строки.
Метод . delete() используется для удаления символов из текстового поля. Он
работает так же, как и метод . delete () для виджетов Entry.
Метод .delete() может использоваться двумя способами:
1) с одним аргументом;
2) с двумя аргументами.
В версии с одним аргументом .delete() передается индексодногоудаляемого
символа. Например, следующая команда удаляет из текстового поля первый
СИМВОЛ Н:
>>> text_box.delete("l.0")
454 ГЛАВА 18 Графические интерфейсы
В результате в первой строке остаются символы "ello":
el lo
lioi::ld
о
х
В версиис двумя аргументамипередаютсядва индекса для удалениядиапазона
символов, от символа с первым индексом и до символа со вторым индексом
(не включая последний).
Например, чтобы удалить оставшиеся символы "ello" из первой строки тек
стового поля, используйте индексы "1.0" и "1.4":
>>> text_box.delete("l.0", "1.4")
Текст в первой строке исчезает. На его месте остается пустая строка, а за ней
следует слово "World" во второй строке:
о
х
lioi::ld
18.5. Работа с виджетами
455
При этом в первой строке остается символ, хотя он и не виден, - символ новой
строки.
В этом можно убедиться при помощи метода . get():
>>> text_box.get("l .0")
'\ n'
Еслиудалить этот символ, то остальное содержимое текстового поля сдвигается
вверх на одну строку:
>>> text_box.delete("l.0")
Теперь слово "World" выводится в первой строке текстового поля:
,tk
о
х
iiorld
Сотрем остальной текст из текстового поля. В первом аргументе передается
значение "1 .0" , а во втором - tk.END:
>>> text_box.delete("l.0" , tk.END)
Текстовое поле становится пустым.
Для вставки текста в текстовое поле используется метод .insert():
>>> text_box.insert("l.0", "Hello")
456 ГЛАВА 18 Графические интерфейсы
В начале текстового поля появляется слово "Hello":
,tk
о
х
B eJ.J.o
Посмотрим, что произойдет при попытке вставить слово "World" во вторую
строку:
»> text_box.insert("2.0 ", "World")
Однако текст вставляется не во второй строке, а в конец первой строки:
,tk
о
х
BeJ.J.oWorJ.d
Если вы хотите вставить текст в новую строку, придется вручную добавить
символ новой строки во вставляемый текст:
»> text_box.insert("2.0" , "\nWorld")
18.5. Работа с виджетами
457
Теперь слово "World" оказывается во второй строке текстового поля:
Hello
World
о
х
Таким образом, . insert() либо вставляет текст в заданной позиции, если в ней
уже есть текст, либо присоединяет его к заданной строке, если количество сим
волов больше индекса последнего символа в текстовом поле.
Отслеживать индекс последнего символа обычно неудобно. Лучший способ
вставки текста в конец виджета Text - передача tk. END в первом параметре
.insert():
text_box.insert(tk.END, "Put me at the end!")
Не забудьте включить символ новой строки \n в начало текста, если вы хотите
вывести его в новой строке:
text_box.insert(tk.END, "\nPut me оп а new line!")
Label, Button, Entry и Text - всего лишь малая часть виджетов, доступных
в Tkinter. Также существуют виджеты для флажков, переключателей, полос
прокрутки и индикаторов прогресса. Дополнительную информацию о других
доступных виджетах вы найдете в учебнике на сайте TkDocs.com (https:j/tkdocs.
com/tutorial/widgets.html).
В этой главе мы будем работать только с пятью виджетами - с четырьмя вы уже
познакомились, а сейчас пришло времядля пятого - Frame. Виджеты Frame игра
ют важную роль в организации расположения виджетов в вашем приложении.
Прежде чем рассказывать о формировании визуального представления вид
жетов, я 11окажу, как работают ви11.жеты Frame и как связать их с другими
виджетами .
458 ГЛАВА 18 Графические интерфейсы
Связывание виджетов с Frame
Следующая программасоздает пустой виджет Frame и связывает его с главным
о кн ом прил ожен ия:
import tkinter as tk
window = tk.Tk()
frame = tk.Frame()
frame. pack()
window.mainloop()
Вызов frame. pack() упаковывает фрейм в окно так, чтобы оно имело мини
мально возможный размер, но при этом вмещало фрейм.
При выполнении приведенного выше кода вы получите абсолютно неинтерес
ный результат:
о
х
Пустой виджет Frame практически невидим. Фреймы лучше всего рассматри
вать как контейнеры для других виджетов. Чтобы связать виджет с фреймом,
задайте значение атрибута master виджета:
frame
label
tk.Frame()
tk.Label(master=frame)
Чтобы вы получили представление о том, как это работает, напишем программу,
которая создаетдва виджета Frameс именами frame_a и frame_b . Фреймframe_a
должен содержать виджет Labelс текстом "I 'm in Frame А", а frame_b - виджет
Label с текстом "I 'm in Frame В". Одно из возможных решений выглядит так:
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 А")
label_a .pack()
label_b = tk.Label(master=frame_b, text="I'm in Frame В")
labe l_b. pa ck ()
frame_a.pack()
frame_b .pack()
window.mainloop()
18.5 . Работа с виджетами 459
Обратите внимание: frame_a упаковывается в окне доframe_b. Воткрывшемся
окне виджет Label в frame_a располагается над виджетом Label в frame_b.
-1D
х
l'm in FrameА
l'm in Frame В
Посмотрим, что произойдет, если поменять местами вызовы 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 А")
label_a.pack()
frame_b = tk.Frame()
label_b = tk.Label(master=frame_b, text="I'm in Frame В")
label_b .pack()
# 'f ra me _a' и 'frame_ь- м еняю тся мес тами
frame_b. pack()
frame_a .pack()
window.mainloop()
Результат выглядит так:
-1D
х
l'm in Fram.e В
l'm in.Fram.eA
Теперь виджет label_b располагается сверху. Таккак виджетlabel_bбыл связан
с frame_b , он перемещается каждый раз, когда вы перемещаете frame_b .
Все четыре типа виджетов, о которых вы узнали, - Label, Button, Entry и Text -
поддерживаютатрибут master, который задается при созданииэкземпляра. Он
позволяет управлять тем, с каким фреймом связывается виджет.
Виджеты Frame очень удобны для логического упорядочения другихвиджетов.
Взаимосвязанные виджеты можно связать с одним фреймом, чтобы при пере
мещении фрейма в окне все эти виджеты оставались в одной группе.
460 ГЛАВА 18 Графические интерфейсы
Кроме логической группировки виджетов, Frame могут добавить немноголоска
внешнему облику вашего приложения. Далее вы узнаете, как создавать различ
ные визуальные эффекты при отображении виджетов Frame.
Изменение внешнего вида фреймов
с использованием атрибута relief
Виджеты Frame можно настроить атрибутом relief, который создает визу
альный эффект вокруг фрейма. Атрибуту relief можно присвоить любое из
следующих значений:
•
tk.FLAT - визуальный эффект отсутствует (значение по умолчанию);
•
tk.SUNKEN - эффект углубления;
•
tk.RAISED- эффект возвышения;
•
tk.GROOVE - эффект выемки;
•
tk.RIDGE - эффект гребня.
Чтобы применить визуальный эффект, необходимо присвоить атрибуту
borderwidth значение больше 1. Этот атрибут задает ширину обрамления
в пикселях.
Чтобы понять, что делает каждый эффект relief, проще всего увидеть их
в действии. Следующая программа упаковывает в окне пять виджетов Frame
с разными значениями аргумента relief:
import tkinter as tk
border_effects = {
" flat": tk.FLAT,
"sunken": tk.SUNKEN,
"r aised": 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)
#з
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.
Окно, которое создается при выполнении приведенного выше кода, выглядит
примерно так:
о
х
flat F
raisedl groovel@
На иллюстрации показаны примеры всех рельефных эффектов:
•
tk.FLAT создает плоское обрамление (отсутствие визуального эффекта);
•
tk.SUNKEN добавляет эффект «погружения» фрейма в окно;
•
tk. RAISED добавляет эффект, когда фрейм кажется выступающим из
экрана;
•
tk.GROOVE создает эффект углубленной борозды, окружающей плоский
ф рейм;
•
tk.RIDGE создает эффект приподнятой кромки, окружающей фрейм.
Правила именования виджетов
При создании виджету можно присвоить любое имя, если оно является
валидным идентификатором Python. Тем не менее на практике имя класса
462 ГЛАВА 18 Графические интерфейсы
виджета часто включается в имя переменной, которой присваивается экземп
ляр виджета.
Например, если виджет Label используется для вывода имени пользователя,
емуможно присвоитьимя label_user_name . А виджет Entry, предназначенный
для ввода возраста пользователя, можно называть entry_user _age.
Включая имя класса виджета в имя переменной, вы помогаете понять любому
читателю вашего кода, к какому типу виджета относится имя переменной.
Включение полного имени класса виджета может привести к появлению длин
ных имен переменных, поэтому для типов виджетов стоит ввести сокращенные
обозначения. Далее в этой главе в именах виджетов мы будем использовать
следующие префиксы.
КЛАССВИДЖЕТА
ПРЕФИКС
ПРИМЕР
Label
lЫ
lЫ _name
Button
btn
btn_submit
Ent ry
ent
e nt_a ge
Text
txt
txt_notes
Frame
frm
fr m_a ddr ess
В этом разделе я показал, как создавать окна, использовать виджеты и работать
с фреймами. На данный момент вы умеете создавать простые окна, в которых
выводятся сообщения, но до полноценных приложений дело еще не дошло.
В следующем разделе вы научитесь управлять макетом ваших приложений при
п о м ощ и топо логических мен едж еро в Tkin teг.
Упражнения
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 tkiпter as tk
wiпdow = tk.Tk()
framel = tk.Frame(master=window, width=100, height=100, bg ="red")
framel.pack()
frame2 = tk.Frame(master=window, width=50 , height=50 , bg ="yellow")
frame2.pack()
464 ГЛАВА 18 Графические интерфейсы
frameЗ = tk.Frame(master=window, width=25, height=25, bg="Ьlue")
frameЗ.pack()
window.mainloop()
Поумолчанию .pack() размещает каждый виджет Frame подпредыдущим в том
порядке, в котором они связывались с окном.
о
х
11
Каждый виджет Frame размещается в высшей доступной позиции. Красный
виджет Frame размещается у верхнего края окна. Желтый виджет Frame раз
мещается непосредственно под красным, а синий - под желтым.
Существуют три невидимые области, каждая из которых содержит один из трех
виджетов Frame. Каждая область имеет ширину окна и высоту виджета Frame,
который в ней содержится. Так как при вызове .pack() для каждого виджета
Frame якорная точка не указывалась, все они выравниваются по центру своих
областей, а следовательно, по центру окна.
Метод . pack() получает ключевые аргументы, позволяющие точнее настро
ить расположение виджетов. Например, ключевой аргумент fill позволяет
указать, в каком направлении должны заполняться фреймы. Поддерживаются
три значения:
1) tk.X - заполнение в горизонтальном направлении;
2) tk.У - заполнение в вертикальном направлении;
3) tk. вотн - заполнение в обоих направлениях.
В следующем примере три фрейма размещаются так, чтобы каждый заполнял
все окно по горизонтали:
18.6 .Управление макетом при помощи менеджеров геометрии
465
import tkinter as tk
window = tk.Tk()
framel = tk.Frame(master=window, height=100 , bg= "red")
framel.pack(fill=tk.X)
frame2 = tk.Frame(master=window, height=50 , bg ="yellow")
frame2.pack(fill=tk.X)
frameЗ = tk.Frame(master=window, height=25 , bg="Ьlue")
fr ameЗ .pa ck( fil l=t k.X )
window.mainloop()
Обратите внимание:для виджетов Frameзначение width более не задается. Оно
не нужно, потому что .pack() настраивается на горизонтальное заполнение
в каждом фрейме, с переопределением любой заданной ширины.
Окно, созданное этим фрагментом, выглядит так:
Одно из преимуществ заполнения окна методом . pack () заключается в том, что
заполнение реагирует на изменение размеров окна. Чтобы понять, как это про
исходит, попробуйте увеличить ширину окна, сгенерированного приведенным
выше кодом.
При увел ичени и ширины окна ши р ин а т ре х видже тов Frame увеличивается для
заполнения окна. Однако следует заметить, что виджеты Frame не расширяются
в верти кально м нап равле нии .
Ключевой аргумент side метода . pack() указывает, с какой стороны окна дол
жен размещаться виджет. Допустимые варианты - tk.ТОР, tk.воттом, tk.LEFT
и tk. RIGHT. Если сторона не задана, .pack() автоматически использует tk.ТОР
и размещает новые виджеты у верхнего края окна или в самой верхней части
окна, которая еще не занята виджетом.
466 ГЛАВА 18 Графические интерфейсы
Например, следующая программа размещает три фрейма рядом друг с другом
слева направо и расширяет каждый фрейм, чтобы он заполнял окно по верти
кали:
import tkinter as tk
window = tk.Tk()
мframel = tk.Frame(master=window, width=200 , height=100 , bg ="red"}
framel.pack(fill=tk.Y, side=tk.LEFT}
frame2 = tk.Frame(master=window, width=100, bg="yellow")
frame2.pack(fill=tk.Y, side=tk.LEFT}
frameЗ = tk.Frame(master=window, width=50, bg="Ьlue")
frameЗ.pack(fill=tk.Y, side=tk.LEFT}
window.mainloop()
На этот раз нам приходится задать ключевой аргумент height по крайней мере
для одного из фреймов, чтобы задать высоту окна.
Полученное окно выглядит так:
о
Подобно тому как присваивание fill=tk.X меняло ширину фреймов при из
менении ширины окна, присваивание fill=tk. У меняет высоту фреймов при
изменении размеров окна по вертикали. Убедитесь сами!
Чтобы макет полноценно реагировал на корректировку размеров, можно за
дать исходный размер ваших фреймов при помощи атрибутов width и height.
Затем задайте ключевому аргументу fill метода . pack() значение tk. ВОТН ,
а ключевому аргументу expand - значение True:
import tkinter as tk
window = tk.Tk()
framel = tk.Frame(master=window, width=200, height=100 , bg= "red")
framel.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
frameЗ = tk.Frame(master=window, width=50 , bg="Ьlue")
frameЗ.pack(fill=tk.BOTH, side=tk.LEFT , expand=True)
window.mainloop()
При выполнении этого кода появляется окно, которое изначально похоже на
созданное в предыдущем примере. Различие в том, что теперь при изменении
размеров окна фреймы будут расширяться и заполнять окно соответствующим
образом. Впечатляет!
Менеджер геометрии .place()
Метод .place() позволяет управлять точным расположением виджета в окне
или фрейме. При вызове метода должны передаваться два ключевых аргумен
тах и у, определяющих координаты левого верхнего угла виджета. Значениях и у
задаются в пикселях, а не в текстовых единицах.
Помните, что начало координат (точка,для которойкоординаты х и у равны0)
располагается в левом верхнем углу фрейма или окна. Аргумент у метода
. p lace() можно рассматривать как расстояние в пикселях от верхнего края
окна, а аргумент х - как расстояние в пикселях от левого края.
Пример использования менеджера геометрии .place():
import tkinter as tk
window = tk.Tk()
#1
frame = tk.Frame(master=window, width=150 , height=150)
frame. pack()
#2
labell = tk.Label(master=frame, text="I'm at (0, 0)", bg="red")
labell.place(x=0 , у=0)
#3
label2 = tk.Label(master=frame, text="I'm at (75, 75)", bg="yellow")
lab el2. pla ce (x= 75, у=75)
window.mainloop()
Сначала создается новый виджет Frame с именем frame, имеющий 150 пикселей
в ширину и 150 пикселей в высоту, который размещается в окне вызовом . pack( ).
Затем создается элемент Label с красным фоном, которому присваивается имя
labell; он размещается в framel в позиции (О, О). Наконец, создается второй
виджет Label с желтым фоном, которому присваивается имя label2,и он раз
мещается в framel в позиции (75, 75).
468 ГЛАВА 18 Графические интерфейсы
Окно, созданное этим кодом, показано ниже:
о
х
l'm at(75, 75)
Метод . 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() разместить виджет в третьем столбце второй строки.
Например, следующий код создает сетку ЗхЗ из фреймов, в которых упакованы
виджеты Label:
import tkinter as tk
window = tk.Tk()
for 1 ln range(З):
for j in range(З):
frame = tk.Frame(
master=window,
relief=tk.RAISED ,
borderwidth=l
frame.grid(row=i, column=j)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack()
window.mainloop()
Пос троен ное ок но выг ляди т так.
1tk -
D
х
R owO
R owO
R owO
ColumnO Column 1 Column2.
Row1
Row1
Row1
ColumnO Column 1 Column2.
Row2. Row2.
R ow2.
ColumnO Column 1 Column2.
В этом примере используются два менеджера геометрии. Каждый виджет 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(З):
for j in range(З):
frame = tk.Frame(
master=window,
relief=tk.RAISED ,
borderwidth=l
frame.grid(row=i , column=j, padx=S , pady=S)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label. pack()
window.mainloop()
Получаем окно следующего вида:
1tk
D
Х
R owO
R owO
R owO
Column О Column 1 Column2
Row1
Row 1
Row 1
CofumnO Cofumn 1 Col.umn2
Row2
Row2
Row2
Co!umnО Column 1 Cofumn 2
18.6. Управление макетом при помощи менеджеров геометрии 471
Метод . pack() тоже содержит параметры padx и pady. Следующий фрагмент
почти идентичен предыдущему,не считая того, что вокруг каждоговиджета Label
создаются дополнительные отступы размером 5 пикселей в направлениях х и у:
import tkinter as tk
window = tk.Tk()
for 1 in range(З):
for j in range(З):
frame = tk.Frame(
master=window,
relief=tk.RAISED ,
borderwidth=l
frame.grid(row=i, column=j, padx=S, pady=S)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack(padx=S, pady=S)
window.mainloop()
Дополнительныеотступывокруг виджетов Label создают в каждойячейке сетки
немного свободного места между границей Frame и текстом Label.
1tk
-
о
х
Row O
Ro wO
RowO
ColumnO Column 1 Column2
Ro w1
Row1
Row1
C olum nO
Columл 1
Column2
Row2
R ow2
Row 2
ColumnO Column1 Column2
Симпатично выглядит! Но если вы попробуете расширить окно в каком-либо
направлении, то увидите, что макет плохо реагирует на изменения. При увели
чении окна вся сетка остается в левом верхнем углу.
Чтобы управлять размерами строк и столбцов сетки при изменении размеров
окна, используйте методы .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(З):
window.columnconfigure(i, weight=l, minsize=75)
window.rowconfigure(i, weight=l, minsize=50)
for j in range(0, З):
frame = tk.Frame(
master=window,
relief=tk.RAISED ,
borderwidth=l
frame.grid(row=i, column=j, padx=S, pady=S)
label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
label.pack(padx=S, pady=S)
window.mainloop()
Методы .columnconfigure() и .rowconfigure() размещаются в теле внешнего
циклаfor. Каждый столбец и каждую строку можно настроить явно за преде
ламициклаfor, но для этого потребуется написать шесть лишних строк кода.
Прикаждойитерациициклаi-eстолбец истрока настраиваютсяс весом 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)
lab ell = tk. Labe l(te xt= "A")
labell.grid(row=0, column=0)
label2 = tk.Label(text="B ")
label2.grid(row=l, column=0)
window.mainloop()
Каждая ячейка таблицы - 250 пикселей в ширину и 100 пикселей в высоту.
Виджеты Labelразмещаются в центре каждойячейки, как видноиз следующей
иллю страци и:
1tk
D
х
А
в
Вы можете изменить расположениекаждоговиджета Label внутриячейки сетки
при помощи параметра sticky метода .grid(). Параметру stickyприсваивается
строка, содержащая одну или несколько букв:
474 ГЛАВА 18 Графические интерфейсы
• "n" или "N " для выравнивания по центру верхней стороны ячейки;
•
"s" или "5"* для выравнивания по центру нижней стороны ячейки;
•
"е" или "Е" для выравнивания по центру правой стороны ячейки;
•
" w" или "W" для выравнивания по центрулевой стороны ячейки.
Буквы "n", "s", "е" и "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)
l ab ell = tk.L abe l(te xt=" A")
labell.grid(row=0, column=0 , sticky="n ")
label2 = tk.Label(text="B ")
label2.grid(row=l, column=0 , sticky="n")
window.mainloop()
Результат показан ниже:
1tk
А
в
о
х
18.6 .Управление макетом при помощи менеджеров геометрии 475
Чтобы разместить каждый виджет Labelв углу ячейки, объедините несколько
букв в одной строке:
import tkinter as tk
window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)
lab ell = tk.La be l(te xt=" A")
labell.grid(row=0, column=0, sticky="ne ")
lab el2 = tk.L abe l(te xt= "B ")
label2.grid(row=l, column=0, sticky="sw")
window.mainloop()
В этом примере параметру sticky виджета labell присваивается значение
"пе", при котором виджет размещается в правом верхнем углу ячейки. label2
размещается в левом нижнем углу, для чего sticky присваивается строка" sw" .
Вот как это выглядит в окне:
D
х
А
в
При позиционировании виджета с использованием 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)
labell = tk.Label(text="l", bg="Ыack", fg="white")
label2 = tk.Label(text="2" , bg="Ыack", fg="white")
labelЗ = tk.Label(text="З", bg="Ыack", fg="white")
label4 = tk.Label(text="4" , bg="Ыack", fg="white")
labell.grid(row=0, column=0)
label2.grid(row=0, column=l, sticky="ew ")
label3.grid(row=0, column=2, sticky="ns")
label4.grid(row=0, column=З, sticky="nsew ")
window.mainloop()
Результат выглядит так:
1tk
1-
Этот пример показывает, что при помощи параметра sticky менеджера гео
метрии . grid () можно достичь того же эффекта, что и с параметром fill ме
неджера геометрии .расk().
Соответствие между параметрами sticky и fill показано в следующей таблице.
. GR ID()
stic ky ="n s"
st icky= "e w"
st icky= "n sew"
•РА СК ()
fill=tk.у
fill=tk.X
fill=tk.BOTH
. grid() - очень мощный менеджер геометрии. Обычно он более понятен, чем
.pack(), и намного более гибок, чем .place(). При создании новых приложе
ний Tkinter в качестве основного менеджера геометрии стоит рассматривать
именно .grid().
18.6 .Управление макетом при помощи менеджеров геометрии 477
ПРИМЕЧАНИЕ
Менеджер геометрии .grid()обладает гораздо болеегибкимивозможностями,
чем мнеудалось показатьздесь.Например,вы можете настроить ячейки так,
чтобы они охватывали несколько строк и столбцов.
Дополнительную информацию вы найдете в разделе«GridGeometry Maпager»
(https://tkdocs.com/tutorial/grid.html} учебного руководства TkDocs (https://
tkdocs.com/tutorial/index.htm1}.
Теперь, когда вы познакомилисьсазамииспользования менеджеровгеометрии
Tkinter, я предлагаю вам сделать следующий шаг - оживите интерфейсвашего
приложения, связывая с кнопками выполняемые действия.
Упражнения
1. Попробуйте воссоздать ск риншоты из этого раздела, не подглядывая
в исходный код. Если вы зайдете в тупик, просмотрите код и завершите
упражнение. Затем подождите 10-15 минут и попробуйте снова. Повто
ряйте, покане сможете проделать все это самостоятельно. Сосредоточь
тесь на выводе. Если ваш код будет слегка отличаться от приведенного
в книге, это абсолютно нормально.
2. Ниже показано окно, созданное в Tkinter. Попробуйте воссоздать его
средствами, о которых вы узнали в этой главе. Используйте любой ме
неджер геометр ии н а свое усмотрение.
1Address Entry Form
-
о
х
FirstName: 1
...
Last Name: 1
--
---..
-
Address l ine 1: 1
-~
·
-
·--·· -
~.-
Address l ine 2: 1
-
-
-
City: 1
-
-
State/Province: 1
·-
Post:a/ Code: 1
Countr:y: 1
-
-
-
Г Clear
1 1 Submit
1
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() и обновим код цикла событий:
eve r1ts_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. m ain loop( ) :
import tkinter as tk
# Создать объект окна
window = tk.Tk()
# Создать обработчик события
def handle_keypress(event):
"""Вы вест и сим вол, с вяза нный с нажа той клав ишей" ""
print(event.char)
# Запустить цикл событий
window.mainloop()
. m ainloop() многое делает за вас, но в этом коде чего-то не хватает. Откуда
Tkinter узнает, когда следует вызывать handle_keypress()?
Оказывается, у виджетов Tkinter есть метод . bind(),который помогает в этом.
Метод .blnd()
Чтобы обработчик события вызывался каждый раз, когда в виджете проис
ходит событие, можно воспользоваться методом виджета . 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 () связывается с событием "<Кеу>"
вызовом window. bind( ). Каждый раз, когда во время выполнения приложения
пользователь нажимает клавишу, выводится символ, соот.ветствующий этой
клавише.
Метод. bind() всегда получает два аргумента:
1) событие, представленное строкой в форме "<имя_события>", где им.я_со
бытия может быть любым из событий Tkinter;
2) обработчик события - имя функции, которая должна вызываться при
возникновении события.
Обработчик события связывается с виджетом, для которого вызывается . Ьind( ).
При вызове обработчика события функции-обработчику передается объект
события.
В предыдущем примере обработчик события связывается с самим окном, но его
также можно связать с любым виджетом в вашем приложении. Например, об
работчик события можно связать с виджетом 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>" для правой кнопки.
ПРИМЕЧАНИЕ
Списокчасто используемыхсобытий вы найдете в разделе «Eveпttypes» спра
в о ч ни к а T k int er 8 .5 (http s://rea lpytho п.com/p ybasics -event -types) .
Обработчик событий можно связать с любой разновидностью виджетов методом
. Ь in d( ), но существует и более простой способ связывания обработчиков со
бытий со щелчками на кнопке - он реализуется при помощи атрибута command
виджета Button.
482 ГЛАВА 18 Графические интерфейсы
Атрибут command
Каждый виджет Button содержит атрибут command, который можно присвоить
функции. Она выполняется при нажатии кнопки.
Рассмотрим пример. Сначала создадим окно с виджетом Label, содержащим
числовое значение. Слева и справа от виджета Label располагаются кнопки.
Левая кнопка будет уменьшать значение в Label, а правая - увеличивать его.
Ни же при веден код для создания эт ог о ок на:
import tkinter as tk
window = tk.Tk ()
window.rowconfigure(0, minsize=50, weight=l)
window.columnconfigure([0, 1, 2], minsize=50, weight=l)
btn_decrease = tk.Button(master=window, text="-")
btn_decrease.grid(row=0, column=0, sticky="nsew")
lЫ_value = tk.Label(master=window, text="0")
1Ы_value.grid(row=0, column=l)
btn_increase = tk.Button(master=window, text="+")
btn_increase.grid(row=0, column=2, sticky="nsew")
window.mainloop()
Окно выглядит так:
,ох
о_J
Мы определили макет приложения, теперь оживим его, назначив кнопкам
к оманды.
Начнем с левой кнопки. При ее нажатии значение в виджете Label будет
уменьшаться на 1. Чтобы реализовать эту возможность, необходимо знать, как
выполняются две операции: получение текста Label и обновление текста Label.
У виджетов Label, в отличие от виджетов Entry и Text, нет метода .get ().Од
нако текст метки можно получить, обратившись к атрибуту text в синтаксисе,
сходном с синтаксисом индексирования словарей:
label = Tk .Label(text="Hello")
# По луч ить текст L abel
text = label["text"]
# Назначить новый текст
label["text"] = "Good Ьуе"
18.7. Интерактивность в приложениях 483
Итак, вы научились получать и назначать текст Label и можете написать функ
щ1ю, которая увеличивает значение Label на 1:
def increase():
value = int(lЫ_value["text"])
lЫ_value["text"] = f"{value + 1}"
Функция increase() получает текст из lЫ_value и преобразует его в целое
число вызовом int (). Затем она увеличивает полученноезначение на 1 и при
сваивает результат атрибуту text виджета Label.
Также напишем функцию decrease( ), которая уменьшает значение lЫ_value
на 1:
def decrease():
value = int(lЫ_value["text"])
lЫ_value["text"] = f"{value - 1}"
Разместите функции increase() и decrease() в коде сразу же после команды
import.
Чтобы связать кнопки с функциями, присвойте функцию атрибуту command
кнонки. Это можно сделать при создании экземпляра кнопки. Например, чтобы
пр исвоить in c reas e() п ерем енно й bt n_ in cr ease, приведите с тр оку , в которой
сшдается экземпляр кнопки, к следующему виду:
btn_increase = tk.Button(master=window, text="+", command=increase)
Теперь 11рисвойте decrease() переменной Ьtn_decrease:
btn_decrease = tk.Button(master=window, text="-", command=decrease)
Вот и вес, что следует сделать для связывания кнопок с функциями increase()
и d ecrea se() и наделе ния н ро гра мм ы по лезной фун кцио наль нос тью. Поп ро
буйте сохранить изменения и эапустить приложение.
Ниже для удобства мы приводим полный код приложения:
import tkinter as tk
def increase():
484 ГЛАВА 18 Графические интерфейсы
value • int(lЫ_value["text"])
lЫ_value["text"] • f "{value + 1}"
def decrease():
value • int(lЫ_value["text"])
lЫ_value["text"] • f "{value - 1}"
window • tk.Tk()
window.rowconfigure(0, minsize•50, weight•l)
window.columnconfigure([0, 1, 2], minsize-50, weight•l)
btn_decrease • tk.Button(master•window, text•"-", command•decrease)
btn_decrease.grid(row•0, column•0, sticky•"nsew")
lЫ_value • tk.Label(master•window, text•"0")
1Ы_value.grid(row•0, column•l)
btn_increase • tk.Button(master•window, text•"+", command•increase)
btn_increase.grid(row-0, column•2, sticky•"nsew")
window.mainloop()
Полученное приложение не особенно полезно, но приобретенные сейчас навыки
пригодятся вам при создании любого другого приложения:
• используйте виджеты для создания компонентов пользовательского
интерфейса;
• используйтеменеджеры геометриидля управления макетом приложе
ния;
• создавайте функции, которые взаимодействуют с различными компо
нентами для получения и преобразования пользовательского ввода.
В следующих двух разделах я покажу, как строить приложения, которые делают
что-то полезное. Сначала мы построим программу преобразования температур,
введенных по шкале Фаренгейта, к температурам по шкале Цельсия. Затем мы
создадим текстовый редактор, который может открывать, редактировать и со
хранять текстовые файлы.
Упражнения
1. Напишите программу, которая выводит одну кнопку с цветом фона по
умолчаниюичерным текстом "Clickme". Когда пользователь щелкнет
на кнопке, ее фон должен окрашиваться в цвет, случайным образом вы
бранный из следующего списка:
["red", "orange", "yellow", "Ыuе", "green", "indigo", "violet"]
18.8 . Пример приложения: конвертер температур 485
2. Напишите программу, моделирующую бросок игрального кубика. В про
граммедолжнабытьоднакнопкастекстом"Roll". Когда пользователь
щелкает на кнопке, должно выводиться случайное цел.:ое число от 1до 6.
Окно приложения должно выглядеть примерно так:
о
х
Ro ll
4
18.8 . ПРИМЕР ПРИЛОЖЕНИЯ:
КОНВЕРТЕР ТЕМПЕРАТУР
Сейчас мы создадим программу преобразования температур: пользователь
вводит температуру в градусах по шкале Фаренгейта и щелчком кнопки пре
образует ее к значению по Цельсию.
Рассмотрим код шаг за шагом. Полный код приложения мы приводим в конце
этого раздела.
Чтобы извлечь максимум пользы из этого раздела, откройте окно редактора
I DL E и воспрои зводите пр иве де нн ый код.
Прежде чем браться за кодирование, давайте уделим немного времени дизайну
приложения. Нам понадобятся три основных элемента:
1) виджет Eпtry с именем eпt_temperature для ввода значения по шкале
Фаренгейта;
2) виджет Label с именем lЫ_result для вывода результата по шкале
Цельсия;
3) виджет Buttoп с именем btп_convert, который читает значение из вид
жета Eпtry, преобразует его из значений по Фаренгейту к значениям
по Цельсию и присваивает результат тексту виджета Label при щелчке.
Эти виджеты можно разместить в сетке с одной строкой, где для каждого
виджета выделяется один столбец. Вы получите работающее приложение, но
486 ГЛАВА 18 Графические интерфейсы
вряд ли его можно назвать дружественным для пользователя. Все виджеты
желательно снабдить полезными надписями. Разместите виджет Label со
знаком °F справа от виджета ent_temperature, чтобы пользователь знал, что
значение ent_temperature должно задаваться в градусах по Фаренгейту. Для
этого присвойте тексту виджета Label значение "\N{DEGREE FAHRENHEIТ} ",
использующее поддержку именованных символов Юникода в Python для
вывода символа.
Чтобы виджет Ьtn_convert выглядел чуть более стильно, присвойте его тексту
значение "\N{RIGHTWARDS BLACK ARROW}", которое ВЫВОДИТ черную стрелку, на
правленную вправо. Также можно сделать так, чтобы за полем lЫ_result всегда
следовал знак 0С; для этого добавьте в конец значение "\N{DEGREE CELSIUS} ",
показывающее, что результат выводится в градусах по шкале Цельсия.
Итого вое окно д ол ж но выгляд еть так:
1 Те...
о
х
•F
_:J 100.04 <:
Теперь, когда вы знаете, какие виджеты вам понадобятся и как будет выгля
деть окно, можно переходить к кодированию! Сначала импортируйте tkinter
и создайте новое окно:
import tkinter as tk
window = tk.Tk ()
window.title("Temperature Converter")
Вызов window.title() задает заголовок существующего окна. Если вы запустите
приложение, в заголовке окна будет выводиться текст "Temperature Converter" .
Затем создайте виджет ent_temperature с виджетом lЫ_temp и свяжите оба
виджета с виджетом Frame с именем frm_entry:
frm_entry = tk.Frame(master=window)
ent_temperature = tk.Entry(master=frm_entry, width=10)
lЫ_temp = tk.Label(master=frm_entry, text="\N{DEGREE FAHRENHEIТ}")
В виджете ent_temperature пользователь вводит значение по шкале Фаренгейта,
а lЫ_temp помечаетent_temperature знаком °F. frm_entry представляет собой
конте йнер для г рупп ировк и en t_ temp er atu r e и lЫ_temp.
18.8. Пример приложения: конвертер температур 487
Надпись lЫ_temp должна размещаться непосредственно справа от ent_
temperature, поэтому для размещения виджетов в frm_entry можно восполь
зоваться менеджером геометрии . grid () с одной строкой и двумя столбцами:
ent_temperature.grid(row=0, column=0, sticky="e")
1Ы_temp.grid(row=0, column=l , sticky="w ")
Параметру sticky виджета ent_temperature задаем значение "е", чтобы виджет
всегдазакреплялся у правого края ячейки. Параметру sticky виджета lЫ_temp
задаемзначение "w", чтобы он закреплялся у левого края ячейки. Эти действия
гарантируют, что виджет lЫ_temp всегда будет находиться непосредственно
справа от ent_temperature.
Теперь можно переходить к созданию btn_convert и lЫ_result для преобразо
вания температуры, введенной в ent_temperature, и вывода результата:
btn_convert = tk.Button(
master=window,
text="\N{RIGHTWARDS BLACK ARROW}"
lЫ_result = tk.Label(master=window, text="\N{DEGREE CELSIUS}")
Как и frm_entry, виджеты btn_convert и lЫ_result связаны с окном. Вместе эти
три видже та обр азуют три яче йки основн ой с е т ки прил ожени я. Восп ользуемся
вызовом .grid() для их размещения:
frm_entry.grid(row=0, column=0, padx=10)
btn_convert.grid(row=0, column=l, pady=10)
1Ы_result.grid(row=0, column=2 , padx=10)
Наконец, запустите приложение:
window.mainloop()
Выглядит превосходно, но кнопка еще ничего не делает. В начале файла с кодом,
непосредственно подстрокой import, добавьте функцию с именем fahrenheit_
to_celsius (). Эта функция читает значение, введенное пользователем, из
ent_temperature, преобразует его из значения по Фаренгейту к значению по
Цельсию, после чего выводит результат в lЫ_result:
def fahrenheit_to_celsius():
"""Преобразовать значение no шкале Фаренгейта
к шкале Цельсия и вставить результат в lЫ_result.
fahrenheit = ent_temperature.get()
celsius = (5/9) * (float(fahrenheit) - 32)
lЫ_result["text"] = f"{round(celsius, 2)} \N{DEGREE CEL5IUS}"
488 ГЛАВА 18 Графические интерфейсы
Перейдите к строке, в которой определяется btn_convert, и присвойте его па
раметру command значение fahrenheit_to_celsius:
btп_ coпv ert = tk .But toп(
mast er=w iпdo w,
text="\N{RIGHTWARDS BLACK ARROW}",
commaпd=fahreпheit_to_celsius # <--- Добавьте эту строку
Вот и все! Вы создали полностью работоспособное приложение для преобразо
вания температур всего в 26 строках кода! Впечатляет, не правда ли?
Ниже для удобства приведен полный код приложения:
import tkiпter as tk
def fahreпheit_to_celsius():
"""Преобразовать значение по шкале Фаренгейта
к шкале Цельсия и вставить результат в lЫ_result.
fahreпheit = eпt_temperature.get()
celsius = (5/9) * (float(fahreпheit) - 32)
lЫ_result["text"] = f"{rouпd(celsius, 2)} \N{DEGREE CELSIUS}"
# Set up the wiпdow
wiпdow = tk.Tk()
wiпdow.title("Temperature Coпverter")
wiпdow.resizaЫe(width=False, height=False)
# Создать фрейм для ввода значения по шкале Фаренгейта, содержащий виджет Eпtry
# и Label
frm_eпtry = tk.Frame(master=wiпdow)
eпt_temperature = tk.Eпtry(master=frm_eпtry, width=10)
lЫ_temp = tk.Label(master=frm_eпtry, text="\N{DEGREE FAHRENHEП}")
# Разместить виджет Eпtry для температуры и Label в frm_eпtry
# с использованием топологического менеджера .grid()
eпt _te mper atu re. grid (ro w=0 , co lumп=0, stic ky=" e")
1Ы_temp.grid(row=0, columп=l, sticky="w ")
# Создать кнопку преобразования и виджет Label для вывода результата
bt п_co пve rt = tk. Butt oп(
)
m aste r=wi пdow ,
text="\N{RIGHTWARDS BLACK ARROW}",
com maп d=f ahr eпh eit _to _cel siu s
lЫ result = tk.Label(master=wiпdow, text="\N{DEGREE CELSIUS}")
# Определить макет с использованием топологического менеджера .grid() geometry
# maпa ger
18.9 . Пример приложения: текстовый редактор 489
frm_entry.grid(row=0, column=0, padx=10)
btn_convert.grid(row=0, column=l, pady=10)
1Ы_result.grid(row=0, column=2 , padx=10)
# Зап уст ить при ложе ние
window.mainloop()
А теперь замахнемся на решение более амбициозной задачи и построим про
стой текстовый редактор.
Упражнение
Попробуйте воссоздать приложение-конвертер температур из этого раздела,
не подглядывая в исходный код. Если вы зайдете в тупик, просмотрите код
и завершите упражнение. Затем подождите 10-15 минут и попробуйте снова.
Повторяйте, пока не сможете проделать все это самостоятельно. Сосредоточьтесь
на результате. Если ваш код будет слегка отличаться от приведенного в книге,
это абсолютно нормально.
18.9 . ПРИМЕР ПРИЛОЖЕНИЯ:
ТЕКСТОВЫЙ РЕДАКТОР
Сейчас мы построим текстовый редактор, который должен уметь создавать,
открывать, редактировать и сохранять текстовые файлы.
Нам понадобятся три основных элемента:
1) виджет Button с именем btn_open - открывает файл для редактиро
вания;
2) виджет Button с именем Ьtn_save - сохраняет файл;
3) виджет TextBox с именем txt_edit - создает и редактирует текстовый
файл.
При помощи виджетов две кнопки разместим в левой части окна, а текстовое
поле - в правой.
Окно должно иметь минимальную высоту 800 пикселей, а поле txt_edit -
минимальную ширину 800 пикселей. Макет сделаем динамичным, чтобы
при изменении размеров окна также корректировались размеры виджета
txt_edit. При этом ширина виджета Frame, содержащего кнопки, меняться
не /\ОЛЖНа.
490 ГЛАВА 18 Графические интерфейсы
Вот скетч будущего окна:
"f "i;;:".,- 6.o11
'
"1:tf
'
t..
-
ох.
ёр~ ~
~.-;з ~
~
~
Для построения нужного макета воспользуемся менеджером геометрии .grid().
Макет состоит из одной строки и двух столбцов: узкий столбец слева 11ред11а
значен для кнопок, а более широкий столбец справа - для текстового поля.
Чтобы задать минимальные размеры для окна и 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=l)
window.columnconfigure(l, minsize=800, weight=l)
#3
txt_edit =
f r _buttons
btn_open
Ьtn save
tk.Text(window)
= tk.Frame(window)
tk . B utton( fr _buttons,
tk. B utton (f r_ butto ns,
text="Open")
text="Save As... ")
18.9 . Пример приложения: текстовый редактор
491
Сначала (#1) импортируем пакет tkinter и создадим новое окно с заголовком
"S imple Text Editor" . Зат ем (#2) настроим конфигурации строк и столбцов.
В завершение (#3) создадим четыре виджета: текстовое поле txt_edit, фрейм
fr_buttons, атакже кнопки btn_open и btn_save.
Присмотримся повнимательнее к части #2. Параметру minsize метода
. ro wc o nfigure() присваивается значение 800,а параметру weight - значение 1 .
Первый аргумент равен 0, поэтому эта команда назначает первой строке высо
ту 800 пикселей и обеспечивает изменение размера строки, пропорциональное
изменению высоты окна. Макет состоит только из одной строки, поэтому эти
настройки пр им ен яют ся к о всему ок ну.
В следующей строке метод . columnconfigure() используется для назначения
атрибутамwidthи weightстолбца с индексом1 значений 800 и 1соответственно.
Помните: индексы строк и столбцов начинаются с нуля, поэтому эти настройки
применяются ко второму столбцу.
Настраивая только второй столбец, мы гарантируем, что текстовое поле будет
естественно расширяться и сужаться при изменении размеров окна, тогда как
столбец с кнопками сохранит фиксированную ширину.
Далее возьмемся за макет приложения. Сначала две кнопки связываем с фрей
мом fr_buttons с использованием менеджера геометрии .grid():
btn_open.grid(row=0, column=0, sticky="ew", padx=S, pady=S)
btn_save.grid(row=l, column=0, sticky="ew ", padx=S)
Эти две строки кода соадают сетку с двумя строками и одним столбцом во фрей
ме 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. Вертикальные отступы есть
только у Ьtn_open. Так как Ьtn_open располагается сверху, вертикальный от
стун немного смещает кнопку вниз от верха окна и создает небольшой отступ
между Ьtn_open и Ьtn_save.
492 ГЛАВА 18 Графические интерфейсы
Кнопка fr_buttons размещена и готова кработе, можно переходить к настройке
макета сетки для остальной части окна:
fr _buttons. gri d(r ow=0 , c ol um n= 0 , stic ky=" ns")
txt_edit.grid(row=0, column=l , sticky="nsew ")
Эти две строки кода создают в окне сетку с одной строкой и двумя столбцами.
Фрейм fr_buttons располагается в первом столбце, аtxt_edit - во втором, так
что fr_buttons располагается слева от txt_edit в макете окна.
Параметру sticky фрейма fr_buttons задаем значение "ns", которое обеспе
чивает вертикальное расширение всего фрейма и заполнение всей высоты
столбца. Поле txt_edit заполняет всю ячейку сетки, потому что его параметру
sticky присвоено значение "nsew", заставляющее его расширяться во всех
направлениях.
Макет приложения готов. Добавьте вызов window. mainloop() в конец программы,
сохраните и запустите файл. На экране появляется вот такое окно:
#SimpleTextEditor
ох
Смотрится отлично! Но программа пока ничего не делает, поэтому следует на
писать команды для кнопок.
Кнопка Ьtn_open открывает диалоговое окно для выбора файла. Затем нужно
открыть этот файл и заполнить txt_edit содержимым файла.
18.9. Пример приложения: текстовый редактор
493
Это делает функция open_file( ):
def open_file():
"""Open а file for editing."""
#1
filepath = askopenfilename(
#2
#3
filetypes=[("Text Files", "* .txt") , ( "All Files", "* .*")]
if not filepath:
return
txt_edit.delete("l.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 () непосредственно под командами
im por t.
494 ГЛАВА 18 Графические интерфейсы
3. П рис вои ть атрибуту com m an d ви джета btn_op en значение op en _f i le.
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 а 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("l.0 " , tk.END)
output_file. write(text)
#4
window. title( f "Sim ple T ext Edito r - {f ile pa th }")
Сн ача ла ( #1) ди алоговое окно as k sav eas f ile nam e получает от пользователя
путь для сохранения информации. Выбранный путь сохраняется в переменной
filepath. Если пользователь закрывает диалоговое окно или щелкаетна кнопке
Cancel (#2), то, поскольку переменная filepath содержит None, функция воз
вращает управление без выполнения кода, который сохраняет текст в файле.
18.9. Пример приложения: текстовый редактор 495
Если пользователь выбрал файл (#3), функция создает новый файл. Текст из
txt_edit извлекается методом .get(), присваивается переменной text и за
писывается в выходной файл.
Наконец (#4), в заголовок окна включается путь к новому файлу.
Теперь можно обновить программу так, чтобы виджет Ьtn_save вызывал save_
file() при щелчке. Для этого необходимо выполнить три действия:
1. Импортировать asksaveasfilename() из модуля tkinter. filedialog,для
чегоследует привести команду import в начале программы к следующему
виду:
from tkinter.filedialog import askopenfilename, asksaveasfilename
2. Доб авит ь определе ние s ave _fi le () непосредственно под о преде ление м
open_file().
3. Присвоить атрибуту command виджета Ьtn_save значение save_file:
btn_save = tk.Button(
fr_buttons, text="Save As ... ", command=save_file
Сохраните файл и запустите его. У нас получился простейший, но полностью
функциоюtльный текстовый редактор!
Ниже для удобства мы приводим 110лный код приложения:
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(l.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(
de fa ult exte ns ion= "txt ",
496 ГЛАВА 18 Графические интерфейсы
filetypes=[("Text Files", "* .txt") , ( "All Files", "*.* ")],
if not filepath:
return
with open(filepath, "w") as output_file:
text = txt_edit.get(l.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=l)
window.columnconfigure(l, minsize=800, weight=l)
txt_edit =
fr _butto ns
btn_open
btn_save =
tk.Text(window)
= tk.Frame(window, relief=tk.RAISED, bd=2)
tk.Button(fr_buttons, text="Open", command=open_file)
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=l , column=0, sticky="ew ", padx=5)
fr _buttons. gr id(r ow= 0, c olu mn = 0, st i cky= "ns")
txt_edit.grid(row=0, column=l, sticky="nsew")
window.mainloop()
Вы создали в Python два приложения с графическим интерфейсом. При этом
применили многие концепции, о которыхмы рассказывали в книге. Это немалое
достижение, и вы можете заслуженно гордиться тем, что сделали.
Теперь вы готовы к тому, чтобы взяться за самостоятельное программирование!
Упражнение
Попробуйте воссоздать текстовый редактор, не подглядывая в исходный код.
Если вы зайдете в тупик, просмотрите код и завершите упражнение. Затем подо
ждите 10-15 минут и попробуйте снова. Повторяйте, пока нс сможете построить
приложение с нуля самостоятельно. Сосредоточьтесь на результате. Если ваш
код будет слегка отличаться от приведенного в книге, это абсолютно нормально.
18.1 О. ЗАДАЧА: ВОЗВРАЩЕНИЕ ПОЭТА
В этом упражнении мы напишем GUI-приложение, генерирующее стихи. В ос
нову приложения заложен генератор из главы 9. Окно приложения должно
выглядеть пр им ер но так:
18.1О
.
Задача: возвращение поэта 497
1Makeyourownpoeml
-
о
х
Enteryourmoritewords, separated Ьу commas.
Nouns:lprogrammer,laptop,code
Verbs: ltyped,napped,cheered
Adjectives: lgreat,smelly,robust
Prepositions:lto.from,on.like
Ad verЬs: l gracef ul l y
,G;;,;,; -t; 1
Yourpoem:
Аgreatprogrammer
Аgreat programmercheeredfrom therobust code
gracefully,theprogrammertyped
thecodenapped onаsmellylaptop
Savetofile 1
Вы можете использовать любой менеджер геометрии на свое усмотрение,
но приложение должно удовлетворять всем требованиям из следующего
списка.
1. Пользователь должен ввести правильное количество слов в каждом
виджете Entry:
• не менеетрех существительных;
• не менеетрех глаголов;
• не менеетрех прилагательных;
• не менее трех предлогов;
• хотя бы одно наречие.
Если в каком-либо из виджетов Entryвведено слишком мало слов, в той
области, где выводится сгенерированное стихотворение, должно выво
диться сообщение об ошибке.
2. Программа должна случайным образом выбрать из пользовательского
ввода три существительных, три глагола,три прилагательных итри пред
лога, а также одно наречие.
3. Программа должна сгенерировать стихотворение по следующему ша
блону:
498 ГЛАВА 18 Графические интерфейсы
{A/An} {nрилl} {сущl}
{A/An} {nрилl} {сущl} {глl} {npeдl} the {nрил2} {сущ2}
{наречl }, the {сущl } {г л2}
the {су щ2} {глЗ} {nре д2} а {nрилЗ } {с ущЗ}
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 . рас k (),
. place() и . grid() выстраивать макет GUI-приложений. Вы научились управ
лять различными параметрами макета, например внутренними и внешними от
ступами, а также создавать динамичные макеты с использованием менеджеров
.pack() и .grid().
Наконец, мы закрепили полученные навыки, создав два завершенных GUI-
приложения - конвертер температур и простой текстовый редактор.
18.11. Итоги и дополнительные ресурсы 499
ИНТЕРАКТИВНЫЙ ТЕСТ
.
К этой главе прилагается бесплатный интерактивный тестдля проверки усво
енных вами знаний. Тест доступен на телефоне или компьютере:
realpython .comlquizzes!pybasics -gui
Дополнительные ресурсы
За дополнительной информацией обращайтесь к следующим ресурсам:
•
«Tkintertutorial» (https.j/tkdocs.com/tutonal/index.html)
ГЛАВА 19
Мысли напоследок
и следующие шаги
Поздравляю! Вы дочитали книгу до конца. Вы уже знаете, как сделать много
полезных вещей на языке Python, но сейчас начинается самое интересное: при
шло время самостоятельных исследований!
Учиться лучше всего на реальных задачах, с которыми вы сталкиваетесь в по
вседневной жизни. Конечно, на первых порах ваш код будет не самым краси
вым или эффективным, но он будет полезным. Нужен источник вдохновения?
В статье «13 ProjectIdeas fог Intermediate PythonDevelopers» (https.j/realpython.
com/intermediate-python-project-ideas/) вы найдете некоторые идеи, которые
помогут вам начать!
Есть еще один факт, который делает программирование на Python таким при
влекательным, - сообщество питонистов. Знаете других людей, изучающих
Python? Помогите им! Если вы хотите по-настоящему глубоко понять какую-то
концепцию, попробуйте объяснить ее другим.
Затем обратитесь к более сложному материалу - на сайте realpython.com - или
к статьям и учебникам, рекомендуемым в новостной рассылке PyCoder's Weekly
(https:j/pycoders.com/).
А когда почувствуете, что готовы, подумайте об участии в проекте с открытым
исходным кодом на GitHub (https:j/github.com/topics/python). Если же вы
предпочитаете ре шат ь го ловоломки, поп робу йте з ан я т ь с я мат ема тич еск ими
зада чами н а са й т е Pr o je ct E ule r (http .j/projec teuler.ne t/proЬlem s).
Если в какой-то момент вы зайдете в тупик, знайте: почти наверняка кто-то
уже сталкивался с точно такой же проблемой (и, возможно, успешно решил
ее!). Поищите ответы в интернете, особенно на сайте Stack Overflow (https.j/
stackoverflow.com/questions/tag,ged/python), или найдите сообщество питонистов
(например, https:j/realpython.com/community) - они всегда придут на помощь.
19.2 . Книга «Чистый Python» 501
Если все попытки окажутся безуспешными, выполните import this и помеди
тируйте над тем, что есть Python.
Р. S. Посетите нас в интернете и продолжите свое путешествие в мир Python
на сайте realpython.com и в аккаунте Twitter @realpython.
19.1 . ЕЖЕНЕДЕЛЬНЫЕ БЕСПЛАТНЫЕ СОВЕТЫ
для питонистов
Хотите еженедельно получать подборку советов от разработчиков Python
о т ом , к а к пов ысит ь производите льность и оптими зирова ть рабоч ие про цессы?
Хорошие новости - у нас есть бесплатная рассылка по электронной почте для
таких же разработчиков Python, как и вы.
Наша рассылка не относится к материалам типа «вот вам список популярных
статей». Мы стараемся поделиться хотя бы одной оригинальной мыслью в не
делю в формате (короткого) очерка.
Если вам захотелось узнать, о чем идет речь, переходите на realpython.com/
newsletter и вводите свой адрес электронной почты в форму. С нетерпением
ожидаем встречи!
19.2 . КНИГА «ЧИСТЫЙ РУТНОN»
Итак, вы овладели азами Python. Настало время изучить язык поглубже, до
полнить и расширить ваши знания.
В книге «Python Tricks: А Buffet of Awesome Python Features»1 вы найдете
эффективные приемы программирования на Python, а также ощутите всю
мощь красивого питонического кода с простыми примерами и пошаговыми
оп исаниями .
Вы еще на шаг приблизитесь к профессиональному владению Python и сможете
писать чистый идиоматический код - легко и непринужденно.
Изучить все хитросплетения Python достаточно сложно. Эта книга поможет
вам поднять ваши навыки работы с Python на следующий уровень.
1 БейдерД. Чистый Python. Тонкости программирования для профи. - СПб.: Питер.
502 ГЛАВА 19 Мысли напоследок и следующие шаги
Найдите тайные сокровища, скрытые в стандартной библиотеке Python. и на
чинайте писать чистый питонический код прямо сегодня. Ознакомительную
главу книги можно загрузить бесплатно с realpython.com/pytricks-book1•
19.З. БИБЛИОТЕКА ВИДЕОКУРСОВ
REALPYТHON
Большая (и постоянно растущая) подборка учебников Python и учебных мате
риалов поможет вам достичь уровня всесторонне развитого питониста. Новые
материалы публикуются еженедельно, и вы всегда найдете что-то интересное
для повышения вашей квалификации.
• Осваивайтепрактическизначимыенавыкипрограммирования на языке
Python: созданием, отбором и контролем наших учебных материалов ;~а
нимается целое сообщество опытных разработчиков. На сайте Real Python
вы найдете ресурсы, заслуживающие доверия, которые щшгодятся вам
при совершенствовании мастерства программирования на Pytlюn.
• Зна ком ьтес ь с дру ги ми питонистами: присо единяйтес ь к ч а ту с ообще
ства Real Python и еженедельным Q&А-сессиям, чтобы 1юзнакомиться
с командой Real Python и с другими неофитами. Получайте ответы на
свои вопросы, относящиеся к Python, обсуждайте вопросы программиро
вания и построения карьеры или просто проводите с нами время у этого
виртуального кофе йног о а вт ом ат а .
• Пройдите интерактивныетесты итраектории обучения: реальные задач11
по кодированию, интерактивные тесты и 11рограммы обучения, ориен
тированные на получение необходимых навыков, 110зволят вам оценип,
ваш текущий уровень и потренироваться в применении новых л1аний.
• Отслеживайте ход обучения: помечайте уроки как завершенные или
«в процессе», занимайтесь в наиболее подходящем для вас темпе. Созда
вайте закладки на самых интересныхуроках и просматривайте их позднее,
чтобы запомнить надолго.
• Получайте сертификаты об окончании курсов: по окончании каждот
курса вы получаете сертификат, который можно предъявить (вэлектрон
ном или печатном виде). Включайте сертификаты в свой проектный
портфель, резюме на Linkedln или на других веб-сайтах, чтобы показать
миру, что вы продвинутый питонист.
1 Или rюлистап, главу на русском я:Jыкс на сайте издательства «ilитср» 1oш1!''.fliler.com. -
Примеч. ред.
19.4 . Благодарности
503
•
Идите в ноrу со временем: поддерживайте свои рабочие навыки на долж
ном уровне и не отставайте от технологических новинок. Мы постоянно
вып уск аем нов ые уче бны е мат ери алы только д ля зареги стриро ванных
участников, а также регулярно обновляем контент.
За информацией о доступных курсах обращайтесь на realpython.com/courses.
19.4 . БЛАГОДАРНОСТИ
Эта книга появилась на свет только благодаря помощи и поддержке многих
друзей и коллег. Мы хотим поблагодарить всех вас за содействие.
Нашим семьям: спасибо за то, что вы мирились с нашими авралами, когда мы
работали днем и ночью, чтобы книга вовремя попала в руки читателей.
Команде CPython: спасибо за то, что вы создали замечательный язык програм
ми ров ани я и инструменты, к оторы е мы об ожа ем и ис пользуем в повседневной
ра бот е.
Сообществу Python: спасибо за вашу усердную работу над тем, чтобы сделать
Python самым гостеприимным и доброжелательным языком программирова
ния в мире, за проведение конференций и поддержание такой критической
инфраструктуры, как PyPI.
Читателям realpython.com - таким, как вы: спасибо за то, что читаете наши
материалы и купили эту книгу. Без вашей поддержки и внимания вся наша
работа была бы напрасной!
Надеемся, вы продолжите активно участвовать в работе сообщества, задавая
вонросы и делясь советами. Читательские отклики сформировали эту книгу
и нродолжают помогать нам вносить улучшения в будущих изданиях, поэтому
мы надеемся услышать ваше мнение.
Наша искренняя благодарность всем, кто поддержал нас на Kickstarter, - т ем,
кто поверил в будущее этого проекта в 2012 году. Мы никогда не рассчитывали
собрать такую большую группу отзывчивых, доброжелательных людей.
Наконец, мы хотим поблагодарить читателей ранней версии этой книги за пре
восходную обратную связь; вот их имена: Зохеб Айнапор (Zoheb Ainapore),
Лютер Рид (Luther Reed), Роб Сандаски (Rob Sandusky), Лютер (Luther), Марк
(Маге), Рики Митчелл (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), Джоанна Яблонски (JoannajaЬlonski), Миrель Алвес (Miguel Alves),
Мурсалин Симпсон (Mursalin Simpson), Сю Чуньян (Xu Chunyang), Лукас
(Lucas), Уорд Уокер (Ward Walker), W ., Вл ад (Vlad), Джим Андерсон (Jim
Anderson), Мохамед Альшихани (Mohamed Alshishani), Мелвин (Melvin), Ал
брехт Кадауке (Albrecht Kadauke), Патрик Старренбурr (Patrick Starrenburg),
Вивек (Vivek), Шринивасан Самуэль (Srinivasan Samuel), Сампат (Sampath),
Сиджей Сервантес (Ceejay Cervantes), Лиам (Liam), TyWait, Marp, Хорхе Алберк
(Jorge Alberch), Эдит (Edythe), Мигель Галан (Miguel Galan), Том Карневаль
(Tom Carnevale), Флорент (Florent), Питер (Peter), Джон Рэдью (Jon Radue),
Мэтт Гарднер (Matt Gardner), Роберт (Robert), Шон Янг (Sean Yang), Дэвид С.
(David S.) , Хане ван Нилен (Hans van Nielen), Юрий Торчальский (Youri
Torchalski), Гэвин (Gavin), Карен Калхаун MD (Karen Н 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), Айзек
Зикер (lzak Zycer), Майк (Mike), Норман Гринвуд (Norman Greenwood), Фор
рест (Forrest), Патрицио (Patricio), Рене (Rene), Ричард Мерц (Richard Mertz),
Крис Робинсон (Chris RoЬinson), Пит Сторер (Pete Storer), Расе Гарсайд (Russ
Garside), Мэтт (Matt), Ричард (Richard), Тиаго Мендес (Tiago Mendes), Майкл
(Michael), Дэниел Алвес Мертинс (Daniel Alves Mertins), Марко Умек (Marko
Umek), Крис Дженкс (Chrisjenks), Эдди (Eddy), Дмитрий (Dmitry), Келсанг
Шераб (Kelsang Sherab), Томас (Thomas), Дом Дженнингс (Domjennings),
Мартин (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 Е Rogers), Тай Уэйт (Ту Wait), Бимперн Уэн (Bimperng Uen), Си
Джей Хван (CJ Hwang), Гвидо (Guido), Эван (Evan), Мигель Галан (Miguel
Galan), Хан Ци (Han Qi), Джим Бремнер (Jim Bremner), Мэтт Чан (Matt Chang),
Дэниел Дразан (Daniel Drazan), Коул (Cole), Боб (ВоЬ), Риб Ховальд (Reed
Howald), Эдвард Дуарте (Edward Duarte), Майк Паркер (Mike Parker), Аарт
Клейнендорст (Aart Кleinendorst), Рок (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 Рое), Джонатан Сейберт (Jonathan Seubert), Марк
Пулен (Marc Poulin), Ли Джордан (Lee Jordan), Мэтью Чин (Matthew Chin),
Джеймс Митчелл (James Mitchell), Уэйн (Wayne), Зарата (Zarata), Лиза (Lisa),
Райан Отеро (Ryan Otero), Ли (Lee), Рафаэль Байтбир (Raphael ByteЬier), Грэм
Эдварде (Graeme Edwards), Джефф Скиппер (Jeff Skipper), Боб Д (ВоЬ D),
Андерсон Томазели (Anderson Tomazeli), Селемани Саид Джава (Selemani Said
jawa), Мью Картер (Meow Carter), Расе Гарсайд (Russ Garside), Луис Шелдон
(Louis Sheldon), Джеймс Рэдфорд (James Radford), Николай Джоне (Nikkolai
Jones), Джордж Загас (George Zagas), Лен Гульд (Len Gould), Дэниел Капитан
(Daniel Kapitan), Крис (Chris), Шен Джун (Shengjun), Уолт Бюссе (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), Скотт Киппен (С. Scott Kippen), Джимми (Jimmy), Вольфрам Блехнер
(Wolfram Blechner), Марк Мэтьюсон (Mark Mathewson), Франко Брюне (Fraщ: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 Кlengel), Ксрри Альфред (Kerry Alfred), Афиз Попула (Afeez Popoola),
Синди А. (Cindy А.), 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), Том Флинн (Тош Flynn), Белу (Velu),
Майкл Линдси (Michael Lindscy), Суло Колемайнен (Sulo Kolehmainen), Джей
(Jay), Майлос «Оа:шкс» Косик (Milos "Ozzyx" Kosik), Хане де Кок (Hans de
506 ГЛАВА 19 Мысли напоследок и следующие шаги
Cocq), Гленн Мьюлз (Glen Mules), Натан Лунднер (Nathan Lundner), Фил (Phil),
Шуб (Shubh), Пувэй Ван (Puwei Wang), Алекс Мак (Alex Mtick), Алекс (Alex),
Хитоси (Нitoshi), Бруно Ф. Де Лима (Bruno F. De Lima), Дарио Дэвид (Dario
David), Раджеш (Rajesh), Харольдас Вальчукас (Haroldas Valciukas), GVeltaine,
Сьюзен Фаул (Susan Fowle), Джаред Симмс Qared Simms), Нейтан Коллинз
(Nathan Collins), Дилан (Dylan), Лес Черчмэн (Les Churchman), Стефан Ли
Тяо-Тэ (Stephane Li-Thiao-Te), Фрэнк П. (Frank Р), Пол (Paul), Дэмьен Муртах
(Damien Murtagh), Джейсон Qason), Тхан ле Куан (Th~ng Le Quang), Нейл
(Neill), Леле (Lele), Чарльз Уилсон (Charles Wilson), Дэмьен (Damien), Кри
стиан (Christian), Андреас Крейзиг (Andreas Kreisig), Марко (Marco), Марио
Панагиотопулос (Mario Panagiotopoulos), Нерино (Nerino), Мариуш (Mariusz),
Михаил (Mihhail), Микениr (Mikonig), Фабио (FaЬio), Скотт (Scott), А, Педро
Торрес (Pedro Torres), Матиас Йоханссон (MathiasJohansson), Джошуа С.
Qoshua S.), Матиас (Mathias), Скотт (Scott), Дэвид (David Корру), Рохит
Бхарти (Rohit Bharti), Филип Дуглас (Phillip Douglas), Джон Стивенсон Qohn
Stephenson), Джефф Джоне Qeffjones), Джордж Мает (George Mast), Аллардс
(Allards), Палак (Palak), Никола Н. (Nikola N.) , Палак Калси (Palak Kalsi),
Аннекатрин (Annekathrin), Цун-Ю Ян (Tsung-Ju Yang), Ник Хантингтон (Nick
Huntington), Сай (Sai), Джордан Qordan), Вим Алсемгеест (Wiш Alsemgeest),
Ди Джей (DJ), Боб Харрис (ВоЬ Harris), Эндрю (Andrew), Регги Смит (Re~ie
Sшith), Стив Санти (Steve Santy), Мохи Джарада (Moheejarada), Марк Арза
га (Mark Arzaga), Пулос Маттен (Poulose Matthen), Брент Гордон (Brent
Gordon), Гэри Батлер (Gary Butler), Брайан (Bryaвt), Дана (Dana), Коджак
(Koajck), Perrи (Reggie), Луис Браво (Luis Bravo), Элайджа (Elijah), Николай
(Nikolay), Эрик Льетч (Eric Lietsch), Фред Янссен (Fredjanssen), Дон Стиллу
элл (Don Stillwell), Гаурав Шарма (Gaurav Sharma), Майк Маккенна (Mike
McKenna), Картик Бабу (Karthik Babu), Булат Мансуров (Bulat Mansurov),
Август Трилланес (August Trillanes), Дэррен Со (Daпen Saw), Джагадиш
Qagadish). Кайл (Kyle), Техас Шетти (Tejas Shetty), Баба Сариффодин (ВаЬа
Sariffodeen),Дон (Don), Ян (lan), Ян Барбур(Ian Barbour), Редуан (Redhouane),
Уэйн Розинr (Wayne Rosing), Эмануэль ( Eшanuel), Тойгонгонбай (Toigongonbai ),
Джейсон Кастильо (Jasoп Castillo), Кришна Чайтанья Свами Кесаварупу
(Krishna Chaitanya Swamy Kesavarapu), Кори Хагалей (Corey Huguley), Ник
(Nick), Сючуньян (Xuchuпyang), Дэниел Буис (Danicl Buis), Кеннет (Kenneth),
Леоданис Позо Рамос (Leodanis Pozo Raшos). Джон Феникс (John Pheпix),
Линда Моран (Linda Moran), Лало (W Laleau), Трой Флинн (Troy Flynп), Эбер
Нильсен (Heber Nielseп), Рок (Rock), Майк Лерой (Mike LeRoy), Томас Дэвис
(Thomas Davis), Джейкоб (Jacob), Шаболч Синка (Szabolcs Sinka), Калайселван
(Kalaiselvan), Леанна Кусе (Leanпe Kuss), Андрей (Andrey), Омар (Ошаr),
Джейсон Уоден (Jason Woden), Дэвид Себало (David Cebalo), Джон Миллер
(John Miller), Дэвид Буи (Davi<l Bui), Нико Занферрари (Nico Zanferrari), Ари
эль (Ariel), Борис (Boris), Борис Эндер (Boris Ender), ЧарлиЗ (CharlieЗ), Осси
19.4. Благодарности 507
(Ossy), Маттиас Кюль (Matthias Kuehl), Скотт Кох (Scott Koch), Хесус Авина
Ucsнs Aviпa), Чарли (Charlie), Авадеш (Awadhesh), Энди (Aпdie), Крис Джон
сон (ChrisJ оhпsоп ), Мал ан (Ма\ап), Киро (Ciro), Тамижсслван (Tha111izhselva11),
Ilexa (Nclы), Кристи;ш Лангпап (Christiaп Laпgpap), Иван (lvan),доктор Крейг
Леви (Dr. Craig Lcvy), Х. Б. Робинсон (Н. В. RoЬinsoп), Стефан (Stephane),
Стив Макилри (Stcve Mcllrce), Ив (Yves), Тереза (Teresa), Аллард (Allard), Том
Коун мл. (Tom ConcJr.) , Дирк (Dirk), Иоахим ван дер Вейден Uoachi111 van der
Weijden), Джим Ву;щорд Ui111 Woodward), Кристоф Липка (Christoph Lipka),
Джон Всргслли Uohп Vergel\i), Джерри (Gerry), Лу (Lu), Роберт Р. (Robert R.),
Влад (Vlacl), Ричард Хитвол (Richard Heatwole), Гэбриел (Gabriel), Кшиштоф
Суровсцки ( Krzysztot" Surowiecki ), Александра Дэвис ( Alexandra Davis), Джсй
сон Волл Uason Voll) и Дуэйн Девер (Dwayпe Dever).
Если мы ;1абыли у11омянуть ваше имя, пожалуйста, знайте, что мы в высшей
степени благодарны вам за помощь. Спасибо всем!
Дэ н Бейдер,
Дэвид Эймос, Джоанна Яблонски, Флетчер Хейслер
Знакомство с Python
Перевел с английского Е. Матвеев
Руководитель дивизиона
Ведущий редактор
Литературный ре д а кт о р
Художествен ный р е д ак т ор
Корректоры
Верстка
Ю. Сергuенко
Е . Строг анова
Ю . Леонова
В. Мостuпан
С Беляева, Л. Галаганова
Л. Егорова
Изготовлено в России. Изготовитель: ООО «Прогресс книга}).
Место нахождения и фактический адрес: 194044, Россия, г. Санкт-Петербург,
Б. Сампсониевский пр" д. 29А, пом.52. Тел.: +78127037373.
Дата изготовления: 09.2022. Наименование: книжная продукция. Срок годности: не ограничен.
Налоговаяльгота- общероссийский классификатор продукции ОК 034 -2014, 58.11.12 -
Книги печатныепрофессиональные,техническиеи научные.
V!мпортерв Беларусь:ООО «ПИТЕР М», 220020, РБ, г. Минск, ул. Тимирязева, д. 121/3, к. 214, тел./факс: 208 80О1
Подписано в печать28.07.22. Формат ?Ох100/16. Бумагаофсетная. Усл. п. л. 41,280. Тираж 1500. Заказ 0 -2358.
Отпечатано в типографиифилиала АО «ТАТМЕДИА» «ПИК «Идел-Пресс».
420066, Россия, г. Казань,ул.Декабристов,2.