Text
                    AUTOMATE ТНЕ
BORING STUFF
WITH PYTHON
Practica/ Prograттiпg for Tota/ Begiппers
Ьу Д[ SWEiGART
св
по starch
press
5а" Fra"cisco


АВТОМАТИЗАЦИЯ рvтинныx ЗАДАЧ С ПОМОЩЬЮ PYTHON Практическое руководство для начинающих Эл СВЕйrАрТ . Москва . СанктПетербурr. Киев 2017 
ББК 32,973,26Ol8.2.75 С24 УДК 681.3.07 Издательский дом "Вильямс" lлавный рt.:дактор С.1/. ТрU2уб Зав. редакцией Е.Р. rU'n,..1буj)2 Перевод с анrлийскоrо и редакция канд. хим. наук А.т. Iу.1U1Севuча По общим вопросам обращайтесь в Издательский дом "Вильямс" по адресу: info@williamspublislIillg.com, httр://www.wiшашsрtlыihillg.соIIl Свейrарт, Эл. С24 Авroмаrnзшщя руrnнных задач с помощью PytJlOI1: практическое руководстве для начинающих.: Пер. санrл.  М.: 000 "ИД Вильямс", 2017.  592 с.: ил.  Парал. тит. aнrл. ISBN 97858459-20904 (рус.) ББК 32.973.26-018.2.75 Все Нilзвания про['раммных продуктов являются эареrистрированными торrовыми марками со- ответствующих tjэирм. Никакая часть наrтоящеrо издания ни в каких целях ие может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические. включая фотокопироваиие и запись на маrнитный иоситель, если на это нет письмеииоro разреmе- ШIЯ издательства No Stal'ch Press, AutllOri7.ed Russiall tJ'allslatiol\ of the ЕllglЫI editiol1 01' Аlliота/I' /111' &/i1l1( S/lIff I/Ji/h Py/hOlI: Pтctical P1'ograrltmittgfor Тotal Вegi/l/IR/:5 (ISBN 978.1.593-27599.{) ,С'! 2/11:' IJ}' AI Sweig;l1'[. Tl1is lI'anslation is pubIislled and sold ьу pel'missiol1 01' No StЩ'1:11 PI'CSS, wllirll OWIlS 01' controls all l'igl1tS [о puhlisll al\d sell the saще, AII riglJts I'esel'\'('d. No pal't of tlJis book тау ье reproduced 0[' tl'aIlsll1ittcd in апу fОl'Ш 01' ьу апу means, elect\'onic or lI1ecl1anical, iJ1cluding photocopying, re('ording, 0[' Ьу ilВУ i1lfol'I1Jation sto['age 0[' l'etl'icv,,1 systel1l, witllOlJt tl1e РI;ОI'\пittеll permissioJ1 of tl1c rOP}'light OW1\CI' aJ1d the PubIishel'. КиШа отпечатаиа соrлаl'НО доroвору с 000 ПРИМСI IAБ, н а:учн,(}-nоnУЛЛРНое 'IlздаН:llе Эл Свейrарт Автоматизации рyrинных задач с помощью Python: практическое руководство Д1IJI начинающих Литературный редактор Верстка Художественный редактор Корректоры Л.Н Красн.оЖQ/I л'в. черн.(f/(.ОЗUНСКОJl в./: ПавлютU1/ л'А. /ордитко Подписаио в печать 05.08.2016. Формат 70х 100/ 16 Усл. печ,л. 47,7, Уч,'изд. л, 27.4. Тираж 400 ;K3. Заказ.Ni! 8141 Отпечатано в АО "Первая Образцовая ТИIIOI'рафия" Филиал "ЧехоIICКИЙ Печатный Двор' 14200, Московская область, 1: Чехов, ул, ПОЛИl'рафиеr()h, А,I ОО() "И, Д. Вильямс', 127055, r, Москва, ул, Леl:ная, д. 4;4, crp. 1 ISBN 978.5-8459--2090-4 (рус.) ISBN 978.1.593-27599--0 (аНl'Л.) (!) 2016 Издателы:киЙ дОМ "ВИЛЬЯМI:" @ 2015 Ьу А\ Sweigm't 
оrпАвпЕНИЕ Введение 25 Часть 1. Основы проrраммирования на языке Python 39 fлава 1. Основные понятия языка Python 41 fлава 2. Поток управления 61 fлава 3. Функции 95 rлава 4. Списки 115 rлава 5. Словари и структурирование данных 145 rлава 6. !dанипулирование строками 165 Часть 11. Автоматизация задач 189 rлава 7. Поиск по шаблону с помощью реryлярных выражений 191 fлава 8. Чтение и запись файлов 223 fлава 9. Управление файлами 253 fлава 10. Отладка 275 fлава 11. Автоматический сбор данных вИнтернете 299 rлава 12. Работа с электронными таблицами Ехсеl 337 rлава 13. Работа с документами в форматах PDF и Word 373 rлава 14. Работа с СSV-файлами и данными в форматеJSОN 403 rлава 15. Обработка значений даты и времени, планировщик заданий и запуск проrрамм 423 lЛава 16. Отправка сообщений электронной почты и текстовых сообщений 457 fлава 17. Работа с изображениями 491 rлава 18. Управление клавиатурой и мышью с помощью средств автоматизации rрафическоrо интерфейса пользователя 525 Приложение А. Установка модулей сторонних разработчиков 559 Приложение Б. Запуск проrрамм 561 Приложение В. Ответы на контрольные вопросы 565 Предметный указатель 581 
СОДЕРЖАНИЕ Об авторе О техническом рецензенте 23 23 Введение 25 Для KOro предназначена эта книra 25 Исходные предположения 26 Что такое проrpаммирование 27 Что означает название Pythoп 28 Проrраммисту вовсе не обязательно в совершенстве знать математику 28 Проrраммирование  творческий вид деятельности 30 Структура книrи 30 Зarрузка и установка PytIЮll 32 Запуск IDLE 34 Интерактивная оболочка М Как получить справку 35 Правильно формулируйте вопросы, ответы на которые ищете 36 Резюме 38 Часть 1. Основы проrрамиирования на языке Python rлава 1. Основные понятия JlЗЫка Python Ввод выражений в интерактивной оболочке Типы данных: целые числа, вещественные числа, строки Конкатенация и репликация строк Сохранение значений впеременных Инструкции присваивания Имена переменных Ваша первая проrрамма Анализ проrpаммы Комментарии Функция print () Функция input () Вывод имени пользователя Функция len ( ) Функции str (), int () и float () Резюме Контрольные вопросы 39 41 41 45 46 47 47 49 51 52 53 53 54 54 54 55 59 59 
Содержание 7 rлава 2. ПОТОК управления б 1 Булевы :шачения б2 Операторы сравнения б3 Булевы операторы б5 Бинарные булевы операторы 65 Оператор not б6 Сочетание операторов сравнения с булевыми операторами б7 Элементы потока управления б8 Условия б8 Блоки кода б8 Выполнение ПрOl'раммы б9 Управляющие инструкции б9 Инструкция if б9 Инструкция else 70 Инструкция elif 71 Цикл while 7б Инструкция break 80 Инструкция continиe 81 Цикл for и функция range ( ) 86 ИМIюртирование модулей 89 Инструкция from import 91 ПРСЖ;J;евременное прекращение выполнения ПрOl'раММЫ с помощью вызова sys. exit () 91 Рсэюме 92 Контрольные вопросы 92 rлава 3. Функции 95 Инструкции def с парамстрами 9б Инструкция retиrn и во:шращаемыс значения 97 Значсние None 99 Имснованные aprYMcHTbI и функция print () 100 Локальная и l'Лобальная области видимости 101 Локальные переменныс не MOryт исполь:юваться в l'Jюбальной области ВИi(ИМОСТИ 102 В локальных областях видимости не MOIyr использоваться нсременные из друтих локальных областей видимости 103 1Jюбальные персменные MOryт читаться иа локальной области видимости 104 Локальные и I'J!обальные неременные с одинаковыми именами 104 Инструкция g:obaJ 105 
8 Содержание Обработка исключений 108 Короткая проrрамма: yraдай число 11 О Резюме 112 Контрольные вопросы 113 Учебные проекты 113 Последовательность Коллатца 114 Проверка корректности ввода 114 Thaвa 4. СПИСКИ 11 !) Что такое список 115 Доступ к отдельным элементам списка с помощью индексов 116 Orрицательныс индексы 118 Получение Ч<lСТИ списка с помощью среза 118 Получение длины списка с помощью функции len ( ) 119 Изменение значений в списках с помощью индексов 119 Конкатенация и репликация списков 120 Удаление значений из списка с помощью инструкции del 120 Работа со списками 121 Использование циклов for со списками ] 22 Операторы in и not in 124 Трюк с rрупповым присваиванием ] 24 Комбинированные операторы присваивания 125 Методы 126 Поиск значения в списке с помощью метода index ( ) 126 Добавление значений в список с ПОМОlЦью методов append () и ir:.sert () 127 Удаление значений из списка С помощью метода remove () 128 Сортировка значений в списке с помощью метода sort ( ) 129 Пример проrраммы: Magic 8 ВаН со списком 130 Типы данных, подобные спискам: строки и кортежи 132 Изменяемые и неизмсняемыс типы данных 132 Кортежи 135 Преобразование типов с помощью функций list () и tuple () 136 Ссылки 136 Передача ссылок 139 Функции сору () и deepcopy () модуля сору 140 Резюме 141 Контрольные вопросы 142 Учебные проекты 142 Запятая в качестве разделителя 143 Рисование символами 143 
Содержание 9 fлава 5. Словари и структурирование дaJIIIых 145 Что такое словарь 145 Сравнение словарей и списков 146 Методы keys ( ) , values () и i tems ( ) 148 Проверка существования ключа или значения в словаре 149 Методgеt() 150 Метод setdefault () 150 Красивая печать 152 Использование струюур данных для моделирования реальных объектов 153 Поле для иrpы в "крестики-нолики" 154 Вложенные словари и списки 160 Резюме 162 Контрольные вопросы 162 Учебныепроеклы 163 Инвентарь приключенческой иrры 163 Функция преобраэования списка в словарь для приключенческой иrpы 164 fлава 6. Манипулирование строками 165 Работа со строками 165 Строковые литералы 165 Индексирование строк и извлечение срезов 168 Исполь:ювание операторов in и not in со строками 170 Полезные методы для работы со строками 170 Методы upper () , lower () , isupper () и islower ( ) 170 Строковые методы isX ( ) 172 Методы startswi th () и endswi th () 174 СтроковыеметодЫjоiп() иsрlit() 175 Выравнивание текста с помощью методов rjust (), ljust () И center ( ) 176 Удаление пробелов с помощью методов strip (), rstrip () и lstrip () 178 Копирование и вставка строк с помощью модуля pyperclip 179 Проект: парольная защита 180 Шаr 1. Проектирование проrpаммы и структур данных 180 Шаr 2. Обработка apryмeHТOB командной строки 181 Шar 3. Копирование пароля 182 Проект: добавление маркеров в разметку Wiki-документов 183 Шar 1. Копирование и вставка посредством буфера обмена 184 Шar 2. Разбивка текста на строки и добавление звездочек 184 Шar 3. Объединение измененных строк 185 Резюме 186 Контрольные вопросы 187 
10 Содержание Учебный проект Табличный вывод данных 187 187 Часть 11. Автоматизация задач 189 IЛава 7. Поиск по шаблону с помощью реrуля:рных выражений 191 Поиск образцов текста без использования реryлярных выражений 192 Поиск образцов текста с помощью реryлярных выражений 194 Создание объектов Regex 195 Поиск соответствий объектам Regex 196 Пошаroвая процедура поиска соответствий реryлярному выражению 197 Друrnе возможные шаблоны реryлярных выражений 197 Создание rрупп с помощью крyrлых скобок 197 Выбор альтернативных I'РУПП с помощью канала 199 Указание необязательной rруппы символов с помощью вопросительноrо знака 200 Указание соответствия rpуппе символов, повторяющейся нуль или несколько раз, с помощью звездочки 201 Указание соответствия одному или нескольким повторениям rpуппы с ПОМОЩЬЮ плюса 201 Указание соответствия определенному количеству повторений rруппы с помощью фиrypных скобок 202 Жадный и нежадный виды поиска 203 Метод f indall ( ) 204 Символьные классы 205 Создание собственных символьных классов 20б Символ крышки и знак доллара 207 fрупповой символ 208 Указание соответствия любому тексту с помощью комбинации "точказвездочка" 208 Указание соответствия символам новой строки с помощью точки 209 Сводка символов реryлярных выражений 210 Иrнорирование реrистра при поиске соответствий 211 Замена строк с помощью метода sub () 211 Работа со сложными реryлярными выражениями 212 Комбинация констант re. IGNORECASE, re. DOTALL и re. VERBOSE 213 П роект: извлечение телефонных номеров и адресов электронной почты 214 Шаr 1. Создание реryлярноro выражения для поиска телефонных номеров 215 Шаr 2. Создание реryлярноrо выражения для поиска адресов электронной почты 216 
Содержание 11 Шаr 3. Поиск всех (ОВlIадений в тексте, СКОlIированном в буфер обмена 217 Шar 4. Объединение совпадений в одну строку для копирования в буфер обмена 218 Выполнение проrpаммы 218 Идеи относительно создания аналоrичных проrрамм 219 Резюме 219 Контрольные вопросы 220 Учебные проекты 222 Обнаружение сильных паролей 222 Версия функции strip (), использующая реryлярные выражения 222 IЛава 8. Чтение и запись файлов 223 Файлы и пути доступа к ним 223 Использование обратной косой черты в Windows и косой черты в 08 Х и Linux 224 Текущий рабочий каталоr 225 Абсолютные и относительные пyrи доступа 226 Создание новых папок с помощью функции os . та kedi r s ( ) 227 Модуль os.path 227 Обработка абсолютных и относительных nyrей 228 Определение размеров файлов и содержимоro папок 230 Проверка существования пути 231 Чтение и запись файлов 232 Открытие файла с помощью функции open ( ) 233 Чтение содержимоro файла 234 Запись в файл 235 Сохранение переменных с помощью модуля shel ve 236 Сохранение переменных с помощью функции pprint .pformat () 238 Проект: rенерация файлов случайных экзаменационных билетов 240 Шаr 1. Сохранение данных билетов в словаре 240 Шаr 2. Создание файлов билетов и перемешивание вопросов 241 ШаI' 3. Создание вариантов ответов 243 Шаl' 4. Запись содержимоro в файлы билетов и ключей ответов 244 Проект: буфер обмена для работы с несколькими значениями 245 Шаr 1. Комментарии и настройка хранилища 246 Шаr 2. Создание содержимоro буфера обмена, ассоциируемоro с ключевым словом 247 Шаr 3. Список ключевых слов и заrpузка содержимоrо, асеоциированноro (: ключевым словом 248 Резюме 249 
12 Содержание Контрольные вопросы 249 Учебные проекты 250 Расширение возможностей буфера обмена, рассчитанноro на работу с несколькими значениями 250 Проrрамма Mad Libs 250 Поиск с помощью реryлярнbIX выражений 251 rлава 9. Управление файлами 253 Модуль shutil 254 Копирование файлов и папок 254 Перемещение и переименование файлов и папок 255 Безвозвратное удаление файлов и папок 257 Сохраняйте резервные копии удаленных файлов и папок с помощью модуля send2trash 258 Обход дерева каталоrов 259 Сжатие файлов с помощью модуля zipfile 261 Чтение ZIР-файлов 261 Извлечение файлов из ZIР-архива 262 Со:щание ZIР-файлов и добавление в них новых файлов 263 П роект: переименование файлов с заменой американскоro формата дат европейским 2М Шаr 1. Создание реryлярноro выражения для поиска дат, указанных в американском формате 264 Шаr 2. Идентификация частей имен файлов, соответствующих датам 266 Шаr 3. Формирование HOВOro имени файла и переименование файлов 267 Идеи относительно создания аналоrичных проrрамм 268 Проект: создание резервной копии папки в виде ZIР-файла 269 Шаr 1. Определение имени, которое следует присвоить ZIР-файлу 269 Шаr 2. Создание HOBOro ZIР-файла 270 Шаr 3. Обход дерева каталоroв и добавление содержимоro в ZIP-фaйJI 271 Идеи относительно создания аналоrичных I1porpaмM 272 Резюме 272 Контрольные вопросы 273 Учебные проекты 274 Выборочное копирование 274 Удаление ненужных файлов 274 Заполнение пропусков в нумерации файлов 274 rлава 10. Отладка 275 Возбуждение исключений 276 Получение обратной трассировки стека вызовов в виде строки 278 
Содержание 13 Утверждения 279 Использование yrверждений в проrрамме, имитирующей работу светофора 281 Отключение yrверждсний 282 Протоколирование 283 Иснользование модуля logging 283 Не выполняйте отладку с помощью инструкции print () 285 Уровень критичности сообщений 286 Отключение протоколирования 287 Запись сообщений нротоколирования в файл журнала 288 Отладчик IDLE 288   KHOH Stcp 290 Кноп Over 290 KHOH Out 290 Кноп Quit 290 Отладка проrраммы для сложения чисел 291 Точки останова 294 Резюме 295 Контрольные вопросы 296 Учебный проект 297 Отладка проrраммы, имитирующей подбрасывание монсты 297 IЛава 11. Автоматический сбор данных вИнтернете 299 Проект: проrрамма maPlt,py с модулем webbrowser 300 ШЮ" 1. Определение URLaдpeca 300 Шаr 2. Обработка apryMeHToB командной строки 301 Шаr 3, Обработка содержимоrо буфера обмена и запуск браузера 302 Идеи относительно создания аналОl'ИЧНЫХ проrрамм 303 Эаrрузка файлов из Интернета с номощью модуля Rcquests 303 3аl'РУЗ всб-страницы посредством функции requests. get () 304 Проверка ошибок 305 Сохранение заrpуженных файлов на жестком диске 306 HTML 308 Ресурсы для изучения HTMI. 308 Краткие сведения но llTML 308 Просмотр исходною HTMI.KOдa веб-страницы 309 Открытие окна инструментов разработчи в браузере 311 Использование инструментов разработчи для поис HTML элементов 312 
14 Содержание Синтаксический анализ HTMI. с помощью Beautiful Soup 314 Создание объеКТd BeautifulSoup на основе HTMI. 314 Поиск элемента с помощью метода select ( ) 3] 5 Получение данных из атрибутов элемента 317 Проект: кнопка "Мне повезет" поисковика Google 318 Шаr 1. Получение apryмeHTOB командной строки и запрос поисковой страпицы 318 Шar 2. Поиск всех результатов 319 Шаr 3. Открьrrие браузера для каждоrо из результатов поиска 320 Идеи относ.ительно создания аналоrичных проrрамм 321 Проект: заI'рУЗка всех комиксов на сайте ХКСО 322 Шаr 1. Проектирование проrраммы 323 Шar 2. Заrрузка вe(kтраницы 324 Шаr 3. Поиск и заrрузка изображения комикса 325 Шаr 4. Сохранение изображения и поиск предыдущеrо комикса 326 Идеи относительно создания аналоrичных проrрамм 327 Управление браузером с помощью модуля Selепiuш 328 Запуск браузера, управляемоro модулем Selеlliuш 328 Поиск элементов на странице 329 Щелчок па страпице 33] Заполнение и отправка форм 332 Отправка кодов специальпых клавиш 332 Щелчки па КПОIIКах браузера 333 Получение дополнительной информации о модуле SelепiulП 334 Резюме 334 КОНТРОЛЫlые вопросы 334 Учебпые проекты 335 Проrрамма для отправки электронной почты из командной строки 335 Заrрузчик изображений из Интернета 336 "2048" 336 Верификация ссылок 336 rлава 12, Работа с электронными таблицами Excel 337 OКYMeHTЫ Ехсеl 338 Устаповка модуля openpyxl 338 Чтение документов Ехсеl 339 Открытие документов Ехсеl с помощью модуля OpenPyXL 339 Получение списка листов рабочей книrи 340 Получение ячеек рабочих листов 341 Выполнение прсобразований между буквенными и цифровыми обозначениями столбцов 342 
Содержание 15 Получение строк и столбцов рабочих листов 343 Рабочие книrи, листы и ячейки 345 Проект: чтение данных электронной таблицы 345 Шаr 1. Чтение данных электронной таБлицыI 347 Шаr 2. Заполнение структуры данных 348 Шаr 3. Запись результатов в файл 349 Идеи относительно создания аналоrичных nporpaMM 351 Запись документов Ехсеl 351 Создание и сохранение документов Ехсеl 352 Создание и удаление рабочих листов 352 Запись значений в ячейки 353 Проект: обновление электронной таблицы 354 Шаr 1. Создание структуры, содержащей данные для обновления 355 Шаr 2. Проверка всех строк и обновление некорректных цен 356 Идеи относительно создания аналоrичных проrрамм 357 Настройка 'rипов шрифтов, используемых в ячейках таблицы 358 Объекты Fопt 359 Формулы 360 Настройка строк и столбцов 362 Настройка высоты строк и ширины столбцов 362 Слияние и отмена слияния ячеек 364 Закрепление областей 365 Диаrраммы 366 Резюме 368 Контрольные вопросы 369 Учебные проекты 370 lенератор таблиц умножения 370 ПрОJ'рамма для вставки пустых строк 370 Отражение электронной таблицы относительно диаrонали 371 Преобразование текстовых файлов в электронную таблицу 372 Преобразование электронной таблицы в текстовые файлы 372 IЛава 13. Работа с документами в форматах PDF и Word 373 РDF-документы 373 Извлечение текста из РDF-файлов 374 Дешифрование РDFдокументов 376 Создание РDF-документов 377 Проект: объединение выбранных страниц из мноrИХ PDI<'-документов 382 Шаr 1. Поиск всех РDF-файлов 383 Шаr 2. Открытие РDFфайлов 384 
16 Содержание UПаr3.Добавлениестраниц 385 UПаr 4. Сохранение результатов 385 Идеи относителыlO создания аналоrичных IIporpaMM 386 ДOКYMeНТbI Word 386 Чтение документов Word 388 Получение полноro текста из файла .docx 389 Стилевое оформление абзаца и объекты Run 390 С..оздание документов Word с нестандартными стилями 391 Атрибуrы объекта Run 392 ЗаписьдокументовWоrd 394 Добавление заroловков 396 Добавление разрывов строк и страниц 397 Добавление изображений 397 Резюме 398 Контрольные вопросы 399 Учебные проекты 99 РDFпаранойя 399 Персонализированные ПРИIJlашения в виде документов Word 400 Взлом паролей PDF методом Iрубой силы 400 lЛава 14. Работа с СSV.файлами и данными в форматеJSОN 403 Модуль C5V 403 Объекты Reader 405 Чтение данных из объектов Reader в ци.кле for 406 Объекты Wr i ter 406 Именованные apryMeHTbI delimi ter и lineterminator 408 Проект: удаление заrоловков из СSV-файла 409 UПаr 1. Цикл по всем СSV-файлам 410 UПаr 2. Чтение CSV-фaйJIa 410 UПаr 3. Запись С5V-файла без первой строки 411 Идеи относительно создания аналоrичнbIX проrрамм 412 jSON и интерфейсы прикладноro проrраммирования 413 Модуль j 50n 414 Чтение данныхjSОN с помощью функции load5 () 414 Запись JSON-даннbIX с помощью функции dumps ( ) 415 Проект: получение текущеrо проrноза поroды 415 UПаr 1. Получение расположения из apryмeнтa командной строки 416 UПаr 2, 3аrрузкаjSОN-данных 417 UПаr 3. ЗаrрузкаjSОN-данных и вывод проrноза поrоДЫ 417 Идеи относительно создания аналоrичных проrрамм 419 
Содержание 17 Резюме 420 Контрольные вопросы 420 Учебный проскr 420 Проrрамма для преобра.'ювания данных из формата Ехсеl в формат CSV 421 rлава 15. Обработка значений даты R времени, lVIавиРОВlЦик заданий и запуск проrpамм 423 Модуль time 423 ФУНКЦИЯ time. time ( ) 424 Функция time . sleep ( ) 425 Окруrление чисел 426 Проект: суперсекундомер с остановом 427 Шаr 1. Создание каркаса проrраммы для отслеживания времени 428 Шаr 2. Отслеживание и вывод длительности замеров 428 Идеи относительно создания аналоrичных проrрамм 430 Модуль datetime 430 Тип данных timedel ta 432 Орraнизация паузы до наступления определенной даты 434 Преобразование объектов da tetime в строки 434 Преобразование строк в объекrы datetime 436 Обзор функций Python для работы с датами и временем 436 Мноroпоточность 437 Передача apryмeHToB целевой функции 440 Проблемы параллелизма 441 Проект: мноrollОТОЧНЫЙ заl'РУЗЧИК файлов с сайта ХКСО 441 Шаr 1. Видоизмепение проrраммы пyrем вынесения ее кода в функцию 442 Шаr 2. Создание и запуск потоков ВbIПолнения 443 Шаr 3. Ожидание завершения всех потоков 444 Запуск друrих проrрамм изl'ylllOП 445 Передача арryментов командной строки функции Popen () 447 ПЛанировщик заданий Willdows, система инициализации launchd и демон-планировщик cron 448 Открытие ве&сайтов с помощью Python 448 Запуск друrих сценариев PyttlOn 448 Открытие файлов проrраммами по умолчанию 449 Проект: простая проrрамма обратноro отсчета времени 451 Шаr 1. Обратный отсчет 451 Шаr 2. Воспроизведение звуковом файла 452 Идеи относительно (:озд.шия аналОI'ИЧНЫХ проrрамм 453 
18 Содержание Резюме Контрольные вопросы Учебные проекты Приукрашенный хронометр 3аrрузка веб.комиксов по расписанию rлава 16. Отправка сообщениii электронной почты и текстовых сообщений SMTP Отправка электронной почты Установление соединения с SMTP-сервером Orправка строки приветствия SMTP.cepBcpy Начало ТLS-шифрования Выполнение процедуры входа на SMTP-сервер Orправка почты Разрыв соединения с SMTP.cepBepoM IMAP 454 454 455 455 455 457 457 458 459 460 461 461 462 462 463 Извлечение и удаление сообщений электронной почты с помощью IМAP 463 Соединение с IMAP-сервером 464 Вход в учетную запись на IМAP-cepвepe 465 Поиск сообщений 466 Извлечение сообщений электронной почты и снабжение прочитанных писем специальной меткой 471 Получение адресов электронной почты из "сырых" сообщений 472 Получение тела письма из "сыроro" сообщения 473 Удаление сообщений 474 Разрыв соедипения с сервером IMAP 475 Проект: рассьтl<a по электронной почте напоминаний о необходимости уплаты членских взносов 475 Шаl" 1. Открытие файла Ехсеl 476 Шаr 2. Поиск всех членов клуба, не уплативших взнос 477 Шаr 3. Отправка персональнbIX напоминаний по электронной почте 478 Отправка текстовых сообщений с помощью Twilio 480 Создание учетной записи lwilio 481 Отправка текстовых сообщений 481 Получение текстовых сообщений с помощью Python 484 Проект: модуль "Черкни мне" 484 Резюме 485 Контрольные вопросы 486 Учебные проекты 486 
Содержание 19 Распределение рyrинных задач пyrем рассылки по электронной почте 486 Напоминание о зонтике 487 Автоматический отказ от подписки 487 Дистанционное управление компьютером посредством электронной почты 488 rлава 17. Работа с изображениями 491 Основы компьютерной обработки изображений 491 Цвета и RGВА-значения 491 Кортежи координат и прЯМОУl'ОЛЬНИКОВ 494 Манипулирование изображениями с помощью библиотеки РШоw 495 Работа с типом данных Iшаgе 497 Обрезка изображений 499 Копирование и вставка изображений в друrие изображения 499 Изменение размеров изображения 504 Поворот и зеркальное отображение изображений 504 Изменение отдельных пикселей 507 П роект: добавление лоютипа 508 Шаr 1. Открытие изображения ЛОl'Oтипа 510 Шаr 2. Цикл по всем файлам и открытым изображениям 511 Шаr 3. Изменение размеров изображений 512 Шаr 4. Добавление лоrотипа и сохранение изменений 513 Идеи относительнО создания аналоrичных I1porpaMM 514 Рисование изображений 515 Рисование фиryр 516 Рисование текста 518 Резюме 520 Контрольные вопросы 521 Учебные I1роекты 522 Расширение и доработка проrpамм основною проекта этой 1:Jl3.ВbI 522 Обнаружение папок (: фотоrрафиями на жестком диске 523 Персональные приrлашения 524 rлава 18. Управление клавиатурой и МhlПlЫО с помощыо средств автоматизации rpафическоro интерфейса пользователя 525 Установка модуля pyaиtogиi 526 Сохранение контроля над клавиатурой и мышью 52б Прекращение выполнения всех задач пуreм выхода из учетной записи 527 Паузы и безопасный резервный выход 527 
20 Содержание Управление перемещениями указателя мыши 528 Перемещение указателя мыши 529 Получение позиции указателя мыши 530 Проект "[де сейчас находится указатель мыши?" 530 Шаr 1. Импортирование модуля 531 Шаr 2. Код выхода из проrраммы и бесконечный цикл 531 Шаr 3. Получение и вывод координат указателя мыши 532 Управление взаимодействием с мышью 533 Щелчки мышью 533 Перетаскивание указателя мыши 534 Прокрутка 536 Работа с экраном 538 Получение снимка экрана 538 Анализ снимка экрана 539 Проект: расширение проrраммы mouseNow. ру 540 Распознавание образов 540 Управление клавиатурой 542 Orправка строки, набранной на виртуальной клавиатуре 542 Обозначения клавиш 543 Нажатие и отпускание клавиш 54!') IЬрячие клавиши 545 Обзор функций PyAutoGUI 546 Проект: автоматическое заполнение формы 547 Шаr 1. Составление плана действий 549 Шаr 2. Настройка координат 550 ШЮ' 3. Начало ввода данных 552 Шаr 4. Обработка списков выбора и переключателей 553 Шаr 5. Отправка формы и ожидание 555 Резюме 556 Контрольные вопросы 556 Учебные проекты 557 Как притвориться занятым 557 Бот для отправки MrH08eHHbIX сообщений 557 Руководство по созданию иrpовоro бота 558 Приложеиве А. Установка модулей стороавих разработчиков 559 УТИJlита р i р 559 Установка сторонних модулей 560 
Содержание 21 Приложение Б. Запуск проrрамм "Мш'ичсская" строка Запуск проrрамм на Python в Willdows Запуск проrрамм на PythOIl в 08 Х и Ijllux Запуск проrрамм на Pytholl с отключенными утверждениями 561 561 561 563 563 Приложение В. Ответы на контрольные вопросы rлава 1 lлава 2 rлава 3 rлава 4 rлава 5 rлава 6 rлава 7 rлава 8 rлава 9 rлава 1 о lлава 11 rлава 12 rлава 13 rлава 14 rлава 15 rлава 16 rлава 17 rлава 18 565 565 566 567 568 569 570 570 572 572 572 574 575 576 576 577 577 578 578 581 Предметный указатель 
06 авторе Эл Свейrарт  разработчик ПО, автор киИl' по проrраммированию, жи вет в Сан-Франциско. Ero любимый язык проrраммирования  PytllOll, для KOToporo он разработал несколько модулей с открытым исходным кодом. Друrие ero книrи доступны на условиях бесплатной лицензии Creative CommoIlS (http://www . inventwithpython. сот). о техническом рецензенте Ари Лаценски разрабатывает приложения для Android и проrраммное обеспечение на языке PythOIl. Живет в СанФранциско, rде публикует ста- тьи ПО проrраммированию для Android на сайте http://gradlewhy . ghost. io и занимается преподавательской деятельностью внекоммерческой орrани- зации "Wошеn Who Code", Ее хобби  иrра на rитаре. 
ВВЕДЕНИЕ "За каКИХJfО пару часов ты сделал то, на что у нас троих ушло бы целых три дня". В начале 200о..х rодов мой сосед по комнате в колледже работал в маrазине электроники. Время от времени они получали электронные таблицы с ценниками своих конкурентов, включающие тысячи наи менований. Распечатка одной таблицы представляла собой толстую стопку бумажных листов. Обработкой данных занимались три со-- трудника маrазина. Они сравнивали цены, указанные в таблице, с ценами в своем маrазИне и отмечали тот товар, который конкуренты продавали по более низкой цене. На эту работу у них уходило примерно два дня. "А знаете что? Если вы дадите мне исходный файл таблицы, то я напишу проrрамму, которая выполнит всю работу вместо вас",  сказал мой TOBa рищ, увидев, как они копошатся среди rруды разбросанных на полу и ело-- женных в стопки бумажных листов. Через пару часов у Hero была rOToBa небольшая проrрамма, которая чи тала данные о ценах конкурентов из файла, находила для каждоrо продукта ана.тюr в базе данных маrазина и отмечала весь товар конкурентов, цена ко-- Toporo была ниже. Здесь уместно сказать, что мой товарищ был вcero лишь начинающим проrраммистом, и БШlЬшую чаСТЬ этоro времени он потратил ыа просмотр нужных разделов в книrе по проrраммированию. Собственно выполнение проrраммы заняло вcero лишь несколько секунд, что дало воз- можность моему товарищу и ero коллеrам по работе насладиться в тот день удлиненным обеденным переРЫ80М. Этот пример наrлядно демонстрирует всю мощь проrраммирования. Компьютер подобен армейскому ножу, который можно использовать в са- мых разных ситуациях. МНOI'Ие люди часами работают за клавиатурой, вво- дя данные для выполнения повторяющихся задач, и даже не доraдываются, что их компьютер, если снабдить ero соответствующими инструкциями, способен выполнить ту же работу за считанные секунды. Дп. Koro предназначена эта книrа в наши дни трудно найти сферу человеческой деятельности, в которой вообще не используется проrpаммное обеспечение (ПО). Почти каждый из нас общается в социальных сетях, телефоны мноrих из нас  это по сyrи компьютеры, подключенные к Интернету, а бальшая часть офисноrо перСОllала для выполнения своих функциональных обязанностей нужда ется в компьютерной технике. Как следствие, это привело к необычайно высокому спросу на специалистов, способных писать проrраммный код. 
26 Введение Бесчисленные КНИI'И по проrраммированию, интсраКТИВНblе онлайновые руководства, практические семинары для ра;раБОТI(ИКОВ  все это lIаправ лено на превращение амбициозных новичков в специалистов проrрамм ной индустрии, заработная плата которых выражается шестизначными числами. Эта книrа предназнаtlена не для них. Она предназначена для всех ocтa.1IW ных. Про чтение одной только ЭТОЙ КНИI'и не сможет сделать из вас разработчикапрофессионала, точно так же как пяти уроков иrры на rита ре вряд ли будет Достаточно для Toro, чтобы стать рокзвездой. Но если вы офисный работник, администратор, преподаватель или вообще один из тех, кто использует компьютер для работы или развлечения, то изучения основ проrраммирования в том объеме, который предлaraeтся в данной книrе, вам хватит для автоматизации следующих простых задач: . перемещение и переименование тысяч файлов и их сортировка по папкам; . заполнение онлайновых форм без ввода данных вручную; . заrрузка файлов или копироваНI1:е текста с веб-сайта при ero обнов лении; . вывод компьютером заранее подrотовленных уведомлений; . оБНОR1Iение и форматирование электронных таблиц Excel; . проверка электронной почты и отправка заранее подroтовленных ()'I'-- ветных писем. Все эти задачи просты, но отнимают у человека массу времени. Кроме Toro, ачастую они настолько тривиальны или узкоспециальны, что поды <KaTЬ какую-то rотовую nporpaMМY для их выполнения не удается. Воору- жившись даже минимальными знаниями в области проrраммировапия, вы сможете заставить свой компьютер выполнять эти задачи вместо вас. ИсходныепредпопожеНИR Эта книra  не справочник, а руковод<:тво для начинающих. ИСПОЛl>зу емый в ней стиль проrpаммирования иноrда идет вразрез с принцинами наилучшей практики (например, в некоторых проrраммах используют<:я rлобальные переменные), но это компромиссное решение, позволяющее сделать код более леrким для изучения. Книrа предназначена для тех, кому будет достаточно научиться писать простой одноразовый код, поэтому сти- лю оформления проrрамм и приданию им элеrантноro вида не уделяется особоrо внимания. Такие ПОНятия "продвинyrоrо" проrpаммирования, как "объектно-ориентированный подход", "списковые включения" и "reHe- раторы", не рассматриваются, дабы не усложнять излarаемый материал. 
Введение 27 Возможно, опытные проrраммисты леrко укaжyr в книrе те места, rде код следовало бы изменить, чтобы сделать ero более эффективным, но в этой книre нас в основном заботит создание работоспособных проrрамм с мини- мальными усилиями. Что такое проrраммирование в телевизионных шоу и фильмах часто показывают проrpaмми<,:тов, при. с.тально Вrлядывающихся в потоки заrадочных нулей и единиц, беryщих по экрану, но современное проrраммирование далеко не столь таинственно. Проzpам.мированue Bcero лишь снабжение компьютера инструкциями, при- казывающими ему что-либо сделать. Это может быть оперирование числа- ми, изменение текста, поиск информации в файлах или обмен данными с друrими компьютерами через Интернет. Во всех проrраммах в качестве строительных блоков используются эле- ментарные инструкции. Вот как выrлядят некоторые из наиболее распро- страненных инструкций TaKoro рода, если перевести их на обычный чело- веческий язык. "Сделай зто; затем сделай то". "Если данное условие удовлетворяется, выполви такое-то дей- ствие; в противном случае выполни такое-то действие". "Выполни это действие такое-то количество раз". "Продолжай выполнять эти действИJI до тех пор. пока удовлетво- ряется данное условие". Эти строительные блоки можно комбинировать для реализации более сложных решений. В качестве примера ниже приведены инструкции (так называемый исходныЙ 'Код) простой проrраммы на языке PytI1OIl. Проrрамм- ное обеспечение последовательно выполняет каждую строку кода, начиная с первой (при этом некоторые строки выполняются лишь при соблюдении определенных условий, иначе выполняется друrая строка), пока не будет достиrнут конец nporpaMMbI. о passwordFile  open('SecretPasswordFile.txt') . secretPassword  passwordFile.read() . print ( 'Введите пароль . ' ) typedPassword  inpиt() О if typedPassword == secretPassword: О print ( 'Доступ разрешен.') Ф if typedPassword == '12345': . рriпt('Рекомендуем установить более сложный пароль! ') else: . print ('В доступе отказано.') 
28 Введение Даже если вы ничеrо не смыслите в проrраммировании, вы все равно сможете сделать разумные предположения относительно Toro, что дела ет этот код, просто читая ero. Сначала открывается файл SecretPasswordf'ile. ехе О, из KOToporo считывается секретный пароль .. Затем поль:ювателю предлаrается ввести свой пароль (с помощью клавиатуры).. Далее оба па роля сравниваются между собой О, и в случае их совпадения nporpaMMa выводит на экран текст Доступ разрешен.. Далее IIporpaMMa проверяет, является ли введенный пароль числом 12345 ., и, если это так, подсказыва- ет, что такой вариант выбора пароля не ЯR1lЯется оптимальным .. В случае несовпадения паролей nporpaмMa выводит на экран сообщение в доступе отказано .. Чrо о,но.,о,т но,.он., "'''оп Название Руеhoп относится как к языку проrраммирования Python (с ero собственным синтаксисом, определяющим правила написания корректно- ro кода), так и к интерпретатору Pyt:hon  проrpамме, предназначеннойД1lЯ Чтения исходноrо кода (написанноrо на языке Python) и выполнения ero инструкций. Различные версии интерпретатора Python, ориентированные на платформы l.illux, OS Х и Windows, доступны для бесплатной зarpуэки по адресу http://python . org. Своим названием языК Python обязан вовсе не одноименному виду пре- смыкающихся (питон), а комедийной rруппе из Великобритании "Monty PytllOn"J, работавшей в жанре сюрреалистическоrо юмора. Проrраммистов на Python шутливо называют пUтOHucтa.мU (Pythonistas), а мноrочисленные руководства и документация по языку Python пестрят ссылками как на rpуп- пу "Monty PytllOll", так и на рептилию. nРО'РО..."1 .0." н, 06.'О"II6НО . ".ерш,н"" ,но" .0".0"'''1 По моим наблюдениям, наибольшую озабоченность у тех, кто собирает- ся учиться проrраммированию, вызывает то, что, по их мнению, для ЭтОro надо хорошо знать математику. На самом деле большинству проrраммистов не нужно знать ничеrо кроме элементарной арифметики. В этом смысле хорошему проrраммисту понадобится не HaMHoro больший объем матема- тических знаний по сравнению с тем, который требуется для решения ro- ловоломок судоку. I Читается как "Монти Пайl'ОН". fрамматически правильное звучание названия языка РуйЮl1  также [пай-тон), однако среди проrраммистов принято произно- сиl'ь cro как lпи-тон).  Пpu.м.eч, ред. 
Введение 29 Cyrb rоловоломки судоку заключается в заполнении цифрами от 1 до 9 каждOl'О из внyrренних квадратов размером 3х3, расположенных на иrpo-- вом поле размером 9х9, таким образом, чтобы ни одна cTpOl<a и ни один столбец большоrо квадрата не содержали повторяющихся цифр. Для Ha хождения решения необходимо использовать дедуктивный лоrический метод, исходя из заданной начальной конфиrypации цифр. Например, по-- скольку в rоловоломке, показанной на рис. 1, цифра 5 находится в левом верхнем уrлу иrpовоro поля, она не может появиться ни в верхней строке, ни в крайнем слева столбце, ни в левом верхнем квадрате размером 3х3. Последовательное применение подобной лоrики к строкам, столбцам и внyrренним квадратам будет предоставлять вам подсказки, позволяющие заполнять пустые клетки roловоломки. 5 3 7 б 1 9 5 9 8 6 8 6 3 4 8 3 1 7 2 6 б 2 8 4 1 9 5 8 7 9 5 3 4 б 7 8 9 1 2 6 7 2 1 9 5 3 4 8 1 9 8 3 4 2 r, 6 7 . 8 5 9 7 б 1 4 2 3 4 2 б 8 5 3 7 Q 1 --" 7 1 3 9 2 4 8 5 б 9 6 1 5 3 '7 2 8 4 2 8 7 4 1 9 б 3 5 3 4 5 2 8 б 1 7 9 Рис. 1. rоловоломка судоку (слева) и ее решение (справа). Несмотря на то что 8 rоло- воломке используются числа, никаких особых математических знаний для нахождения решения не требуется (изображения предоставлены компанией Wikimedia Commoпs) Из Toro факта, что в rоловоломке судоку используются числа, вовсе не следует, что для нахождения решения необходимо быть хорошим MaTeMa тиком. То же самое справедливо и в отношении проrраммирования. Как и в судоку, написание проrрамм предусматривает разбиение задачИ на ряд отдельных этапов. Аналоrичным образом при отладке проера.м.м (процесс обнаружения и предотвращения возможности возникновения ошибок) вы кропотливо анализируете результаты интересующих вас Действий, выпол ненных проrраммой, пытаясь выявить причину ОIlIибки. И, как это харак- терно для любоrо друrоrо вида деятельности, чем больше вы проrрамми- руете, тем лучше у вас это будет получаться. 
30 Введение nроrpo"'.РО'"И.'  7IОР"'''.' '.III1'.".'ИО". Проrраммирование  это вид творчества, несколько напоминающий возведение замков из элементов LEGO. Сначала вы формулируете для себя основные идеи ОТНОСИтельно Toro, что собой должна предстаwшть будущая nporpaмMa и какие строительные элементы имеются в вашем распоряж нии. После этоrо вы при ступаете к построению nporpaMMbI. Завершив по- строение ПрOl'раммы, вы наводите окончательный порядок в своем КОДе, аналоrично тому как по окончании строительства замка принялись бы за уборку еro территории. Различие между проrраммированием и дрyrими творческими видами д ятельности заключается в том, rro все необходимые исходные материалы находятся R вашем компьютере, и вам не нужно дополнительно закупать какие-либо холсты, краску, пленку, нитки, блоки I.EGO или электронные компоненты. Написав проrрамму, вы можете леrко поделиться ею через Интернет с целым миром. И даже если в процес(:е проrраммирования вы будете допускать неизбежные ошибки, это занятие доставит вам массу удо- вольствия. Структура книrи в части 1 книrи рассмотрены основы проrраммирования на языке PytllOn, тоrда как часть 11 ПОСВЯЩена различным задачам, решение которых можно автоматизировать. Каждая rлава части 11 включает nporpaMMHbIe проекты, с которыми вам предстоит работать. Ниже приведено краткое описание rлав. Часть 1. Основы проrраимироваиия на языке Python . rлава 1. Основные понятия языка Python. В этой rлаве рассматри ваются выражения  базовый тип инструкций PythOIl, а также описы вается использование интерактивной проrраммной оболочки PythOIl для экспериментирования с кодом. . rлава 2. Поток управления. В этой rлаве речь идет о том, как заста вить проrрамму принимать решения, касающиеся последовательно сти выполнения инструкций, чтобы код MOI' самостоятельно реаrиро- вать на возникновение различных условий. . rлава 3. Функции. В этой rлаве показано, как использовать собствен ные функции для разбиения кода на отдельные части, С которыми проще работать. . fлава 4. Списки. Вводится понятие списка, одноrо из встроенных ти пов данных PytllOll, и рассказывается о том, как использовать списки для орrанизации данных. 
Введение 31 . rлава 5. Словари и структурирование данных. Вводится понятие друrоrо BCTpoeHHoro типа данных PythOIl, словаря, и демонстрируются более совершенные способы орrанизации данных. . I'лава 6. Манипулирование строками. Описываются методы работы с текстовыми данными (которые в языке Python принято называть стрrжа.мu). Часть 11. Автоматизация задач . rлава 7. Поиск по шаблону с помощью реryлярных выражений. Обсуждаются приемы обработки строк и способы поиска образцов текста, соответствующих заданному шаблону, с помощью реryлярных выражений. . rлава 8. Чтение и запись файлов. В этой rлаве речь идет о том, как орrанизовать в проrрамме Чтение данных из текстОвых файлов и со-- хранить информацию на жестком диске. . rJlaBa 9. Управление файлами. Рассматриваются автоматизирован ные способы КОIlИРОвания, перемещения, пере именования и удале ния файлов, позволяющие выполнять данные операции быстрее, чем это можно сделать вручную. . I'лава 10. Отладка. Рассматриваются средства PythOIl, предназначен ные для обнаружения и устранения лоrических ошибок. . I'лава 11. Автоматический сбор данных в Интериете. Показано, как писать проrраммы, выполняющие автоматическую заrрузку веб-страниц и их синтаксический анализ с целью извлечения полез ной информации. Для этоrо проце<:са часто используют термин веб- С1(рапи'Не. Соответствующие про.'раммы называют uumepneт-бomaмu. . rлава 12. Работа с электронными таблицами Excel. Рассматрива ются методы манипулирования электронными таблицами Excel, ори ентированныс lIa обработку данных без их чтения человеком. Такая возможноет.) чрезвычайно полезна в тех случаях, коrда количество обрабатываемых документов исчисляется сотнями и даже тысячами. . rJlaBa 13. Работа с документами в форматах PDF и Word. Рассматри ваются проrраммные методы Чтения документов, подrотовленных в форматах PDJo' и Word. . rлава 14. Работа с СSV-файлами и даиными в формате jSON. Рассматриваются методы манипулирования СSVайлами и JSON данными. . ThaBa 15. Обработка значений даты и времени, планировщик за- даний и запуск проrpамм. Рассказывается о способах обработки ин формации, связанной с датой и временем, и выполнении задач по расписанию. Также показано, как запускать проrраммы, написанные на языках, отличных от P}11101l, из PytllOnI1poI'paMM. 
32 Введение . rлава 16. Отправка сообщений электронной почты и текстовых сообщеиий. Обсуждается написание проrpамм, осуществляющих ав- томатическую рассылку сообщений электронной почты и текстовых сообщений. . rлава 17. Работа с изображенИJlМИ. Рассматриваются способы про- rpaMMHoro манипулирования изображениями, сохраненными в раз- личных форматах, таких как JPEG или PNG. . I'лава 18. Управление клавиатурой и мышью с помощью средств ав. томатизации rрафическоrо интерфейса пользователя. Речь идет о возможностях управления клавиатурой и мышью пуrем проrраммной эмуляции щелчков мышью и нажатий клавиш. Исходный код примеров доступен в виде архивноrо файла Automatethe Boring....Sttlffoпliпeтaterials.zip на сайте ht tps: / /www.nostarch.com/automa te stuff/. Там же содержатся друrие полезные ресурсы с примерами, предла- raCMble автором в дополнение к данной книrе, с указанием rлав, к которым они относятся, а также публикуется информация об ОlUибках И опечатках, обнаруженных в книre. 3arpY3Ka и установка Python Версии Python для Windows, 05 Х и Ubuntu доступны ДЛЯ бесплатной за- rpузки по адресу http://python.org/downloads/. Если вы заrpузите текущую версию для своей системы, то все при меры проrpамм, приведенные в кни- re, должны будyr работать. ПptUynpeжiJeнue ОбязателыtO зazpyзume версию Python 3 (иanpuм,ejJ, 3.4.5). Все npuмep'Ы nроерам.м в 'К:ниее наnисан'Ы с использованием Python 3, и если вы nоn'Ытаетесь заnускатъ их в версии Руеhon 2, mo они м.оеут в'Ыnолняться нenравил'Ьно Шlи вообще не ВЫ- nолнят'bfЯ. На указанной странице заrрузки ДЛЯ каждой операционной системы IIредлaraются отдельные установщики, рассчитанные на 6+ и 32-разрядные версии, поэтому предварительно определитесь, какой именно установщик вам нужен. Если вы приобрел и компьютер в 2007 roдy или позже, то, скорее Bcero, на нем установлена 6+разрядная операционная система. В против- ном случае можно полarать, что вы пользуетесь 32-разрядной версией, но лучше убедиться в этом непосредственно, выполнив следующие дсйствия. 
Введение 33 . Если вы используете Windows, выберите пункты меню ПускПанель упраsленияСистемs и проверьте, какая система указана в качестве зна чения параметра Тип системы  6+ или 32разрядная. . Если вы используете 05 Х, перейдите в меню Apple, выберите пункты меню About This MacMore IпfoSystem ReportHardware, а затем проверь те значение поля Processor Name. Если в этом поле отображается текст "Core 5010" или "Intel Core Duo", то У вас 32разрядный компьютер. Если же в поле отображается какой-либо дрyroй текст (включая "Iпtеl Core 2 Duo"), то У вас 6+разРЯДIIЫЙ компьютер. . Если вы используете Ubuntu Ипих, откройте терминал и выполните команду unаше M. Orвeт iб8б означает 32-раэрядный компьютер, oт вет х8 б  64  6+разрядный. Для Windows заrpузите установщик Python (файл с расширением .msi) и дважды щслкните на нем. Чтобы установить Python, следуйте инструкциям, которые установщик отображает на экране. 1. Выбсрите вариант Install for AlI Users, а затем щелкните на кнопке Next. 2. Выполните установку в папку С:\РуthoпЗ4, щелкнув на кнопке Next. 3. Вновь щелкните на кнопке Next, чтобы пропустить раздсл Customize Python. Для МАС 05 Х заrрузите файл с расширением .dтg, <:оответствующий аlIlей версии 05 Х, и дважды щелкните на нем. Чтобы установиТl) PytllOll, следуйте инструкциям, которые у<..тановщик отображае1' на экране. 1. Korдa 8 новом окнс откроется I1акет DMG, дважды щелкните на файле Python. тpkg. Возможно, вам придется ввести пароль администратора. 2. Щелкните на кнопке Сопtlпuе для прохождения раздела Welcome, а за- тем на кнопке Agree для принятия условий лицензии. 3. Выделите имя ЖеСТКОl'О диска, на который выполняется установка, и щелкните на кнопке Iпstall. В случас использования Ubuntu можете установить Python из окна Tep минала, выполнив следующие действия. 1. Orкройте окно Тermiпsl. 2. Введите команду sudo aptget install pythоnЗ. 3. Введите команду sudo aptget install idlеЗ. 4. Введите команду sudo aptget install руthоnЗрiр. 
34 Введение Запуск IDLE Если uHmepnpemamopPython это проrpаммное обеспечение, предназна- ченное для выполнения проrрамм на Python, то uптера",тuвная среда разра- бom",u (IDLE)  ;TO проrраммное обеспечение, с помощью KOТOpOl'O можно вводить текст проrрамм примерно так же, как это делается с помощью тек- cToBoro процессора. Приступим к запуску IDLE. . Если ваш компьютер работает под управлением операционной сист мы Windows 7 или более новой версии Windows, щелкните на кнопке Пуск в левом нижнем уrлу экрана, введите IDLE в строке поиска и вы- берите в раскрывшемся меню пункт IDLE (Python GUI). . Если ваш компьютер работает под управлением операционной систс' мы Windows Хр, щелкните на кнопке Пуск и выберите последовательно пункты меню programsqpython 3.4qIDLE (Python GUI). . Е(ли ваш компьютер работает под управлением операционной си- стемы МАе 05 Х, откройте окно Finder, выберите последовательно Applications и Python 3.4, а затем щелкните на значке IDLE. . Если ваш компьютер работает под управлением операционной систе- мы Ubuntu, выберите ApplicationsqAccessoriesqTerminal, а затем введи- те команду idlеЗ. (Вы также можете щелкнyrь на кнопке Applications в верхней части экрана, выбрать раздел Programming и щелкнyrь на пун- кте IDLE 3.) Инreр""'..н". 060.0'1"" Независимо от Toro, в какой операционной системе вы работаете, окно IDLE при ero первом открытии будет в ОСНОВIIОМ пустым, не считая текста, который будет выrлядеть примерно так. Python 3.4.0 (v3.4.0:04f714765c13, Mar 16 2014, 19:25:23) [MSC v.1600 64 bit (AМD64)] оп win32Type "copyright", "credits" or "license()" for more information. »> Это окно называется иHtnepa",тuвпoй оболuч",ой. Оболочка  это проrрам- ма, которая позволяет вводить инструкции в компьютер во MHoroM анало- rично тому, как это делается в окне терминала или командной строки на компьютерах 08 Х и Windows соответственно. Команды, которые вы вво- дите в интерактивной оболочке, выполняются интерпретатором PythOIl. Компьютер (IИтает введенные инструкции (команды) и немедленно выпол- няет их. 
Введение 35 Чтобы увидеть, как это работает на практике, введите в интерактивной оболочке сразу же за приrлашением к вводу (>>» следующую инструкцию: »> print('Hello world! ') После 1'01"0 как вы введете эту инструкцию и нажмете клавишу <Enter>, интерактивная оболочка должна отреarировать на это выводом следующей строки: »> print('Hello world! ') He1lo world! Какпопучитьсправку Самостоятельно находить решения проблем, возникающих 8 процес се проrраммирования, rораздо леrче, чем вы думаете. Чтобы убедить вас в этом, давайте намеренно вызовем ошибку при попытке выполнить ин струкцию. Введите в интерактивной оболочке инструкцию '42' + З. Вам необязательно знать сейчас, что она означает, но результат должен выrля деть так. >>> '42' + 3 О Traceback (most recent call last): File "<pyshe1l#O>", Нпе 1, in <module> '42' + 3 . TypeError: Can't convert 'int' object to str irnplicitly >>> Появление здесЬ сообщения об ошибке. обусловлено тем, что смысл введенной вами инструкции остался неПОНЯТIIЫМ для PytllOIl. В той части сообщения, которая касается текущеrо стека вызовов (Traceback) ., ото- бражаются конкретная инструкция и номер строки, в которой Python стол кнулся с проблемой. Если сообщение об ошибке ни о чем вам не rоворит, выполните поиск в Интернете по точному тексту сообщения. Введите текст "ТypeError: Can't convert 'int' object to str i:ш.рliсitlу" (включая кавычки) в своем поисковике, и вы увидите тысячи ссылок, по которым можно узнать о том, что означает данное сообщение и что породило ошиб- ку (рис. 2). 
36 Введение ,,""' '...;.;o i-}ie TypeError: Can-I CCH)Vert 'irr 10 str ir.lpllcltly -  11  :., -"\.r!! .!;.;.,.:.....,: (.:')БР.'" r-c )'!(.'М, эапр::.-с' во. Мс.+.е'Те "':;Й1' c,; на р)'('<:ком я3ыfеe '/'зз.:rь rЧ',";;';:I'\С'.j"':\'.еr.ь!-\ь.'" "'ЗЬ'У ;:i,,'''Ч1 pe-1'"rb:""':c.s net. f,'с..ню е ,3Д"?:-: I-;,)Ч::i"'н' pythO" - TypeError: Ca"'t CO"Vert 'int' obJect to str impllcitly - s... . ',:\и,"'''''':':-:' c':.r'-,' tурееrrоr.СЗПI-СОПV'n-IП . Пt>р.;>е'='ПI! .;Т', ИР,-,"I'Ц:. \.'::'-'.' :,., ..: ':':-,:il:,r';! .., '!I:::' '. :I Ji" if' .'.::i, :"',. . ::\ .:  :", ,, I) ';'iri')', ',0 :'Л' ..;,. 1.,.1":'_ 11: " ".; C..:' ."1111.1rtjl?, : ':"T<): -:ry"" :(;  Can't convert 'int' obJect to str impllcitly' Python 3+ - Stack Оуе.. :: :.:. "')';' "!"':.: ((J""].' ct1nt-conv.n.jnt.,:;..:e(  .(: .. nee.I?( ТJ1 Зl''С' r. rр.ltШLf..- : j":-I: .:.' :: "':J r; :,:Cj.  t (. I!'I '...r ;  ..: ::j.' t r:. s ,: I; f:!.('iti  .0;" ':: ,! :::! ; j.:, . ",:. 1;". ''!l::':JI!i ''''':i:>':jH' :. 'i":n:-';oI '.',У':'. p'I".:1te >t!.' I  нН ':t'.If:( TypeError: Can't convert 'Int' obJect to str implicitly errcr python,. ,::,.".':"..,":";,, ,:(,",' .tур..rror.сOIпt-сопv.nlп . 'lе.,=,си Э.. С!,'i1НИЩ д.? ф,. ;:/: > 1?1..?::: .:' "ri .' ,,. .; ';'\. '::a''! ':1C; ip.t -:!"!"'\  .; 50 '')!:!":':;: ";I .-  "'r,:rL! "'I:, ! I';:',-:I'! ':.,:;11 ;;.."!<':': !':;" S'.::!"'' !!I'; .;', ..:;t;I=?, Проrрамма не работает. что делать? I Python 3 ДЛЯ начина...  \ !r\(I:;.', :J! (II tl ('-:':O(;v\ l':'(,}"Jr"1,,.,-r.е.:'JrjQ,)е I"ttln! ... 1 -: [1 ;'.' I'[I.: .", '.'1,: ,,,. ,!t,' ":'.:.' ..,(:.; ;.:"I: . ": !..t.:>"EH':'.( j'f:-'!,!:,,:I.;,';: ECJ ,," l'I":"f"',' 'If':("! ,',i:' r''llv !!1.'.).!t?.rt.::'"t H1-;p!::;H'.. L_ Рис. 2. Получение дополнительной информации о природе ошибки путем поиска в Goog/e с использованием текста сообщения об ошибке в качестве ключа Часто вы будете сталкиваться с тем, что у Коrо-то уже возникал тот же во- прос, что и у вас, и на этот вопрос уже был дан ответ. В проrраммировании никому не дано знать абсолютно ВСС, поэтому свыкнитесь с мыслью о том, Что поиск ответов на различные вопросы техническоrо характера  Неотъе- млемая часть ежедневной деятельности I1роrраммиста-ра.зработчика. Правипьно формупируйте вопросы, ответы на которые ищете Если онлайновый поиск не позволил получить ответ на интересующий вас вопрос, то поспрашивайте людей на таких форумах, как Stack Overflow (http://stackoverflow.com), или Посетите учебный раздел сайт" Rcddit (http://reddit .com/r/learnprogramming). Однако имейте в виду, что при обращении за помощью очень важно правильно формулировать свои во- просы. Обязательно посетите разделы Frequently A'iked Questiol1s (часто 
Введение 37 задаваемые вопросы) этих сайтов, rде вы сможете ознакомиться с форму лировками вопросов, которые послужат вам образцом для подражания. Задавая вопросы, касающие<:я проrраммирования, старайтесь придер- живаться следующих рекомендаций. . Объясните, что именно вы пъtтaemecь сделать, а не только то, что вы делали. Это позволит тому, кто хочет вам помочь, определить, находи тесь вы на верном или На неверном пyrи. . Укажите, Коrда именно возникает ошибка: сразу после запуска про- rpaMMbl или после Toro, как вы выполняете определенные действия. . Скопируйте и вставьте пОЛ1tъtй т{KCT сообщения об ошибке и ваш код в буферное хранилище по адресу http://pastebin.com/ или http:/ / gist.github.com/. Указанные веб-сайты упрощают обмен большими объемами кода че рез Интернет без риска потерять форматирование текста. Затем мо- жете перес.лать URL-aдpec размещенноrо в буферном хранилище кода нужному человеку по электронной почте или опубликовать ero на фо- руме. Чтобы увидеть, как это работает, просмотрите код, который я разместил в хранилищах по следующим адресам: http://pastebin . сom/ SzP2DbFx/ и https: / /gist. github. соm/аswеigаrt/б9121б8/. . Объясните, какие меры вы предпринимали для разрешения возник шей проблемы. Тем самым вы покажете, что уже приложили усилия со своей стороны, стараясь самостоятельно выяснить причину непо-- ладок. . Укажите версию Python, которую используете. (Между интерпретато-- рами, входящими в состав вер(:ий PytllOn 2 и 3, имеются важные раз личия.) Также укажите используемую вами операционную систему и ее версию. . Если ошибка появилась после Toro, как вы внесли изменения в код, детально опишите, какие именно измененИЯ были вами внесены. . Расскажите, воспроизводится ли ошибка всякий раз, коrда вы выпол няете IIРOl'рамму, или она возникает лишь после Toro, как вы совер- шаете определенные действия. В последнем случае опишите, в чем именно заключаются эти дей<:твия. Кроме Toro, cTporo соблюдайте правила ceTeBoro этикета. Например, размещая на форуме свои вопросы, не набирайте весь текст прописными буквами, пытаясь сделать ero более заметным, и не ВЫДВИI'айте необосно-- ванных требований к людям, которые пытаются вам помочь. 
38 Введение РеЗlOме Для большинства людей компьютер  это вcero лишь полезНое устрой СТВО, а не инструмент. Вместе с тем, научившись нроrраммировать, вы получите доступ к одному из наиболее мощных инструментов в современ- ном мире, работа с которым доставит вам, кроме Bcero прочеrо, orpoMHoe удовольствие. Проrраммирование вовсе не сродни нейрохирурrии  оно предостаВ.IIЯет новичкам великолепную возможность экспериментировать и при этом не бояться, что допущенные ошибки MOryr быть чреваты ката- строфическими последствиями. Мне нравится помоrать людям открывать для себя Python. Я пишу руководства по проrраммированию на своем блоrе по адресу http: / / inventwi thpython. com/blog/, и вы можете связаться со мной и задать вопросы, отправив сообщение электронной почты по адресу al@inventwithpython. сот. Эта книrа лишь помоraет преодолеть начальный барьер в изучении про rраммировании, поэтому вы не всеrда найдете в ней ответы на все свои во- просы. Не забывайте о том, что умение правильно формулировать ВОПРОСJ)( и знание TOro, как находить ответы на них, окaжyr вам неоценимую помощь в вашем путешествии в мир проrpаммирования. Итак, приступим! 
ЧАСТЬ I ОСНОВЫ проrРАММИРОВАНИЯ НА ЯЗЫКЕ PYTHON 
ОСНОВНЫЕ ПОНЯТИЯ ЯЗblКА PYTHON Язык проrраммирования Pyt]lOn предлаrает боrатейший набор синтаКсических конструкций, функций стандартной библиотеки и средств интерактивной разработки. К счас- тью, без большинства этих средств можно спокойно обой- тись, ведь все, что вам нужно,  это научиться писать ко-- роткие полезные проrраммы. Прежде чем вы сможете что-либо сделать. вам следует усвоить некото- рые базовые понятия проrраммирования. Поскольку на данном этапе вы еще только учитесь, рассматриваемый в этой rлаве материал поначалу мо-- жет показаться вам скучным и чересчур сложным. Но даже самых скром- НbIX знаний и небольшой практики, которыс вы приобретете, вам хватит ДЛЯ тoro, чтобы управлять компьютером подобно Mary, который вооружен волшебной палочкой и способен соверш"ть самые невероятные подвиrи. В этой rлаве мы разберем несколько примеров, которые пробудят в вас интерес к работе с интерактивной оболочкой. С ее помощью вы сможете выполнять по одной команде Python за один раз и сразу же видеть результа- ты. Интерактивная оболочка будет вашим надеЖНЫМ помощником в изуче- нии основных инструкций Python, поэтому отказываться от такой возмож- ности не стоит. Коrда делаешь что-то своими руками, а не просто читаешь книry, все запоминается rораздо лучше. Ввод выражений в интерактивной оБОllочке Для вызова интерактивной оболочки необходимо запустить ин- терактивную среду IIН.Е, процедура установки которой описана во введении. В Windows откройте меню Пуск и выберите пункты меню Все nporpaMMblPython 3.3, а затем IDLE (Python GUI). В 05 Х выберите пункты меню ApplicationsMacPython 3.3QIDLE. В Ubuntu откройте новое окно терми- нала и введите команду idlеЗ. 
42 r лава 1 В результате должно открыться окно с ПрИlJlашением ко вводу (»»; это и есть интерактивная оболочка. Введите в командной строке инструкцию 2 + 2 в ответ на приrлamение, trrобы Python выполнил для вас простое Ma тематическое действие. »> 2 + 2 4 Теперь в окне IDLE должен отображаться примерно такой текст. Python 3.3.2 (v3.3.2:d047928ae3f6, Мау 16 2013, 00:06:53) [MSC v.1600 64 bit (AМD64)] оп win32 Туре "copyright", "credits" or "license()" formore information. >>> 2 + 2 4 »> В PytllOn запись 2 + 2 нааывается 8'ЫражeuueJК. Выражение  это наи БОJlеt фундаментальная разновидность проrраммных инструкций языка. Выражения состоят из з'Н.а'Чf!1tuu (таких, как 2) и операторов (таких, как +) и всеrДа MOryr 8ъtчuс.п.ятъся (сводиться) до получения единственноrо знач ния. Отсюда следует, ЧТО в коде Python выражения MOryr использоваться везде, rде допускается ис.пользование значения. В предыдущем "римере выражение 2 + 2 вычисляется, сводясь к един ственному значению  4. Одиночное значение без операторов также счи тается выражением, результатом вычисления KOToporo является само зна- чение. Проrраммные ошибки не представляют уrрозы для оборудования компьютераl Если компьютеру встречается непонятнь,й для Hero проrраммный код, то про- rpaMMa завершоется аварийно, и Python выводит сообщение об ошибке. Эти сооб- щения не MOryт причинить вред вашему компьютеру, так что не бойтесь совершать ошибки. Аварийное завершение, или крох, nporpaMMbI  это просто неожиданное прекращеиие ее выполнения. Если вы Хотите получить более подробную информацию о каком-то сообщении об ошибке, используйте точный текст сообщения для проведения СООТ8етствующе ro поиска вИнтернете. Кроме Toro, вы сможете воспользоваться ресурсами, но- ходящимися по адресу http://nostarch.com/autornatestuff/, для просмотра списка наиболее часто встречающихся сообщений об ошибках в Python вместе с их описаниями. 
Основные понятия языка Python 43 >>> 2 2 Кроме оператора +, существует множество друrих операторов, допусти мых в выражениях Python. Полный список математическИХ операторов Python приведен в табл. 1.1. Т.ица 1.1. Математические операторы Python в ПОрllДКе умен"wения их приоритета Оператор Операц- "рим., Реау""тат ** Возведение в степень 2 ** 3 8 с Деление по модулю/ остаток 22 ::' 8 б , // Целочисленное деление с отбрасыванием дробной части 22 // 8 2 / Деление 22 / 8 2.75 * Умножение 3 * 5 15 Вычитание 5  2 3 + Сложение 2 + 2 4 Очередность (cTporoe название  приopuтет) выполнения математичес ких операторов Python совпадает с очередностью, принятой в математИ- ке. Сначала выполняется оператор **; затем, в порядке следования слева направо, выполняются операторы *, /, / / и %; и наконец, последними вы- полняются операторы + и  (также в порядке ('ледования слева направо). для изменения очередности выполнения операций используются круrлые скобки. Введите в интерактивной оболочке <:ледующие выражения. »> 2 + 3 * 6 20 »> (2 + 3) * 6 30 »> 48565878 * 578453 28093077826734 »> 2 ** 8 256 »> 23 1 7 3.2857142857142856 »> 23 11 7 3 »> 23 % 7 2 »> 2 + 2 4 »> (5  1) * «7 + 1) 1 (3  1» 16.0 
44 rлава 1 Во всех этих случаях вы как проrраммист лишь вводите выражения, Tor- да как всю тяжелую работу по сведению выражения к единственному зна- чению берет на себя интерпретатор. Python последовательно вычисляет отдельные части выражения до тех пор, пока не преобразует ero в един- ственное значение (рис. 1.1). (5  1) * «7 + 1) / (3  1» + 4 * «7 + 1) / (3  1» + 4* « 8 ) / (3  1» + 4* ( 8 ) / ( 2 ) + 4* 4.0 + 16.0 Рис. 1. 1. Резупьтат вычисления выражения сводится к единственному значению Вышеперечисленные правила cOBMeCTHoro использования операторов и значений для формирования выражений являются фундаментальной ча- стью языка проrраммирования Python точно так же, как правила обычной rрамматики обеспечивают нам возможность общения на родном языке. Рассмотрим соответствующий пример. Это предложение на русском языке rpамматически корректно. Это корректно предложение не на языке rpамматически русском. Понять смысл второй строки практически невозможно, поскольку ОНа не следует правилам построения предложений, ПРИНЯТblМ в русском языке. Точно так же и Python, встретив неправильно составленную инструкцию, не сможет ее однозначно интерпретировать и выведет сообщение осин. таксической ошибке (SyntaxError). »> 5 + File "<stdiп>", line 1 5 + SyntaxError: invalid syntax »> 42 + 5 + * 2 File "<stdin>", line 1 42 + 5 + * 2 SyntaxError: invalid syntax 
Основные понятия языка Руthоп 45 Вы всеrда можете проверить, работает ли та или иная инструкция, введя ее в интерактивной оболочке. Вам не о чем волноваться  компьютер вы не сломаете. В самом худшем случае PytllOn отреаrирует на неправильную инструкцию выводом сообщения об ошибке. Даже профессиональные раз работчики проrраммноrо обеспечения постоянно получают подобные со-- общения в процессе написания кода. Тип... данных: цеllые чиспа, вещественные ЧИСllа, строки Вспомните о том. что выражения  это просто сочетания значений и операторов и что результат их вычисления всеrда сводится к единственно-- му значению. Тип дшн:н:ых  это определенная катеrория значений, причем каждое значение относится к одному и только одному типу данных. Наи- более простые типы данных Python приведены в табл. 1.2. О значениях 2 и 30 rоворят. что они ЯR1lЯЮТСЯ цtИО'ЧИCJteu'l/:Ы,м,и. Цело численному (int) типу данных соответствуют значения в виде целых чисел. Числа с десятичной точкой, такие, например, как 3.14, называются 'Числами с плавающей тичКой (тип данных f1 оа t) ИЛИ веществeu'Н:ы,м,и 'Числами. Заметьте, что значение 4 2  целое число, тоrда как значение 42. О  вещественное. Та&tица 1.2. Простые типы даниых Python Тип данн"х Целые числа Числа с плаlающей точкоii (Iещест"нные) Строки Пример.. 2, 1, О, 1, 2, 3, 4, 5 1.25, 1.0, 0.5, 0.0, 0.5, 1.0, 1.25 'а', 'аа', 'ааа', 'Hella!', '11 cats' Проrраммируя на языке Python, вы сможете использовать и текстовые значения, так называемые строки (тип данных str). Всеrда заключайте строку в апострофы ('), чтобы Python MOr распознать, rде она начинается и заканчивается (например, 'Hella' или' Goodbye cruel world!'). Строка может вообще не содержать ни одНоrо символа ( , '); такие строки называ ются nустъl.МИ. Более подробно строки рассматриваются в rлаве 4. Если вам коrдалибо приходилось сталкиваться с сообщением об ошибке SyntaxError: EOL while scanning string litеrаl,то,вероятно,этобыло вызвано тем, что вы забыли ввести символ апострофа, завершающий стро-- КУ, как в приведенном ниже примере. »> 'Hello world! SyntaxError: EOL while scanning string literal 
46 r пава 1 КонкатенаЦИII и реппикаЦИII строк Смысл оператора может меняться в зависимости от типа соседних с ним данных (контекста). Например, если оператор + при меняется к двум ЗНа- 'Iениям, являющимся числами (целыми или вещественными), то он трак- туется как оператор сложения. Но еСЛИ ero применить к двум строковым значениям, то он объединит их в одну строку, иrрая роль оператора KO'ltKa тенац,ии строк. Введите в интерактивной оболочке следующее выражение. »> 'Alice' + 'ВоЬ' 'АliсеВоЬ' Данное выражение сводится к новому строковому значению, объединя- ющему текст обеих исходных строк. Если же попытаться применить опера- тор + к строке и целому числу, то Python не сможет определить, чеrо имен- но от Hero хотят, и выведет сообщение об ошибке. »> 'Alioe' + 42 Traceback (most recent call 1ast): Fi1e "<pyshell#26>", Нпе 1, in <module> 'АНсе' + 42 TypeError: Can't convert 'int' object to str imp1icit1y Сообщение Сап' t convert 'int' object to str implicitly означает, что Python предположил, будто вы пытаетесь конкатенировать строку' Аliсе ' с целочисленным значением. В IIОДобных ситуациях ваш код должен явно преобра:ювывать целые числа в строки, так как Python не может автомати- чески выполнить такое преобра:ювание. (Преобразования типов данных рассматриваются в разделе "Анализ проrраммы" при обсуждении функций str (), int () и float () .) Если оператор * применяется к двум целочисленным или вещественным значениям, то он трактуется как оператор умножения. Но если одно из значений  строка, а второе  целое число, то он становится оператором penяuкац,uu стр(Ж,. Введите в интерактивной оболочке строку, умноженную на число, чтобы увидеть, как это работает. »> 'Alioe' * 5 'AliceAliceAliceA1iceAlice' Результатом вычисления этоrо выражения является строковое значе- ние, представляющее собой MHoroKpaTHo повторенную исходную строку, число повторов которой равно данному целочисленному значению. p 
Основные понятия языка Python 47 пликация строки  очень полезный прием, хотя и ИСПОЛЬ3)'ется реже, чем конкатенация строк. Оператор * может использоваться только в отношении двух числовых значений (умножение) или одной строки и одноrо целочисленноrо значе- ния (репликация строк). В противном случае PythOI1 отобразит сообщение об ошибке. »> 'Alice' * 'ВОЬ' Traceback (most recent call last): File "<pyshell#32>", line 1, in <modиle> 'Alice' * 'ВоЬ' TypeError: can't rnиltiply sequence Ьу nonint of type 'str' »> 'Alice' * 5.0 Traceback (rnost recent call last): File " <pyshell #33>", Нпе 1, in <module> 'Аliсе' * 5.0 TypeError: can't rnultiply sequence Ьу nonint of type 'float' Совершенно очевидно, почему Python не CMor определить смысл ЭТих выражений: невозможно умножить одно слово на друrое, равно как и по- вторить строку дробное количество раз. Сохранение значений в переменных пере.МЕН:Н,ая  это область памяти компьютера, в которой может хранить- ся одиночное значение. Если вы предполarаете, что результат вычисления выражения впоследствии будет использоваться в проrpамме, то можете со- хранить ero в переменной. иНару".... "р.,.".."н.. для сохранения значений в переменных используется инструкция nрисва- иванШl. В ней указываются имя переменной, знак равенства (называемый оператором npuсваиваиuя) и сохраняемое значение. Например, инструкция IIРИСВаивания sparn = 42 обеспечивает сохранение значения 42 в перемен- НОйsрam. Образно rоворя, переменную можно уподобить ящику с Мe'J'кой, В КОТо- рый переменная помещается для постоянноrо или BpeMeHHoro хранения (рис. 1.2). Введите в интерактивной оболочке следующие инструкции. . >>> SPaIII = 40 »> SPaIII 40 >>> eqgs = 2 
48 r лава 1 . >>> ер_ + eqgs 42 »> Вpalll + eqgs + ераа 82 . »> ераа . араа + 2 >>> SPaDI 42 Pl1c. 1.2. ИНСТРУКЦI1Я spaт  42 сообщает nporpaMMe: "Теперь в переменной spaт хранится цеЛОЧl1сленное значение 42" lсрмин u1tuц,uалuзац,uя (или создание) перемеи'Н-ой относится к ПСрВОllа чальному I1РИСВОСНИЮ переменной какоrолибо значения 8, После ЭТОrо переменную можно использовать в выражениях совместно с дрyrими пере менными и значениями 8. В процессе присваивания переменной HOBoro значения ее прежнее значение теряется, и именно поэтому значением пе ременной spam в конце примера является 42, а не 40. Изменение значения называется nepезапuсью переменной. Попробуйте перезаписать строку, B дя В интерактивной оболочке следующий код. »> SPaDI · 'Не110' »> SPaDI 'Hello' > > > SPaDI = I Goodbye , >>> SPaDI 'Goodbye' 
Основные понятия языка Python 49 в этом примере значение' ИеНо' хранится в переменной spam лишь до тех пор, пока вы не замените ero значением 'Goodbye' (рис. 1.3). И_е." "ере_еи"..х в табл. 1.3 приведены примеры допустимых имен переменных. Пер Менным можно присваивать любые имена, при условии, что они удовлстшr ряют следующим трем требованиям. Рис. 1.3. Коrда переменной присваивается новое значение, старое значение теряется 1. Имя перемеНfIОЙ должно представлять собой одно слово. 2. В имени переменной MOryr использоваться только буквы, цифры и символ подчеркивания С). 3. Имя переменной не может начинаться с цифры. Та6ltица 1.3. Допуанмые н недопустимые имена переменных Дрпуnимwеим-иаnеременных balance cиrrentBalance cиrrent balance НeAoIlJC1'llМlll4t имена nepeмeнHЫX currentbalance (недопустимый дефис) current balance (недопустимый пробел) 4account (имя не может начинаться с цифры) 
50 r лава 1 account4 ОкО1t'Ча'ltш табл. 1.3 Недопустим..е имена "еременн"х 4 2 (имя не может начинаться с цифры! total $ит (специаЛllные символы, такие как $, I именах недопустимы! I hello' (специальные символы, такие как 1, В именах Н&допустимы! Дрnynим..емменаnеременн"х spam SPAМ Имена переменных чувствительны к реrистру, Это означает, что spam, SPAM, Spam и sPaM  четыре разных имени. В соответствии с IlрИНЯТЫМ в Python соrлашением имена переменных должны начинаться с маленькой буквы. В этой книrе вместо стиля именования, преДПОЛaraIOщеro использование символа подчеркивания, используется так называемый "верблюжий стиль", в котором имена наподобие lookinglikethis пре06разуются в имена напо-- добие lookingLikeThis. Некоторые опытные проrpаммисты моrли бы Уl1ре кнуть меня в том, что я Не следую официальному руководству PythOI1 по сти- лям, РЕР8, поощряющему использование символов подчеркивания в именах переменных. Совершенно верно, я самым непочтительным образом Ilред почитаю использовать "верблюжий стиль", а в свое онравдание сошлюсь на раздел "А Foolish Consistency Is the Hobgoblin of Little Minds" I caMoro же руководства РЕР8 2 , в котором есть такие строки: "Соrласованность с этим руководством очень важна. СOI:ласованность внyrри одноrо проекта еще важнее. А соrласованность внyrри модуля или функции  самое важное. Но важно помнить, что Иноrда это руко- водство неприменимо, и понимать, коrда можно отойти от рекомен- даций. Коrда вы сомневаетесь, просто посмотрите на друrие примеры и решите, какой выrлядит лучше". Хорошими считаются описательные имена, которые раскрывают смысл данных, содержащихся в переменных. Представьте, что, lIересзжая в HO вый дом, вы разложили в(:е вещи по ящикам и пометили каждый из них одной и той же надписью  "Вещи". Леrко ли вам будет после этоrо что либо найти? В данной КНиrе, как, впрочем, и в большей части ДOКYMeHTa ции по Python, в качестве типичных имен в примерах используются такие имена, как spaт, eggs и ли bacon (здесь явно сказалось влияние скетча "Spatn" I "rлупая последовательность  пуrало маленьКИХ умов"  цитата из эссе "Доверие к себе" известноro американскоro писателя Ральфа Уолдо Эмерсона (см. перевод на русский язык по адресу https://raw.githиbиsercontent.com/boretskiy/selfreliancerи/ master/ selfreliancerи. txt).  Прu.'IIеч. ред. 2 https: / /www.python.org/dev/peps/pep0008/ (см. перевод на русский язык по адресу http://pep8 . ru/doc/pep8/).  Примеч. ред. 
Основные понятия языка Python 51 rРУIIПЫ "Mollty Руtlюп"), но в своих проrраммах: вам лучше использовать описательные имена, что повысит удобочитаемость вашеrо кода. Ваша перва. проrрамма Интерактивная оболочка отлично подходит для выполнения инструк ций PythOH 110 одной за один раз, но при написании полноценных про-- rpaMM на Python вы будете подrотавливать их текст с помощью какоrо--либо файловоrо редактора. Файловые реда",торы аналоrичны текстовым, таким как Notepad (Блокнот) или TextMate, но предлаraют дополнительные воз можно<ти при Вlюде исходноro кода. Чтобы открыть файловый редактор в IDLE, выберите пуикты меню FileNewfile (ФайлСоздать файл). В открывшемся окне вы увидите курсор, ожидающий ввода, но это окно отличается от окна интерактивной оболочки, которое выполняет BвeдeH ную вами инструкцию Pytl10n сразу же после нaж<rrия клавиши <Ellter>. Фай ловый редактор позволяет ввести множестВО инструкций, сохранить файл и выполнить проrрамму. Ниже указано, чем различаются эти два окна: . признаком окна интерактивной оболочки служит приrлашение к вво-- ду вида >>>; . в окне файловоrо редактора приrлашение к вводу вида »> oтcyrcTBYeт. А теперь пришло время создать вашу первую IIporpaMMY! Открыв окно файловоrо редактора, ВВедите в нем следующий Текст. о # Эта проrрамма приветствует пользователя и запрашивает # ВВОД информации. . print ( 'ИеНо world! 1) print('What is your пате?') # запрос имени . myName  input ( ) . print('It iз good to meet you, , + myName) . print('The length of your пате i5:') print(len(myName)) Ф print('What iз your age?') # запрос возраста myAge  input ( ) print('You will Ье I + str(int(myAge) + 1) + ' in а year. ') Введя и<,ходный код, сохраните ero, чтобы набирать ero заново при сле- дующем запуске IDLE. Выберите в меню, расположенном в верхней части окна файловоrо редактора, пункты FileSave As (ФайлСохранить как), введите hello.py В поле File Name (Имя файла) и щелкните на кнопке Save (Сохранить). В процессе ввода теКста ПрOl'раммы периодически сохраняйте файл. Это позволит избежать потери уже введенноrо кода, если работа компьютера 
52 r лава 1 завершится аварийно или вы случайно выйдете из IDLJo:, Вместо сохране- ния файла с помощью меню можно нажать комбинацию клавиш <Ctrl+S> (Windows и Linux) или <X+S> (OSX). После Toro как файл будет сохранен, запустите проrрамму, Выберите пункты меню RunRun Module (ВыполнитьВыполнить модуль) или просто нажмите клавишу <F5>. Ваша проrpамма должна запуститься в окне инте- рактивной оболочки, которое открывалось, коrда вы впервые запускали IDLE. Не забывайте о том, что клавишу <F5> следует нажимать Не в окне интерактивной оболочки, а в окне файловоrо редактора. Введите свое имя в ответ на приrлашение nporpaмMbl. Вывод проrраммы в окне интерактив- ной оболочки должен выrлядеть примерно так, Python 3.3.2 (v3.3.2:d047928ae3f6, Мау 16 2013, 00:06:53) [MSC v.1600 64 bit (AМD64)] оп win32 Туре "copyright", "credits" or "license ()" for more information. »> ============================== RESTART =================== »> НеНе werld! What is your пате? Аl It is good to meet you, Аl The length of your пате is: 2 What is your age? 4 You will Ье 5 in а year. »> Исчерпав все строки КОДа, подлежащие выполнению, прш'рамма завер- шается, Т.е. ее выполнение прекращается. (В этом случае также rоворят о выходе из проrраммы.) Чтобы закрыть окно файловоrо редактора, щелкните на кнопке х в верх- ней части окна. Чтобы перезarрузить ('охраненную проrрамму, выберите в меню пункты FileOpen (ФайлОткрыть). Проделайте это сейчас, выбери- те в открывшемся окне имя файла мио.ру и щелкните на кнопке Ореп ( крыть). В окне файловоrо редактора должна открыться nporpaмMa, кото-- рую вы перед этим сохранили в файле hello.py. АнаllИЗ проrраммы Сейчас мы кратко рассмотрим все инструкции, которые ИСПОЛЬ.1}'ЮТся в новой проrрамме, открытой в окне файловоrо редактора, и проанализиру- ем, что именно делает каждая строка кода. 
Основные понятия языка Python 53 lо_е.,.,.. Приведенные ниже строки кода называются комментарием. . # Эта прорамма приветствует пользователя и запрашивает # ввод информации. l>yt.llOn иrнорирует комментарии, и вы сможете использовать их для за писи примечаний или напоминаний самому себе относительно 1'01'0, что делает данный код. Любой текст от символа "решетка" (#) до конца строки считается частью комментария. Иноrда проrраммисты помещают символ # перед строкой кода для Toro, чтобы фактически исключить ее из кода, или, как rоворят, заКOJ,f,.М,eumuро- ватъ, на время тестирования проrраммы. Этот прием оказывается очень полезным при выяснении причин нарушения нормальной работы проrрам- мы. Впоследствии вы сможете восстановить отключенный код, удалив сим- вол #, или, как rоворят, раско.м.чeumuровавстроку, Пустые строки lIосле комментария иrнорируются. Вы можете добавить в свою проrрамму столько пустых строк, сколько пожелаете. Такое форма- тирование кода, напоминающее разбивку КНИЖlюrо текста на абзацы, об леl'чает ero чтение. фуНК.... pr:i.n t ( ) Функция print () отображает указанное в скобках строковое значение на экране. . print ( 'Не110 world!') print('What is your пате?') # запрос имени Строка print ( 'Hella world!') означает "Вывести текст, хранящийся в строке 'Не 110 wor ld! '". При словесном Оllисании этой строки I'ОВОРЯТ, что Python в'Ы3'Ьюает функцию print () , передавая ей строковое значение. Значе ние, передаваемое функции, называется apYMeHтOM. Обратите внимание на то, что кавычки не ВЫВОДятся на экран. Они лишь обозначают начало и конец строки и не являются частью CTpoKoBoro значения. Прu.чечакие Эту же ФУЮ(''I,!,UЮ можно UCnОЛ'Ь30вam'Ь для в'ывода на экран пустой страки; для это- O достаmич1tО в'Ьtnол'Uum'Ь в'Ы30в pr in t () без указан:ия аРiЗументов. Наличие пары скобок после имени указывает на то, что данное имя обозначает функцию. Именно поэтому в книrе везде используется запись print () , а не print. Более подробно функции рассматриваются в rлаве 2. 
54 rлава 1 фунК.... input () Функция input () ожидает ввода пользователем текста с последующим на- жатием клавиши <El1ter>. . myName  input () В этой строке кода текст, введенный пользователем, преобразуется в строковое значение, которое присваивается переменной myNarne. Вызов функции input () можно рассматривать как выражение, значени- ем KOToporo является строка, введенная пользователем. Если пользователь ввел' Al ' , то предыдущая строка кода эквивалентна строке myNarne = 'Al'. '..8. ..,н. 110..308"".. В следующем вызове функции pr in t () в скобках указано выражение '1 t is good to meet уои, , + myNarne. . print('It is good to meet уои, , + myName) Вспомните о том, что результатом вычисления выражения всеrда явля. ется одиночное значение. Если I Al'  это значение, сохраненное в пере- мен ной myName в предыдущей строке, то значением дaHHoro выражения яв- ляется 'It is good to meet уои, Al'. Затем это одиночное строковое значение передается функции pr in t ( ) . которая выводит ero на экран. фунК.... leп ( ) Вы можете передать функции lеп () строковое значение (или перемен ную, содержащую строку). и она возвратит целое число, равное количеству символов в аIlIlОЙ строке. . print ('The length of yoиr пате is: 1) print(len(myName)) Чтобы увидеть, как это работает на практике, введите в интерактивной о6оЛО(lке следующий код. »> 1en( 'Ье110') 5 »> 1en('Мy very energetic Dlonster just scarfed naсЬоа. ') 46 >>> 1еn(") О 
Основные понятия языка Python 55 Точно так же, как в этих примерах, вычисление выражения len (myNarne) дает целое число. Далее это число передается функции pr in t ( ) , которая выводит el'o на экран. Заметьте, что функции print () можно передавать любые целочиеленные или строковые значения. Но если вы введете в ин- терактивной оболочке приведенный ниже код, то получите сообщение об ошибке. »> print('I аа ' + 29 + ' years old. ') Traceback (most recent са11 1ast): Fi1e "<pyshell#6>", line 1, in <modи1e> print('1 ат ' + 29 + ' years old.') TypeError: Can't convert 'int' object to str imp1icitly Ошибка связана не с самой функцией print () , а с выражением, которое вы пытаетесь ей передать. То же самое произойдет и в том случае, если вы введете в интерактивной оболочке одно только это выражение. »> '! аа ' + 29 + ' yearB old.' Traceback (most recent са11 1ast): File "<pyshell#7>", Нпе 1, in <modиle> '1 ат ' + 29 + ' years old.' TypeError: Can't convert 'int' object to str imp1icitly PytllOn выводит сообщение об ошибке 110 той причине, что оператор + можно использовать ЛИШь для сложения двух чисел или конкатенации двух строк. Сложить число со строкой невозможно, потому что это запрещено rрамматикой Python. Данную проблему можно устранить, используя вместо целоrо числа ero строковую версию, о чем пойдет речь в следующем раз- деле. Функ.."" str () I int () "float () Если вы хотите конкатенировать целое число, такое как 29, со строкой для передачи функции print () , вы должны получить значение' 29' , явля щееся строковой формой числа 29. Функции str () можно передать целое число, и она преобразует el'o в соответствующую строку. »> etr(29) '29' »> print('I am ' + str(29) + I years old.') 1 ат 29 years old. Поскольку вычисление s tr (29) дает строку' 2 9' , мы получаем в результа- те вычисления выражения' 1 am I + str (29) + ' years old. ' значение' 1 am I + 
56 rЛQВО 1 '29' + ' years old. " которое, в свою очередь, возвращает результат в виде строки' 1 ат 29 years old. '. Это значенис и передается функции print (). Функции str (), int () и float () соотвстственно возвращают crроковую, целочисленную и всщественную формы передаваемоrо им значения. По-- пробуйте выполнить в интерактивной оболочке преобразование HeKOТO рых значений с помощью этих функций и посмотрите, что при этом будет происходить. >>> str (О) 101 >>> str(З.14) I З.14 I »> int('42') 42 >>> int('99') 99 >>> int(1.25) 1 »> int (1.99) 1 »> flоаt('З.14') 3.14 >>> float(10) 10.0 В этих примерах функции str (), int () и float () вызываются для получ ния строковой, целочисленной и вещественной форм значений, представ ляющих друrие типы данных. Функцию str () удобно использовать в тех случаях, коrда у вас есть целое или вещественное число, которое вы хотите конкатенировать со строкой, Функция int () будет полезной, если имеется число в строковой форме, ко-- торое вы хотите использовать в математических вычислениях. Например, функция inpиt () всеrда возвращает строку, даже если пользователь вводит число. Введите в интерактивной оболочке инструкцию spaт  inpиt ( ) , а за тем в режиме ожидания ввода  число 101. »> ерат = input() 101 »> epu '101' Сохраненное в переменной spam значение является не числом 101, а строкой' 101'. Е<:ли вы хотите выполнить вычисления с использованием значения, хранящсrося в spam, воспользуйтесь функцией in t () ДЛЯ получ ния целочисленной формы значения spaт и сохраните новое значение в этой же переменной. 
Основные понятия языка Python 57 »> spam = int(spaa) »> spam 101 Теперь вы сможете обрабатывать значение spaт не как строку. а как число. »> spam * 10 I 5 202.0 Имейте в виду. что в случае передачи функции int () значения, которое она Не может привести к цело численному виду. Python отобразит сообще- ние об ошибке. »> 1nt('99.99') Traceback (most recent call last): File "<pyshell#18>", line 1, in <modu1e> int('99.99') ValueError: invalid literal for int() with Ьаве 10: '99.99' »> int (' twelve') Traceback (most recent call last): File "<pyshell#19>", Нпе 1, in <module> int ( 'twel ve ' ) Va1ueError: invalid literal for int() with Ьаве 10: 'twelve' Функцию int () удобно использовать для окрyrления вниз веществеННЫХ чисел. »> int(7.7) 7 >>> int(7.7) + 1 8 в вашей проrpамме функции int () и str () используются в последних трех строках кода для приведения значений к соответствующим типам данных. . print ('What is your age?') # запрос возраста myAge = input ( ) print('You will Ье ' + str(int(myAge) + 1) + ' in а year.') в переменной myAge содержится значение, возвращенное функцией input () . Поскольку функция input () всеrда возвращает строку (даже если пользователь ввел число), для преобразования CTpoKoBoro значения, хранящеrося в переменной myAge, в целочисленное следует использо вать код int (myAge). Затем это значение увеличивается на 1 в выражении int (myAge) + 1. 
58 r лава 1 Сравнение строковых и числовых значений В то время как строковое значение числа считается полностью отличающимся от ero целочисленной или вещественной версии, целое зночение может быть равно вещественному значению. »> 42  '42' False >>> 42 42. О True »> 42.0 == 0042.000 True Python учитывоет это различие, поскольку строки  это текст, TorAa как и целый, и вещественный типы  числа. Результат сложения передается функции str (): str (int (myAge) + 1). Пое- ле ЭТоrО возвращенное СТРОК080е значение конкатенируется со строками 'You will Ье ' и ' in а year. ' для получения единоrо CTpoKoBoro значения. Наконец, это строковое значение передается функции print () для вывода на экран. Предположим, что пользователь вводит в качестве значения перемеи- ной myAge (TPOКY '4 ' . Далее эта строка преобразуется в целое (lИсло, к ко- торому можно прибавить единицу. В результате мы получаем значение 5. Функция str () преобраэует этот результат обратно в строку, которую мож- но конкатенировать <:0 второй строкой, , in а year. ' , для создания окон- ttательноrо сообщения. Последовательность выполнения этих действий I1редставлена на рис. 1.4. cPrint( 'You _111 he ' + еи (1nt (ayAqe) + 1) + ' in а year.') str(1nt( '4' ) + 1) + ' in year. ') cPrint('YOu _i11 he ' + а str( 4 + 1 + ' 1n year. ') cPr1nt( 'You _111 he ' + а str( 5 + ' in year. ') cPr1nt('YOu _111 he ' + а cPr1nt('YOu _111 he ' + '5' + ' 1n а year. ') cPr1nt ( 'You _i11 he 5' + ' 1n а year. ') print ( 'You _111 he 5 in а year. ') Рис. 1.4. Последовательность вычислений для случая, коrда знсчение myAge равно 4 
Основные понятия языка Python 59 РеЗlOме Для вычисления выражений можно использовать калькулятор, а для объ- еДинения строк в связный текст  текстовый редактор. Вы также можете реплицировать строки путем их копирования и вставки. Однако выраж Ю'lя и их компоненты  операторы, переменные и вызовы функций  яв ляются теми строительными блоками, на основе которых создаются про rраммы. Научившись обрабатывать эти элементы, вы сможете с помощью pyt ]1011 оперировать большими объемами данных. Вам следуt.'Т запомнить ра.зличные операторы (+, , *, /, / /, % и ** для математических операций и + И * для операций над строками), а также три 'I'ина данных (целые числа, вещественные числа и строки), описанные в этой rлаве. Вы также познакомились с несколькими функциями. Функции print () и inpиt () предназначены ДЛЯ обработки простоrо TeKcToBol'O вывода (на экран) и ввода (с клавиатуры). Функция len() принимает строку и вычис- ляет количество содержащихся в ней символов. Функции st r ( ), in t () и f10at () вычисляют соответственно строковую, целочисленную и веще- ственную формы переданноrо им значения. Из следующей rлавы вы узнаете, как орrанизовать в HporpaMMe на Pyt]lOn принятие решений относительно Toro, какой КОД выполнить, какой пропу- сl'ить, а какой повторить, исходя из проверки выполнения заданных уело- lШЙ. Это обеспечивается управляющими конструкциями языка Python, с помощью которых вы сможете писать проrраммы, способные принимать rибкие решения. Контропьныевопросы 1. Какие из приведснных ниже синтаксических элементов являются операторами, а какие  значениями? * 'hello' 88.8 / + 5 2. Что из приведенноrо ниже является переменной, а что  строкой? sparn 'sparn' 
60 r пава 1 3. Назовите три типа данных. 4. Из чеrо состоит выражение? К чему СВОДИтся любое выражение? 5. В этой rлаве введены выражения присваивания наподобие spaт = 10. В чем cyrb различия между выражением и инструкцией? 6. Каким будет <:одержимое переменной bacon после выполнения сл дующей инструкции? Ьасоп 20 Ьасоп + 1 7. Каким будет результат вычисления следующих двух выражений? 'эрат' + 'spamspam' , эрат' * 3 8. Почему eggs  допустимое имя переменной, а 100 таковым не является? 9. Назовите три функции, которые MOryт быть использованы для полу чения цеJIочисленной, вещественной и строковой версий значения?" 10. Что I1РИВОДИТ К возникновению ошибки при попытке вычисления нриведенноrо ниже выражения? Как избавиться от этой ошибки? 'Я съел' + 99 + ' лепешек.' Дополнительное задание. Изучите подробнее функцию len ( ) . проведя поиск в онлайновой документации Python. Нужная вам информация Haxo дится на вебстранице, озаrлавленной "Built-in Fllllct.iOIlS". Ознакомьтесь со СПИСком друrих функций Python, обратив особое внимание на функцию roиnd ( ) , и с"мостоятельно поэкспериментируйте с этой функцией. исполь зуя интерактивную оболочку. 
поток VПРАвпЕНИЯ Итак, вам уже известны основы синтаксиса отдельных ин- струкций, и вы знаете, что последовательность таких ин- струкций образует проrрамму. Однако реальная мощь про- rpаммироваllИЯ заlUIючается не впростом llоочередном вы- полнении инструкций проrpаммы, напоминающем заранее спланированное расписание встреч С друзьями в выходные дни. Исходя из результатов вычисления выражений, проrрамма может ca мостоятельно принимать решения относительно Toro, какие инструкции следует пропустить, а какие повторить, а также выбирать одну из несколь- ких инструкций для выполнения. В действительности практически Не суще- ,твует проrрамм, которые выполняли бы CTporo последовательно каждую строку кода, начиная с первой и заканчивая последней. PytllOllllредостав- ляет (:интаксис уnравJ/ЯЮ11&UХ инструк:/&ий, или uнструкций ветвления (друrие названия  инстру'Кц,ии передачu управле'ltUFl, и'ltстру'К,/&ии перехода, условн'Ые инструкции), которые предназначены для изменения eCTecTBeHHoro после- довательиоrо порядка выполнения инструкций IlpOrpaMMbI, позволяя коду решать, какая инструкция должна выполняться следующей, исходя из со-- блюдения определенных условий, Последовательность передач управления в процессе выполнения проrpаммы образует ее потак уnравлен,ия (также по- так 8'ЬtnОЛ'ltе'ltия). Управляющие инструкции непосредственно соответ(:твуют определен- ным условным символам, используемым в блок-схемах процессов, поэтому при обсуждении проrрамм в этой rлаве я буду обращаться к их rрафическо-- му представлению в виде блок-схем. На рис. 2.1 показана блок-схема процес- са принятия решений, определяющеrо порядок действий, которые должны предприниматься в зависимости от Toro, идет ли дождь. Обычно на таких блок-схемах отображается несколько возможных марш- pyrOB, ведущих от начальной точки к конечной. То же самое справедливо 
62 rлава 2 и для строк кода в компьютерной проrрамме. Блоки условий, в которых происходит ветвление проrраммы, обозначаются на блок-схеме ромбами, блоки действий  прямоуrольниками. Конечный и начальный блоки про-- rpaмMbI представляlOТСЯ скрyrленными прямоyrольниками. да Нет HeMHoro выждать Нет да Нет да Рис. 2.1. Блок-схема, определяющая порядок действий на случай дождя Однако, прежде чем приступить к изучению управляющих инструкций, вам предстоит ознакомиться со способами представления отображаемых на блок-схемах вариантов да и нет, а также записи точек ветвления в виде кода на языке Pyt.hon. В качестве основы для ЭТоrо расс.мотрения мы будем использовать булевы значения, операторы сравнения и булевы операторы. 5YlleBbl значеНИII в то время как целый, вещественный и строковый типы данных MOryт иметь неоrраниченное количество возможных значений, лоzu.ческuu, или булев, тип данных может принимать только два значения: True (истина) и False (ложь). (Булев тип данных получил свое название в честь анrЛИЙСКОI'О математика, основателя математической лоrики Джорджа Буля.) При ис- пользовании в коде Python булевы значения True и False не заключаются в кавычки и всеrда начинаются с БОЛЫIIОЙ буквы Т или F, тоrда как остальная 
Поток управления 63 часть слова ЗaIlИсывается маленькими буквами. Введите в интерактивной оболочке следующие инструкции (некоторые из них намеренно сделаны некорректными и будут сопровождаться выводом сообщений об ошибке при попытке их выполнения). о »> врав = 'l'rue >>> врав True . >>> true Traceback (mo5t recent call last): File "<pyshell#2>", line 1, 1n <module> true NameError: пате 'true' 15 not def1ned . »> 'l'rue = 2 + 2 SyntaxError: a55ignment to keyword Как и любые друrие значения, булевы значения MOryr входить в выраж иия и сохраняться впеременных О. В случае lIрименения неправилыlOro p rистра букв" или попытки использования идентификатора True или Fa1se в качестве имени переменной .. PytIlOIl вывеДеТ сообщение об ошибке. Операторы сравнения Операторы сравueuия сравнивают два значения между собой и возвраща- ют результат в виде булева значения. Операторы сравнения приведены в табл.2.1. Та6ltица 2.1. Оператор..' сравиеНИII Оператор На.lание РаlНО ! Не равно < Меньше чем > Бопьwе чем <= Меньше или равно >= Бопьwе МIIМ равмо Результатом действия этих операторов, в зависимости от предоставлен ных им значений, является значение True или False. Посмотрим, как они работают, начав с операторов == и ! =. »> 42 == 42 True »> 42 == 99 
64 rлаВQ 2 False >>> 2 != 3 Trиe >>> 2 != 2 False как и следовало ожидать, результат применения оператора  (равно) равен Trиe в случае равенства значений, записанных слева и справа от Hero. тоrда как результат применения оператора ! (не равно) равен True, еСЛII эти значения различаются. Операторы  и !  MOIyr работать, по сyrи, с любыми типами значений. »> 'hello'  'hello' True »> 'hello' == 'Rello' False »> 'doq' != 'cat' True »> True .... True Trиe »> True != False True »> 42 ..... 42. О Trиe О »> 42 .... '42' False Обратите внимание на то, что целые и вещественные значения никоr да не MorYT быть равны строковым. Результат выражения 42 == '42'" равен False, поскольку для Python целое число 42 и строка' 42'  разные значения. С дрyrой стороны, операторы <, >, <== и >== работают надлежащим обра зом лишь с целыми и вещественными значениями. >>> 42 < 100 Trиe >>> 42 > 100 False >>> 42 < 42 False »> eggCount = 42 . >>> eggCount <= 42 True >>> Dlуще = 29 . >>> ауще >= 10 True 
Поток управления 65 Раз.nичие между операторами == и = Возможно, вы обратили внимание на то, что оператор проверки равенства (==) обозначается двумя знаками равенство, тorдo кок оператор присваивания (=)  од- ним. Эти операторы можно леrко перепутать. Чтобы этоrо не произошло, запомните следующее. · Оператор == (равно) проверяет равенство двух значений. · Оператор = (присвоение) помещает значение, указанное справа, в перемен- ную, указанную слева. Вам будет леrче запомнить, что ecn. что, прибеrнув к следующей мнемонике: оба родственных оператора сравнения (== и ! =) остоят из двух символов. Операторы сравнения часто используют для Toro, чтобы сравнить зна ..ение переменной (: некоторым дрyrим значением, как в строках кода eggCount <= 42. иmуАgе >= 10.. (В конце концов, если бы речь шла лишь о сравнении двух значений, то, например, вместо инструкции' dog' ! = 'cat' в своем КОДе вы моrли бы просто использовать значение True.) Со мноrими аналОl'ИЧНЫМИ примерами вы еще встретитесь далее, КОI'да будете изучать управляющие инструкции. Iупевы операторы Для сравнения булевых значений используются три булева оператора: =.nd, or и not. Подобно операторам сравнения, они вычисляют булевы вы- ражения, сводя их к единственному булеву значению. Приступим к более подробному рассмотрению этих операторов, начав с оператора and. '"наРНII' 6,.,... Оll'Р"'ОРII Операторы and (ЛOl'ическое и) и or (лоrическое или) всеrДа работают с двумя булевыми значениями (или выражениями), и поэтому их называют бинарными. Оператор and возвращает значение True только в том случае, если одновременно оба булева значения равны Trиe; В противном случае p зультат равен False. Чтобы увидеть, как это работает, ВЫlIолните в интерак- тивной оболочке следующие примеры. »> True and True :rue »> True and :&'alse :alse 
66 r лава 2 Для представления всех возможных результатов применения булевых операторов используют таблицы истин/н ости. Таблица истинности для опе ратора and приведена в табл. 2.2. Та6nица 2.2. Таблица иcrиннасти ДПЯ оператора and В..ражение True and True True and False False and True False and False Реаупllтат True False False False Возможные результаты применения оператора or приведены в ero таб- лице истинности (табл. 2.з). Та6nица 2.3. Табllица истинности дпя оператора or В..ражение True or True True or False False or True False or False Реаупыат True True True False О",ротор not Оператор not, В отличие от операторов and и or, воздействует только на одно булево значение (выражение) и поэтому является yuajпtым.. Этот оп ратор обращает булево значение в ero противоположность. »> not True False . »> not not not not True True В полной аналоrии с использованием двойных отрицаний в устной речи и на письме допускается использование вложенных операторов not 8. хотя в реальных nporpaMMax в этом вряд ли возникнет необходимость. Возмож- ные результаты применения оператора not приведеныl в ero таблице истин- ности (табл. 2.4). Та6nица 2.4. ТабllИца истин насти дпя оператора not В..ражение not True not False Реаупыат True False 
Поток управления 67 Сочетание операторов сравнеНИII с 6Уllевыми операторами Поскольку результатом вычисления выражений, ВКЛЮЧаЮЩИХ операто ры сравнения, являются булевы значения, их можно исполь:ювать в Bыpa жениях совместно с булевыми ОlIераторами. Вспомните, что операторы and, or и not на.lываются булевыми, ПОСКОЛk ку всеrда воздействуют на булевы значения Trиe и False. В то время как выражения наподобие 4 < 5 не являются булевыми значениями, будучи вычислеННЫМИ, они дают как раз такие ;шачения. Чтобы увидеть, как это работает, выполните в интерактивной оболочке следующие примеры. »> (4 < 5) and (5 < б) :rue »> (4 < 5) and (9 < б) :alse »> (1 == 2) or (2 == 2) :rue Сначала КОМlIьютер вычисляет левое выражение, а .затем  правое, KoJ'- да оба тих результата становятся известными, вычисляется конечный p зультат BcerO выражения в целом, который будет IIре/l,ста8ЛЯТЬ собой един ственное булево значение. И<:пользуемый компьютером процесс вычисле lIия выражения (4 < 5) and (5 < 6) ПРОИЛJlIOCТРИРОван на рис. 2.2. (4 < S) and (S < 6) + True and (S < 6) + True and True + True Рис. 2.2. Процесс вычисления выражения (4 < 5) aпd (5 < б), привадящий к значению True ДОllускается совместное использование в одном выражеlIИИ как булевых I щераторов, так и операторов ("равнения. .» 2 + 2 == 4 and not 2 + 2 == 5 and 2 * 2 2 + 2 _ :....:е Как и для математических операторов, для булевых операторов определен порядок выполнения. После Toro как будут вычислены все 
60 rЛQва 2 математические операторы и операторы сравнения, первыми выполняют- ся операТОРbl nat, затем  операторы and и только после этоrо  операто- ры or. Эllементы потока упраВllеНИII Начальная часть инструкций, управляющих порядком выполнения дру- rих инструкций проrраММbI, часто является условием, за которым следует блок кода. Прежде чем приступить к изучению конкретных управляющих инструкций PytJlOl1, рассмотрим, что собой представляют условие и ассо. циироваННblЙ с ним блок кода. У,.о... Все булеВbI выражения, с которыми вы к этому времени успели позна- комиться, MOryт рассматриваться как условия. Условue это вcero лишь бо. лее специализированное название выражения в контексте управляющих инструкций. Вычисление условия всеrда дает булево значение, True или False. Управляющая инструкция принимает решение относительно даль- нейших действий в зависимости от Toro, какое из этих двух значений при- нимает условие, и условия используются ПОЧти во всех управляющих ин- струкциях. 5по". "011" Строки кода Pyt]lOI1 MOryт rруппироваться в маки. О том, rде находятся начало и конец блока, можно судить по отступам строк кода в тексте про- rpaMMbI. В отношении блоков действуют следующие три правила. Признаком начала блока служит увеличение oTcТYlla. Блоки MOryт содержать друrие блоки. Признаком конца блока служит уменьшение отступа до нулевой величи- ны или до веЛИЧИНbI отступа содержащеrо (внешнеrо) блока. Вам будет леrче понять, что такое блоки, определив, rде они находятся в приведенной ниже части небольшой иrровой IIporpaMMbI. if . . пате == 'Mary I : print ('НеНа Mary') if password == 'swordfish': print ( 'Ассевв granted. I ) else: print{'Wrong password') . else: print{'User nat registered') о 
ПОТОК управления 69 Первый блок кода. начинается строкой print ( 'НеНе Mary') И содер- жит все последующие строки, предшествующие второй инструкции else. В этом блоке содержится второй блок ., содержащий только одну строку: ;:rint ( I Access granted. ' ) . Третий блок" также (остоит только из одной строки: print ('Wrong password. 1). Четвертый блок, состоящий из строки ;:rint ('User not registered 1), относит(я ко второй инструкции else, ассо-- циированной с первой инструкцией i f. ВЫПОllнение npOrpaMMbl в проrрамме heUo.py из предыдущей rлавы Pyt]lOn последовательно BЫ 1I0ЛНЯЛ инструкции одну за ДРУI'ОЙ от начала и до конца проrраммы. BЬt r/олиe1-tие пpoepa.M,MЬt  это термин, ОТНОСЯЩийся к текущим выполняемым инструкциям. Если вы напечатаете на бумажных листах исходный код про-- rpaMMbI и будете lJеремещать палец по строкам сверху ВНиз, то это и будет схематической иллюстрацией выполнения проrpаммы. Однако не все проrраммы выполняются столь прямолинейно. Если вы приметесь отслеживать пальцем порядок выполнения проrраммы, в ко-- торой есть управляющие инструкции, То вашему пальцу придется переме щаться в пределах Bcero исходноrо кода в соответствии с заданными усло виями И, возможно, пропускать целые блоки. УпраВIIlllOщие инструкции ПРИСТУIlИМ К самым важным элементам потока управления: собствен- но управляющим инструкциям. На блоксхеме, которая была показана на рис. 2.1, эти инструкции представлены ромбами, и именно они реализуют принятие решений вашей проrpаммой. иНару".... iz Наиболее общим типом управляющих инструкций является инструкция :. :. Следующий за инструкцией i f блок кода будет выполняться только в TOI случае, если результат вычисления условия равен True (условие истин- но). Если же результат вычисления условия равен False (условие ложно), то ;).10К не будет выполняться. На обычном языке инструкция if интерпретируется следующим обра- )()e "Е<:ли условие истинно, то выполнить данный блок кода". В Python ин. струкция if включает такие элементы: . ключевое слово if; . условие (т.е. выражение, вычисление KOTOpOl'O дает значение True или False); 
70 r лава 2 . двоеточие; . блок кода с- отступом, начинающийся в следующей строке. Предположим. у вас имеетСЯ код, проверяющий совпаденис с именем "Alice". (Здесь предполaraeтся. что переменной пате ранее было присвоено некоторое значение.) if пате == 'Alice': print ( 'Hi, Аliсе.') Все управляющие инструкции заканчиваются двоеточием, за которым следует блок кода (тело инструкции). В данном случае блок состоит из одной инструкции: print «Hi, A1ice.'). Блок-схема рассматриваемом кода I1РИ8Сдена на рис. 2.3. print ('Hi, Alice.') False Рис. 2,3. Блок-схема инструкции if и.пру".... else За блоком кода инструкции if может следовать необязательная инструк- ция else со своим блоком кода, который выполняется лишь в том случае, если условие i f ложно. На обычном языке конструкция i f / е lse может быть прочитана так: "Если условие истинно, выполнить данный блок кода. В про- тивном случае выполнить следующий блок кода". В PytJlOn инструкция else не имеет условия и Всеrда состоит из следующих элементов: . ключевое слово else; . двоеточие; . блок кода с отступом, начинающийся на следующей строке. 
Поток управления 71 Возвращаясь к примеру с именем 'Hce", рассмотрим код, содержащий инструкцию else, которая выводит дрyrое приветствие, если имя пользова- теля  не "A1ice". if пате == 'Alice': print('Hi, Аliсе.') else: print('Hello, stranger. ') Блок-схема этоrо кода представлена на рис. 2.4. True False print('Hello, stranqer.') Конец Рис. 2.4. Блок-схема инструкции else IIНар,".... el:i.f В то время как из двух блоков кода, ассоциированных с инструкциями if и else, выполняется только один, иноrда желательно выбирать для выпол нения один из uесколькuх возможных блоков кода. Инструкция е 1 i f (COKpa щение от еМе iЛ может помещаться только после инструкции if или дрyrой инструкции elif. Она предоставляет еще ОДНо условие, которое проверя- ется лишь в том случае, если все предыдущие условия оказались ложными. В Python инструкция elif всеrда состоит из следующих элементов: . ключевое слово elif; . условие (т.е. выражение, вычисление KOToporo дает значение True или False); . двоеточие; . блок кода с отступом, начинающийся в следующей строке. 
72 rЛQва 2 Добавим инструкцию elif в код, проверяющий имя, чтобы увидеть, как это работает на практике. if пате == 'A1ice': print('Hi, A1ice.') elif age < 12: print('You are not A1ice, kiddo.') На этот раз дополнительно проверяется возраст поль:ювателя, и если он меньше 12, то текст выводимоrо (:ообщения будет дрyrим. Блок-схема этоrо кода представлена на рис. 2.5. True рrint('Иi, Alice.') False True print ('You are not Alice, kiddo.') False Рис. 2.5. Блоксхемо инструкции elif Блок кода elif выполняется, если выражение age < 12 истинно, а Bыpa жение пате == I Аl1се I ложно. Но если оба условия ложны, то пропускаются оба блока кода. Никаких raрантий Toro, что выполнится хотя бы один из этих двух блоков кода, нет. Если имеется цепочка инструкций elif, то будет выполнен либо только один блок кода, либо ни один из них. Как только об- наруживается, что одно из условий истинно, все остальные блоки кода elif автоматически пропускаются. В качестве примера откройте в файловом pe дакторе новое окно, введите в нем приведенный ниже код и сохраните ero в файле vaтpire.py. 
Поток управления 73 if пате  'A1ice': print('Hi, A1ice.') elif аче < 12: print('You are not A1ice, kiddo.') elif age > 2000: print('Un1ike you, A1ice is not an undead, immorta1 vampire.') е1Н аче > 100: print('You are not Alice, grannie.') В этом коде добавлены две ДОПOJШительные инструкции elif, обеспечи- вающие вывод разных сообщений в зависимости от возраста (age), указан- Horo пользователем. Блок-схема этоrо кода показана на рис. 12.6. Имейте, однако, в виду, что порядок расположения инструкций e1if имеет значение. Сейчас мы намеренно внесем в код ошибку, переставив ин- струкции местами. как вам уже известно, если одно из условий оказывается истинным, все остальные блоки кода elif автоматически пропускаются, и поэтому перестановка условий может создавать проблемы в КОДе. Измени- те код, как по казан о ниже, и сохраните ero в файле vaтpire2.py. if пате  'A1ice': print (1 Hi, АНсе.') е1Н age < 12: print('You are not Alice, kiddc.') . elif аче > 100: print('You are not A1ice, grannie. ') elif age > 2000: print('Unlike you, Alice is not an undead, immortal vampire.') Предположим, что до выполнения кода в переменной age содержится значение 3000. Вы ожидаете, что код выведет на экран строку 'Unlike уои, Аliсе is not an undead, immortal vampire. '. Однако, поскольку вычисление условия age > 100 дает значение True (ведь 3000 больше 100) 8, на экран будет выведена строка 'Уои are not Аliсе, grannie. " тоrда как остальны<, инструкции elif будyr автоматически пропущены. Вспомните о том, что ВЫIIОЛНЯется не более чем один из блоков инструкций elif, а порядок сл дования этих инструкций имеет значение! Блок-схема предыдущеrо кода представлена на рис. 2.7. Обратите вни мание на то, что ромбы для условий age > 100 и age > 2000 переставлены fестами. При необходимости вслед за последней инструкцией elif можно помес- ТИ1'Ь инструкцию else. В этом случае rарантируется, что будет выполнен хотя бы один (и только один) блок кода. Если условия во всех инструкциях i-f и elif окажутся ложными, то выполнится блок кода инструкции else. Например, переделаем проrрамму для распознавания имени "Alice" таким образом, чтобы в ней использовались инструкции if, elif и else. 
74 r лава 2 Fa18e Fa1ge Fa18e False True print (' Hi, Alice.') Рис. 2.6. Блок-схемо кода с несколькими инструкциями elifB nporpaMMe vamp;re.py True print('You are not Alice, kiddo.') True print('Unlike уои, Alice i8 not an undead, immortal vampire,') True print('You are not Alice, grannie.') if name  'Alice': print ( I Hi, Alice.') elif age < 12: print('You are not Alice, kiddo.') else: print('You are neither Alice nor а little kid. ') 
Поток управления 75 True print (' Hi, Аliсе.') False Тсие print('You асе not Alice, kiddo,') False Тсие print('You асе not Alice, grannie.') False print('Unlike уои, Alice is not an undead, immortal vampire.') False Конец Рис. 2.7. Блоксхема nporpoMMbl vampire2.py. Выполнение проrpаммы ВДОЛЬ перечеркнутоrо пути лоrически невозможно, поскольку если значение age больше 2000, 'То оно заведомо больше 100 На рис. 2.8 1I0казана блок-схема IIOBOI'O кода, который мы сохраним в файле littleКid. ру. Пользуясь обычным языком, смысл управляющей конструкции ЭТОI'О типа можно lIepeaTb так: "Если первое условие истинно, выполнить этот 
76 r лава 2 блок кода. Иначе, если второе условие истинно, выполнить ЭТОТ блок Кода. В противном случае выполнить этот блок кода". в случае cOBMecTHoro ис- пользования всех трех инструкций не забывайте о правилах, устанавли- вающих последовательность их расположения, чтобы избежать ошибок, подобных той, которая ПРОИJuпострирована на рис. 2.7. Во-первых, должна быть только одна инструкция if. Любые инструкции elif, которые вам мо-- ryr понадобиться, должны следовать за инструкцией if. Во-вторых, если вы хотите быть уверены в том, что выполнится хотя бы один блок кода, замкните всю управляющую структуру инструкцией else. True print (' Hi, АНсе.') False True print('You are not Alice, kiddo.') False print('You are neither Alice nor а little kid.') Конец Рис. 2,8. Блоксхема nporpoMMbl Iiff/eКid.py ц.". while Инструкция while 1I0зволяет ОР"aIlИЗ0вать MHoroKpaTHoe повторное BЫ полнение блока кода. Блок кода инструкции while выполняется до тех пор, 
Поток управления 77 пока выполняется указанное в ней условие. В PythOIl инструкция while вcer- да состоит из следующих элементов: . ключевое слово while; . условие (т.е. выражение, вычисление KOToporo дает значение True или False); . двоеточие; . блок кода с отступом, начинающийся в следующей строке. Нетрудно заметить, что инструкции while имеет сходную с инструкци- еЙ i f структуру, однако ведет себя иначе. По достижении конца блока кода if управление выполнением передается следующей инструкции. Однако 110 достижении конца блока кода инструкции while управление передает- ся в ее начало, и проrрамма продолжает выполнение Toro же блока кода. Инструкцию while часто называют 14иКJlOM while или просто 14U1СЛОМ. Сравним, как работают инструкция if и цикл while, ИСПОJ1Ь.1ующие одно и то же условие, на основании проверки KOToporo в обоих случаях предпри- нимаются одни и те же действия. Вот так ВЫrЛЯДИТ код с инструкцией i f. эрат = о if spaт < 5: print ('Hello, world.') spaт = эрат + 1 А вот так выrлядит код с инструкцией whi le. spam = о while spaт < 5: print('Hello, world.') эрат = эрат + 1 Эти инструкции весьма похожи: как if, так и while проверяют значение переменной spaт, и если оно меньше пяти, то выводится сообщение. Тем не менее эти фраrменты кода выполняются совершенно по-разному. Если в инструкции if вывод представляет собой одиночное сообщение "Неllо, world. ", то в инструкции while это сообщение выводится целых пять раз! Чтобы разобраться в том, почему так происходит, обратимся к соответству- ющим блок-схемам, представленным на рис. 2.9 и 2.10. Код <: инструкцией if тестирует условие и выводит текст Иеllа, world., если условие истинно. С друrой стороны, код с инструкцией while выво- дит этот текст пять раз. После этоrо он прекращаст свою работу, поскольку целочисленное значение, хранящее<:я впеременной spaт, увеличивается на единицу на каждой итерации ЦИКЛа, а это означает, что цикл выполнится именно пять раз, прежде чем условие spaт < 5 примет значение False. 
78 r лава 2 True print ('НеНо, world.') spam spam + 1 False Рис. 2.9. Блок-схема кода с ИНСТРУКЦl1ей if True print('Hello, world.') spam spam + 1 False Рис. 2.10. Блок-схема кода с I1нструкцией while 
Поток управления 79 в цикле while условие вссrда проверяется в начале каждой итерации (т.е. каждый раз, Коrда выполняется цикл). Если условие интерпретируется как Trиe, то блок кода выполняется, а затем вновь проверяется условие. Сразу же после Toro как обнаруживается, что условие равно False, блок кода while пропускается . Нозоi"I'''Й ЦIО while Ниже приведен пример простой проrраммы, которая без устали просит вас ввести свое имя, но на самом деле ожидает ввода не вашеrо имени, а бук- валЬНоrо текста I yoиr пате' ('ваше имя'). Выберите пункты меню FileqNew Window (ФайлqНовое окно) для открытия HOBoro окна файловоrо редакто-- ра, введите приведенный ниже код и сохраните eI'o в файле yourNaтe.py. о пате  " . while пате != 'yoиr пате': print('Please type yoиr пате. ') . пате = input () . print ( 'Thank уои! 1) Сначала проrрамма присваивает переменной пате О значение в виде пустой строки. Это сделано для Toro, чтобы вычисление у(:ловия пате !". , yoиr пате I дало значение Trиe и проrрамма приступила к выполнению бло- ка кода цикла while .. Затем код в теле цикла запрашивает ввод имени пользователя и присваи вает ero переменной пате .. Поскольку это последняя строка блока, вы- полнение возобновляется с caмoro начала цикла while, rде вновь тестирует-- ся условие. Если значение пате не равно строке' yoиr пате' , то вычисление условия дает значение Trиe, в результате чеrо вновь начинает выполняться тело цикла. Но как только пользователь введет текст yoиr пате, у(:ловие примет сле дующий вид: 'yoиr пате' !  'yoиr пате', что эквивалентно значению False. Поскольку теперь условие ложно, выполнение цикла прекращается и управ ление передается коду, следующему за циклом О. Блок-схема проrраммы yourNaтe.py приведена на рис. 2.11. Давайте проверим, как работает проrрамма yourNaтe.llJ' Нажмите клави шу <F5> и несколько раз введите текст, отличный от yoиr пате, прежде чем предоставить "porpaMMe то, что она требует. ?lease type yoиr пате. Al ?lease type yoиr пате. Albert ?lease tvpe voиr пате. 
80 r лава 2 Please type yoиr пате. yoиr пате Thank уои! пате  n print('Please type your пате.') False пате = input () print('Thank you!') Рис. 2. 11. Блок-схема проrраммы yoиrName.py Если вы так и не введете текст yoиr пате, то условие цикла while НИКоrда не сможет принять значение False, и проrрамма будет бесконечно повто- рять Свой запрос. В данном случае вызов функции inpиt () предоставляет пользователю ВОЗМОЖНОСть ввести строку, которая позволит проrрамме 110" кинyrь цикл. В дрyrих проrраммах возможны случаи, коrда значение уело- вия никоrда не сможет измениться, и это станет проблемой. Рассмотрим, как можно ОРl'анизовать принудительный ВЫХОД из цикла while. IfнtтруICЦ". break Существует быстрый способ заставить проrрамму преждевременно выйти из цикла while. Если проrрамма в процесс е выполнения достиrает 
Поток управления 81 ИНСТРУКЦИИ break, то выполнение цикла немедленно прерывается (break  ключевое слово PytllOП). Довольно просто, не правда ли? Ниже ПРИDCден пример проrраммы, ко- торая делает то же самое, что и предыдущая, но использует инструкцию break для выхода из цикла. Введите следующий код и сохраните ero в файле yourName2.py. . while True: print('Please type your пате.') . пате = inpиt ( ) . if пате == 'your пате': . break . print ( 'Thank уои! ' ) Первая строка. создает бесконеч:н:ый 14U1UL, Т.е. цикл while, условие KO Toporo всеrда истинно (True). Проrрамма всеrда будет входить в цикл и выйдет из Hero только в том случае, если выполнит(я инструкция break. (Бесконечный цикл, выйти И3 KOToporo невозможно,  распространенная "роrpаммная ошибка.) Так же, как и ранее, проrрамма запрашивает ввод пользователем текста yoиr пате .. Однако теперь в цикле while I1РИcyrствует инструкция i f ., ко- торая провсряет, совпадает ли значение перемеllНОЙ пате со строкой your пате. В случае истинности этоrо условия выполняется инструкция break О, и управление передается инструкции print ('Thank уои') .. В противном случае предложение Н, содержащее инструкцию break, пропускается, про- rpaмMa переходит в конец цикла while и сразу же возвращается в ero нача- ло для проверки условия. Поскольку условие  это просто булево значение True, IIporpaMMa вновь входит в цикл и запрашивает ввод текста your паТЕе. Блок-схема этой nporpaMMbI приведена на рис. 2.12. Запустите nporpaMМY yourNuтber2.py и введите тот же текст, который вы вводили для nporpaMMbI yourNuтber.py. Новая версия проrраммы должна реаrировать на ваш ввод так же, как и исходная. иН"ру".... continue Как и инструкция break, инструкция continue используется в циклах. Коrда проrрамма в процессе выполнения достиrает инструкции conti:1ue, управление немедленно передается в начало цикла, rде условие вычисляет- ся заново. (То же самое происходит и при обычном достижении "porpaM мой конца цикла.) 
82 r лава 2 True рrint('Рlеазе type your name.') пате = input () * True break False print ( I Thank you! 1) Рис. 2. 12. Блок-схема nporpaMMbl yoиrName2.py с бесконечным циклом. Обратите внимание на то, что перечеркнутый путь выполнения nporpaMMbl лоrически никоrда не может быть реализован, поскольку условие цикла BcerAa истинно Продолжим написание проrраммы, запрашивающей ввод имени и паро- ля. Введите следующий код в новом окне файловоrо редактора и сохраните ero в файле sword.fish, ру. 
Поток управления 83 Попали в повушку бесконечноrо цикла' Если вы обнаружили, что ваша nporpaMMa увязла в бесконечном цикле, на- жмите комбинацию клавиш <Ctrl+C>. В результате будет сrенерирована ошибка KeyboardInterrиpt, что приведет к HeMeдneHHoMY прекращению работы nporpaM- мы. Проверьте, как это работает на практике, создав простой бесконечный цикл в файловом редакторе и сохранив ero в файле ;nf;niteloop.py. while True: print('Hello world!') Коrда вы запустите эту проrpoмму, она начнет безостановочно выводить на экран приветствие Hello wor ld!, поскольку условие инструкции while Bcerдa остается истин ным. В интерактивной оболочке IDLE существуют два способа "ринудительноrо заверше- ния работы проrраммы: нажатие клавиш <Ct+C> и выбор пунктев меню SheIIRestart Shell (Оболачка Перезапустить оболочку). Первый способ удобно использовоть в любой ситуации, коrда требуется HeMeдneHHo прекротить работу проrpаммы, даже если это никак не связано с необходимостью прервоть выполнение бесконечноro цикла. ./ while Trиe: print('Who are уои?') пате = inpиt ( ) . if пате ! = 'Joe': . continue print('Hello, Joe. What is the password? (It is а fish.) ') . password = input ( ) if password == 'swordfish': О break . print ( 'Ассевв granted. I ) F..с.ли пользователь вводит любое друrое имя, кроме Joe"., инструкция continue . заставляет проrрамму перейти в начало цикла. П()('ле повторной IIроверки условия проrpамма всеrда входит D тело цикла, поскольку условие вcela истинно (True). В случае же llрохождения инструкции if запраши- вается ввод пароля .. Если пользователь вводит пароль swordfish, то вы- полняется инструкция break ., nporpaMMa покидает цикл while и выводит на экран текст Access granted .. В противном случае управление выполне- нием проrpаммы передается в конец ЦИКJIа while и сразу же возвращается D ero начало. Блок--схема этой проrрамМЫl1редставлена на рис. 2.13. 
84 r лава 2 True pr1nt ('Who are уои?') пате  input () cont inue True * False print('Hello, Joe. llhat 19 the pa99word? (It 19 а fi9h.) 1) password  input() break Fa1ge Рис. 2.1 З. Блок-схема nporpaMMbl swordfish.py. перечеРI<НУТЫЙ путь выполнения nporpaMMbl лоrически никоrдо не может быть реализован, поскольку условие цикла всеrда истинно 
Поток управления 85 .Истинные" и "nожные" значения Существуют значения друrих типов ДаННЫХ, которые при проверке условий счи- таются эквивалентными значениям True и False. При использовании в выражениях условий значени" О, О. О и ' , (пустая строка) считаются ложными (False), тorAo как все друrие значения считаются истинными (True). Взrляните, например, на следую- щую nporpOMMY. name = " while not name:. print('Enter your name:') name = input ( ) print('How many guests will уои have?') numOfGue5ts = int(input()) if numOfGuests:. print('Be sure to have enough room for all your guests.')8 print ( 'Oone') Если пользователь вводит пустую строку в качестве имени, то условие цикла while принимает значение True ., и проrрамма продолжает запрашивать имя. Если значение переменной nurnOfGuests не равно О ., то значение условия счита- ется равным True, и nporpaммa выводит напоминание .. Вместо not пате вы моrли бы ввести not пате ! = ", а вместо numOfGuests  numOfGuests ! = О, но использовоние истинных и ложных значений облеrчает чте- ние кода. Запустите nporpaMMY и введите какой-либо текст. Проrрамма не запро-- сит ввод пароля до тех пор. пока вы не подтвердите, что ваше имя  "Joe"; после ввода правильноro пароля она должна завершить выполнение. Who are уои? 11т fine, thank5. Whc are уои? Who are уои? Joe Hello, Joe. What i5 the pas5word? (1t i5 а fi5h.) Mary Who are уои? Joe Hello, Joe. What i5 the pa55word? (1t i5 а fish.) swordfish Ассе55 granted. 
86 r лава 2 ц.к. zor . фуНК.... raпge ( ) Цикл while продолжает выполняться до тех пор, пока условие остается истинным. Но что если вы хотите ВЫПОЛНить блок кода лишь определен- ное количество раз? Это можно сделать с помощью цикла for или функции range ( ) . Инструкция for выrлядит в коде примерно как for i in range (5) : и всеrДа включает следующие элементы: . ключевое слово for; . имя переменной; . ключевое слово in; . вызов функции range ( ) , которой можно передать до трех целых чисел; . двоеточие; . блок кода с отступом, начинающийся в следующей строке. Чтобы проверить на практике, как работает цикл for, создадим новую проrрамму в файле f i veT imes . ру. print('My пате is') for i in range(5): print('Jimrny Five Times (' + str(i) + ') ') Блок кода цикла for выполняется пять раз. На первой итерации значе- ние переменной i устанавливается равным О. Вызов функции print () в теле цикла выводит текст Jimmy Five Times (О). Коrда Python завершает итера- цию, выполнив весь блок кода, управление передается в начало цикла, rде ин<:трукция for увеличивает значение переменной i на единицу. Именно поэтому вызов функции range (5) обеспечивает пятикратное выполнение блока кода цикла, устанавливая для i последовательные значения О, 1,2,3 и 4. Целое значение, указанное при вызове функции range () , в этот ряд не входит. Блоксхема проrраммы.fivеllтеs.рупоказана на рис. 2.14. Коrда вы запустите эту проrрамму, она должна вывести пять строк текста Jimmy Five Times, каждая из которых заканчивается текущим значением i, и покинуть цикл for. Му пате is Jirnmy Five Tirnes (О) Jirnmy Five Tirnes (1) Jirnmy Five Tirnes (2) Jirnmy Five Tirnes (3) Jirnmy Five Tirnes (4) 
Поток управления 87 print ('Ну name is') print('Jimrny Five Times (' + str(i) + ')') Цикл завершен  Рис. 2. 14. Блок-схема nporpaMMbl five Times.py Примечание 'в 'ЦиКJI,ax for также доnуск.ается uсnолъзоватъ UнстjJy1c'ЦUU break U continue. Инст/J'Y'К'Ция сопипие 8озвращает уnравленuе в наЧШlО 'ЦиКJI,a для выполнения сле-- дующей иmepа'Ции, как ес.лu бы nроерам.ма достима кон'Ца 'ЦиКJI,a U вериуласъ к еео началу обычны,м, способо,м,. В действuтелъностu инструк'Ции contiпue U break ,м,оеут исnолъзоватъся толъко в 'Циклах while U for. Если вы nоnытаетесъ uсnолъ- зоватъ их едлuбо еще, Python выведет сообщение об ошuбке. в основу еще ОДНоrо примера цикла for положен исторический факт, связанный с известным математиком Карлом Фридрихом rayccoM. Как-то, коrда I'aycc еще учился в школе, учитель дал клаСL'У следующее задание: най- ти сумму всех чисел от О до 100. Используя остроумный прием, юный raycc нашел нужную сумму в течение вcero лишь нескольких секунд, но вы може- те проделать соответствующие вычисления самостоятельно, написав про-- rpaмMY на языке Python, в которой используется цикл for. . tota1 = О . for ПuпI in range (101) : . tota1 = tota1 + ПuпI . print (tota1) 
88 r лава 2 Правильный ответ  5050. Сразу после запуска проrраммы значение переменной total устанавливается равным О .. Затем в цикле for . КОД total = total + пит . Вblполняется 100 раз. По завершении 100 итераций цикла в переменной total будет сохранена сумма всех целых чисел от О до 100. После этоrо значение total выводится на экран.. Даже на самых Meд ленных компьютерах выплнениеe этой проrpаммы займет менее секунды. (Юный raycc доrадался, что Bcero имеется 50 пар чисел, сумма которых дает 100: 1 + 99,2 + 98,3 + 97 + ...; 49 + 51. Поскольку 50 х 100" 5000, то после прибавления оставшеrося без нары числа 50 мы получаем окончательный результат  5050. Сообразительный ребенок!) Iв.ваnеll,и..ii ЦIIU while Все то, что делает Цикл for, можно сделать с помощью цикла while; просто циклы for более компактны в записи. Перепишем код проrраммы fiveТiтes,py, использовав в новой версии цикл while, квивалентный цик- лу for. print('My пате is') i = О while i < 5: print('Jimmy Five Times (' + str(i) + ') ') i = i + 1 Если вы занустите эту проrрамму, то увидите, что она позволяет полу- чить те же результаты, что и ее версия, в которой используется цикл for. Арryмеи,.. .a..ana, IОllца 11 wara Аllаnазоиа "".1&1111 range () Некоторые функции MOryr вызываться с передачей им нескольких apry- ментов, разделенных запятыми, и функция range ()  одна из них. Это по- зволяет изменять диапазон целых чисел, задаваемый функцией range ( ) , включая ero начальный и конечный элементы. for i in range(12, 16): print(i) Первый apryMeHT определяет, с KaKoro значения начинает изменяться переменная цикла for, а второй  верхнюю rраницу диапазона изменения этой переменной, но сам в ЭТОт диапазон не включается. 12 13 14 15 
Поток управления 89 Также допускается вызов функции range () с тремя арryментами. Первые два apryмeHTa определяют начало и конец диапазона изменения переменной цикла for в соответствии с приведенным выше описанием, а тpt..'Тий задает шar. Шае  это приращение переменной в конце каждой итерации цикла. for i in range(O, 10, 2): print(i) Следовательно, вызов range (О, 1 О, 2) обеспечивает изменение перемен ной цикла от нуля ДО восьми С шаrом два. О 2 4 6 8 Функция range () очень rибкая в отношении формирования последова- тельностей целых чисел для циклов for. Например, задание отрицатель Horo числа в качестве шаrа обеспечивает изменение переменной цикла от больших значений к меньшим. for i in range(5, 1, l): print(i) Выполнение цикла for для вывода значений переменной i с использова нием вызова range (5,  1,  1) даст следующий результат. 5 4 3 2 1 О Импортирование МОДУllей Любой проrрамме на языке Python доступен базовый набор функций, которые называются встроен:н:ыми, в число которых вхоДЯТ такие функции, как print () , input () и lеп (), с которыми вы уже успели познакомиться. Кроме Toro, в поставку Python входит набор модулей, который называется сrпшндартuой библиотекой. Каждый модуль  это "porpaMMa на Python, содер- жащая rруппу родствеНIIЫХ функций, которые вы можете внедрять в свои 
90 r лава 2 проrраммы. Например, модуль math включает математические ФУНКЦИИ, модуль random  функции для работы со случайными числами, и Т.д. Прежде чем вы сможете использовать функции, входящие в модуль, ero необходимо импортировать с помощью инструкции import. Инструкция import в коде состоит из следующих элементов: . ключевое слово import; . имя модуля; . необязательные дополнитеJII>ные имена модулей, разделенные запя. тыми. как только модуль импортирован, вы можете использовать любую из входящих в Hero функций. Давайте проверим, как работает модуль random, предоставляющий доступ к функции random. ranint () . Введите в файловом редакторе приведенный ниже код и сохраните ero в файле printRandom. ру. import random for i in range(5): print(random.randint(l, 10)) Выполнив проrрамму, вы должны получить следующий вывод. 4 1 8 4 1 Функция random. randint () возвращает случайное число, лежащее в диа- пазоне межу двумя целочисленными значениями, которые передаются функции в качестве apryмeHToB. Поскольку функция randint () находится в модуле random, ero имя должно указываться в виде префикса (через точку) перед именем функции, чтобы Pyt]lOn Mor определить, что данную функ- цию следует искать в модуле random. Вот пример инструкции import, которая импортирует четыре различ- ных модуля: import random, зуз, оз, math Теперь мы можем использовать любую из функций, находящихся в этих четырех модулях. 
Поток управления 91 Ifн"рукц.. zrom import Альтернативная форма инструкции import состоит из ключевоrо сло ва from, за которым следуют имя модуля, ключевое слово import и символ "звездочка", например from random import *. При использовании этой формы импорта добавлять префикс random. к имени функции, вызываемой из модуля random, не требуется. Однако ис пользование полноrо имени функции повышает удобочитаемость кода, поэтому лучше использовать обычную форму инструкции import. Преждевременное прекращение ВЫПОllнеНИII nporpaMMbl с помощыо вызова ауа . exi t () и в завершение хочу познакомить вас с тем, как прекратить выполн ние проrраммы. Это всеrда происходит автоматически после выполнения последней инструкции проrраммы. Однако существует возможность при нудительно прекра1'ИТЬ работу проrраммы, или, как rоворят, выйти из нее, с помощью вызова функции sys. exi t () . Поскольку эта функция находится в модуле sys, вы должны импортировать eI'o до Toro, как он будет исполь зоваться. Откройте новое окно файловоrо редактора, введите в Hero приведен- ный ниже код и сохраните ero в файле exitExaтple.frJ. irnport sys while True: print('Type exit to exit.') response = inpиt() if response == 'exit': sys.exit() print('Yoи typed ' + response + '. ') Запустите эту проrрамму в IDLE. В проrрамме имеется бесконечный цикл, в котором отсутствует инструкция break. Единственная возможность завершить работу этой проrраммы  это ввести exi t в ответ на запрос, что приведет к вызову функции s ys. exi t ( ) . в случае совпадения значения пе ременной response со строкой' exi t I выполнение проrраммы прекраща ется. Поскольку значение переменной response устанавливается функцией inpиt (), для завершения проrраммы пользователь должен ввести слово ехН. 
92 r лава 2 РеЗlOме Используя выражения, результатом вычисления которых является зна- чение True ИЛИ False (такие выражения называют условиями), можно пи- сать проrраммы, способные принимать решения относительно Toro, какие участки кода должны выполняться, а какие  проnyскаться. Кроме Toro, можно орrанизовать MHoroKpaTHoe выполнение кода в цикле до тех пор, пока вычисление условия дает значение True. В тех случаях, Коrда требу- ется осуществить выход из цикла или возврат к ero началу, используются инструкции break и continue. Управляющие инструкции позволяют писать HaMHorO более интеллекту. альные ПрОI'раммы. Существуют и друrие возможности по управлению вы- полнением проrрамм, обеспечиваемые написанием собственных функций, о чем пойдет речь в следующей rлаве. KOHTpOlIbHble вопросы 1. Каковы два возможных значения данных булева типа? Как они запи- сываются? 2. Назовите три булевыx оператора. 3. Напишите таблицы истинности (т.е. запишите результаты для всех возможных комбинаций оператора и BYX булевых значений) для каж- доrо из булевых операторов. 4. Каковы результаты вычисления приведенных ниже выражений? (5 > 4) and (3 == 5) not (5 > 4) (5 > 4) or (3 == 5) not ((5 > 4) or (3 == 5)) (True and True) and (True (not False) or (not True) Fa1se) 5. Назовите шесть операторов сравнения. 6. В чем Cyrb различия между оператором равенства и оператором при- сваивания? 7. Объясните, что такое условие и rде используются условия. 8. Идентифицируйте три блока в приведенном ниже Коде. эрат  О if эрат == 10: print ( 'eggs' ) if эрат > 5: print ( 'Ьасоп I ) AlctA- 
Поток управления 93 print ('ham') print ( 'spam' ) print ('spam') 9. Напишите КОД, который выводит разные сообщения в зависимости от значения, хранящеrося в переменной spaт: Hello  при значении 1, Howdy  при значении 2 и Greetings!  при любом Друl'ОМ значении. 10. Какую комбинацию клавиш следует нажать, чтобы вывести проrрам- му из бесконечноrо цикла? 11. Чем различаются инструкции break и continue? 12. Чем различаются вызовы функций range (10), range (О, 10) и range (О, 10,1) в цикле for? 13. Напишите короткую проrрамму, выводящую числа от 1 до 10 с помо- щью цикла for. Затем напишите аналоrичную проrрамму, в которой используется цикл whi1e. 14. Как бы вы вызвали функцию bacon ( ) , хранящуюся в модуле эрат, 110- еле TOI'O, как ИМIIОРТИРОВали этот модуль? Дополнительное задание. Поищите в Интернете информацию о ФУНК- циях round () и abs () и Выясните, что они делают. Самостоятельно поэксп риментируйте с ними в интерактивной оболочке. 
фуНКции в предыдущих rлавах вы уже познакомились с функциями print () , inpиt () и len () . Python предоставляет встроенные функции наподобие этих, но вы можете писать и собствен- ные функции. ФуНК1&ия  это нечто вроде мини-проrраммы в рамках большой проrраммы. Чтобы лучше понять, как работают функции, обратимся к конкретному примеру. Ввеlщте в файловом редакторе исходный код при- веденной ниже проrpаммы и сохраните ero в файле helloPunc.py. о def hello () : . print ( 1 Howdy! ' ) print ('Howdy!! ! 1) print('Hello there.') . hello() hello ( ) hello () Первая строка  это инструкция de f ., которая определяет функцию с именем hello ( ) . Блок кода, следующий за инструкцией def ., образует тело функции. Этот код выполняется при вызове функции, а не при ее первона чальном определении. Последующие три строки hello () .  ЭТО вызовы функции. В коде вызов функции обозначается указанием ее имени с последующей парой круrлых скобок, которые MOryr содержать некоторое количество apl)'MeHToB. Коrда проrpамма в IIроцессе выполнения достиraет вызова функции, управление передается первой строке кода этой функции, п<кле чеrо выполняется весь ее код. По достижении конца функции управление возвращается строке, которая ее вызвала, и продолжается выполнение дальнейшеrо кода. 
96 r лава 3 Поскольку в данной проrрамме ФУНКЦИЯ hel10 () вызывается три раза, столько же выполняется и код этой функции. Выполнив эту проrрамму, вы должны получить следующий вывод. Howdy! Howdy!! ! Hel10 there. Howdy! Howdy!! ! Hello there. Howdy! Howdy ! ! ! Не1l0 there. Основное назначение функции  rруппирование MHoroKpaTHo выпл-- няемоrо кода. Если бы мы не определили функцию, то пришлось бы копи- ровать этот код в буфер обмена и вставлять ero в nporpaMMY везде, rде он используется, в результате чсrо проrрамма выrлядела бы примерно так. print ( I Howdy! ' ) print ( 'Howdy! ! ! ' ) print('Hello there.') print ( 'Howdy! ' ) print (' Howdy! ! ! ') print('Hello there.') print ('Howdy!') print ('Howdy!!!') print('Hello there.') В целом старайтесь избеrать дублирования кода, поскольку, если понадо- бится обновить проrpамму (например, для испрамения ошибок), вам при дется вносить изменения в код везде, куда вы ero копировали. По мере накопления проrраммистскоro опыта вы заметите, что все чаще занимаетесь тем, что избамяетесь от дублирования кода, сводя к минимуму использование метода копирования и вставки. Такие меры позволяют со- кратить размер проrрамм, а также упростить их чтение и обновление. ИНСТРУКЦИИ def С параМ8трами Вызывая функции наподобие print () или len (), вы передаете им знач ния, которые называются ареумен.тOJИU в данном контексте, указывая их в скобках. Вы также можете определять собственные функции, принимаю- щие арryмеиты. Введите в файловом редакторе приведенный ниже пример и сохраните ero в файле heUoFunc2.frJ. 
Функции 97 о ctef hell0 (пате) : . print (' Неl10 ' + пате) . hell0 ( , Аliсе ' ) hel10 (' ВоЬ') Выполнив эту проrpамму, вы должны нолучить следующий вывод. Hello Аliсе Неll0 ВоЬ в данной проrрамме определение функции hel10 () содержит параметр :1ате .. Парa.мemр  это псрсмеllНая, в которой сохраняется aplyмeHT функ ции при ее вы:ю"е. Коrда функция вызываетея в первый pa:J, ей передается арryмеит 'Alice' .. Коrда ноток управлспия псреходит " функцию, Hepe менной пате автоматически присваивается значение' Alice', которое и BЫ водится на экран инструкцией print () .. Следует особо отметить то обстоятельство, что ПОСJlС выполнения воз врата из функции сохраненное в параметре значение теряется. Например, если вы добавитс вызов print (пате) ПOCJlе вызова hello ('ВоЬ') в предыду- щей проrрамме, то будет выведено сообщение об ошибке NameError, по скольку вне функции переменной с именем пате не существует. При возвра те из функции после вызова hel10 ('ВоЬ') ЭТа IIсременная уничтожается, по-- этому вызов print (name) будет ссылаться на несуществующую перемснную. Это аналоrично тому, как при завсршении ПрOl'раммы информация о ее переменных теряется. Более подробно о том, почему так происходит, речь пойдет далее, Коrда мы будем обсуждать локальную область видимости переменных внyrри функции. ИНСТРУКЦИII return и возвращаемые значеНИII Коrда вы вызываете функцию len () и передаете ей арryмеит, например I Hel10 ' , функция ВЫЧИСJlЯСТ целое :ша"сние 5, нреДставляющсе длину CTpO ки, которая была передана функции. В общем СЛУЧаС значение, вычисляе юе в результате вызова функции, называется во.16ращае.м:ь/'м' зuа'Ц,еuuр.м, ДаН- ной функции. Создавая функцию с использованием инструкции de f, ВЫ можетс ука- зать, какое значение должно возвращаться, с IIOМОЩI)Ю инструкции retиrn. Инструкция retиrn состоит из следующих частей: . ключевое слово retиrn; . значение или выражение, которое должна вернуть функция. 
98 rпaoa 3 Если в инструкции return используется выражение, то возвращается ре- зультат вычисления данноrо выражения. Например, в следующей проrрам ме определяется функция, которая каждый раз возвращает дрУI]'Ю строку, в зависимости от Toro, какое число передано в качестве apryMeHTa. Введите в файловом редакторе следующий код и сохраните ero в файле тagic8BaU.py. о import random . def . getAn5wer(an5werNumber) : if an5werNumber == 1: retиrn 'It i5 certain' elif answerNumber == 2: return 'It i5 decidedly 50' e1if answerNumber з: retиrn 'Уе5' elif an5werNumber 4: retиrn 'Reply hazy try again' e1if an5werNumber == 5: return 'A5k again later' e1if an5werNumber == б: retиrn 'Concentrate and a5k again' elif an5werNиmber == 7: return 'Му reply i5 по' e1if an5werNumber == 8: return 'Outlook not 50 good' elif answerNumber == 9: retиrn 'Very doubtfu1' е r = random.randint(1, 9) . fortиne = getAn5wer (r) Ф print(fortune) Коrда запускается эта проrрамма, Python в первую очередь импортирует модуль random .. Затем определяется функция getAn5wer () .. Поскольку функция ЛИlUь определяется (а не вызывается), ее код не выполняется. Дa лее вызывается функция random. randint () с двумя арryментами  1 и 9 .. Эта функция возвращает случайное целое число, при надлежащее диапазо-- ну чисел от 1 до 9 (включая сами эти значения), и это число сохраняется в переменной r. Функция getAnswer () вызывается с передачей ей переменной r в качес тве арryмеита .. Выполнение проrраммы переходит в наtlaЛО функции getAn5wer () ., и значение r сохраняется в параметре answerNurnber. Затем функция возвращает одно из множества в03можныx строковых значений, зависящих от значения an5werNumber. Выполнение возвращается строке в нижней части проrраммы, которая первоначально вызвала функцию getAn5wer () .. Возвращенная строка присваивается переменной fortune, которая затем передает(я функции print () ф и выводится на экран. 
ФУНКЦИИ 99 Поскольку возвращасмые значения МOIУ!' lIередаваться в качестве apry ментов друrим вызываемым функциям, вместо строк r = random.randint(l, 9) fortune = getAnswer(r) print(fortune) можно использовать слсдующую эквивалентную им строку: print (getAnswer(random.randint (1, 9))) Вспомните, что выражения состоят из значений и Оllераторов. Вызов функции можно использовать в выражениях, поскольку это эквивалентно использованию возвращаеМОI"О значеllИЯ функции. Значение None В языке Python Оllределено значение None, IIрсдставляющее отсутствие значения. None  единственный прсдставитсль ТИllа даНIIЫХ NoneType. (Ана- JIоrичные значения в друrих языках IIpOI-раммирования фиrypируют IIОД именами пиН, nil и undefined.) Точно так жс, как имсна булсвых значений True и False, имя значения None всеrда должно вводиться с ИСIIОЛЬЗ0ванием IIрОПИСIIОИ буквы N. ЭТО "Зllаченис, lIе имеющее значения" может быть очень IIОЛСЗНЫМ, если вам нужно сохранить нечто такое, что невозможно перенутать с на- стоящим значением неременной. В частности, оно исполЬ.зуется в качсстве возвращаемою значения функции print (). Последняя отображает текст lIа экране, и ей необязательно возвращать значение так, как это дслают функ- ции lеп () и input () . Однако, поскольку вычисление любой функции долж- но давать возвращаемое значение, функция print () возвращает значение one. Чтобы увидетl>, как это работает, введите в интерактивной оболочке следующий код. »> spam = print('Hello! ') ::е 110 ! > > > None == spam rue Незаметно для нользователя, "за кулисами", Pytholl добавляет инструк- цию return None в конец Оllределения любой функции, в которой инструк- ция return отсутствует. Это аllалоrично тому, как цикл while или for закан- чивается неявной инструкцией continue. Кроме Toro, если вы используете инструкцию, не указывая значения (т.е. занисываете только само КJlЮчевое слово return), то функция возвращает значение None. 
100 r лава 3 Именованные aprYMeHTbl и ФУНКЦИII print () Большинство арryмеитов идентифицируется по их позиции в вызовс функ- ции. Например, вызов random.randint (1, 10) отличается от вызова random. randint (10, 1). При вызове функции random.randint (1, 10) будет возвра- щено случайное целое число из диапазона от 1 до 10, поскольку первый ар- ryмеит имеет смысл нижней rраницы диапазона, а второй  верхней (в то время как вызов r andom. randin t ( 1 О, 1) приводит К возникновению ошибки). В то же время и.м.еиова'Н:н:ые ареу.м.еит'Ы идентифицируются по имени, указываемому перед ними при вызове функции. Именованныс apryMeHTbI часто используются в качестве необязательных параметров. Например, функция print () имеет необязательные параметры end и sep, с помощью которых можно задать соответственно текст, который должен выводиться в конце apryMeHToB, и текст, который должен выводиться между apryмeHTa- ми (разделитель). Если вы запустите проrpамму print ( 'ИеНо' ) print ('World') ТО вывод будет таким: ИеНо World Две строки появятся в виде отдельных строк вывода, поскольку функция print () автоматически добавляет символы новой строки в конец каждой строки, которая ей передается. В случае необходимости вместо <:имвола новой строки можно использовать друryю строку, задав ее с помощью име- HOBaHHoro apryмeHTa end. Например, для проrpаммы print('Hello', end=") print ('World') вывод будет таким: HelloWorld Здесь текст выводится в одной строке ввиду отсyrствия символа новой строки после текста' ИеНо' . Вместо Hero выводится пустая строка. Этот прием приrодится вам в тех случаях, коrда потребуется отмена использова- ния символов новой строки, автоматически добавляемыIx в конце каждоrо вывода, осуществляемоrо с помощью функции print () . 
ФУНКЦИИ 1.1 Точно так же, если ВЫllередаете функции print () несколько строковых значений, то по умолчанию в качестве разделителя используется пробел. Введите в интерактивной оболочке следующий код. »> print (1 cats', I dogs', 'mice') :a1:S dogs mice Однако вместо заданной по умолчанию пустой строки можно использо-- вать дрyryю, передав функции именованный apryмeнт sep. Введите в инте- рактивной оболочке следующий текст. »> print('cats', 'dogs', 'mice', sep=', ') :ats,dogs,mice Именованные aplyмeHTbI также можно добавлять в функции, написанные вами самостоятельно, однако сначала необходимо изучить такие типы дан- ных, как списки и словари, о которых пойдет речь в двух следующих rлавах. .-\ пока что достаточно знать лишь то, что у некоторых функций имеются именованные apryMeHTbI, которые можно задавать при вызове функции. Локапьнаll и rllo6allbHall обllасти ВИДИМОСТИ о параметрах и переменных, получающих значения в теле вызванной функции, rоворят, ЧТО они существуют в лсжалЪ1tОЙ области видимости этой функции. О перемеllНЫХ, значения которым присваиваются вне функций, rоворят, что они существуют в 2Лобал'Ь1tОЙ области видимости. Переменные, существующие в локальной области видимости, называются ЛlЖал'Ь1t'Ьt.Ми перемен'Ными, Тоrда как переменные, существующие в rлобальной области ВИДИМОСТи, называются ZJlобал'Ь1tыми nеремe'lt1tым.. Переменная может от. носиться только к одному ИЗ этих типов; она не может быть локальной и rлобальной одновременно. Вы можете представлять себе область видимости как контейнер для пер менных. При уничтожении области видимости все значения, которые хра- нятся в относящихся К ней переменных, теряются. Существует только одна rлобальная область видимости, и она создается в момент начала выполн ния проrраммы. Коrда проrрамма завершает работу, rлобальная область ви. J.имости уничтожается, и все ее переменные теряются. В противном случае при запуске проrраммы в очередной раз переменные содержали бы те же значения, что и во время пос.леднеrо сеанса выполнения проrpaммы. Локальная область видимости создается всякий раз, коrда вызывается функция. Любая lIеременная, которой присваивается значение в этой функ- ции, существует в данной локальной области видимости. При возврате из функции локальная область видимости уничтожается, и эти переменные 
102 rлава 3 теряются. Коrда вы в следующий раз вызовете эту же функцию, локальные переменные не будут помнить те значения, которые хранились в них при последнем вызове функции. Области видимости переменных иrрают важную роль по следующим причинам: . локальные переменные не MOryт использоваться в коде, относящемся к rлобальной области видимости; . в то же время локальная область видимости имеет доступ к rлобаль ным переменным; . код, находящийся в локальной области видимости функции, не может использовать переменные из любой дрyrой локальной области види- мо<:ти; . разные переменные MOryт иметь одно и то же имя, если они относят- ся к разным областям видимости. Это означает, что одновременно MOryт существовать как локальная переменная с именем spam, так и rлобальная переменная с таким же иМенем. Использование в Python различных областей видимости и отказ ОТ 1'01'0, чтобы считать все переменные rлобальными, обеспечивает то преимуще- ство, что функции, изменяющие переменныс, MOryт взаимодействовать с остальным кодом проrраммы только через свои параметры и ВО:Jвращаемос значение. При таком подходе контролировать поведение проrраммы на- MHOro леrче и безопаснее. Если бы все переменные в вашей проrpаммс были только rлобальными, то ошибочное значение какой-либо переменной MOr- ло бы передаваться в ДРУl'ие части проrраммы, тем самым затрудняя поиск причины ошибки. Значение rлобальной переменной может устанавливать- ся в любом месте проrpаммы, а вся проrрамма может нас::читывать тысячи строк! Но если ошибка связана с неверным значением локальной перемен- ной, 1'0 для нахождения "ричины ошибки вам достаточно исследовать лишь код функции, в которой устанавливается значение данной переменной. В использовании rлобальных переменных внебольших проrраммах нет ничеro предосудителыlOro, но придерживаться TaKoro же подхода в случае крупных проrрамм  плохая привычка. JlOICO..HII. "'ре_...II. Н. _о", .'110.'30'''''''. . ,.060..0. OUOtrll В.Д._О". Рассмотрим следующую проrрамму, попытка выполнения которой при водит к ошибке. def spam(): eggs = 31337 
Функции 103 эрат() print (eggs) Выполнив эту проrрамму, вы получите следующий вывод. Traceback (most recent cal1 1ast): File "С:/tеstЗ784.ру", line 4, in <modи1e> print (eggs) NameError: пате 'eggs' is not defined Ошибка возникла потому, что переменная eggs существует только в ло- кальной области видимости, созданной при вызове функции spam ( ) . Как только осуществляется возврат из функции spam ( ) , эта локальная область видимости уничтожается, и переменная eggs прекращает существование. Поэтому, коrда ваша I1pOl'paMMa пытается выполнить вызов print (eggs), Python ВbIВОДИТ сообщение об ошибке, информируя вас о том, что пере- менная eggs не определена. Если вдуматься, то в этом есть смысл: коrда про- rpaMMa выполняется в rлобальной области видимости, локальные области видимости не существуют, а это означает, что нет никаких локальных пере- eHHЫX. Вот почему в rлобальной области видимости MOryr использоваться только rлобальные переменные. в .0"".'НIfХ 06."".х ..А..О"" н, .0,.,., .',,011.30.,,"'. "'Р'.'ННIf' .3 IIРУ'.Х .0"".'.IfХ 06."",. ..А..О"" Новая локальная область видимости создается всякий раз, Коrда вызыва- ется функция, включая случаи, коrда функция вызывается из дрyrой функ- ЦИИ. Рассмотрим следующую проrрамму, def spam () : . eggs  99 . bacon() . print(eggs) def Ьасоп(): ham = 101 О eggs '"' О . spam() Коrда она запускается, вызывается функция spam () .. и создается ло- кальная область видимости. Локальной переменной eggs .. присваивается значение 99. Затем вызывается функция Ьасоп () .. и создается вторая ло- кальная область видимости. В одно и то же время MOryr существовать н<..... сколько локальных областей видимости. В этой новой локальной области 
104 r лова 3 видимости значение локальной переменной ham устанавливается равным 101, а кроме Toro, создается и устанавливается равной О локальная перемен ная eggs е, которая отличается от одноименной переменной. созданной в локальной области видимости функции spam ( ) . После возврата из функции Ьасоп () ее локальная область видимости уни чтожается. Выполнение проrраммы продолжается в функции spam ( ) , кото- рая выводит значение переменной eggs О, а поскольку локальная область видимости для вызова функции spam () В это время все еще существует, то значение переменной eggs устанавливается равным 99. Именно это значе- ние и выводит проrрамма. Таким образом, локальные переменные одной функции полностью OT делены от локальных переменных друroй функции. rл060Л6н..е пере.енн..е .о", ".'016t. .3 лоltОЛ6ноj 06лоt,. '.II..ot,. Рассмотрим следующую проrрамму. def spam(): print(eggs) eggs = 42 spam() print (eggs) Поскольку имя eggs oTcyrcTBYeт в списке параметров функции spam () и в коде этой функции OTcyrcTBYeт присваивание значения переменной с Ta ким именем, то Python, встретив эту персменную в теле функции spam () , по- лаrает, что в данном случае имеется в виду ссылка на rлобальную пере мен- ную eggs. Именно поэтому при выполнении данной проrраммы на экран будет выведено значение 42. Ло1tОЛ6н..е . rл060Л6н..е пере.енн..е t O/I.ноltо..... ..ено.. Чтобы не усложнять себе жизнь, избеrайте использования локальных переменных, имена которых совпадают с именами l'Jюбальных или дpy rих локальных переменных. Но с технической точки зрения это вполне допустимо в Pythоп. Чтобы посмотреть, что при этом происходит, введи- те в файловом редакторе приведенный ниже код и сохраните ero в файле saтeNaтe.py. def spam () : О eggs = r spam local' print(eggs) # выводится строка 'spam local' 
Функции 105 def Ьасоп() : . eggs '" 'Ьасоп local' print(eggs) # выводится строка 'bacon local' spam() print(eggs) # выводится строка 'Ьасоп local' . eggs '" 'global' Ьасоп ( ) print(eggs) # выводится строка 'global' Выполнив эту nporpaMМY, вы должны получить следующий вывод. Ьасоп local spam local Ьасоп local global В этой nporpaMMe существуют фактически три разные переменные с одним и тем же именем eggs, что может сбивать с толку. Перечислим эти неременные. . Переменная eggs, которая существует D локальной области видим сти, коrда вызывается функция spam () . . Переменная eggs, которая существует в локальной области видим сти, коща вызывается функция bacon ( ) . . Переменная eggs, которая существует в rлобальной области видимости. Тот факт, что все три независимые переменные имеют одно и то же имя, южет сбивать с толку, если вы хотите отслеживать, какая из них исполь- зуется в любой заданный момент времени. Именно поэтому старайтесь из. беrать использования одних и тех же имен переменных D разных областях видимости. ИНСТРУЦИЯ global Если возникает потребность изменить в коде функции rлобальную пере- Iенную, используйте инструкцию global. Например, наличие инструкции ;lobal eggs в начале функции сообщает PytllOIl следующее: "В этой функ- ции имя eggs ссылается на rлобальную переменную, поэтому создавать ло- кальную переменную с таким же именем не следует". Введите в файловом редакторе следующий код и сохраните ею в файле samPNaтe2.py. def spam() : О global eggs . eggs '" 'spam I c:.nnc:: = 1,,1 ("\n.::.1 I 
116 r лава 3 spam () print(eggs) При выполнении этой проrpаммы завершающий вызов функции print () должен вывести следующий текст: spaт Переменная eggs объявлена как rлобальная в начале функции зрат () О, и поэтому. коrда eggs присваивается зпачение I spaт I О, эта операция вы- полняется по отношению к rлобальной переменной eggs. Никакая локаль- ная переменная eggs не создается. Суще(твуют четыре правила, позволяющие (удить о том, В какой облас- ти видимости находится переменная  локальной или rлобальной. 1. Если переменная используется в I:лобальной области видимости (т.е. вне какой-либо функции), то она вcerдa яВJIЯется rлобальной пере- менной. 2. Если переменная была объявлена в функции с использованием ин- струкции global. то она является rлобальной. 3. В противном случае, если переменная используется в операции при- сваивания в функции, то она является локальной. 4. Но если переменной ниrде в функции не присваивается значение, то она является rлобальной. Рассмотрим пример проrраммы, который поможет вам усвоить эти пра- ВИла. Введите в файловом редакторе приведенный ниже код и сохраните ею в файле sатеNатеЗ.ру. def spam(): . global eggs eggs = 'зрат' # это rлобальная переменная def Ьасоп(): . eggs = 'Ьасоп' # это локальная переменная def ham(): . print(eggs) # это rлобальная переменная eggs = 42 # это rлобальная переменная зрат( ) print(eggs) В функции spam () переменная eggs  l'Лобальная, поскольку в начале функции для eggs используется инструкция global О. В функции Ьасоп () 
Функции 107 переменная eggs  локальная, поскольку в этой функции она вводится с по-- мощью операции присваивания .. В функции ham () . переменная eggs  rлобальная, поскольку в этой функции для нее отсутствует инструкция global и она не вводится с помощью операции присваивания. Выполнив проrрамму saтeNaтe3,py, вы должны получить следующий вывод: spam в функции любая переменная будет либо всеrда rлобальной, либо всеrда локальной. Не может быть такоro, чтобы в одной и той же функции перемен ная использовалась сначала как локальная, а впоследствии как rлобальная. пpu.м,ечаuие Если вы хотите u.мemъ возможuост:ь 'IL1.Мe'НЯmъ с no.м.О?«ъю кода фуUК1,!,ии 31tа'Ч,euue, храuящееся в елобалъuой nсремеииой, mo npuмeuитe к этой nepемеииой ииструк- 1,!,ию global. Если вы попытаетесь использовать в функции локальную переменную до Toro, как присвоите ей какое-либо значение, то Python выведет сообщение об ошибке. Чтобы в этом убедиться, введите в файловом редакторе следукr щий код и сохраните ею в файле saтeNaтe4.frY. def арат(): print(egga) # ERROR! О eggs = 'spam local' . eggs = 'global' spam() Выполнив эту проrрамму, вы получите следующее сообщение об ошибке. Traceback (most recent call last): File "C:/test3784.py", Нпе 6, iп <module> spam() File "С:/tеstЗ784.ру", line 2, in spam print(eggs) # ERROR! UnboundLocalError: local variable 'eggs' referenced before аssigпmепt Эта ошибка обусловлена тем, что PytllOn, обнаружив присваивание пере- менной eggs значения в функции spam () О, предполаrает, что это локальная переменная. Однако, поскольку функция print (eggs) выполняется до Toro, как переменной eggs присваивается какое-либо значение, в момент се вы- зова такой l1среМСIIНОЙ не существует. В этой ситуации PytllOn не будет пы- таты:я использовать одноименную rлоБмыlюю переменную eggs .. 
108 r лава 3 ФУНКЦИИ как Нчерные ящики Н Зачастую все, что вам нужно знать о функции,  TO какие входные данные 'па- раметры) ей следует предоставить и каково ее выходное значение. Вам не BcerAa нужна обременять себя знанием Toro, как в действительности работает ее код. Если вы применяете к функциям такой высокоуровневый подход, то можно сказать; что вы рассматриваете любую функцию как "черный ящик". Эта идея имеет фундаментальное значение для cOBpeMeHHoro проrраммиро вания. В последующих rлавах вы познакомитесь с некоторыми модулями, которые содержат функции, написанные друrими людьми. Если вы любознательны, то може те заrлянуть в их исходный код, однако для Toro, чтобы использовать ти функции, вам вовсе не обязательно знать их внутреннюю структуру. А поскольку написание функций, в которых rлобальные переменные не используются, только приветствует- ся, вам, как правило, не приходится беспокоиться относительно тoro, что код тих функций будет нежелательным образом взаимодействовать с остальным кодом ва- шей nporpaMMbl. Обработка искпючений На данном этапе возникновение ошибки, или ис"лючения, в вашей про-- rрамме на Pyt}lOn означает крах проrpаммы, Т.е. ее аварийное завершение. Однако для реальных проrрамм такое поведение недопустимо. Поэтому в них используются средства, позволяющие обнаруживать ошибки, обраба тывать их и после этою продолжать выполнение nporpaMMbI. В качестве npимера рассмотрим nporpaMМY, в которой возникает ошибка "деление на О". Введите в файловом редакторе следующий код и сохраните ею в файле zeroDivide.frY. def spam(divideBy) : return 42 / divideBy print (spam(2)) print (spam(12)) print (эраm(О)) print (spam(l)) Мы определили функцию spam ( ) , предоставили ей параметр, а затем попытались вывести на экран возвращаемое этой функцией значение при различных параметрах, чтобы I10наблюдать, что при этом происходит, За пустив на выполнение этот код, вы получите следующий вывод. 21.0 3.5 
Функции 109 Traceback (most recent call last): Fi1e "C:/zeroDivide.py", line 6, in <module> print (sраm (О) ) File "C:/zeroDivide.py", line 2, in spam return 42 / divideBy ZeroDivisionError: division Ьу zero Сообщение об ошибке ZeroDi visionError выводится всякий раз, коrда предпринимается попытка разделить число на О. По указанному в сообщ нии об ошибке номеру строки можно леrко определить, что виновницей является инструкция return функции spam (). Ошибки можно обрабатывать с помощью инструкций try И except. Код, относительно KOToporo у вас есть подозрения, что он может привести к ошибке, помещается в блок try. В случае возникновения ошибки выполне- ние проrраммы передается в начало блока except. Вы можете поместить предыдущий код в блок инструкции try и орraни ;ювать обработку соответствующей ошибки в блоке инструкции except. def spam(divideBy): try: return 42 / divideBy except ZeroDivisionError: print('Error: Invalid argument.') print(spam(2) ) print (sраm(12)) print(spam(O)) print (spam(1)) Коrда в коде, номещенном в блок try, возникает ошибка, выполнение проrраммы немедленно нереходит к коду в блоке except. После ВЫПОЛllе ния это ['о кода дальнейшее выполнение проrраммы продолжается, как обычно. Вывод предыдущей проrpаммы должен быть таким. 21.0 3.5 Error: Invalid argument. None 42.0 Обратите внимание на то, что ошибки, которые MoryT возникать при вызовах функций в блоке try, также будут перехватываТl)СЯ. Рассмотрим следующую проrpамму, в которой вызовы функции spam () помещены в блок try. 
11. r лава 3 def spam(divideBy): retиrn 42 / divideBy try: print(spam(2)) print (spam(12)) print(spam(O)) print(spam(l)) except ZeroDivisionError: print('Error: Invalid argument.') Вывод этой проrраммы должен быть таким. 21.0 3.5 Error: Invalid argument. Инструкция print (spam(l)) не была выполнена IIO той причине, что после выполнения кода в блоке except воаврата в блок try не происходит. Вместо этоrо далее, как обычно, выполняются инструкции, следующие за блоком except. Короткаll nporpaMMa: уrадай чиспо Те небольшие примеры, которые приводились до сих пор, были вполне приrодны для знакомства с базовыми понятиями, но теперь настал подхо- дящий момент показать вам, как на основе Toro, что вы уже успели изучить, можно составить более интересную проrpамму. В этом разделе я продемон- стрирую вам простую проrрамму, реализующую иrру в yrадывание чисел. Запустив ее, вы получите примерно следующий вывод. Мною задумано число в интервале от 1 до 20. Попробуйте ero уrадать. Ваш вариант: 10 Предложенное число меньше задуманноrо. Ваш вариант: 15 Предложенное число меньше задуманноrо. Ваш вариант: 17 Предложенное число больше задуманноrо. Ваш вариант: 16 Верно! Количество попыток: 4 
Функции 111 Введите в файловом редакторе следующий код и сохраните ею в файле gиessTheNuтber. ру. # Ира в уадывание чисел. import rапdоm secretNumber = random.randint(l, 20) рriпt('Мною задумано число в интервале от 1 до 20. Попробуйте eo  yaдaTЬ.') # Предоставить ироку 6 попыток для уадывания числа. for gиessesTaken in range(l, 7): print ( 'Ваш вариант:') gиess = int(inpиt()) if gиess < secretNumber: рriпt('Предложенное число меньше задуманноо.') elif gиess > secretNumber: рriпt('Предложенное число больше задуманноо.') else: break # Соответствует правильному ответу! if gиess == secretNиmber: print('BepHo! Количество попыток: ' + str(gиesseSTaken)) else: print('HeT. Было задумано число' + str(secretNиmber)) Проанализируем этот код с caMol'o начала, строка за строкой. # Ира в уадывание чисел. import random secretNиmber = random.randint(l, 20) Первая строка  это строка комментария, содержащая описание Ha значения проrраммы. Далее проrрамме необходимо импортировать MO дуль random, чтобы иметь возможность использовать функцию random. randint () для rенерирования случайноrо числа, которое поль:юватель ДОk жен yraдaTb. Возвращаемое функцией значение, которое будет представ лять собой целое число в диапазоне от 1 до 20, сохраняется в переменной secretNumber. рriпt('Мною задумано число в интервале от 1 до 20. Попробуйте eo  уNдать.') # Предоставить ироку 6 попыток для уадывания числа. for gиessesTaken in range(l, 7): print ('Ваш вариант:') gиess = int(inpиt()) Проrрамма сообщает ю'року, что она задумала секретное число, и IIpe доставляет возможность yraдaTb ero не более чем за шесть попыток. Код, 
112 r лава 3 который предлаrает ввести число и осуществляет проверку этоrо числа, помещен в цикл for, выполняющий не более шести итераций. Первое, что происходит в цикле,  это ввод иrроком пробноrо числа. Поскольку Функ ция inpu t () возвращает строку, ее возвращаемое значение передается неlt средственно функции in t ( ) , которая преобразует строку в целочисленное значение. Это значение сохраняется впеременной guess. if guess < secretNumber: рriпt('Предложенное число меньше задуыанноо. ') elif guess > secretNumber: рriпt('Предложенное число больше задуманноо.') в этих нескольких строках кода пробное число сравнивается с ceKp ным и проверяется, больше ли первое из них, чем второе, или меньше. В обоих случаях на экран выводится соответствующая подсказка. else: break # Соответствует правильному ответу! Если оказывается, что пробное число одновременно не больше и не меньше ceKpeтHoro числа, то это означает, что оно и есть секретное число, и в таком случае инструкция break осуществляет выход из цикла for. if guess == secretNиmber: print('BepHo! Количество попыток: I + str(guessesTaken)) else: print('HeT. Было задумано число' + str(secretNиmber)) Располаrающаяся вслед за циклом for инструкция if/else проверяет, ЯВJIяется ли введенное пользователем число правильным, и выводит на экран соответствующее сообщение. В обоих случаях проrрамма отобра жает переменную, содержащую целочисленное значение (guessesTaken и secretNumber). Поскольку эти значения необходимо конкатенировать со строками, они передаются функции str ( ) , которая возвращает полученные числа в виде строк. Теперь эти строки можно конкатенировать с помощью операторов + и передать результирующую (."Троку функции print (). Реэюме Функции  это основной способ разбиения кода на лоrические rpуппы. Поскольку переменные в функциях существуют в собственных локальных областях видимости, код одной функции не может непосредственно воз- действовать на значения переменных друroй функции. Эти оrраничения, налаrаемые на возможность изменения значений переменных, MOryr быть полезными при отладке кода. 
ФУНКЦИИ 113 Функции ЯВJIяются великолепным средством орrанизации кода. Можете предстаВJIЯТЬ себе любую функцию как "черный ящик". Для вас имеет зна- чение лишь то, какие входные данные необходимо предоставить функции в качестве apryмcHToB и какое значение она возвращает, а также то, что код функции не может воздействовать на переменные в коде ДРУI'ИХ функций. В примерах, приведенных в предыдущих l'Лавах, единственная ОUlибка моrла приводить к краху проrраммы. В этой rлаве вы изучили инструкции try и except, обеспечивающие дальнейшее выполнение проrраммы, даже если в ней возникла ошибка. Это позволяет вам обеспечить устойчивую работу (:во- их проrрамм при возникновении в них распространенных ошибок. Контропьные вопросы 1. Что дает использование функций в проrраммах? 2. Коrда именно выполняется код функции: коrда она определяется или коrда вызывается? 3. С помощью какой инструкции создаются функции? 4. Чем отличается определение функции от ее вызова? 5. Сколько rлобальных областей видимости может иметь проrрамма на языке Pythоп? Сколько локальных? б. Что происходит с переменными, находящимися в локальной области видимости, при возврате из функции? 7. Что такое возвращаемое значение? Может ли возвращаемое значение быть частью выражения? 8. Каково возвращаемое значение функции, если в ней отсутствует ин струкция return? 9. Как заставить переменную в функции с(ыатьсяя на rлобалl)НУЮ псре-- менную? 10. Что такое тип данных None? 11. Что делает инструкция import аrеаllуоurреtsпamеdеriс? 12. Если бы у вас была функция Ьасоп (), содержащаяся в модуле spam, то как бы вы ее вызвали после импортирования этоrо модуля? 13. Как можно предотвратить аварийное завершение проrраммы при возникновении в ней ошибки? 14. Какой код помещается в блок try? Какой код помещается в блок except? Учебные проекты Чтобы закрепиТl. получеппые знания на нрактике, паllишите проrрам- мы для нредложеНIIЫХ lIиже задач. 
114 r лава 3 nооеIlО'''Feл..оm КОЛЛImI" Напишите функцию collatz (), принимающую один параметр: number. Если number  четное число, функция collatz () должна вывести на экран и возвратить значение number / / 2. Если же пшnbеr нечетное число, то функ- ция должна вывести на экран и возвратить значение 3 * number + 1. После этоrо напишите проrpaмму, которая предлarает поль:юnaтелю B сти целое число, а затем последовательно вызывает функцию collatz () для этоrо числа и значений, возвращаемых очередным вызовом этой функции, пока на каком-то fl'апе не будет 80звращено значение 1. (Любопытно отме- тить, что, независимо от выбора наЧaJlьноrо числа, вы все равно рано или поздно получите 1! Даже математики не MOryr объяснить, почему так пp<r исходит. Числовая последовательность, которую вы исследуете с помощью этой проrраммы, называется nоследовате.л:ь1tост'Ью Ко.л.лаm'Ца 1 и иноrда харак- теризуется как "простейшая из неразрешенных пр06лем математики".) Не забывайте о том, что возвращаемое функцией inpиt () значение нуж- дается в преобразовании в целое число с помощью функции in t ( ) , иначе это будет строковое значение. Подсказка. Условие четности значения  number % 2 == О, условие Не- четности  number % 2 == 1. Примерный вывод этой проrpаммы показан ниже. Enter nurnber: 3 10 5 16 8 4 2 1 nро.ерк" KOppeIC'lllO". "011" Добавьте в предыдущий проект инструкции try и except с целью обнару- жения ввода поль:ювателем нецелочисленных значений. Обычно при пере- даче функции int () строки, представляющей нецелочисленное значение, как, например, при вызове int ('рирру'), reнерируется ошибка ValиeError. Поместите в блок except код, который выводит для пользователя сообще- ние о том, что требуется ввод целоrо числа. 1 См. https: / /ru.wikiреdiа.оrg/wiki/rипотеэаКоллатца.  Пршwеч. ред. 
списки Еще одна тема, с которой вам обязательно следует познако- миться, прежде чем приступать к написанию собственных серьезных проrрамм,  это списки и родственный им тип данных: кортежи. Списки и кортежи MOryr содержать более чем одно значение, что упрощает написание проrpамм, об.. раба:rывающих большие объемы данных. А поскольку спи- ски сами MOryr содержать друrие списки, вы сможете использовать их для <:оздания иерархических структур данных. В этой l'JIaBe приведены основные сведения о списках. Вы также узнае- те о методах, которые представляют собой функции, СВЯ:Jaнные с данными апределенноrо типа. Затем мы вкратце рассмотрим такие типы данных, как кортежи и строки, и проанализируем, как они связаны ('О ('писками, аналоraми которых они являются. В следующей rлаве речь пойдет о ДРУl'ИХ типах данных  словарях. Что такое список Список  это значение, которое представляет собой коллекцию значе- ний, образующих упорядоченную последовательность. Термин r.nис'/{;овое значeuueотносится к списку как единому целому (значение KOToporo может сохраняться в переменной или передаваться функции нодобно значению любоrо дрyrorо типа), а не к отдельным значениям, которые в нем coдep жатся. Например, список может иметь следующее значение: ['cat', 'bat', 'rat', 'elephant') Подобно тому как строковы е значения заключаются в кавычки, пока- зывающие, rде начинается и заканчивается строка, список заключается в 
116 rЛQВQ 4 квадратные скобки, []. Значения, образующие список, называются элемен- тами списка. Элементы списка разделяются запятыми. Введите в интерактивной оболочке следующие команды. »> [1, 2, 3] [1, 2, 3] »> ['cat', 'bat', 'rat', 'elephant'] ['cat', 'bat', 'rat', 'elephant'] »> ['hello', 3.1415, True, Нonе, 42] ['hello', 3.1415, True, None, 42] 8»> зраа = ['cat', 'bat', 'rat', 'elephant'] >>> 8palll. ['cat', 'bat', 'rat', 'elephant'] Здесь lIеременной эрат . присвоено лишь одно значение  (писковое. Но это значение само содержит дрyrие значения. Значению [] соответству- ет пустой список, в котором отcyrствуют элементы, аНaJIоrично тому, как значению ' , соответствует пустая строка. IIotryп IC оrllеЛ6Н... ,ле.енr". tп.tlC" t пО.ОЩ61О .Hlle"to. Предположим, у вас есТь список [' cat', 'bat', 'rat', 'elephant'], (:0- храненный в переменной spam. pyt 11011 интерпретирует выражение эрат [О] как 'са t ' , выражение spam [ 1]  как 'Ьа t' И т.д. Целое число, которое ука- зывается в квадратных скобках после имени списка, называется индексом. Первому из значений, входящих в список, соответствует индекс О, второ- му  индекс 1, третьему  индекс 2 и т.д. На рис. 4.1 представлен список, значение Koтoporo присвоено переменной spaт, И показано, какие iлемен- ты соответствуют различным индексным выражениям. spam  ["cat", "bat", / I spam[O] spam[l] "rat", "elephant"] \ "- spam[2) sраm[З] Рис. 4. J. Список, хранящиi1ся в переменнон зрат, и индексные выражения, соответствующие ero отдельным элементам Введите в интерактивной оболочке следующие выражения. Начните с присваивания списка переменной spaт. »> SpaIII. = ['cat', 'bat', 'rat', 'elephant'] > » Spalll. [ О ] 'cat' > » epalll. [ 1 ] 'bat' > » Spalll. [ 2 ] 
Списки 117 'rat' »> spu[З] 'elephant' »> ['cat', 'bat', 'rat' , 'elephant'] [З] 'elephant' . >>> 'Hello ' + spam[O] . 'Неllо cat' >>> 'ТЬе ' + spaш[1] + ' ate the ' + spam[O] + '.' 'The bat ate the cat.' Обратите внимание на то, что выражение 'НеНо ' + spam [О] . вычис ляется как 'Не Но , + 'cat', поскольку значением spam [О] является строка 'cat '. Таким образом, конечным результатом вычисления данноrо выраже- ния ЯWlяется строка' НеНо cat' .. Если вы попытаетесь исполь:ювать индекс, значение KOToporo превыша- ет количество элементов в списке, то PythOH выведет сообщение об ошибке IndexError. »> Зpalll. - ['cat', 'bat', 'rat', 'elephant'] >>> ЗpaИI[10000] Traceback (most recent call last): File "<pyshell#9>", Нпе 1, in <module> spam[lOOOO] IndexError: list index out of range Индексы MOryr принимать только целочи<:ленные значения (не вещес твенные). В следующем примере возникает ошибка ':'ypeError. »> ЗpaиI - ['cat', 'bat', 'rat', 'elephant'] » > 8раш. [ 1 ] 'bat' »> 8palll. [1. О] Traceback (most recent call last): File "<pyshell#13>", liпе 1, in <module> spam [ 1 . О] TypeError: list indices must Ье integers, not float »> spalll.[int(1.0)] 'bat' Элементы списков сами MOryr быть списками. Доступ к значениям в Ta ких списках списков осуществляется с помощью нескольких индексов. >>> spam = [[ 'cat', 'bat'], [10, 20, ЗО, 40, 50]] > » sраш. [ О ] [ 'cat " 'bat'] »> spaш[О] [1] 'bat' 
118 r лова 4 >>> ераш[1] [4] 50 Первый индекс указывает, какой элемент-список следует использовать, а второй  к значению Kaкoro элемента в этом списке осуществляется доступ. Например, для выражения spam [О] [1 J будет выведено значение 'Ьа t ' , Т.е. второе значение в первом списке. Если вы используете только один индекс, то проrрамма выведет в качестве значения полный спи<:ок, cooTBeтcTBYкr щий данному индексу. ОrР8ЦtlrеЛ6н.,е 8HllelCt., Несмотря на то что отсчет индексов начинается с нуля, в качестве индек- сов разрешается использовать отрицательные значения. Отрицательному значению  1 соответствует последний элемент списка, значению 2  пред- IIоследний и т.д. Введите в интерактивной оболочке следующие команды. »> ерам - ['cat', 'bat', 'rat', 'elephant'] >>> epaм[1] 'еlерhапt' »> sрam[З] 'bat' »> 'The ' + зраш[1] + 1 is afraid of the 1 + spaш[З] + 'Te elephant is afraid of the bat.' , , пол,.,еН8е 9t1tr11 tп8tICtI t пOMOIЦ61O tpeJtI В то время как с помощью индексов можно извлекать из списка одиноч- ные элементы, срезы позволяют получать сразу несколько значений в ВИДе HOBoro списка. Срез списка обозначается, как и при индексном доступе, квадратными скобками, однако в скобках указываются два индекса, разде- ленные двоеточием. Обратите внимание на различие между индек(:ами и <:резами: . spam [2]  список с индексом (одно целое число); . spam [ 1 : 4]  список со срезом (два целых числа). Первое целое число в срезе  это индекс, с Koтoporo начинается cpe:J. Второе целое число  это индекс, который обозначает конец среза, но сам в <:рез не включается. Значением среза является новый список. Введите в интерактивной оболочке следующие команды. »> spam = ['cat', 'bat', 'rat', 'elephant'] »> ерам[О:4] ['cat', 'bat', 'rat', 'elephant'] 
Списки 119 >>> sраш.[l: З] [ I Ьа': 1, I rat 1] >>> ераш[О:11 [ I cat 1, I bat 1, 'rat'] Допускается сокращенная запись среза с пропуском одноrо или двух ин- дексов по обе стороны двоеточия. Отсyrствующий первый индекс равноси- лен использованию значения О, Т.е. соответствует началу списка. Отсутству- ющий второй индекс означает расширение среза до конца списка. Введите в интерактивной оболочке следующие команды. »> spam = ['cat', 'bat', 'rat', 'elephant'J >>> spam[:2] [ 'cat " I bat' ] >>> spaJD[l:] ['bat', 'rat', 'elephant'] »> SPaJD[:] ['cat', 'bat', 'rat', 'elephant'] получен.е /lЛ.Н., t".tKII t "0.ОЩ61О ФУ.К".. leп ( ) Функция lеп () возвращает количество значений, содержащихся в пере- данном ей списке, а в случае передачи ей CTpoKoBoro значения  количес- тво символов в строке. Введите в интерактивной оболочке следующие ко- манды. »> sраш. - ['cat', 'dog', 'шооее'] >>> len (sраш.) 3 ".енен.е JНllчен.. . t".tKIIX t "0.ОЩ61О .H/leKtO' Обычно слева от оператора присваивания располarается имя перемен- ной, например spam = 42. Однако для изменения значения в списке, харак- теризующеrося определенным индексом, можно использовать индексацию. Например, инструкция spam[l] = 'aardvark' означает следующее: "Назна- чить элементу с индексом 1 в списке spam строковое значение' aardvark'". Введите в интерактивной оболочке следующие команды. »> spam - ['cat', 'bat', 'rat', 'elephant'] »> spaJD[l] = 'aardvark' >>> spam [' cat', 'aardvark', 'rat', 'elephant'] »> spaJD[2] = ераш[1] >>> spam 
120 rЛQВQ 4 l' cat " I aardvark', 'aardvark', 'elephant'] »> ераш[1] - 12345 > » еpalll. ['cat', 'aardvark', 'aardvark', 12345] Конкатена..,.. . ".л.ка..,.. tn.tKO. с помощью оператора + можно объединить два списка в новый список аналоrИЧ1l0 тому, как с помощью этоrо же оператора можно объединить два строковых значения в одну строку. Кроме Toro, умножая список с помощью оператора * на целое число, можно повторить список заданное количество раз. Введите в интерактивной оболочке следующие команды. »> [1, 2, 3] + ['А', 'В', 'С'] [1, 2, 3, 'А', 'В', 'С'] »> ['Х', 'У', 'Z'] * Э ['Х', 'У', 'Z', 'Х', 'У', 'Z', 'Х', 'У', 'Z'] »> ераш - [1, 2, Э] »> еpalll. - ераш + ['А', 'В', 'С'] > > > Sp8111. [1, 2, 3, 'А', 'В', 'С'] Удален.е зна"ен.. .3 tn.tKa t пО.ОЩ61О ..прУК",.. del Инструкция del удаляет из списка значение с заданным индексом. Все значения, нахОДЯЩиеся после удаленноrо, сдвиraются к началу списка на одну позицию. Например, введите в интерактивной оболочке следующие команды. »> еpalll. - ['cat', 'bat', 'rat', 'e1ephant'] »> d_1 еpalll. [2] »> еpalll. ['cat', 'bat', 'elephant'l >>> de1 ераш[2] »> ераш [ I cat 1, 'bat'] Инструкция del также может удалять простые переменные. Если вы по- пытаетесь использовать удаленную переменную, то получите сообщение об ошибке NameError, поскольку такой переменной больше не существует. На практике вам почти никоrда не придется удалять простые переменные. Основное назначение инструкции del  удаление значений И3 списков. 
Списки 121 Работа со списками у тех, кто впервые приступает к написанию проrрамм, возникает со-- блазн создавать множество отдельных переменных для rруппы родствен- ных значений. Например, если бы я захотел сохранить имена своих котов и кошек, то Mor бы это сделать с помощью примерно Taкoro кода. catName1 catName2 catName3 catName4 catName5 catName6 'Zophie' 'pooka' 'Simon' 'Lady Macbeth' 'Fattail' 'Miss Cleo' (Клянусь, на самом деле у меня не так уж и Mlloro кошек в доме.) Однако этот способ далеко не самый удачный. К примеру, если количество кошек изменится, проrрамма не сможет сохранить имен больше, чем имеется переменных. К тому же в ПРOl'раммах этоrо тина часто наблюдается повто- рение одних и тех же или почти одинаковых фраrментов кода. Посмотри- те, как часто дублирует(:я код в следующей I1pOrpaMMe, которую вы должны ввести в файловом редакторе и сохранить в файле allMyCatsl.py. print('Enter the пате of cat 1: 1) catName1 = input() print('Enter the пате of cat 2:' ) catName2 = input() print('Enter the пате of cat 3:' ) саtNаmеЗ = input() рriпt('Епtеr the пате of cat 4: ') catName4 = input() рriпt('Епtеr the пате of cat 5:' ) catName5 = input() print('Enter the пате of cat 6: ') catName6 = iпрut() print('The cat names are: 1) print(catName1 + , , + catName2 + , , + саtNаmеЗ + , , + catName4 + , , + catName5 + , , + catName6 ) Вместо множества однотипных переменных лучше использовать одну переменную, содержащую список. Ниже приведен пример улучшенной версии проrраммы allМyCatsl.py. В этой новой версии используется всею один список, в котором может храниться любое количество имен, введен- ных пользователем. Откройте в файловом редакторе новое окно, наберите в нем приведенный ниже текст и сохраните ею в файле allМyCats2.py. 
122 r лава 4 catNames = [] while True: print('Enter the пате of cat ' + str(len(catNames) + 1) + , (Or enter nothing to stop.): ') пате = input ( ) i f паmе == ": break catNames = catNames + [пате] # list concatenation рriпt('Тhе cat патез are:') for пате in catNames: print(' , + пате) Запу(тив эту проrрамму, вы lIолучите следующий вывод. Enter the пате of cat 1 (Or enter nothing to stop.) : Zophie Enter the пате of cat 2 (Or епtеr поthiпg to stop.) : Pooka Enter the пате of cat 3 (Or епtеr поthiпg to stop.) : Simon Enter the пате of cat 4 (Or enter nothing to stop.) : Lady Macbeth Enter the пате of cat 5 (Or enter nothing to stop.) : Fattail Enter the пате of cat 6 (Or enter nothing to stop.) : Miss Сlео Enter the пате of cat 7 (Or enter nothing to stop.) : Вот все кошачьи имена. Zophie Pooka Simon Lady Macbeth Fattail Miss Сlео Список дает то преимущество, rro теперь ваши данные сосредоточены в одной структуре. а самапроrpамма стала намноro более rибкой по cpaBH нию с тем ее вариантом, в котором ИСПОЛЬЗОВaJIось множество однотипных Ilеременных. Iftп0ll6J081188e Ц8К.08 ror (О tп8tKII"8 Из ['лавы 2 вы узнали о том, как использовать циклы for для выполнения одноro и TOI'O же блока кода определенное количество раз. С технической точки зрения цикл for выполняет блок кода по одному разу ДЛЯ каждоro значения из списка или подобной ему структуры данных. Например, если выполнить код 
Слиски 123 for i in range(4): print(i) то еro вывод будет выrлядеть так: О 1 2 3 Это происходит потому, что возвращаемое вызовом функции range ( 4 ) зна чение Python трактует как список [О, 1, 2, 3]. Поэтому предыдущий вы- вод можно воспроизвести с помощью следующей nporpaмMbI. for i in [О, 1, 2, З]: print(i) Данный цикл MHoroKpaTHo выполняет блок кода, используя на каждой итерации переменную i, которая принимает последовательный ряд значе- ний из списка [О, 1, 2, 3]. Примечаuие ИспОЛМУЯ в этой к'Н,ии выражение "тип да'Н,"ых "аподoбue спИС1Са", я подразумf'? ваю даЮl'Ш!, дм которЫХ существует терМИ'Н, "последово:те.л:ь'Н,оcm'Ь". Од'Н,ако з'Н,a:m'Ь техиИ'ЧеС1Сое onредeлeuие эmozо терми'Н,а вам вовсе необя.зо:те.л'Ь'Н,О. Один из часто применяемых в PytllOIlприемов  использование выра- жения range (len (someList)) С циклом for для итерирования по индексам в списке. Например, введите в интерактивной оболочке следующие ко- манды. »> supplies - ['репе', 'staplers', 'fl...thrower8', 'binders') »> for i in range(len(supplies»: print('Index ' + str(i) + ' in supplie8 is: I + supplies[i) Index О in supp1ies is: pens Index 1 in supplies is: staplers Index 2 in supp1ies is: flamethrowers Index 3 in supp1ies is: binders Использовать выражение range (len (supplies)) в представленном выше цикле for очень удобно, поскольку код в цикле может иметь доступ к индексу (в виде переменной i) и к значению по этому индексу (предо- ставляемому выражением supplies [i] ). Что самое rлавное, выражение 
124 r лава 4 range (len (supplies) ) обеспечивает итерирование по всем индексам списка supplies, независимо от количества содержащихся в нем значений. Оператор.. in 8 not in Определить, находится ли каколибо значение в списке, можно с по-- мощью операторов in и not in. Как и дрyrие операторы, in и not in исполь- эуются в выражениях, соединяя два значения: То, поиск KOToporo выполня ется в списке, и список, в котором это значение может находиться. Резуль- татом вычисления этих выражений ЯВJIяется булево значение. Введите в интерактивной оболочке следующие команды. »> 'howdy' in ['hello', 'hi', 'howdy', 'heyas'] True »> еpalll. = ['hello', 'hi', 'howdy', 'heyas'] »> 'cat' in ерам False »> 'howdy' not in Spalll. False »> 'cat' not in еpalll. True В качестве при мера рассмотрим проrpамму, которая предлаrает пользо-- вателю ввести имя CBoero домашнеro питомца и проверяет, содержится ли оно в списке pets. Откройте в файловом редакторе новое окно, введите в нею следующий код и сохраните еro в файле туРш.ру. mypets = [' Zophie 1, 'Pooka 1, I Fattail '] print('Enter а pet пате: ') пате = iпрut () if пате not in mypets: print('I do not have а pet named I + пате) else: рrint(пате + I is ту pet.') Вывод этой проrраммы может выrлядеть примерно так. Enter а pet пате: Footfoot 1 do not have а pet named Footfoot 'РlOк t (руппо.... пp8tJa8.aH8e. Используя трюк с еруnповы.м присваивание.м, можно быстро присвоить значения ряду переменных в одной строке кода. Итак, вместо выполнения последовательности ИНСТРУКЦИЙ 
Списки 125 »> cat - ['fat', 'black', 'loud'] »> .ize = cat[O] »> color = cat[l] »> disposition = cat[2] можно оrраничиться следующим КОДОМ: »> cat = ['fat', 'black', 'loud'] »> size, color, disposition = cat Число переменных должно совпадать с длиной списка, иначе Python вы- ведет сообщение об ОJIlибке. »>cat= ['fat', 'black', 'loud'] »> size, color, disposition, паше = cat Traceback (most recent ca11 1ast): File "<pyshell#84>", line 1, in <modu1e> size, co1or, disроsitiоп, пате = cat ValueError: need more thап 3 va1ues to unpack Комбинированные операторы присваивания в ходе присваивания значения переменной справа от оператора присва- иван ия часто используется эта же переменная. Например, если перемсн- ной spam необходимо присвоить значение 42, а затем увеличить cro на 1, то ЭТО можно сделать с помощью следующеrо кода. »> Spalll. - 42 »> spaш - spam + 1 »>8palll. 43 Однако, используя комбинированный оператор присваивания +=, мож но HeMHoro сократить этот код: >>> sраш. - 42 > > > 8palll. += 1 >>> 8palll. 43 Комбинированные операторы присваивания существуют для онерато- ров +,, *, / и % (табл. 4.1). 
126 r лава 4 Тамица 4.1. Комбинированные опsрации присваиваНИI "РИС80ивание spam = spam + 1 spam = spam  1 spam= spam * 1 spam = spam / 1 spam= spam % 1 КомбинированноеnрисваИ80ние spam+= 1 spam = 1 spam *= 1 spam /= 1 spam %= 1 Кроме Toro, оператор += может при меняться для конкатенации, а опе- ратор *=  для репликации строк и списков. Введите в интерактивной обо- лочке следующие команды. »> зраа - 'Hello' »> зраа += ' world!' >>> зраа 'НеНо world!' >>> bac::on = [' Zophie' ] »> bac::on *= 3 >>> bac::on ['Zophie', 'Zophie', 'Zophie'] Метод... Meтoд это то же самое, что и функция, но он вызывается для значения. Например, если список хранится впеременной spam, то вы можете вызвать для нее метод index () списка (о чем далее будет рассказано более подроб- но): spam. index (' hello'). Метод указывается после значения и отделяется от Hero точкой. Каждый тип данных имеет собственный набор методов. Так, ДЛЯ списков предусмотрен ряд полезных методов, позволяющих выполнять поиск, до- бавление, удаление элементов и дрyrие манипуляции со значениями, обра- зующими список. по.,1t ,НII"ен.. . 'п.'ltе , п080Щ61О 8е'01l1l iпdex ( ) Списки имеют метод index () , который принимает значение и возвра- щает ero индекс, если оно содержится в списке. Если указанное значение OTcyrcTByeT в списке, то PythOI1 rенерирует ошибку ValueError. Введите в интерактивной оболочке следующие команды. »> зрам = ['hello', 'hi', 'howdy', 'heyas'] »> sраш.indех('hеllо') О 
Списки 127 >>> spaJD. index ( , Ьеуае ' ) 3 > > > spaID. index ( 'howdy howdy howdy') Traceback (most recent call last): Fi1e "<pyshe11#31>", liпе 1, in <module> spam.index('howdy howdy howdy') ValueError: 'howdy howdy howdy' is not in 1ist В случае наличия в списке дубликатов данноrо значения возвращается индекс первоrо из элементов, в котором встречается это значение. Введите в интерактивной оболочке следующие команды (обратите внимание на то, что метод index () возвращает 1, а не 3). »> ераш = ['Zophie', 'Pooka', 'Fattail', 'Pooka'] »> spaID.index('Pooka') 1 1I06"аеН8е ,на"еН8. . tп8tOlt t 11080"610 8еТОIIО' append () . iпsert () для добаВJIения новых значений в список используются методы append ( ) и insert (). Введите в интерактивной оболочке следующий код, чтобы вы- звать метод append () для списка, хранящеrося вперсменной spam. »> ерам = ['cat', 'dog', 'bat'} »> spaм.append('moose') »> ераш [ , cat " , dog " 'bat', 'moose'] Здесь вызов метода append () добавляет apryMeHT в КОНец списка. Метод insert () позволяет добавить в список элемент с определенным индексом. Первый apryMeHT метода insert ()  это индекс для HOBoro значения, авто- рой  вставляемое значение. Введите в интерактивной оболочке следую- щие команды. »> ерам - ['cat', 'dog', 'bat'] »> spaм.insert(l, 'chicken') »> SPaID [' cat', , chicken', 'dog'., 'bat 1] Обратите внимание на то, в каком виде записан код: spam. append ( 'moose ' ) и spam. insert (1, 'chicken'), а не spam = spam. append ( 'moose') и spam = spam. insert (1, 'chicken'). Ни метод append () , ни метод inse rt () не пре- доставляют новое значение зрат в качестве возвращаемоrо :Jначения. 
120 r лава 4 (В действительности оба метода возвращают значение None, однако вряд ли вы захотите присваивать el'o в качестве новоro значения переменной.) Вместо этоro список изменяется, как rоворят, иа.меcme. Более подробно из менение списка на месте обсуждается в разделе "Изменяемые и неизменя мые типы данных". Методы принадлежат к одному определенному типу данных. Методы append () и insert () являются методами списков и MOryr вызываться только для списков. Вызывать их для дрyrих типов значений, таких, например, как строки или целые числа, нельзя. Введите в интерактивной оболочке сл дующие команды и обратите внимание на появление сообщения об ошибке AttributeError. »> eggs - 'hello' »> eggs.append('world') Traceback (most recent call last): File "<pyshel1#19>", line 1, in <module> eggs.append( 'world') AttributeError: 'str' object has по attribute 'append' >>> bacon - 42 »> bacon.insert(l, 'world') Traceback (most rесепt call last): File "<pyshell#22>", 1ine 1, in <module> bacon.insert(1, 'wor1d') AttributeError: 'int' object has по attribute 'insert' У/lllлен.е ЗНII"ен.й .3 '..'''11 , ПОМОЩ61О ме'О/l1l reшоvе () Методу rernove () передается значение, подлежащее удалению из (:писка, для KOToporo он вызывается. Введите в интерактивной оболочке следую щие команды и обратите внимание на появление сообщения об ошибке. »> ерам - ['cat', 'bat', 'rat', 'elephant'] >>> sрам.reш.оve ('bat') >>> ераш. ['cat', 'rat', 'еlерhапt'] При попытке удалить значение, отсутствующее в списке, будет сrенери- рована ошибка ValиeError. Чтобы в этом убедиться, введите в интерактив- ной оболочке следующие команды. »> ераа - ['cat', 'bat', 'rat', 'elephant'] > > > еpalll. . reш.0V8 ( , ahicken ' ) Traceback (most recent call last): File "<pyshell#ll>", liпе 1, in <module> sраm.rеmоvе('сhiсkеп') ValueError: list.remove(x): х not iп list 
Списки 129 Если в списке имеется несколько одинаковых значений, будет удалено лишь то из них, которое встретится первым. Введите в интерактивной uбо- лочке следующие команды. »> spaш - ['cat', 'bat', 'rat', 'cat', 'hat', 'cat'] »> SPalll..ruove( 'cat') » > Spalll. ['bat', 'rat', 'cat', 'hat', 'cat'] Инструкция de 1 используется в тех случаях, коrда вам известен индекс значения, которое вы хотите удалить, а метод remove ()  коrда известно значение, подлежащее удалению из списка. (орт.ро.ltll ,НII,ен.. . '".'Ile , IIОМОЩ61О меТОДII sort ( ) Для сортировки списков, содержащих числа или строки, используется метод sort () . Введите в интерактивной оболочке следующий код. »> ерам - [2, 5, 3.14, 1, 7] »> Spalll.. sort () »> spaш [7, 1, 2, 3.14, 5] »> 8palll. = ['ants', 'cats', 'dogs', 'badgers', 'elephants'] » sраш..sоrt() >>> sраш. ['ants', 'badgers', 'cats', 'dogs', 'e1ephants'J Чтобы задать сортировку в обратном порядке, следует указать именован ный apryмeHT reverse со значением True при вызове метода sort () . Введите в интерактивной оболочке следующий код. »> spaш.sоrt(reversе-Тrue) >>> SPaIII ['elephants', 'dogs', 'cats', 'badgers', 'ants'J Относительно метода sort () слсдует сделать три замечания. Во-первых, метод sort () выполняет сортировку списка на месте; не нытайтесь ИСПОЛlr зовать ero возвращаемое значение с номощью кода наподобие spaт = spaт. sort (). Во-вторых, невозможно отсортировать список, который содержит oд новременно и числа, и строки, поскольку PytllOIl не знает, как сравнивать разные типы значений между собой. Введите в интерактивной оболочке следующий код и обратите внимание на появление сообщения об ошибке TypeError. 
130 r лава 4 »> арам = [1, З, 2, 4, 'Alice', 'ВоЬ'] >>> ерам. sort О Traceback (most recent са11 1ast): Fi1e n<pY5he11#70>n, 1ine 1, in <modu1e> spam.sort() TypeError: unorderable types: str() < int() В-третьих, метод sort () сортирует строки не в алфавитном порядке, а в соответствии с таблицей А.<;СII. Это означает, что буквы в верхнем реrи стре предшествуют буквам в нижнем реrистре. Поэтому, например, буква а будет располаrаться в процессе сортировки после буквы Z. Введите в инт рактивной оболочке следующий код. »> spam = ['Alice', 'ants', 'ВоЬ', 'badgers', 'Carol', 'cats'] >>> арам. sort О »> .рам [1 A1ice', 'ВоЬ', 'Caro1', 'ants', 'badgers', 'cats' J Если вам необходимо отсортировать строку в обычном алфавитном по рядке, то передайте методу sort () именованный apryMeHT key со значением str.lower. »>ерам= ['а', 'z', 'А', 'Z'] »> spam.sort(k_y=str.low_r) »> ераш ['а', 'А', 'z', 'Z'] Это приведет к тому, что метод 50rt () будет обрабатывать все элементы списка так, как если бы они были записаны только с использованием ниж неro реrистра, не изменяя при этом самих элементов. Пример nporpOMMbl: Mogic 8 8011 со списком Используя списки, можно написать rораздо более элеl'антную версию проrраммы Magic 8 ВаН. Вместо 1'01'0 чтобы использовать нссколько строк, содержащих почти идентичные инструкции е 1 i f, можно создать един ственный список, с которым будет работать код. Откройте в файловом ре-- дакторе новое окно, введите в нем следующий код и сохраните ero в файле тagic8BaU2.py. import random messages = ['It is сеrtаiп', 'It is decided1y 50', 'Уез definite1y', 
Списки 131 'Reply hazy try again', 'Ask again later', 'Concentrate and ask again', 'Му reply i5 по', 'Outlook not 50 good', 'Very doubtful'] print (messages [rапdоm.rапdiпt (О, lеп(mеssаgеs)  1)]) Запустив этот КОД, вы увидите, что он работает точно так Же, как и пред ыдущая версия nporpaMMbI тagic8Ball.py. Обратите внимание на выражение, которое используется в качестве ин декса: random. randint (О, len (messages)  1) . Оно позволяет получать слу чайные числа в нужном диапазоне, независимо от длины списка messages. Исключения из правип использования отступов в коде Python в большинстве случаев величина отступа строки кода сообщает интерпретатору Python, к какому блоку оне относится. Однако из этоrо правила есть исключения. Например, слисок может располаrаться в нескольких строках в файле исходноrа кода. В подобных случаях величина отступа не имеет значения; Python знает, что до тех пор, пока не встретится закрыlOЮщая квадратная скобка, список еще не закон ЧИЛСR. Например, у всс может быть код следующеrо вида. зрат = ['аррlез', 'oranges' , 'bananas', 'cats'] print(spam) Разумеется, большинство людей используют эту особенность поведения Python для Toro, чтобы придать аккуратный вид спискам, подобным списку сообщений в проrрамме Magic 8 Ball, и облеrчить чтение кода. Также допускается разбиение инструкции на несколько строк с ломощью сим-- вопа \, который ставится в конце строки и указывает на то, что допее спедует ее продолжение. Считайте, что СИМ80Л \ эквивалентен такому утверждению: "Эта ин струкция продолжается 8 следующей строке". Величина отступа строки, следующей за СИМ80ЛОМ \, не иrрает никакой роли. Вот пример KoppeICfHoro кода Python. print('Foиr score and seven ' + \ 'years ago...') Эти приемы приrодятся 10М дЛЯ реорrанизации длинных строк с целью повыше- ния удобочитаемости кода. 
132 r лава 4 В данном случае имеется в виду, что вы получаете случайное число в диа пазоне от О до значения len (messages)  1. Преимуществом TaKOl'O подхода ямяется то, что вы можете леrко добамять и удалять строки из списка, не изменяя друrис с.троки кода. Если впослсдствии вы захотите обновить код, то вам придется изменить меныIl{ количе(:тво (TpoK кода, а это означает, что уменьшится и вероятность внесения ошибок при вводе. ТИП". данных, подобные спискам: строки и кортежи Списки  не единственный тип данных, который предстамяет упорядо-- ченные последовательности значений. Например, строки фактически aHa лоrичны спискам, сели рассматривать строку как "список" одиночных TeK стовых символов. MHoroe из 1'01"0, что можно делать со списками, можно делать и со строками: индексирование, получение срезов, а также ИСlIОЛЬ зование в циклах for с функцией len () и с операторами in и not in. Чтобы убедиться в этом, введите в интерактивной оболочке следующие команды. »> паше = 'Zophie' >>> name [О) 'z' »> паше [2) 'i' »> nаше[0:4) 'Zoph' »> 'Zo' in паше True »> 'z' in паше False »> 'р' not in паше False »> for i in паше: print('* * * I + i + ' * * *') * * * Z * * * * * * о * * * * * * р * * * * * * h * * * * * * i * * * * * * е * * * ".ен.е...е . .евме..е...е "'".. II".Н"Х Вместе с тем между списками и строками существует одно важное разли- чие. Список  это uз.меияем:ый тип данных: значения, хранящиеся в списках, можно д06амять, удалять и изменять. Строки же относятся к нeuз.меияе.м,о- МУ типу данных: строку нельзя изменить. Попытка заменить одиночный 
Списки 133 символ в строке приведет к возникновению ошибки TypeError, в чем вы сможете убедиться, введя в интерактивной оболочке следующий код. »> паше - 'Zophie а cat' »> name[7] - 'the' Traceback (most rесепt call last): File "<pyshell#50>", liпе 1, in <module> паmе[7] = 'the' TypeError: 'str' object does not support item аssigпmепt Правильный способ "изменения" строки заключается в использовании Оllераций среза и конкатенации для создания Н0вои С1'рОКИ путем копирова- ния частей исходной строки. Введите в интерактивной оболочке следую- щие команды. »> паше - 'Zophie а cat' »> nе.Маше - nаше[О:7] + 'the' + name[8:12] »> паше 'Zophie а cat' >>> newName 'Zophie the cat' Эдесь для 1'01"0, чтобы сослаться На символы, которые мы не намерены заменять, испольэованы срезы [о: 7] и [8: 12]. Заметьте, что исходная стро- ка 'Zophie а cat' не I10дверrла<ъ изменению ввиду неизменяемости строко- Boro типа данных. Несмотря на 1'0 что списки  изменяемый тип данных, в IIриведснном ниже коде вторая строка не изменяет список eggs. »> eggs = [1, 2, З] »> egче = [4, 5, б] »> egче [4, 5, 6] Списковое значение, которое хранилось в eggs, здесь не изменилоCl.. В действительности было просто создано совершенно новое значение ( [ 4, 5, 6]), которое заменило прежнее ( [ 1, 2, 3]). Эту ситуацию поясняет рис. 4.2. Если бы мы хотели действительно изменить первоначальный список, хранящийся впеременной eggs, чтобы он содержал значения [4,5,6],1'0 ДОЛЖНЫ были бы сделать примерно следующее. »> eggs - [1, 2, З] »> ciel eggs [2] >>> ciel eggs [1] 
134 r лава 4 »> del egg. [О] »> 8ggs.append(4) »> 8ggs.append(5) »> eqgs.арреnd(б) »> eqgs [4, 5, б] -+ -+ Рис. 4.2. При выполнении инструкции eggs  [4, 5, 6] содержимое eggs ЗОменяется новым списковым значением в первом примере списковое значение, которое в конечном счете co держится в переменной eggs, является тем же значением, которое эта псре-- менная имела ИЗllачально. Cyrb в том, что данный список лишь изменился, а не был перезаписан. На рис. 4.3 в иллюстративной форме представлены семь изменений, которые выполняются первыми семью строками кода в последнем примере. Рис. 4.3. Инструкцня del и метод appeпd () изменяют элементы списка на месте 
Списки 135 В случае изменяемых типов данных изменение значений осуществляет ся на месте (как это делают инструкция del и метод append () в предыдущем примере), поскольку значение переменной не заменяется новым списко- вым значением. Разrраничение изменяемых и неизменяемых типов данных может пока- заться не имеющим особоrо смысла, однако, как будет ноказано в разделе "Передача ссылок", различия между вызовами функций с изменяемыми и неизменяемыми арryментами весьма существенны. А сейчас нам предстоит рассмотреть кортежи  тип данных, который является неизменяемой фор-- мой списков. Корте.. Кортежи почти идентичны спискам и отличаются от них лишь в двух от- ношениях. Во-первых, кuртежи заключаются в круrлые скобки, ( и ), а не в квадратные, [ и ] . Введите в интерактивной оболочке следующие команды. »> 89g8 - ('hello', 42, 0.5) >>> egq8 [О) 'hello' >>> 8gg8[1:3] (42, 0.5) >>> len (8gg8) 3 Однако rлавным отличием кортежей от списков является то, что корте- жи, подобно строкам, относятся к неизменяемому типу данных. Их значе- ния не MOryт изменяться, добавляться и удаляться. Введите в интерактив- ной оболочке следующие команды. »> 8gg8 = ('hello', 42, 0.5) »> egg8[1] = 99 Traceback (most recent са11 1ast): Fi1e "<pyshe11#5>", 1ine 1, in <module> eggs[l] = 99 TypeError: 'tuple' object does not support item assignment Если кортеж (остоит Bcero лишь из одноrо значения, после Hero надо ставить запятую внутри скобок. В противном случае PytllOn будет полaraть, что вы ввели обычное значение и просто заключили ero в скобки. Запятая укажет PythoI1, что данное значение является кортежем. (В отличие от не- которых друrих языков проrраммирования, в Рythоп использование завер- шающей запятой в кортежах и списках вполне допустимо.) Введите в инте- рактивной оболочке следующие команды, чтобы увидеть, к чему приводит отсутствие такой запятой. 
136 r лава 4 >>> type( ('hello' ,» <с1азз 'tup1e'> »> type«'hello'» <c1ass 'str'> Используя кортежи, вы тем самым сообщаете тому, кто читает ко/\ вашей проrраммы, что не намереваетесь изменять значения, входящие в данную последовательность. Если вам требуется упорядоченная последователь ность значений, которые не будут изменяться, то используйте кортеж. Еще одним преимуществом кортежей по сравнению со списками является то, что, блаrодаря неизменяемости их содержимоro, Python может реализо- вать некоторые схемы оптимизации, ускоряющие работу кода. пpeo6ptlJo.tlH.e ,.по. t пОМОЩ61О функ..,.. list () . tuple ( ) Подобно тому как вызов функции s t r ( 42) возвращает значение '42', являющееся строковым представлением целоrо числа 42, функции 1 i s t ( ) И tup1e () возвращают версии переданных им значений соответственно й виде списка и кортежа. Введите в интерактивной оболочке следующие ко- маlЩЫ и обратите внимание на различие типов передаваемых и возвращае- мых значений функций. >>> tuple(['cat', 'dog', 5]) ( 'cat " 'dog', 5) »> list«'cat', 'dog', 5» [ , са t " , dog', 5 ] »> list('hello') ['h', 'е', '1', '1', 'о'] Преобра:ювание кортежа в список удобно использовать в тех случаях, коrда необходимо получить изменяемую версию значений кортежа. Ссыпки Как вы уже знаете, переменные хранят строковые и целочисленные зна- чения. Введите в интерактивной оболочке следующие команды. > » Spalll. = 42 > > > сЬееее = Spalll. >>> Spalll. = 100 » > .palll. 100 > > > сЬееее 42 
Списки 137 Здесь lIеременной spaт сначала присваивается значение 42, а затем это :шачение копируется и нрисваивается переменной cheese. Коrда впослед (:твии переменной spaт присваивается значение 100, это никак не отража- ется на значении, хранящемся в переменной cheese. Такое поведение объ- ясняется тем, что spam и cheese  это различные переменные, хранящие различные значения. Однако списки работают не так. Присваивая переменной список, на са- мом деле вы присваиваете ей ссыл"'у на этот список. Ссылка  это значение, которое указывает на некоторую порцию данных, а ссылка на список  это значение, которое указывает на список. Приведенный ниже код позволит вам лучше понять суть этоro различия. Введите в интерактивной оболочке следующие команды. о »> ера - [О, 1, 2, З, 4, 5] . »> сЬе&е& - ера е »> che_s_[l] - '8&1101' »> ера [О, 'Не11о!', 2, 3, 4, 5] »> сЬееее [О, 'Не11о!', 2, 3, 4, 5] Возможно, полученные результаты вас несколько озадачивают. Несмо- тря на то что изменялся лишь список cheese, изменение затронуло также список spaт. Создавая СIIИСОК О, вы присваиваете ссылку на Hero l1еременной spam. В следующей строке О в переменную cheese копируется не сам список, а только ссылка на Hero, храllЯЩаяся в переменной spaт. Это означает, что теперь оба значения, хранящиеся в переменных spaт И cheese, ссылаются на один и тот же список. Собственно список сущtXаВУет лишь в единствен- ном экземпляре, поскольку он никуда не копировался. Поэтому, изменяя первый элемент списка с помощью переменной cheese О, вы изменяете тот же список, на который ссылается переменная spam. Вспомните о том, что переменные можно уподобить ящикам, в KO'ropbIX хранятся значения. Предстааленная на предыдущих рисунках в этой rлаве аналоrия с ящиками для списков не совсем точна, поскольку на самом деле в переменных хранятся не сами списки, а лишь ссылки на них. (Эти ссылки снабжаются числовыми идентификаторами (11), которые используются вну- тренними механизмами PytllOn, но для вас это несущеетвенно.) На рис. 4.4, также с использованием ящика в качестве метафоры для переменной, нока- зано, что происходит, Korдa переменной зрат присваивается список. Далее на рис. 4.5 прсдставлен процесс копирования ссылки из перемен- ной spaт в переменную cheese. При этом создается и сохраняется в пере- 
138 r лова 4 менной cheese новая ссылка, а не новый список. Заметьте, что обе ссылки ссылаются на один и тот же список. Если вы измените список, на который ссылается переменная cheese, то список, на который ссылается переменная spam, также изменится, посколь- ку обе эти переменные ссылаются на один и тот же список. Эта ситуация проиллюстрирована на рис. 4.6. r------. I I Refereпce ID: 57207444 ID: 57207444 [0,1,2, 3, t, 5] Рис. 4.4. Инструкция зрат = {О, 1, 2, 3, 4, 5 J означает сохранение ссылки но список, о не caMoro списка r.............................. I Refereпce ID: 57207444 r.............. I Refereпce Ш: 57207444 ID: 57207444 [0,),2, 3. lt, 5] Рис. 4.5. Инструкция зрат = сЬеезе означает копирование ссылки но список, а не самоro списка 
Списки 139 ,........----.....-........., I Refereпce ID: 57207444 r-.........." I ID: 57207444  [О, 'НеНо', 2, З, 4-, 5] Рис. 4.6. Инструкция cheese (1) "",' Hello! ' изменяет список, но который ссылаются обе переменные Переменные содержат ссылки на списки, а не сами списки. Однако в случае строк и целых чисел l1еременные содержат lIеносредственно сами строковые и целочисленные значения. Python всеrда использует ссылки в тех случаях, Korдa в переменнblX должны храниться изменяемые тины дaH ных, такие как списки или словари. В случае неизменяемых типов данных, таkИX как строки, целые числа или кортежи, 8 переменных Pytl10n хранятся сами значения. Несмотря на то что с технической точки зрения в переменных Рytlюп хранятся лишь ссылки на списки и словари, часто просто rоворят, что п ременная содержит список или словарь. переllО'О ttWЛОIl Ссылки особенно важны для понимания механизма передачи apryMeH- тов функциям. Korдa вызывается функция, значения apryMeHToB копируют- ся в переменные параметров. Для списков (и словарей. о которых lIойдет речь в следующей rлаве) это означает. ЧТО параметры используют ссылки. Чтобы увидеть, к каким следствиям это приводит. введите следующий код и сохраните ею в файле passiпg&ference.py. def eggs(someParameter): someParameter.append('He11o') spam = [1, 2, 3] eggs(spam) print(spam) 
148 r лава 4 Обратите внимание на то, что присваивание Hoвoro значения перемен ной spam осуществляется не за счет использования значения, возвращаемо-- rо вызовом eggs () . Вместо этоrо список изменяется непосредственно на месте. Запустив эту проrpамму, вы получите следующий вывод: [1, 2, 3, 'He110'] Несмотря на то что переменные spam и someParameter содержат отдель- ные ССЫJIКИ, они обе ссылаются на один и тот же список. Вот почему вызов метода append ( 'Hello') в функции оказывает воздействие на список даже после Toro, как был выполнен возврат из функции. Имейте в виду, что забывчивость относительно Toro, как Python обраба- Тывает переменные, в которых хранятся списки и словари, чревата возник- новением самых неожиданных ошибок. .унк",.. сору () . cleepcopy () МОДУЛ. сору Несмотря на то что передача ссылок нередко оказывается самым удоб- ным способом работы со списками и словарями, в тех случаях, коrда их изменяет функция, возможны ситуации, в которых изменение исходноrо списка или словаря является нежелательным. По этой причине Python пре доставляет модуль сору, в котором имеются функции сору () и deepcopy () . Первая из них, сору. сору ( ) , может использоваться для создания дубликата изменясмоrо значения, такоro как список или словарь, а не просто копии ссылки. Введите в интерактивной оболочке следующие команды. >>> import сору »> ераш = ['А' I 'В' I 'С' I 'D'] »> сЬееее · сору.сору(ераш) »> cheese[l] - 42 »> араш ['А' I 'В' I 'С' I 'О'] »> cheese ['А', 42, 'С', 'О'] ТепеРI> переменные spam и cheese ссылаются на разные списки, чем и объясняется тот факт, что в результате присваивания значения 42 элементу с индексом 7 в списке cheese изменяется только этот список. как показано на рис. 4.7, числовые идентификаторы ID CCbVIOK, предстаWlяемых обеими переменными, больше не совпадают, поскольку теперь переменные cCbVIa- ются на два Jlезависимых списка. 
Списки 141 ID: 57205555 ID: 57208868 r.... I I I Refereпce ID: 57205555 ['А', '8', 'С', 'D'] ['А', 't 2, 'С', 'D'] ';..' .  Рис. 4.7. Инструкция cheese  сору. сору (spam) создает второй список, который можно изменять неэависимо от nepBora Если список, копию KOToporo нужно создать, сам содержит списки, то в этом случае вместо функции сору. сору () следует использовать функцию сору. deepcopy ( ) . Эта функция выполняет копирование OCIIOBHoro списка вместе со всеми внyrренними. Р831ОМ8 Списки  весьма полезный тип данных, поскольку позволяют писать код, способный работать с варьируемым количеством значений в одной переменной. Далее вы познакомитесь с lIримерами проrрамм, позволяю щих сделать то, что без использования списков было бы трудно или вообще невозможно выполнить. Списки  изменяемый тип данных в том смысле, что их содержимое ма- жет изменяться. Кортежи и строки, несмотря на их сходство со списками, являются неизменяемыми типами данных и не MOryr изменяться. Значение переменной, содержащей кортеж или строку, может быть переопределено пyrем присвоения ей HOBOl'O значения в виде кортежа или строки, НО это не то же самое, что изменение существующеrо значения на месте, как это, например, делают методы append () и remove ( ) в отношении списков. В переменных хранятся не непосредственно сами СllИСКИ, а ссылки на них. Это чрезвычайно важное обстоятельство следует учитывать при KO пировании переменных или передаче apryMeHToB функциям при их вызо- ВС. Поскольку копируемое значение представляет собой ссылку на список, остереrайтесь Toro, чтобы внести непреднамеренные изменения в дру- rие переменные, имеющиеся в вашей проrрамме. Если вы хотите иметь 
142 r пава 4 возможность изменять копию списка таким образом, чтобы это не влияло на исходный список, то используйте функцию сору () или deepcopy ( ) . KOHTpoпbHble вопросы 1. Что означают эти скобки: [J? 2. Как бы вы присвоили значение' hello' в качестве TpeTьero элемента списка, хранящеrося впеременной spam? (Предполаraется, что в пере- менной spam содержится список [2, 4, 6, 8, 1 О J .) в следующих трех вопросах предполаrается, что переменная spam содержит список [' а', 'Ь', 'с', 'd' J . 3. Каково значение выражения spam[int (' 3' * 2) / 11] ? 4. Каково значение выражения spam [1 J ? 5. Каково значение выражения spam [ : 2 J? В следующих трех вопросах предполаrается, что переменная Ьасоп содержит список [3.14, 'cat', 11, 'cat', TrueJ. 6. Каково значение выражения Ьасоп. index ( 'cat I ) ? 7. Как будет выrлядеть список, хранящийся в переменной Ьасоп, после следующеrо вызова: Ьасоп. append (99) ? 8. Как будет выrлядеть спи{ок, хранящийся в переменной Ьасоп, после следующеrо вызова: Ьасоп. remove ( 'cat ' ) ? 9. Какие операторы используются для конкатенации и реrurnкации списков? 10. В чем состоит различие между предусмотренными для списков MeTO дами append () и insert () ? 11. Назовите два способа удаления значений из списков. 12. Назовите несколько общих признаков списков и строк. 13. Чем кортежи отличаются от списков? 14. Как бы вы записали кортеж, содержащий единственное значение в виде целоrо числа 42? 15. как преобразовать список в кортеж? как преобразовать кортеж в спи- сок? 16. Переменные, которые "содержат" список, на самом деле не содержат непосредственно сам список. Что же тоща они содержат? 17. В чем состоит различие между функциями сору. сору () и сору. deepcopy ( ) ? Учебные проекты Чтобы закрепить полученные знания на практике, напишите проrрам мы для предложенных ниже задач. 
Списки 143 30В.ТО. . Itо"еа.е РОJllел.rеll. Предположим, у вас имеется список наподобие следующеI"О: spam = [' арр1еs' I I bananas', 'tofu', 'cats'] Напишите функцию, которая принимает список в качестве apryMeHTa и возвращает строку, в которой все элементы списка разделены запятой и пробелом, а перед последним элементом вставлено слово апd. Например. передав функции предыдущий список spam, вы должны получить строку 'apples, Ьапапаз, tofu, and cats'. Но ваша функция должна работать с лю- быми списками. '.,о,он.е '...0110.. Предположим, у вас есть список списков, D котором каждое значение внутренних снисков представляет собой строку, состоящую из ОДНOl"о сим вола, как в приведенном ниже примере. grid = [['.' , , , , , I , , , , .'] , , , , . , [1 , 'О' , 'О' , , I , , , . ' ] , . , , . , ['О' , 'О' , 'О' , 'О' , , , , . ' ] , , ['О' , 'О' , 'О' , 'О' , 'О' , '.' ], [' , 'О' , 'О' , 'О', 'О' , 'О'] , . , ['О' , 'О' , 'О' , 'О' , 'О' , '.'] , ['О' , 'О' , 'О' , 'О' , , , , .'] , , [' , 'О' , 'О' , , , , , , . ' ] , . , , . , [' . I , , , , , , , I , . ' ] ] , , , , . , Элемент grid[x] [у] можно интерпретировать как символ с КООРДИllата ми х и у в составе "рисунка", нарисованноI'О текстовыми символами. Нача JIO координат (О, О) находится в верхнем левом уrлу, координата х увсличи вается слева направо, а координата у  сверху вниз. Скопируйте предыдущее значение gr id и напишите код, который ис- пользует ero для вывода следующеl"О изображения. . .00.00.. .0000000. .0000000. . .00000. . .. .000... ....о.... Под сказка. Используйте цикл в цикле для вывода элементов grid [О] [О], grid [1] [О], grid[2] [О] и так далее вплоть до элемента grid [8] [О]. Этим вы 
144 r лава 4 заполните первую строку, после чеrо вам следует вывести символ новой строки. Затем nporpaмMa должна вывести элементы grid [О] [1] , grid [1] [1], grid[2] [1] и т.д. Последний элемент, который должна вывести IIpO rрамма  grid [8] [5]. Кроме Toro, не забудьте передать функции именованный apryMeHT end, если хотите отменить автоматический вывод символа новой строки при каждом вызове функции print ( ) . 
спОВАРИ И СТРУКТУРИРОВАНИЕ ДАННЫХ В этой rлаве речь пойдет о словарях  типе данных, кото- рый обеспечивает rибкие возможности до<:тупа к данным и их эффективную орrанизацию. Затем, объединив словари со списками из предыдущей rлавы, вы создадите структуру даннbIXДJIЯ иrры "крестики-нолики". Что такое сповар.. Как и список, словаръ  это коллекция мноrих значений. Однако в слова рях, в отличие от списков, индексами MOryт служить не только целые числа, но и ряд друrих типов данных. Индексы в словарях называются КJ1,юча.мu, а ключ вместе с ассоциированным с ним значением  парий "КJ1,ЮЧ-З1tа'Чe1tuе ". В коде словари обозначаются фиrypными скобками  { } . Введите в инте- рактивной оболочке следующий код. »> myCat = I'size': 'fat', 'color': 'gray',  disроsitiоп': 'loud'} Здесь переменной туСа t присваивается словарь. Ключами в нем служат строки' size', 'color' и 'disposition 1. Значениями ключей являются сооТ"- ветственно строки' fat', 'gray' И 'loud 1. Доступ К значениям осуществля- ется посредством их ключей. »> myCat['size'] 'fat' »> 'Му cat has ' + myCat['color'] +, fur.' 'Му cat has gray fur.' 
146 r пава 5 Индексами в словарях MOryr служить также целые числа, как и в списках, однако их отсчет не обязательно должен вестись от О, и они MOryr быть любыми числами. >>> SPaJD = {12345: 'Luggage COJDbination', 42: 'Тhe Answer'} 'Рll..е..е ио'''ре. . tп.tKO. В отличие от списков, в словарях элементы не упорядочены. Первым элементом в списке spaт был бы spaт [О] . Однако к словарям понятие "пер- вый элемент" неприменимо. В то время как порядок элементов ш'рает су- ще(:твенную роль при проверке совпадения списков, для словарей не имеет ни малейшеro значения, в каком порядке в них были включены пары "имя значение". Введите в интерактивной оболочке следующие команды. »> spam '"' ['cats', 'dogs', 'аооее'] »> Ьасоn '"' ('dogs', 'moоее', 'cats'] »> sраш .. Ьасоn False »> 8g9s '"' {'паше': 'Zophie', 'SpeOi8S': 'cat', 'age': '8'} »> hаш '"' {'speCies': 'cat', 'ag8': '8', 'паше': 'Zophie'} »> eggs -= ham True Поскольку словари не упорядочены, они не допускают создание срезов, как это возможно в случае списков. Попытки получения доступа к ключу, отсутствующему в словаре, при водят к возникновению ошибки KeyError, что аналоrично возникновению ошибки IndexError error при попытке выхода за пределы допустимоrо диа пазона индексов в случае списков. Введите в интерактивной оболочке сле дующий код и обратите внимание на сообщение об ошибке, которое выво- дится, поскольку В словаре отсуктвует ключ' color' . »> ераш '"' {'паше': 'Zophie', 'age': 7} »> spam['oolor'] Traceback (most recent call last): File "<pyshell#1>", line 1, in <module> spam [' color' ] KeyError: 'color' Несмотря на то что словари не упорядочены, ВОЗМОЖНОСiЬ ИЗWlекать про-- иавольное значение по ero ключу позволяет использовать rибкие способы ор- rанизации данных. Предположим, вы хотите, чтобы ваша nporpaмMa coxpa няла данные о днях рождения ваших друзей. Для этой цели вполне подойдет словарь, в котором ключами являются имена друзей, а значениями  даты их 
Словари и структурирование данных 147 дней рождения. Откройте в файловом редакторе новое окно, введите в нем следующий код и сохраните ero в файле birthdays.frY. . birthdays = {'АНсе': 'Apr 1', 'ВоЬ': 'Оес 12', 'Carol': 'Mar 4' I while True: print('Enter а пате: (blank to quit) ') пате = input ( ) i f пате == ". break . if пате in birthdays: . print(birthdays[naтe] + ' is the birthday of ' + пате) else: print('I do not have birthday information for ' + пате) print('What is their birthday?') bday = input ( ) . birthdays[name] = bday print('Birthday database updated.') Здесь создается исходный словарь, который сохраняет(:я в переменной birthdays О. Проверить, содержится ли введенное имя в качестве ключа в словаре, можно с помощью ключевоrо (лова in О, точно так же, как и в случае списков. Если имя содержится в словаре, то доступ к ассоциирован- lЮму с ним значению осуществляется посредством квадратных скобок о: если же данное имя в словаре OTcyrcTByeT, вы сможете добавить el'O, ис- пользуя тот же синтаксис квадратных скобок в сочетании с оператором присваивания .. Выполнив эту проrрамму, вы получите примерно следующий вывод. Enter а пате: (blапk to quit) A1ice Apr 1 is the birthday of Alice Enter а пате: (blank to quit) Eve I do not have birthday information for Eve What is their birthday? Dec 5 Birthday database updated. Enter а пате: (blank to quit) Eve Dec 5 is the birthday of Eve Enter а пате: (blank to quit) Разумеется, по завершении работы проrраммы все ранее введенные вами данные теряются. О том, как сохранить данныс в файле на жеСТIЮМ диске, вы узнаете в rлаве 8. 
148 r лава 5 МетO/l" keys ( ), val иев () . i tems ( ) Существуют три метода для работы со словарями  keys (), values () и i tems ( ) , возвращающие соответственно ключи, значения и пары "ключ значение" в виде коллекций, подобных спискам. Возвращаемые этими Me тодами значения не являются ИСТИННЫми списками: их нельзя изменить, и они не имеют метода append (), однако эти типы данных (dictkeys, dict values и dictitems соответственно) можно использовать в циклах for. Что- бы увидеть, как работают эти Методы, введите в интерактивной оболочке следующие команды. »> ерам - {'oolor': 'red', 'age': 42} »> for v in spaм.values(): print (v) red 42 Здесь цикл for используется ДJIя итерирования по всем значениям, co держащимся в словаре spam. Однако циклы for можно также использовать ДJIя итерирования по ключам или одновременно 110 ключам и значениям. »> for k in spam.keys(): print (k) color age »> for i in spaa.items(): print(i) ('color', 'red') ('age', 42) Используя методы keys () , val ues () и i tems ( ) , можно орrанизовать с по- мощью цикла for итерации по ключам, значениям и парам "ключзначение" соответственно. Обратите внимание на то, что элементами значения dict  i tems, возвращаемоrо методом i tems ( ) , являются кортежи, образуемые ключами и ассоциированными с ними значениями. Если вы хотите получить реЗУЛlirат в виде истинноrо списка, то пере- дайте возвращаемое любым из этих трех методов значение функции list () . Введите в интерактивной оболочке следующие команды. »> ераа = {'color': 'red', 'ag8': 42} >>> ераа. keys () dictkeys(['color', 'age']) 
Споварн н структурирование данных 149 »> list(spaм.keys(» ['color', 'age'] В строке list (зрат. keys () ) значение dict  keys, возвращенное функцией keys ( ) , передается функции list () , которая возвращает список [' color' , 'age' ]. Кроме тоro, можно воспользоваться rpупповым присваиванием в цикле for ДIIя присваивания ключей и ассоциированных с ними значений отдель- ным переменным. Введите в интерактивной оболочке следующие команды. »> зрам - {'color': 'red', 'age': 42} »> for k, v in spaи.items(): print('R8y: ' + k + ' Value: ' + str(v» Кеу: аче Value: 42 Кеу: color Value: red пр08ерlt" t1ще"808"... кл.,,,,, .л. ,н""ен.. 8 tло'''ре Как вам уже известно из предыдущей rлавы, операторы in и not in по- зволяют проверить, существует ли в списке данное значение. Эти же опе- раторы можно использовать и ДIIя Toro, чтобы проверить, содержится ли в CJIOBape определснный ключ или значение. Введите в интерактивной обо- лочке следующис команды. »> зрам - {'nаше': 'Zophi.', 'age': 7} »> 'naше' in spam.keys() True »> 'Zophie' in spam.values() True »> 'color' in spam.keys() False »> 'oolor' not in sраш.kеуs() True »> 'oolor' in spaи False Обратите внимание на то, что использованная в этом примере инструк- ция 'color' in spam по сути ЯWlяется сокращенной записью инструкции 'color' in spam. ke ys () . Это общее правило: если вам нужно проверить, ЯWlяется ли (или IIС ЯWlяется) данное значение ключом в словаре, то ДЛЯ этоrо достаточно указать после ключевоrо слова in (или not in) одно толь- КО имя словаря. 
15. r лава 5 МеТОII get ( ) Проверять каждый раз при доступе к ключу, есть ли 011 в словаре, ДО-- вольно yrомительно. К счастью, для словарей предусмотрен метод get ( ) , принимающий два apryмeHTa: ключ извлекаемоrо значения и :шачение по умолчанию, возвращаемое в случае отсутствия данноrо ключа в словаре. Введите в интерактивной оболочке следующие команды. »> picnicItems = {'apples': 5, 'сор.': 2} »> '! am bringinq , + str(picnicItems.get('cups', О» + ' cups.' '1 ат bringing 2 сирв.' >>> '! am bringing , + str (picnicItems . get ( 'eggs', О» + ' eggs.' '1 ат bringing О eggs.' Поскольку ключ' eggs' отсрствует в словаре picnicItems, метод get () возвращает заданное по умолчанию значение о. Если не использовать M тод get ( ) , то в коде возникнет ошибка. »> picnicItems - {'apples': S, 'cups': 2} »> '! am bringing , + str(picnicItems['eggs') + ' eggs.' Traceback (most rесепt call last): E'ile "<руshеll#З4>", Нпе 1, in <module> '1 ат Ьriпgiпg , + str(picnic1tems['eggs']) + ' eggs.' KeyError: 'eggs' МеТОII setdefaul t ( ) Часто приходится устанавливать значение для определенноrо ключа лишь в том случае, если значение ему еще не присваивалось. В коде это BЫ I'ЛЯДИТ примерно так. spam = {'пате': 'Pooka', 'аче': 5} if 'color' not in spam: spam['color'] = 'black' с помощью метода setdefaиl t () это можно сделать в одной строке кода. Данный метод принимает два apryмeHTa. Первый из них  это проверяе мый ключ, а второй  значение, устанавливаемое для этоrо ключа в случае ero отсутствия. Если же ключ существует, то метод setdefaиl t () возвращает ero значение. Введите в интерактивной оболочке следующий код. »> ераа = {'пате': 'Pooka', 'age': 5} »> spam.setdefault('color', 'blaCk') 'black' >>> араш 
Словари и структурирование данных 151 {'color': 'black', 'age': 5, 'пате': 'Pooka'} »> spaш.sеtdefаult('соlоr', 'white') 'black' >>> SPaID {'co1or': 'black', 'age': 5, 'пате': 'Pooka'} Первый вызов метода setdefault (} изменяет словарь, который те- перь выrлядит так: { 'co1or': 'black', 'age': 5, 'пате': 'Pooka'}. Метод setdefau1 t (} возвращает значение' black', которое бьvlO устаномено даll ным вызовом для ключа' co1or' . Значение этоrо ключа не изменяется при последующем вызове spam.setdefau1t ('co1or', 'white'}, поскольку в слова ре уже имеется ключ 'co1or'. Метод setdefau1 t () очень удобно использовать в ситуациях, коrда Tpe буется rарантированное наличие ключа. Ниже приведена короткая про- rрамма, которая подсчитывает, сколько раз встречается в строке каждая из входящих в нее букв. Откройте новое окно в файловом редакторе, введите в Hero следующий код и сохраните ero в файле characterCount.py. message = 'It was а bright cold day in Apri1, and the clocks  were striking thirteen.' count = {} for character in message: count.setdefault(character, О) соuпt [ct1aracter] = count [character] + 1 print(count) Проrрамма циклически просматривает B(e символы строки в перемен- ной message и IlOдсчитывает, как часто встречается каждый из них. Вызов метода setdefau1 t () rарантирует существование ключа в словаре (значение KOToporo по умолчанию равно О), и поэтому при выполнении инструкции count [character] = count [character] + 1 ошибка KeyError возникать не бу- дет. Выполнив эту проrрамму, вы получите примерно следующий вывод. {' ': 13, ',': 1, '.': 1, 'А': 1, 'I': 1, 'а': 4, 'с': 3, 'Ь': 1, 'е': 5, 'd': З, 'g': 2, 'i':б, 'h': 3, 'k': 2, '1': 3, 'о': 2, 'n': 4, 'р': 1, '5': 3, 'r': 5, 't': 6, 'w': 2, 'у': 1} Отсюда, например, видно, что, как и следовало ожидать, буква с в ниж- нем реrистре встречается 3 pa:Ja, пробел  13 раз, а буква А в верхнем реrи- стре  1 раз. Эта проrрамма будет работать со (:трокой любой длины, даже если в переменной message хранится строка, содержащая миллионы симво-- лов! 
152 r лава 5 /(p"t..". пе,,,т. Импортировав в свою проrрамму модуль pprint, вы получите доступ к функциям pprint () и pformat (), осуществляющим "красивую печать" зна- чений словаря. Такая возможность может быть полезной, если требуется более аккуратный вывод элементов словаря, чем это обеспечивает функция print (). Измените предыдущую проrрамму characterCount.py, как показано ниже, и сохраните ее в файле prettyCharacterCount.py. illlpOrt pprint message = 'It was а bright cold day in April, and the clocks were striking thirteen.' count = (} for character in message: count.setdefault(character, О) count[character] = count[character] + 1 ррrint.ррrint(cюunt) На этот раз ВЫВОД выrлядит HaMHoro аккуратнее, и к тому же он pacC0}F тирован по ключам. {' ': 13, 1, 1: 1, 1 1. 1, 'А': 1, 'I': 1, 'а': 4, 'Ь': 1, 'с': З, 'd': З, 'е': 5, 'g': 2, 'h': 3, 'i': 6, 'k': 2, '1': 3, 'n': 4, 'о': 2, 'р': 1, 'r': 5, 'а': 3, 't': 6, 'w': 2, 'у': 1} Функция ppr in t . ppr in t () особенно полезна в тех случаях, Korдa сам ело- варь содержит вложенные списки или словари. 
Словари и структурирование данных 153 Если вы хотите получить аккураТIIО оформленный текст в виде строко. Boro значения, а не выводить ero на экран, то воспользуйтесь функцией pprint .pformat (). Следующие две строки эквивалентны. ррriпt.ррriпt(sоmеDiсtiопаrуVаluе) print(pprint.pformat(someDictionaryValue) ) ИспопЬЗ0вание структур данных дпll модепироваНИII реапьных объектов Возможность иrрать в шахматы с партнером, находящимся на друrой стороне земноrо шара, существовала еще до Toro, как появился Интернет. Каждый из иrроков, сидя у себя дома за шахматной доской, сообщал пар- тнеру о ходах, которые он делал своими фиrypами, по почте. Эrо требовало применения способа записи шахматных партий, позволяющеrо однознач- но описывать положения фиryp на доске и их перемещения. В алrебраической шахматной нотации клетки шахматной доски обозна чаются с использованием буквенно-цифровой записи (рис. 5.1). .. \. . t1t . ' ' . ""'"'.'" ... ;{181 8ft ',. ..... "л...,!,,4Р :::.",мi:'t'i i$:;"''fl> ! ' ! '!il! , eJ а 9 h Рис. 5.1. Система координат но шахматнон доске, в /(оторон используется буквенно-цифровоя нотация Шахматные ФИl'УРЫ обозначаются буквами: К (king)  король, Q (queel1)  ферзь (королева), R (rook)  ладья, В (bishop)  слон и N (knigllt)  конь. Описание хода включает букву, соответствующую фиrypе, которая делает ход, и координаты поля, в которое перемещается данная фи- rypa в резулЮ'ате выполнения хода. Запись пары таких ходов информирует 
154 rЛQВQ 5 о том, что происходит при выполнении иrроками хода и oTBeтHoro хода (первый ход за белыми). Например, запись 2. NfЗ Nc6 означает, что на вто- ром ходу белые переместили коня на поле f3, а черные  cBoero коня на поле с6. Это далеко не полное описание системы записи шахматных партий, од. нако для нас важнее Bcero тот факт, что существует возможность однознач. но описывать шахматную партию, даже не находясь за шахматной доской. Вы можете просто читать ходы противника, которые он пересьтает вам по почте, и мысленно обновлять положения фиryp на шахматной доске. Компьютеры обладают хорошей памятью. Современные проrpаммы по- зволяют хранить в компьютере миллиарды строк наподобие' 2. Nf3 Nc6' . Это 1I0зволяет компьютеру иrрать в шахматы без использования шахмат. ной доски. Он моделирует данные для представления шахматной ДОСКИ, а вы можете писать код, который работает с этой моделью. Вот тут-то и приходят на помощь списки и словари. Их можно исполь- зовать для моделирования объектов реальноrо мира, таких как шахматы. В качестве первоrо при мера мы используем более простую, чем шахматы, иrру "крестики-нолики". поле /PI. 8'Р" . Ultреn81t8.НОЛ81t8U Поле для иrры в "крестики-нолики" похоже на увеличенный символ "ре- шетки" (#) с девятью клетками, каждая из которых может быть пустой или содержать "крестик" (х) или 'нолик" (о). Чтобы представлять клетки иrpо- Boro поля с помощью словаря, можно назначить каждой И3 них ключ В виде строки (рис. 5.2). 'top-L' 'top-M' 'topR' 'mid-L' 'midM' 'mid-R' 'Iow-L' 'lowM' 'Iow-R' Рис. 5.2. Клеткн иrры в Uкрестики-нолики" С указанием соответствующих ключей 
Словари и структурирование данных 155 Для представления содержимоrо клеток можно использовать строки: 'Х', 'О' и ' , (символ пробела). Таким образом, Bcel'O нам понадобится девять таких строковых значений. Чтобы связать эти значения с клетка- ми иrровоrо поля, используем словарь. Состояние BepxHero правоrо уrла можно представить с помощью строковоro значения, ассоциированноrо с ключом 'topR', состояние нижнеrо леВОl'О Уl'Ла  с помощью CTpoKoBoro значения, ассоциироваННОl'О с ключом 'lowL', состояние центральной клетки  с помощью CTpoKoBoro значения, ассоциированноrо с ключом 'midM' , и Т.д. Этот словарь и есть структура данных, которая представляет состояние поля для иrры в "крестики-нолики". Сохраните ero в переменной theBoard. Откройте новое окно файловоrо редактора, введите в Hero следующий ис- ходный код и сохраните ero в файле ticTacToe.py. theBoard  {'topL': I I 'topM':" ItopR': 'midL': " 'midM': l' 'midR': 'lowL': I " 'lowM': I " 'lowR': , , I I , '} Структуре данных, сохраненной в пере мен ной theBoard, соответствует состояние поля, представленное на рис. 5.3. Рис. 5.3. Пустое поле ДЛЯ иrры в "крестикн-нолнки" Поскольку в словаре theBoard каждому ключу соответствует строка в виде одиночноrо символа пробела, данное состояние соответствует пусто- му полю. Если иrрок Х своим первым ходом выберет центральную клетку, то :TO состояние поля будет преДСТawIено следующим словарем. 
156 r лава 5 theBoard {'topL': ' , 'midL': ' , 'lowL': ' , 'topM' : 'midM' : 'lowM' : , 'Х' , , , , I , topR ': ' , 'midR': ' , , lowR': ' '1 Теперь структуре данных, сохраненной в переменной theBoard, соответ-- ствует вид иrровоrо поля, представленный на рис. 5.4. х Рис. 5.4. Вид иrpО80ro поля после nep8oro хода Ниже показана структура данных, соответствующая выиrрышу иrрока О, разместившеrо три символа Ов верхних клетках. theBoard = ('topL': 'midL' : '.lowL' : 'О' , 'х' , , I I topM' : 'midM' : 'lowM' : 'О' , 'Х', , , 'topR': 'О', 'midR': ' " , 1 ow R 1: 'Х' I Этой структуре данных соответствует вид поля, представленный на рис. 5.5. Разумеется, иrроки MOryт видеть только то, что выведено на экран, а не непосредственное содержимое переменных. Создадим функцию, отобра- жающую содержимое словаря на экране. Внесите в файл ticTacToe. ру сле- дующие изменения (новый код выделен полужирным шрифтом). theBoard = ('topL': I " 'midL': ' I , low L ': I , def printвoard(board) : 'topM': ' " 'topR': ' " 'midM': " 'midR':' " , lowM': ' " 'lowR': ' '1 
Словари и структурирование данных 157 print(board[ I topL 1] + 11' + board[ 'topM'] + '1' + board[' topR']) print ( , ++ , ) print(board['midL'] +'1' +board['midM'] + '1' + board[ 'midR']) print(' ++') print(board[ 'lowL'] + 11' + board[ 'lowM'] + '1' + board[ 'lowR']) printВoard(theВoard) о о о х х х Рис. 5.5. 8blHrpon HrpoK О Коrда вы запустите эту lIporpaммy, функция printBoard () выведет на экран пустое иrровое поле. I 1 ++ I 1 ++ 1 I Функция printBoard () может обрабатывать любую структуру "крестиков-ноликов", которую вы ей передадите. Внесите в код следую щие изменения. theBoard = {'topL': 'О', 'midL': 'Х', '101fL': ' , 'topM': 'О', 'lIIidM': 'Х', 'lowM': ' , 'topR': 'О', 'midR': I 1, , lO1fR': I Х' } 
158 rЛQВQ 5 def printBoard(board) : print(board[ltopL'] + III + board[ltopM'J + 11' + board [ I topR 1] ) print ( I ++ I ) print(board[lmidL'] + III + board['midM'] + '1 I + board [ 'midR 1] ) print ( ,++ I ) print(board['1owL'] + '1 I + board['lowM'] + III + board [ I lowR I ] ) printBoard(theBoard) В результате выполнения этой nporpaMMbI на экран будет выведено сле дующее состояние иrровоrо поля. 01010 ++ XIXI ++ 'Х Поскольку вы создали структуру данных для предстаВJIения иrр080rо поля и написали код (функцию printBoard(), способный интеРI1Ретиро-- вать эту структуру, тем самым вы создали nporpaMМY, которая "моделирует" иrру в "крестики-нолики". Вы моrли бы орraнизовать свою структуру дaH ных иначе (например, использовать ключи наподобие 'TOPLEFT' вместо 'topL') , но коль скоро ваш код правильно обрабатывает выбранную cтpyк туру данных, IIporpaмMa будет работать корректно. Например, функция printBoard () ожидает, что ей будет передаваться структура данных в виде словаря с ключами для всех девяти клеток. Если, скажем, в передаваемом функции словаре отсутствует ключ 'midL', то ваша nporpaMMa работать не будет. 01010 ++ Traceback (most recent са11 1ast): Fi1e "ticTacToe.py", line 10, in <modu1e> printBoard(theBoard) Fi1e "ticTacToe.py", 1ine 6, in printBoard print(board['midL'] + '1' + board['midM'] + '1 I + board [ I midR' ] ) KeyError: 'midL' А сейчас мы добавим код, который позволяет иrрокам вводить свои ходы. Внесите изменения в nporpaMMY tic7ac1Oe.py, чтобы она приняла с.ле дующий вид. 
Словари и структурирование данных 159 theBoard {'topL': I , 'midL': I I '101fL': I I 'topM' : 'midM' : 'lowM' : . , 'topR': ' · 'midR': · , 'lo_R': · '} , . , , def printBoard(board): print(board['topL'] + '1 I + board['topM'] + '1' + board [ , topR ' ] ) print ( I ++, ) print(board['midL'] + '1' + board['midM'] + '1' + board [ I midR ' ] ) print ( ,++ I ) print(board['lowL'] + '1' + board['lowM'] + '1' + board [ 'lowR 1] ) turn = 'Х' for i in range(9) : О printВoard (theBoard) print ( 'Тurn for ' + turn +, мove оп _hich .расе?') . move = input () . theВoard [move] - turn . if turn ... 'Х': turn - 'О' else: turn - 'Х' рriпtВоаrd(thеВоаrd) НОВЫЙ код выводит состояние иrровоrо поля В начале каждоrо очеред HQrO хода О, получает ход активноrо иrрока О, соответствующим образом обноWlЯСТ иrровое поле О. а затем передает право хода дрyrому иrроку 8, прежде чем перейти к следующему ходу. Коrда ВЫ запустите проrрамму. ВЫ- вод в процессе иrpы будет выrлядеть примерно так. I I ++ 1 I ++ I I Turn for Х. Move оп which space? midM 1 I ++ IXI ++ I I Turn for о. Move оп which space? lo_L I I ++ IXI ++ 01 I 
160 r лава 5 опущено OIOIX ++ XIXIO ++ 01 IX Turn for Х. Move оп which space? 101fM OIOIX ++ XIXIO ++ OIXIX Это еще далеко не полный вариант проrраммы, поскольку в нем, Ha npимер, вообще не определяется, выиrpал ли иrpок. Однако и Taкoro кода вполне достаточно для Toro, чтобы понять, как структуры данных MOryr ш:- пользоваться в проrраммах. Прu.мечаuие Любозна:тел/ь'Н/ые читатели .моеут 0знако.митъся с nOJт'ЬLМ иcxoдu'ыJlt кодо.м про- ера.м..мъ' для иаръ, в "крестики-нолики", посетив сайт http://пostarch.com/ automatestuff/. Вложенн.,е tловар. . tп.tlt. Моделировать иrру в "крестики-нолики" было сравнительно просто: для описания состояния иrровоrо поля потребовался Bcero лишь один (ловарь с девятью парами "ключзначение". Может оказаться так, что по мере воз- растания сложности ваших моделей вам понадобятся словари и списки, со. держащие друrие списки и словари. Списки удобны для хранения упорядо" ченных последовательностей значений, а словари  для ассоциирования значений с 1UIючами. Ниже приведена проrрамма, в которой для описания Toro, что приносит с собой каждый из roстей, приrлашенных на пикник, ис.. пользуется словарь, содержащий друrой словарь. Функция totalBrought ( ) способна читать эту структуру данных и рассчитывать общее количество принесенноro rостями. allGuests = {'АНсе': ('apples': 5, 'pretzels': 12}, 'ВоЬ': {'ham sandwiches': 3, 'apples': 2}, 'Carol': ('cups': 3, 'apple pies': 1)} 
Словари и структурирование данных 161 def totalBroиght(gиests, item): nurnВroиght = О . for k, v in gиests.items(): . numBroиght = nиmBrought + v.get(item, О) retиrn nиmBroиght print('Number of things print ('  Apples being broиght:') , + str(totalBroиght(allGиests, 'apples'))) , + str(totalBroиght(allGиests, 'сирэ'))) , + str(totalBroиght(allGuests, , cakes ' ) ) ) , + str(totalBroиght(allGиests, 'ham sandwiches'))) str(totalBroиght(al1Gиests, 'applepies'))) рriпt ('  Cups print ('  Cakes print('  Нат Sandwiches print('  Apple Pies ' + В функции totalBroиght () цикл for выполняет итерации по парам "ключзнаqение", хранящимся в переменной gиests О. В цикле перемен ной k присваивается строка с именем юстя, а переменной v  словарь с информацией опринесенном rостями. Если в словаре имеется ключ, со- ответствующий тому, что принес ['ость, то еro значение (количество при несенных единиц) прибавляется к переменной numВroиght о. В случае от. сyrствия TaKoro ключа метод get () возвращает значение О, которое, будучи прибавленным к значению numВroиght, не влияет на результат сложения. Вывод этой проrpаммы выrлядит так. Number of things being broиght:  Apples 7  Сирэ 3  Cakes О  Нат Sandwiches 3  Apple Pies 1 Может показаться. что эта модель слишком простая, чтобы обременять себя написанием специальной проrpаммы для нее. Однако задумайтесь над тем, что эта же функция totalBroиght () позволит леrко обрабатывать слова ри, содержащие тысячи rостей, которые MOryr приносить тысячи ра3JIИЧ ных блюд. В подобных случаях сохранение такой информации в структуре данных и ее обработка с помощью функции totalBroиght () сохранит вам MaCt"}' времени. Вы можете моделировать различные задачи с помощью структур данных по своему усмотрению при условии, что остальная часть кода корректно обрабатывает выбранную модель. Коrда вы только начинаете проrрам мировать, не задумывайтесь особенно о выборе "наилучшей модели" для 
162 r лава 5 описания своих данных. Возможно, по мере обретения опыта вам удастся найти более подходящие модели, но самое rлавное заключается в том, что- бы эта модель удовлетворяла задачам вашей проrраммы. Резюме Из этой rлавы вы узнали вес о словарях. Спш:ки и словари MOryт содер- жать множество различных значений, включая друrие списки и словари. Словари удобны тем, что позволяют отображать одни элементы (ключи) в друrие (значения), в отличие от списков, которые просто содержат упоря' доченные последовательности значений. Доступ к значениям словаря осу- ществляется посредством использования квадратных скобок, точно так же, как и в списках. Однако вместо целочисленных индексов в словарях допу- скается использование ключей в виде самых разных типов данных: целых и вещественных чисел, строк, кортежей. Орrанизуя данные проrраммы в структуры, можно создавать представления реальных объектов. Примером этоrо может служить иrра в "крестики-нолики". Рассмотренный материал охватывает почти все основные концепции проrраммирования на Python. В оставшейся части книrи вам предстоит узнать еще очень MHoro HOBoro, но вам уже известно достаточно для Toro, чтобы писать полезные проrраммы, автоматизирующие выполнение ряда важных задач. Возможно, вы даже не осознаете, что ваших знаний вполне достаточно для Toro, чтобы заrружать веб-страницы, обновлять электрон- ные таблицы или ОТПРawIять текстовые сообщения, и неоценимую помощь в этом вам окажут модули РytJlOП! Эти модули, написанные друrими про- rраммистами, предоставляют функции, которые упрощают для вас выпл-- пение задач TaKoro рода. Поэтому беритесь за написание реальных про- rрамм для выполнения полезных автоматизированных задач. Контропьныевопросы 1. Как ВЫf'Лядит код для пустоro словаря? 2. Как выrлядит элемент словаря с ключом' foo' и значением 42? 3. Опишите основные различия между словарем и списком. 4. Что произойдет при попытке получения доступа к элементу spam[' foo'], если spam  это ('bar': 100 '? 5. Если в переменной spam хранится словарь, то в чем состоит разница между выражениями' cat I in spam и 'cat' in spam. keys ()? 6. Если в переменной spam хранится словарь, то в чем состоит разница между выражениями I cat I in spam и 'cat' in spam. va1иes (I? 7. В какой сокращенной форме можно записать приведенный ниже код? 
Словари н структурирование данных 163 if 'co1or' not iп зрат: spam['co1or'] = 'black' 8. Какие модуль и функция MOryr быть использованы для "кра<:ивой пе чати" значений словаря? Учебные проекты Чтобы закрепить полученные знания на практике, напишите проrрам мы для предложенных ниже задач. Инаентар. "Р.КЛIO"ен,еtltоi .rp'" Вы создаете приключенческую видеоиrру. Структурой данных для ин- вентаря иrрока должен быть словарь, в котором ключи  это строковые значения, опйсывающие инвентарь, а значения  количество имеющихся у и['рока единиЦ данноrо инвентаря. Например, в случае словаря {'rope': 1, 'torch': 6, 'gold coin': 42, 'dagger': 1, 'arrow': 12} это означает, что у и['рока есть 1 веревка, 6 фонарей, 42 золотые монеты, 1 кинжал и 12 стрел. Нишите функцию displayInventory (}. которая принимает в качестве apryмeHTa описание любоro "инвентаря" и отображает еro примерно в сле- дующем виде. Inventory: 12 arrow 42 gold coin 1 rope 6 torch 1 dagger Tota1 number of items: 62 Под сказка. Для просмотра всех ключей словаря можно использовать цикл for. # iпvепtоrу.ру stuff = {'rope': 1, 'torch': 6, 'gold coin': 42, 'dagger': 1, 'arrow': 12} def disр1ауlпvепtоrу(iпvепtоrу): рriпt("Iпvепtоrу;") item tota1 = О for k, v in inventory.items(): print (str (v) + ' I + k) 
164 r лава 5 item total += v print ("Total number of items: " + str(itemtotal)) displayInventory(stuff) .УНItЦ.. пpe06ptlJO'tlH.. t..tKtI , tло,tlр' АЛ. пР.ItЛIO"енчеtкоi .rp'" Представьте, что добыча побежденноrо дракона представлена в виде списка строк наподобие следующеrо: drаgопLооt = ['gold coin', 'dagger', 'gold coin', 'gold coin',  I ruby I ] Напишите функцию addToInventory (inventory, addedItems), в которой параметр inventory  это словарь, предстаВJIЯЩИЙ инвентарь иrрока (как в предыдущем проекте), а параметр addedItems  это список наподобие dragonLoot. Функция addTOInventory () должна возвращать словарь, пред- ставляющий обновленный инвентарь. Обратите внимание на то, что в списке addedItems один и тот же элемент может встречаться несколько раз. Ваш код может выrлядеть примерно так. def addToInventory(inventory, addedItems): # сюда должен быть вставлен ваш код inv = {'gold coin': 42, 'rope': 1} dragonLoot = ['gold coin', 'dagger', 'gold coin', 'gold coin',  I ruby' ] inv = addToInventory(inv, dragonLoot) displayInventory(inv) Предыдущая проrpамма (с вашей функцией displayInventory () из преды дущеrо проекта) должна вывести следующее. Inventory: 45 gold coin 1 rope 1 ruby 1 dagger Total number of items: 48 
МАнипvпИРОВАНИЕСТРОКАМИ ТСКСТ  одна из наиболее распространенных форм дан- HbIX, которые будут обрабатываться вашими ПрOl'раммами. Вы уже знаете, как конкатенировать два <:троковых значе- ния с помощью оператора +, но ваши возможноcrи rораздо шире. Python позволяет извлекать части crpoK из строковых значений, добawmть и удалять пробелы, преобразовывать буквы из нижнеro рсrистра в верхний и обратно, а также форматировать строки по своему желанию. Вы даже можете написать код на PytJ1Ol1 для до-- ступа к буферу обмена, используя ero для копирования и вставки текста. В этой rлаве мы поработаем над двумя проrраммными проектами: про-- стым менеджером паролей и проrраммой для автоматизации рyrинной ра- боты по форматированию текста. Работа со строками Приступим К рассмотрению различных способов записи строк, а также их вывода на экран и получения доступа к ним с помощью кода на языке PythOl1. С'роко,ые литераЛ61 Ввод строк в кодс Pythоп не составляет труда: они начинаются и заканчи. ваются символами апострофа (одинарными кавычками). Но как быть, если кавычки требуются в самой строке? Ввод строки в виде 'Tha t is АНсе ' s са t. ' не сработает, поскольку Ру. 110n интерпретирует вторую кавычку по. (ле слова Alice как конец строки и сочтет остальную часть текста (5 са t. ,) нсдопустимым кодом. К счастью, существует несколько способов ввода строк. 
166 rЛQВQ 6 10."'10 Начало и конец строки можно обозначать не только парой апострофов, но и парой кавычек. Одно из преимуществ использования кавычек  в том, что они позволяют включать символ апострофа в состав строки. Введите в интерактивной оболочке следующий код: »> SPaJD '"' "Тhat is Alioe's cat." Поскольку строка начинается с кавычки, Pytlюп в состоянии идентифи цировать апостроф как часть строки. Если же в составе строки требуются как апострофы, так и кавычки, то необходимо прибеrать к экранированию символов. Jкраиироваии..ес..IO." Техника экранирования позволяет использовать символы, вставка кото- рых в строку иными способами не возможна. Эк.ра'ltuрова'lt'Н:ый C1J.Мвол вклю- чает символ обратной косой черты (\), за которым следует сам символ, ДО-- бавляемый в строку. (Несмотря на то что экранированный символ состоит из двух символов, ero рассматривают как одиночный.) Вот так, например, выrлядит экранированный апостроф: \ '. Ero можно использовать даже в строке, которая начинается и заканчивается апострофом. Чтобы увидеть, как работают экранированные символы, введите в интерактивной оболоч ке следующий код: >>> ераш. = 'Вау hi to ВоЬ\'е mother.' Поскольку в слове ВоЬ\ 's апострофу предшествует символ обратной KO сой черты, Python знает, что в данном случае апостроф не служит MapKe ром конца строки. Экранированные символы \ ' и \" позволяют включать в строку соответственно апострофы и кавычки. Экранированные символы, которые можно использовать, приведены в табл.6.1. Таблица 6.1. Экранированные СИМВОЛ"I \t \п \\ Отображаемый СИМIlOll Апостроф Кавычка Символ табуляции Символ новой строки (разрыва строки) Символ обратной косой черты ЭкранмраванныйсимlIOII \ ' \" 
Манипупнрование строками 167 Введите в интерактивной оболочке следующую команду. >>> print("Hello there!\nHow are you?\nI\'m doing fin..") Hello there! How are уои? 1'm doing fine. UС..рме" строки Поместив символ r перед начальной кавычкой, вы обозначаете строку как "сырую". Сирая (т.е. необработанная) строка полностью иrнорирует механизм экранирования и выводит все символы обратной косой черты, которые встречаются в строке, как обычные символы. Введите, например, в интерактивной оболочке следующую команду. »> print(r'Тhat is Carol\'s cat.') That is Carol\'s cat. Поскольку это "сырая" строка, Python считает ВСС символы обратной косой черты ее частью, а не началом экранированноrо символа. "Сырые" строки полезны в тех случаях, кш'да вы вводите строковые значения с MH rочисленными Символами обратной косой черты, как это бывает при ис пользовании реryлярных выражений, описанных в следующей rлаве. МиоrОСТРОllиwе 610lИ Несмотря на то что вы Bcerдa можете включить в строку символ НОВОЙ строки с помощью экранированноrо символа \п, во мноrих случаях rораздо проще использовать мноrоcrрочные блоки. В PytllOП мноrострочпые блоки представляют собой rруппу строк, заключенных в тройные апострофы или кавычки, Любые апострофы, кавычки или символы новой строки в блоке, оrраниченном такими "тройными кавычками", считаются частью строки. Правила PytllOn, реrламентирующие форматирование блоков кода с помо- ЩЬЮ отступов, в отношении мноrострочных блоков не действуют. Откройте файловый редактор и введите следующий код. print(' I 'Dear Alice, Eve's cat has Ьееп arrested for саtпаррiпg, cat burglary, and  extortion. Sincerely, ВоЬ' I 1) 
168 rЛQва 6 Сохраните эту проrрамму в файле catпapping.py и выполните ее. Вы долж- ны получить следующий вывод. Dear АНсе, Eve's cat has been arrested for саtпаррiпg, cat burglary, and extortion. Sincerely, ВОЬ Обратите внимание на то, что для символа апострофа 1\ слове Е ve ' s экранирование не понадобилось. В "сырых" строках экранирование апо- строфов и кавычек необязательно. Для вывода текста в таком же виде без использования мноrострочноrо блока потребуется следующий вызов функ- ции print (). print('Dear Alice,\n\nEve\'s cat has been arrested for catnapping, cat burglary, and extortion.\n\nSincerely,\nBob') МиоrОnРОllиwе коммеитарии В то время как символом "решетка" (#) обо:шачается однострочный ком- ментарий, длина KO'ropOl'o оrраничивается концом строки, мноrОСТРОЧlfые блоки часто используют в качестве МНOI'острочных комментариев, охваты- вающих произвольное количество строк. Приведенный ниже текст абсо- лютно корректен 8 PytllOn. '""'This is а test Python program. Written Ьу Al Sweigart al@inventwithpython.com This program was designed for Python 3, not Руthоп 2. """ def spam(): """This is а mиltiline comrnent to help explain what the spam() function does.""" print ( 'Иеllо! ' ) IfHlleKt_po.tlH_e nрок _ _J.лечен_е tpeJo. в случае строк операции индексирования и извлечения срезов выпол- няются точно так же, как и в случае спи(ков. Можете рассматривать строку 'ВеНо world! I как список, в котором каждый символ строки является эле- ментом списка и име<.--т соотвt.'Тствующий индекс. 
Манипулирование строками 169 Н О е 1 1 2 1 3 о 4 5 w 6 о 7 r 8 1 9 d 10 11 Пробелы и восклицательный знак входят в число символов, поэтому фраза 'Не 110 wor 1d! ' содержит 12 символов, от символа Н с индексом О до символа! с индексом 11. Введите в интерактивной оболочке следующие команды. »> ерам = 'Hello world!' ерам[О] 'н' » > Вpalll. [ 4 ] 'о' »> ер&lll[1] '! I >>> ераш[О:5] '!1ello' » > Вpalll. [ : 5 ] 'Hello' >>> врam[б:] 'world! ' Указав индекс, вы получаете символ, находящийся в соответствующей позиции в строке. В случае указания диапазона индексов, Т.е. ИЗWlечения среза, элемент с начальным индексом включается в срез, тоrда как эле мент с конечным индексом не включается. Поэтому, если spam  это строка 'Hello wor1d! ',то срез spam[O: 5] содержит строку 'Hello'. Подстрока, по-- лучаемая с помощью среза spam [о: 5] , будет включать в себя все символы от spam [О] до spam [ 4 ] , тоrда как символ пробела, имеющий индекс 5, в нее не войдt.'Т. Имейте в виду, что извлечение части строки посредством среза не со-- провождается изменением исходной строки. Вы можете сохранить срез, извлеченный из одной переменной, в дрyrой переменной. Введите в интеJr активной оболочке следующие команды. »> SPaID = 'Hello world!' »> fizz = spaш[О:5] >>> fizz I Иеllо I Извлекая срез и сохраняя результирующую подстроку в друrой перемен ной, вы получаете быстрый и простой доступ как ко всей строке, так и к ее подстроке. 
170 r лава 6 И,пОЛ6J08"н.е опеptl,ОР08 in . not: in ,О "Pol(".. Операторы in и not in применяются к строкам точно так же, как и к сни- скам. Результатом вычисления выражения в виде двух строк, соединенных любым из этих операторов, является булево значение Trиe или t'alse. Вве- дите в интерактивной оболочке следующие команды. »> 'Hello' in 'Hello World' True »> 'Hello' in 'Hello' True »> 'НELLO' in 'Hello World' False »> l' in '8раш.' True »> 'cats' not in 'cats and dogs' False Эти выражения проверяют, содержится ли первая строка (о CTporOM <:0-- ответствии с рсrистром набора символов) во второй <:троке. Попезные методы дпя работы со строками Некоторые методы анализируют строки или создают нреобразованные строковые значения. В этом разделе описаны те из них, которые вы будете использовать наиболее часто. "е'ОIl'" upper ( ) , lower ( ) , isupper () . islower ( ) Методы upper () и lower () возвращают новую строку, в которой все буквы исходной строки преобразованы соответствеlllЮ в верхний или нижний рсrистр. Небуквенные символы не иснытывают никаких изменений. Вве- дите в интерактивной оболочке следующий код. »> ераш = 'Hello worldl' >>> ераш = ераш. upper () »> ераш I HELLO WORLD! I »> ераш = spam.lower() »> ераш 'hello world!' Имейте в виду, что эти методы возвращают новые строковые значения, не изменяя саму исходную строку. Чтобы изменить исходную строку, не- обходимо вызвать для нее метод upper () или lower () и присвоить реЗУJII). тирующее строковое значение той же переменной, в которой хранилась 
Манипулирование строками 171 исходная строка. Именно поэтому для тою, чтобы изменить строку, хра- нящуюся в Ilеременной spaт, следует выполнить операцию приснаивания spaт = spaт. upper () , а не просто вызвать функцию spaт. upper () . (В каче(тне аналоrии можно привести перемеНIlУЮ eggs, содержащую значение 10. Вы- ражение eggs + 3 не изменит значения eggs, но это можно сделать с помо- ЩЬю инструкции eggs  eggs + 3.) Методы upper () и lower () удобно Ilрименять в тех случаях, коrда при сравнении строк не должен учитываться реrистр букв. Строки 'great' и 'GREat'  это разные строки. Однако в приведенной ниже небольшой про- rpaмMe не имеет значения, в какой форме пользователь введет слово, Great, GREAT или grEAT, поскольку строка предварительно преобразуется в верх- ний реrистр, print('How are уои?') feeling = input() if feeling.lower() == 'great': print('I feel great too.') else: print('I hope the rest of your day is good.') Korдa вы запустите эту проrpамму, на экране отобразится вопрос, и даже если вы ответите на Hero, введя слово GREat, все равно будет выведена стро- I,а I feel great too. Добавляя в свою проrрамму код, нивелирующий раз- личия в написании слов и иrнорирующий такие ошибки, как неправильное использование строчных и прописных букв, вы упростите работу с ней и уменьшите вероятность ее аварийноrо завершения из-за ошибок, допущен- ных пользователем при вводе текста. How are уои? GREat 1 feel great too. Методы isupper () и islower () возвращают булево значение True, если в строке имеется хотя бы одна буква и все буквы относятся соответственно к верхнему или нижнему реrистру, В противном случае метод возвращает значение False. Введите в интерактивной оболочке следующие команды и обратите внимание на возвращаемые методами значения. »> ераш  'Hello world!' »> spam.islower() False »> sраш.isuрреr() False >>> 'НELLO'. i supper () 
172 r лава 6 True »> 'аЬс12З45' .islower() True »> '12345'.islower() False »> '12345'.isupper() False Поскольку методы upper () и lower () сами возвращают (:троки, для этих строк, в свою очередь, также можно вызывать строковые методы. Выра- жения, с помощью которых это делается, выrлядят как цепочки вызовов методов. Введите в интерактивной оболочке следующие команды. »> 'Hello'.upper() 'HELLO' »> 'Hello'.upper().lower() 'hel!o' »> 'Hello'.upper().lower().upper() 'HELLO' >>> I НELLO' .lower () 'hello' »> 'НELLO'.lower().islower() True 'ТрОК0861е .еТОIl" isX() Наряду с функциями islower () и isupper () существует ряд мет<?дов для работы со строками, имена которых начинаются со слова is. Методы этой катеrории возвращают булево значение, описывающее природу строки, ДЛЯ которой они вызываются. Ниже приведен перечень часто используе- мых строковых методов типа isX() . . isalpha () возвращает значение Trиe, если строка состоит только из букв и не является пустой. . isalnum () возвращает значение True, если строка состоит только из буквеннцифровых символов и не является пустой. . isctecimal () возвращает значение True, если строка состоит только из цифровых символов и не ЯWlяется пустой. . isspace () возвращает значение True, если строка состоит только из символов пробела, табуляции, новой строки и не является пустой. . istitle () возвращает значение True, если строка состоит только из слов, которые начинаются с буквы в верхнем реrистре, за которой следуют только буквы в нижнем реrистре. Введите в интерактивной оболочке следующие команды. 
Маннпупирование строками 173 »> 'hello'.i8alpha() True »> 'hel10123'.isalpha() False »> 'hеllо12Э' . i8alnum() True »> 'hello' .i8alnum() True »> '123'.i8decimal() True »> ' '.i8spaceO True »> 'Тhi8 I8 Title Ca8e'.i8title() True »> 'Thi8 I8 Title Са8е 123' .i8title() True »> 'Thi8 I8 not Title Са8е' .i8title() False »> 'Thi8 I8 NOT Title Са8е Either'.i8title() False Методы isX() удобно применять для проверки допустимости введенных пользователем данных. Например, приведенная ниже проrрамма повторя ет запрос ввода пользователем cBoero возраста и пароля до тех пор, пока :ти данные не будут предоставлены в корректном формате. Откройте но-- аое окно в файловом редакторе, введите в нем следующий код nporpaмMbI и сохраните ero в файле validatelnput.py. while True: print('Enter your age:') age = input ( ) if age.isdecimal(): break print('Please enter а numЬer for your age.') while True: print('Select а new password (letters and numbers опlу): ') password = input() if password.isalnum(): break print('Passwords сап only have letters and numЬers.') в первом цикле while nporpaмMa запрашивает ввод возраста пользовате- ля и сохраняет ввеДенное значение. Если пользователь ввел возраст в виде допустимor'о значения (десятичноro числа), первый цикл прерывается и Уl1рawIение передается второму циклу while, в котором запрашивается na роль. В противном случае nporpaмMa информирует пользователя о том, что должно быть введено число, и предлаrает повторно указать возраст. 
174 r лова 6 Во втором цикле while запрашивается и сохраняется пароль. Если поль зователь ввел буквенно-цифровое значение, выполняется выход из цикла. В противном случае nporpaмMa информирует пользователя о том, что дo пускаются только пароли, состоящие из букв и цифр, и предлаrает ему по- вторить ВБОд. Запустив nporpaммy, вы должны получить примерно такой вывод. E:nter yoиr age: forty t_o Please enter а nиmber for yoиr age. Епtеr yoиr age: 42 Select а new password (letters and nиmbers only): sесrЗt! Passwords сап only have letters and nиmbers. Select а new password (letters and nurnbers only) : sесrЗt Вызывая методы isdecirnal () и isalnиm() для переменных, мы можем BЫ яснить, являются ли введенные пользователем значения цифровыми или буквенно-цифровыми. В данном случае эти про верки позволили OТBeprнyrb ввод пользователем строки forty two при указании возраста, но принять ввод числа 42, а также oTвeprнyrb ввод значения sесrЗt! в качестве пароля, но принять ввод значения secr3t. Мето"" starts"i th () . ends"i th () Методы startswith () и endswi th () возвращают значение True, если стро- ка, для которой они вызываются, соответственно начинается или заканчи- вается строкой, переданной методу; в противном случае они возвращают значение False. Введите в интерактивной оболочке следующие команды. »> 'Hello _orldl '.start8_ith('Hello') True »> 'Hello _orldl' .enda_ith('_orldl') True »> 'аЬо12Э'.stаrts_ith('abcdef') False »> 'аЬс12З'.еnds_ith('12') False »> 'Нello _orldl '.start8with('Hello worldl') True »> 'Hello _orldl'.ends_ith('Hello worldl') True 
Манипулирование строками 175 Эти методы  полезная альтернатива оператору сравнения , если cpaB нение с друrой строкой требуется выполнить не для всей исходной строки, а только для первой или последней ее части. "роко..., ."011" joiп () . spli t () Метод j oin () удобно использовать в тех случаях, коrда ряд строк, npeд ставленных в виде списка, необходимо объединить в одно строковое зна чеllие. Метод join () вызывается для некоторой строки, принимает спи- сок строк в качестве apryмeHTa и возвращает строку. Возвращаемая строка представляет собой результат конкатенации всех строк, переданных в виде списка. Введите в интерактивной оболочке следующие команды. »> " '.join(['cats', 'rats', 'bats') 'cats, rats, bats' »>' '.join(['Мy', 'паше', 'is', 'Simon') 'Му пате is Simon' »> 'AВC'.join(['Мy', 'паше', 'is', 'Simon') 'MyABCnameABCisAВCSimon' Обратите внимание на то, что строка, ДIIЯ которой вызывается метод join (), вставляется между переданными посредством apryMeHTa строками. Например, если выполнить вызов j oin ( [ 'cats', 'rats', 'bats']) ДIIЯ стро- ки " ',ТО будет возвращена строка' cats, rats, bats'. Не забывайте о том, что метод j oin () вызывается для <:TpOKoBoro значе- ния и в качестве apryMeHTa принимает список. (Вы можете вызвать метод как-то иначе, сами Toro не осознавая.) Метод spli t () делает совершенно противоположное: он вызывается для CTpoKoBoro значения и возвращает список строк. Введите в интерактивной оболочке следующую команду. »> 'НУ паше is SiJUon'.split() [ , Му 1, ' пате', 'is " 'Simon'] По умолчанию строка 'Му пате is Simon' разбивается на отдельные стро-- ки в тех местах, ['Де встречаются пробельные символы: пробел, символ та- буляции, символ новой строки. Эти пробеШ)IIЫС символы не включаются в строки, возвращаемые в виде списка. Вы можете указать дрyryю CTPOКY разделитель, передав ее методу spH t ( ) . Например, введите в интеракти& ной оболочке следующие команды. »> 'НУАВСnашеАВСisAВСSiаоn'.sрlit('АВС') ['Му', 'пате', 'is', 'Simon'] »> 'НУ namе is Simon' .split('m') [ 'Му па', 'е is Si', 'оп'] 
176 r лава 6 Обычный способ применения метода spli t ()  разбиение мноrостроч Horo блока по символам новой строки. Введите в интерактивной оболочке следующие команды. »> 8pam - "'Dear Alice, How have you been? 1 u fine. There is а oontainer in th. fridqe that is labeled "Мilk Experiment". Please do not drink i t. Sincerely, ВоЬ" , »> spu.split('\n') ['Dear Alice, " 'Иоw have уои Ьееп? I am fine.', 'There is а container in the fridge', 'that is labeled nMilk Experiment".', ", 'Please do not driпk it.', 'Sincerely,', 'ВоЬ'] Передача методу spli t () строки I \n' в качестве apryмeHTa позволяет вы. полнить разбивку МНОl'острочноrо блока, coxpaHeHHoro впеременной эрат в позициях символов новой строки, и возвратить список, каждый элемент KOToporo соответствует одной строке текста. Вырtl.н..tlн.е "каtl t п0801Ц61O 8еТО1I0' rjust (), ljust () . ceпter () Строковые методы rjиst () и 1just () возвращают версию строки, для которой они вызываются, выровненную за счет вставки пробелов. В обоих методах первый apryмeHT  целое число, определяющее длину выровнен- ной строки. Введите в интерактивной оболочке следующие команды. »> 'Hello' .rjust(10) , Ие1l0' »> 'Нello'.rjust(20) , Неllо I »> 'Hello World'.rjust(20) , Иеllо World' »> 'Hello' .ljust(10) 'Иеllо I Выражение 'Не 110' . rj иэ t ( 10) rоворит о том, что мы хотим выровнять строку' Не110' вправо так, чтобы полная длина строки составляла 10. В сло- ве 'Ве110' насчитывается пять символов, поэтому слева от Hel'o будут до- баВJIены пять пробелов, в результате чеrо полная длина строки составит 10 символов, причем слово' Не 110' будет выровнено вправо. 
Монипулирование строками 177 Необязательный второй apryмeHT в обоих методах указывает на символ- заполнитель, отличный от пробела. Введите в интерактивной оболочке следующие команды. »> 'Hello'.rjust(20, '*') '***************Hello' »> 'Hello'.ljust(20, '') 'Hello1 Метод center () работает аналоrично методам ljust () и rjust (), но цeH трирует ero, а не выравнивает по правому или левому краю. Введите в инт рактивной оболочке следующие команды. »> 'Hello'.center(20) , Hello I »> 'Hello'.center(20, '=') '=======Hello========' Эти методы особенно полезны в ситуациях, коrда необходимо вывести табулированные значения с подходящей разрядкой. Откройте новое окно в файловом редакторе, введите в HeI'O следующий код и сохраните ero в файле picnicTahle.frY. def printPicnic(itemsDict, leftWidth, rightWidth): print('PICNIC ITEMS' .center(leftWidth + rightWidth, '')) for k, v in itemsDict.items(): print(k.ljust(leftWidth, '.') + str(v) .rjust(rightWidth)) picnicltems = {'sandwiches': 4, 'apples': 12, 'cups': 4, 'cookies': 8000) printPicnic(picnicltems, 12, 5) printPicnic(picnicItems, 20, 6) В этой проrрамме мы определяем метод printPicnic () , который получа ет данные в виде словаря и использует методы center (), ljust () и rjust () для отображения этих данных в аккуратном формате в виде выровненной страницы. Функции printPicnic () передается словарьрiспiсltеms. В нем содержат- ся переменные, Имеющие следующие значения: sandwiches  4, apples  12, cups  4 и cookies  8000. Мы хотим представить информацию в двух столб.. цах, причем слева должно отображаться название блюда, а справа  коли- чество. Для этоrо нужно решить, какой ширины должны быть левый и правый столбцы. Эти значения будут передаваться методу printPicnic () вместе со словарем. 
178 r лова 6 Метод printPicnic () принимает словарь, а также значения ширины для левою (leftWidth) и правоrо (rightWidth) столбцов. Он выводит заrоловок PICNIC ITEMS над таблицей по ее центру. Затем он обрабатывает в цикле эл " » Й менты словаря, выводя каждую пару имязначение в отдельно строке таблицы, причем ключ выравнивается влево, значение  вправо, а остав- шиеся в каждом столбце пустые позиции заполняются пробелами. Определив метод printPicnic () , мы определяем словарь picnicItems и дважды вызываем метод printPicnic () , передавая ему различные значения ширины левоrо и правою столбцов. В процессе выполнения проrрамма дважды выводит данные. В пер вый раз ширина левой колонки составляет 12 символов, а правой  5. Во второй раз эти значения СОСТawIяют соответственно 20 и 6 символов. PICNIC ITEMS sапdwiсhеs.. 4 app1es. . . . .. 12 сирs. . . . . . .. 4 ceekies..... 8000 PICNIC ITEMS sandwiches.......... 4 app1es. . . . . . . . . . . . .. 12 cups. . . . . . . . . . . . . . .. 4 ceekies............. 8000 Используя методы rj ust () , lj ust () и center () , вы можете быть уверены в том, что данные в строках таблицы аккуратно выровнены, даже если точ ное количество символов, содержащихся в каждой строке, неизвестно. 'lIt1лен.е пробело. t пО.ОЩ61О .e1Ollo. strip () , rstrip () . lstrip () Иноrда возникает необходимость в удалении из строки ее ведущих, за- мыкающих или и тех, и друrих пробельных символов (пробелы, симв<r лы табуляции и символы новой строки). Метод strip () возвращает новую строку без начальных и конечных пробельных символов. Методы lstrip () и rstrip () удаляют пробельные символы соответственно в начале и конце строки. Введите в интерактивной оболочке следующие команды. »> .p8111. - , Hello World ' »> sp8111.. strip () 'НеНе Werld' »> 8p8111..1strip() 'НеНе Wer1d ' »> 8p8111..rstrip() , НеНе Wer1d' 
Манипупирование строками 179 с помощью необязательноrо CTpoKoBoro apryмeHTa можно указать, какие символы должны удаляться с обоих концов строки. Введите в интерактив- ной оболочке следующие команды. »> зрам = 'sрамSpaшВааоnSрamEggsSpaшSраш' »> spaм.strip('aмpS') 'ВасопSраmЕggs' Передавая методу strip () apryMeHT I ampS' , мы сообщаем ему, что в на- чале и конце строки, сохраненной в переменной spam, должны быть удале- ны все вхождения символов а, т, р и S. Порядок указания символов в стро-- ке, передаваемой методу strip (), несущеетвен: вызовы strip ('mapS') или strip (1 Spam') дaдyr тот же результат, что и вызов strip (' ampS'). Коп8РО.IIН8е 8 '''"'К" "рок , пОМОЩ61О модул. pyperc:lip В модуле pyperclip имеются функции сору () и paste () , которые MoryT выполнять операции копирования и вставки текста через буфер обмена ва- шеrо компьютера. Пересылка вывода nporpaMMbI в буфер обмена упрощает вставку текста в сообщения электронной почты или документы TeKCToBoro процессора, а также еro передачу друroму nporpaмMHOМY 06е<:печению. Модуль pyperclip не постаWlЯется вместе с Python. Чтобы cro устано- вить, следуйте указаниям по установке модулей сторонних разработчиков, приведенным в приложении А. Установив модуль, введите в интерактивноЙ оболочке следующие команды. »> import pyperclip >>> pyperclip. сору (' Hello 1forld! ') »> pyperclip.paste() 'НеНо world!' Разумеется, если содержимое буфера будет изменено внешней nporpaм- мой, то именно оно будет возвращено методом paste (). Например, если я С1Соnирую дан/ное nред.п.ожен:ие в буфер об,М,еиа, а затем 8'Ы3oвy.мemoд pas te (), 7tЮ получу следуЮ11Jий lJ'Ы8oд: »> pyperclip.paste() 'Например, если я скопирую это предложение в буфер обмена, а затем вызову метод paste(), то получу сле вывод:' 
180 r лава 6 Выпопнение сценариев Python вне IDLE До сих пор вы выполняли свои сценарии на Python с помащью интерактивной оболочки или фаЙl10воrо редактара в IDLE. Однако вряд ли вам понравится откры- вать IDLE всякий раз, Korдa потребуется выполнить сценарий. К счастью, существуют более удобные способы, упрощающие запуск сценариев Python. Соответствующие процедуры запуска сценариев дпя Windaws, OS Х и Linux HeMHoro различаются, но все они описаны по отдельности в приложении Б. 3аrляните в Hero, если хотите узнать подробнее об зтих способах, а также о том, как передавать сценариям apry- менты командной строки. (В IDLE такая ВОЗМОЖНОСТь отсутствует.) Проект: паропьная защита Возможно, у вас есть учетные записи на МНОI'ИХ веб-сайтах. Использо- вать для каждой из них один и тот же пароль  плохая привычка, поскольку при наличии брешей в системе безопасности любоrо из этих сайтов хаке- рам открывается доступ ко всем вашим учетным записям. Наилучшее реше- ние  использовать менеджер паролей на своем компьютере <: одним rлав. ным паролем для доступа к нему. СЛкрыв менеджер паролей, вы сможете скопировать нужный пароль в буфер обмена и вставить ero в поле Введите пароль, предоставляемое при попытке доступа к ве&сайту. Предлаrаемая в этом примере проrрамма менеджера паролей не обеспt..... чивает полной безопасности, но демонстрирует базовые принципы работы проrрамм подобною рода. Проекты в rnaBax Это первый из "npoeUOB в rлаве", которые встречаются в книrе. Начиная с этой rnaBW и во всех последующих вам будут пpeдnаrатъся "роекты, демонстрирующие применение на практике понятий, рассмотрению которых была посвящена rлава. Особенностью написания всех праектов ямяется то, что каждый из них начинается с "чистоrо листа" (nycToro окна файловоrо редактора) и заканчивается полностью функциональным вариантом nporpaММbl. Как и при работе с примерами в интерак- тивной оболочке, было бы желательно, если бы вы не просто читали зти разделы, а прорабатывали их у себя на компьютере. \ Шоr ,. проеКТ8рО'ОН8е пporpa..... 8 иpyкryp ДОНН"Х МЫ хотим, чтобы проrрамма принимала apryмeHT командной строки, ко-- торым будет служить имя учетной записи, например учетной записи элек- тронной почты или блоra. Пароль для доступа к этой учетной записи будет 
Манипулирование строками 181 копироваться в буфер обмена, откуда пользователь сможет вставить ero в ноле Password (Пароль). Блaroдаря этому пользователь не должен держать в памяти длинные пароли, которые надежны, но трудно запоминаются. Откройте новое окно файловоrо редактора и сохраните ero пустое со-- держимое в файле frшру. Проrpамма должна начинаться строкой директи вы #! (см. приложение Б), за которой следует комментарий, содержащий краткое описание назначения проrpаммы. Поскольку вы хотите связывать с именем каждой учетной записи пароль для доступа к ней, целесообразно хранить эти данные в виде строк словаря. Этот словарь и будет той CTPYКТY рой данных, которая обеспечивает орrаниэованное хранение имени учет ной заПИСИ и соответствующеrо пароля. Введите следующий код. #! python3 # pw.py  Прорамма для незащищенноо хранения паролей. PASSWORDS = ('email': 'F7minlBDDuvMJuxESSKHFhTxFtjVB6', 'blog': 'VmALvQyКAxiVH5G8v01iflMLZF3sdt', , luggage': '12345') Шаr 2. Обработка apry8eHTo' К08а81180. (ТРОК8 ApryMeHTbI команДНОЙ строки будут храниться впеременной зуз. argv. (Более подробная информация о том, как использовать apryMeHTbI KOMaнд НОЙ строки В проrраммах, приведена в приложении Б.) Первой в списке sys. argv всеrда должна быть строка, содержащая имя файла проrраммы ( 'pw. ру' ), а вторым  первый из apryMcHToB командной строки. Таким apry ментом для нашей проrраммы ЯWlяется имя учетной записи, с которой вы хотите ассоциировать lIароль. П(){кольку этот apryMeHT обязательный, вы отображаете сообщение, напоминающее пользователю о необходимости ввода имени учетной записи, если он забыл ero ввести (это будет в том слу чае, если в списке sys. argv (:одержится менее двух apryMeHToB). Дополните ранее введенный код (:лсдующим. #! руthопЗ # pw.py  Прорамма для незащищенноrо хранения паролей. PASSWORDS {'email': 'F7minlBDDuvMJuxESSKHFhTxFtjVB6', 'blog': 'VmALvQyКAxiVH5G8v01if1MLZF3sdt', 'luggage': '12345') import .У. if len(sys.argv) < 2: рrint('Ис::попЪ80Вание: python p1f.py [ИIPI )'Четной записи]   копирование napoпR )'Четной записи') ауа .exit() aoc::oun t - ауа. argv [ 1 ] I nераЪ1Й ap%'YМ8Н'l' командной C'l'p01СИ  это I ИIPI )'Четной записи 
182 r лава 6 Шоr 3. Коп.ро,он.е порол. Теперь, коrда имя учетной записи сохранено в виде строки в перемен ной account, вы должны проверить, существует ли оно в словаре PASSWORDS в виде ключа. Если существует, вы копируете значение ключа в буфер об- мена, используя вызов pyperclip. сору (). (Поскольку используется модуль pyperclip, ero необходимо импортировать.) Заметьте, что на самом деле без переменной ассоип t можно было бы обойтись, поскольку везде в про- rpaMMe вместо нее можно было бы использовать выражение sys . argv [1] . Однако лучше использовать переменную account, так как читать проrрамму в этом случае roраздо леrче. Дополните раиее введенный код, как ноказано ниже. #! руthопЗ # pw.py  Проrрамма для незащищенноrо хранения паролей. PASSWORDS ('email': 'F7miпlВDDuvМJuхЕSSКНFhТхFtjVВ6', 'blog': 'VmALvQуКАхiVН5G8vО1iflМLZFЗsdt', 'luggage': '12345'} import зуз, pyperclip if len(sys.argv) < 2: рriпt('Использование: python pw.py [имя учетной записи]  копирование пароля учетной записи') зуз. exit () account = sys.argv[l] # первый apryмeHT командной строки  это # имя учетной записи if account in PASSNORDS: руреrсliр.сору(РASSИORDS[ассоunt) рrint('паропь ДnR ' + account + ' Qхоnиpoван в буфер. ') else: print ( 'Учетна. запись ' + account + 'отсутствует в cnисхе.') Добавленный код выполняет поиск имени учетной записи в словаре PASSWORDS. Если это имя ЯWlяется ключом в словаре, то мы получаем значе иие, соответствующее этому ключу, копируем ero в буфер обмена и выводим сообщение, которое подтверждает выполнение копирования. В противном случае выводится сообщение о том, что учетная запись с таким именем 0'1'- cyrCTBYeт в списке. Этот сценарий предстаWlяет собой полностью завершенную проrрамму, Вопюль;ювавшись приведенными в приложении Б инструкциями по запу- (KY проrрамм из командной строки, вы можете теперь быстро копировать l1арОJlИ своих учетных записей в буфер обмена. Если вам понадобится об- новить проrрамму для изменения или д06awJения паролей, то достаточно будет внести необходимые изменения в код словаря PASSWORDS. 
Манипулирование строками 183 Разумеется, вряд ли вы захотите хранить все свои паро.ли в одном Mecre, от- куда любой человек может леrко их скопировать. Но можно модифицировать эту проrрамму и исполыовать ее для быстроrо копирования обычноro текста в буфер обмена. Предположим, вы отправляете несколько писем электронной почты, в которых содержится MHoro общих абзацев. Вы можете поместить каждый абзац в качестве значения в словарь PASSWORDS (в этом случае вы, веро- ятно, измените имя словаря), а затем будете иметь возможноcrь быcrро выби рать и копировать множество стандартных фрarментов текста в буфер обмена. Пользователи Windows MOryт создать пакетный файл (файл с расшире.- нием . Ьае) для запуска этой проrраммы из окна Выполнить, которое можно леrко открыть, нажав комбинацию клавиш <Windows+R>. (Более подробно о пакетных файлах читайте в приложении В.) Введите в окне фаЙЛОВОI'О редактора и сохраните в файле рш.Ьаtв папке С:\Windoмследующий код. @ру.ехе C:\Python34\pw.py %* @pause с использованием только что созданноrо пакетноrо файла выполнение проrраммы для хранения паролей сводится к нажатию комбинации клавиш <Windows+R> и вводу команды pw <имяучетной записи>. Проект: добавпение маркеров в разметку Wiki-Аокументов Редактируя статьи в Википедии, можно создавать маркированные спи- ски, вводя каждый элемент списка в отдельной строке, которая предваря- ется маркером в виде звездочки. Предположим, что у вас имеется очень большой список, в который нужно добавить маркеры. Вы моrли бы сделать это вручную, вводя символы звездочки в начале каждой строки, и так стро- ка за строкой. Но эту задачу можно лсrко автоматизировать с помощью ко- pOTKoro сценария Pytlюп. Сценарий bulletPointAdder.py будет получать текст из буфера обмена, до- бавлять звездочку и пробел в начале каждой строки, а затем возвращать этот новый текст в буфер обмена. Например, если бы я скопировал в буфер обмена текст (предназначенный ДIIЯ публикации в Википедии статьи "Спи- сок списков списков") Lists of animals Lists of aquarium life Lists of biologists Ьу author abbreviation Lists of cultivars а затем ВЫIIOJlНИЛ ПрOl'рамму bulletPointAdder.py, то в буфере обмена содер- жался бы следующий текст: 
184 r лава 6 * Lists * Lists * Lists * Lists of animals of aquarium life of biologists Ьу author abbreviation of cultivars Этот предваряемый звездочками текст полностью подroтовлен к вставке в статью Википедии в качестве маркированноrо списка. ш", ,. (ОnllРО'''IIII' 11 ."".К" nocpella.o. 'уф.", 0'..11" Вы хотите, чтобы проrpамма bиlletPointAdder.py делала следующее: 1) вставляла текст из буфера обмена; 2) выполняла над ним некоторые действия; 3) копировала новый текст в буфер обмена. С пунктом 2 придется немноro повозиться, а вот пункты 1 И 3 достаточно просты: они требуют использования Bcero лишь двух вызовов  pyperclip. сору () и pyperclip. paste (). Поэтому на данном этапе мы напишем только ту часть проrраммы, которая реализует шаrи 1 и 3. Введите в файловом peдaк торе приведенный ниже код и сохраните ею в файле bиlletPointAdder.py. #! руthопЗ # bulletPointAdder.py  Добавляет маркеры Википедии в начало # каждой строки текста, cOXpaHeHHoro в буфере обмена. import pyperclip text = pyperclip.paste() # TODO: разделить строки и добавить звездочки. pyperclip.copy(text) Комментарий TODO (ЧТО СДЕЛА ТЬ)  напоминание о том, что эту часть проrраммы еще предстоит написать. Следующий шаr  фактическая реали- зация данной части проrраммы. Ш",2. '''J'"'K'' reKt", н" арОКII 11 1I0'"и'Нllе J"J/I0"'K Вызов pyperclip.paste () возвращает весь текст из буфера в виде одной большой строки. Если бы мы использовали пример со статьей "Список списков списков", то строка, сохраненная в виде текста, выrлядела бы так: 'Lists of animals\nLists of aquarium life\nLists of biologists Ьу author abbreviation\nLists of cultivars' 
Манипулирование строками 185 Наличие в этой строке символов новой строки \п приведет к тому, что она будет отображаться в виде мноrострочноrо текста при выводе на экран или вставке из буфера обмена. В этом одном строковом значении содер- жится MHoro "строк". Вам нужно добавить звездочку в начале каждой из этих строк. Можно было бы написать код, который выполняет поиск всех символов \n и вставляет после каждоrо из них звездочку. Однако rораздо проще ис пользовать метод spli t () для возврата списка строк, по одной для каждой строки ориrинальноrо текста, а затем добавить звездочку в начале каждой строки, входящей в список. Придайте проrрамме следующий вид. #! руthопЗ # bulletPointAdder.py  Добавляет маркеры Википедии в начало # каждой строки текста, coxpaHeHHoro в буфере обмена. import pyperclip text = pyperclip.paste() t Pa8AeпReT строки и добаапяет lines = text.split('\n') for i in range(len(lines»): lines[i] - '* , + lines[i] звездочки. t ЦИJCJJ по cnиску "lines" I доб8.8lUlет ЗВ&8доЧJCУ в KaJКДytO I строку в cnиске "lines" pyperclip.copy(text) Выполняя разбиение текста по символам новой строки, мы получаем СIIИ. сок, в котором каждый элемент списка располаrается в отдельной строке текста. Мы сохраняем этот список в строках, а затем проходим в цикле по всем элементам в этих строках. В начале каждой строки мы добавляем зве дочку и пробел. Теперь каждая текстовая строка начинается со звездочки. Шаr 3. 06.еll.нен.е .,.ененн.,х (трок Теперь строки списка содержат измененные строки, начинающиеся со звездочек. Но метод pyperclip. сору () ожидает значение в виде одиночной строки, а не списка строковых значений. Чтобы создать это одиночное строковое значение, передайте строки методу j oin () для их объединения в одну строку. Дополните проrраммный код, как показано ниже. #! руthопЗ # bulletPointAdder.py  Добавляет маркеры Википедии в начало # каждой строки текста, coxpaHeHHoro в буфере обмена. import pyperclip text = pyperclip.paste() 
186 r лова 6 # Разделяет строки и добавляет lines = text. split (1 \n') for i in rапgе(lеп(liпеs)): lines[i] = '* , + lines[i] звездочки. # цикл по списку "lines" # добавляет звездочку в каждую # строку в списке "lines" text - '\n'.join(lines) pyperclip.copy(text) В процессе выполнения проrрамма заменяет текст из буфера обмена тек- стом, в начале каждой строки KOToporo располаrаются звездочки. Этот код пред(тавляет завершенный вариант проrраммы, и вы можете попытаться выполнить ее в отношении текста, скопированноro в буфер обмена. Даже если вы не испытываете потребности в автоматизации :той дo вольно специфической задачи, вам MOryт понадобиться друrие виды мани пуляций с тек(:том, такие как удаление замыкающих пробелов в конце строк или преобразование текста в верхний или нижний реrистр. Какой бы ни была специфика ваших запросов, вы можете использовать буфер обмена для выполнения операций ввода и вывода. Резюме 1скст  распространенная форма представления данных, и PyLllOll по-- ставляется с множеством полезных методов, предназначенных для обра- ботки текста, сохраненнш'о в строковых значениях. Вы можете использо-- вать индексирование, ИЗWlечение срезов и строковые методы практически в любой проrpамме на Python, которую будете пи(ать. Проrраммы, которые вы сейчас пишете, кaжyrся не слишком сложными: они не имеют rрафическоrо интерфейса пользователя с красочными изо-- бражениями и цветным текстом. Пока что вы отображаете текст с помо щью метода print () и предостаWlяете пользователю возможность вводить текст, используя метод input () . Однако пользователь может ускорить ввод больших объемов текста посредством буфера обмена. Эта возможность открывает широкий простор для написания nporpaмM, манипулирующих большими объемами текстовых данных. Пусть даже эти проrраммы не со- держат окон или rрафики, но они Moryr быстро выполнять массу полезной работы. Дрyroй способ манипулирования большими объемами текста  это чт ние и запись текста путем непосредствеШlOrо обмена данными с жестким диском. О том, как это делается с помощью РytlЮI1, вы узнаете из следую. щей rлавы. 
Манипулирование строками 187 Контропьные вопросы 1. Что такое экранированные символы? 2. Что представляют собой экранированные символы \п и \ t? 3. Как добавить символ обратной косой черты (\) в строку? 4. Строковое значение "Howl' s Moving Castle"  это допустимая строка. Почему она не вызовет ошибку, несмотря на наличие неэкранирован Horo символа апострофа в слове Howl' s? 5. Если вы не хотите вставлять символ \п в свою строку, то как вы напи шете строку, содержащую символы новой строки? 6. Каковы будут результаты вычисления приведенных ниже выражений? . 'Hello world!' [1] . 'Helloworld!' [0:5] . 'Helloworld!' [:5] . 'Helloworld!'[3:] 7. Каковы будут результаты вычисления npиведенных ниже выражений? . 'Hello' .upper() . 'Hello' .upper() .isupper() . 'Не Но'. upper () .lower () 8. Каковы будут результаты вычисления приведеllНblX ниже выражений? . 'RememЬer, rememЬer, the fifth of NovernЬer. '.split() . ''. join ('There сап Ье only опе. ' . split () ) 9. Какие методы используются для выравнивания строки 110 левому краю, правому краю и центру? 10. как удалить пробельные символы в начале и конце строки? Учебный проект Чтобы закрепить полученные знания на практике, напишите проrрамму для предложенной ниже задачи. т",..,,.... ....OIII1".HIIX Напишите функцию printTable (), которая принимает список списков строк и отображает ero в виде аккуратной таблицы с выравниванием тек(:та по правому краю в каждом столбце. Исходите из предположения, что вну- тренние списки будyr содержать одно и то же количество строк. Например, список Mor бы выrлядеть так. 
188 rЛQВQ 6 tableData [['app1es', 'oranges', 'cherries', 'Ьапапа'], ['A1ice', 'ВоЬ', 'Caro1', 'David'], ['dogs', 'cats', 'moose', 'goose']] Manipu1ating Strings 143 ВЫВОД функции printTable () будет примерно таким. app1es oranges cherries Ьапапа Alice dogs ВоЬ cats Carol moose David goose Подсказка. Ваш код должен в первую очередь найти самую длинную строку в каждом из внyrренних списков; ширина столбца должна быть дo стаТОЧIIО большой для 1'01"0, чтобы В нем уместилась любая строка. Значения максимальной ШИРИНЫ столбцов MOryr храниться в виде списка целых чи. сел. Код функции printTable () может начинаться с инструкции co1Widths = [О) * 1еп (tableDa ta) , которая создает список, содержащий значения О в ко-- личестве, равном количеству внyrренних списков в списке tab1eData. Таким образом, в элементе co1Widths [О] может храниться ширина самой длинной строки, содержащейся в tableData[O], в элементе colWidths [1]  ширина самой длинной строки, содержащейся в tableData [1] и т.Д. Затем вЫ може- те найти самое большое значение в списке colWidths, чтобы определить, какое целочисленное значение ширины следует передать строковому ме- тоду rjust (). 
ЧАСТЬ 11 АВТОМАТИЗАЦИЯ ЗАДАЧ 
ПОИСК по ША&пону с ПОМОЩЬЮ РЕrупярных ВЫРАЖЕНИЙ Вероятно, вам не раз приходилось выполнять текстовый поиск, вводя слова, которые нужно найти, и нажимая клави II1И <Ctrl+F>. Рееулярuш выражения представляют собой сле.- дующий шar в этом нанраWlении: они позволяют задавать uщбло1t искомоro текста. Вы можете не знать телефонный номер компании, но если вы живете в США или Канаде, то вам известно, что он будет включать три цифры, за которыми следуют де- фис и еще четыре цифры (в самом начале номера может указываться необя зательный территориальный код). Зная этот шаблон, можно сразу же IIpeд ПОЛОЖИТЬ, что цифры 4155551234 означают телефонный номер, а цифры 4,155,551,234 таковым не ЯWlяются. Как ни полезны реryлярные выражения, лишь немноrие из тех, кто не является ПРOl'раммистом, знают, что это такое, хотя большинство COBpe меНIlЫХ текстовых процессоров, таких как Microsoft Word или ОрспОШсс, прсдлarают средства IIОИСка и замсны, основанные на использовании peI)' лярных выражсний. Реryлярные выражения сэкономят массу времени не только пользователям, но и проrраммистам. Более Toro, по мнению KaHaд cKoro писателя Кори Доктороу, изучение реryлярных выражений ДОЛЖНО предшествовать изучению caмoro проrpаммирования: "Знание [реryлярных выражений] может означать разницу между ре- шением задачи за 3 действия или за 3000 действий. Korдa ты дока в проrраммировании, то леrко забываешь о том, что на задачу, решение которой стоит тебе нажатия какой-то пары клавиш, дрyrие MOryт по- тратить несколько дней напряженной работы, чреватой внесением дополнительных ошибок"l. ] Cory Doctorow, "Here's what ICT shou}(l really (еаС)l kids: how to do regular expressions," Guardian, December 4, 2012, http://www.theguardian.com/ technology/2012/dec/04/ictteachkids regularexpressions/.I1t.. 
192 r лава 7 в этой rлаве вы сначала напишете проrрамму для нахождения образцов текста без использования реryлярных выражений, а затем увидите, как реryлярные выражения позволяют сделать то же самое с использованием rораздо более ЯСlюrо кода меньшеrо объема. Я продемонстрирую вам, I<ак работают простые шаблоны на основе реryлярных выражений, после чеro мы займемся более сложными операциями, такими как замена строк и соз дание собственных символьных классов. Наконец, в завершение rлавы вы напишете проrpамму для автоматическоrо извлечения телефонных HOMe ров и адресов электронной почты из блока текста. Поиск образцов текста без испопьзования реrупярных выражений Предположим, вы хотите найти телефонный номер, содержащийся в строке. Исходный текстовый шаблон вам известен: три цифры, дефис, три цифры, дефис, четыре цифры, например 4155554242. Для проверки соответствия строки данному образцу мы будем исполь- зовать функцию isРhопеNшnbеr (). возвращающую одно из двух возможных значений: Trиe или False. Откройте в файловом редакторе новое окно, B дите в нем приведенный ниже код и сохраните ero в файле isphoпeNиmЬer.py. def isPhoneNиmber(text): . if len (text) ! = 12: retиrn False for i in range(O, З): . if not text[i].iSdecimal(): retиrn Fa1se . if tехt[З] != '': retиrn False for i in range(4, 7): . if not text[i].isdecimal(): retиrn False . if text[7] != '': retиrn False for i in range(8, 12): Ф if not text[i].isdecimal(): retиrn False . retиrn Trиe print('4155554242  это телефонный номер:') print(isPhoneNиmber('4155554242'») print('Moshi moshi  это телефонный номер: ') print(isPhoneNиmber('Moshi moshi')) Выполнив эту проrрамму, вы должны получить следующий вывод. 
ПОИСК ПО шаблону с ПОМОЩЬЮ реryлярных выражений 193 4155554242  это телефоннь номер: Trиe Moshi moshi  это телефонный номер: False Код функции isPhoneNumber () выполняет несколько видов тестов, цель которых  проверить, представляет ли содержащаяся в переменной text строка телефонный номер в корректном формате. При отрицательном ис ходе любой из проверок функция возвращает значение False. Сначала про-- веряетси, содержит ли строка в точности 12 символов О. Затем выполняет-- си проверка Toro, что территориальный телефонный код (т.е. первые три символа текста) состоит только из цифр О. Остальная часть кода функции проверяет соответствие строки формату шаблона телефонноrо номера: за территориальным кодом следуют первый дефис О, еще три цифры О. еще один дефис О и, наконец, еще четыре цифры ф. Если ПрOl'рамме удается выполнить все виды проверки, то возвращается значение True О. При вызове функции isPhoneNиmber () с apryмellToM '4155554242' воз- вращается значение True. Вызов функции isPhoneNumber () с apryмeHToM 'Moshi moshi' возвратит значение False; при этом не будет пройден уже первый тест, поскольку количество символов в строке 'Moshi moshi' отли- чается от 12. Для нахождения приведенноrо выше TeKcToBoro образца в строке боль- шеr() размера вам потребуется написать еще больше кода. Замените послед- ние четыре вызова функции print () в файле isPhoпeNuтber.frY следующим кодом. message = 'Позвони мне завтра по номеру 4155551011. 4155559999  это телефонный номер Moero офиса.' for i in range(len(message)): . chunk = message[i:i+12] . if isPhoneNumber (chunk) : рriпt('Найденный телефонный номер: I + chunk) print ( 'rOTOBO' ) Выполнив эту nporpaMмy, вы должны получить следующий вывод. Найденный телефонный номер: 4155551011 Найденный телефонный номер: 4155559999 rOTOBO На каждой иrерации цикла for переменной chunk присваиваются очеред' ные 12 последовательных символов строки message О. Например, на первой итерации i равно О, и переменной chunk присваиваеl'СЯ срез message [о: 12 ] 
1М rnOBO 7 (т.е. строка' Позвони мне '). На следующей итерации i равно 1, и lIеремен- ной chunk присваивается срезmеssаgе [1: 13] (т.е. строка 'озвони мне з'). Вы передаете очередную порцию строки функции isPhoneNumber ( ) , что- бы проверить, соответствует ли она образцу телефонноrо номера О, и если это действительно так, то выводите эту часть строки на экран. Циклический просмотр строки message продолжается, и в конечном счете будут обнаружены 12 символов, представляющих телефонный но. мер. В ходе выполнения Bcero цикла просматривается вся строка, причем каждый раз тестируются части строки длиной 12 символов и выводят ся те из них, которые удовлетворяют условиям, проверяемым в функции i sPhoneNumber () . По окончании анализа всей строки message будет выведено слово rOTOBO. В то время как содержащаяся в данном примере впеременной message строка очень короткая, в друrих случаях она может содержать миллионы символов, и тем не менее проrрамма выполнится менее чем за секунду. Аналоrичная проrрамма, выполняющая поиск телефонных номеров с П0 мощью реryлярных выражений, также будет выполняться менее ceКYHДьr, однако реryлярные выражения позволяют тратить на написание подобных проrрамм rораздо меньше времени. Поиск образцов текста с помощью реryпярных выражений Предыдущая проrрамма для нахождения телефонных номеров вполне функциональна, однако, учитывая ее относительно большой размер, пред- лаrает лишь оrраниченные возможности: функция isphoneNumber () вклю- чает 17 строк кода, но способна нахОДить телефонные номера, соответ, ствующие только одному виду шаблона. Что если телефонный номер будет записан в строке в формате 415.555.4242 или (415) 5554242? Что если теле- фонный номер включает дополнительные цифры и выrлядит, например, так: 4155554242 х99? Функция isPhoneNumber () не в состоянии идентифи- цировать подобные номера. Чтобы учесть дрyrие допустимые шаблоны, вы моrли бы написать дополнительный код, однако существует rораздо более простой способ. Компактное описание текстовых шаблонов возможно с помощью pery- лярных выражений. Например, реryлярному выражению \d соответствует любой цифровой символ, Т.е. любая одиночная цифра от О до 9. Реryлярное выражение \d\d\d\d\d\d\d\d\d\d используется в Python для нахождения текстовых строк 1'01"0 же формата, что и в случае функции isPhoneNumber ( ) : строка из трех цифр, дефис, еще три цифры, дрyrой дефис и еще четыре цифры. Никакая дрyrая строка не будет соответствовать реryлярному BЫ ражению \d\d\d\d\d\d\d\d \d\d. 
Поиск ПО шаблону с помощью реrулярных выражений 195 Вместе с тем реryлярные выражения MOryт быть l'ораздо более СЛОЖНbl ми. Например, указав 3 в ФИl'УРНblХ скобках ( (3) ) после шаблона, мы co общаем следующее: "Искать троекратное соответствие этому шаблону". Поэтому корректному телефонному номеру будет соответствовать также следующий шаблон: \d{ 3} \d{3}\d{4}. '0111"..' 06.,.108 'евех В PytllOn все функции, предназначенные для работы с реryлярными BЫ ражсниями, содержатся в модуле re. Введите в интерактивной оболочке ОIедующую команду для Toro, чтобы импортировать этот модуль: >>> ilDport re Примечание Для вЫnОЛНe1tuя бол'Ьшuнства nос.ледующuх npшtеров z.ла8'bl вам потребуется мо- дул'Ь re, поэтам у не заб'bl8аuте uмnopтиpoвaт'Ь еео в но:чале люfЮeо Сl/.епаpuя, кото- рый напишете, шu при каждом nepl!3anycкe IDLE. В npomивШJМ случае вы noлучu- те сообще1tuе об ошибке NaтeError, z.ласящее о 'Т1UJМ., чmo имя ' re' не оnредeлeuо. Вызов метода re. compile () с передачей ему CTpoKoBoro значения, пред- ствляющеrо реryлярное выражение, возвращает объект соответствующеrо реryлярноrо выражения (или, как их принято называть, объект Regex). Чтобы создать объект Regex, совпадающий с шаблоном телефонноro но- мера, введите в интерактивной оболочке следующую команду (вспомните, /" Передача 11 сырых" строк методу re. COIIIpile () Вспомните, что в Python ДЛЯ экранирования символов используется символ аб- ротной косой черты (\). Строковае значение' \n' представляет одиночный символ новой строки, а не символ обратной косай черты, за котарым следует строчная бук- ва n. Чтобы выеести символ обратной косой черты, вам патребуется ero экрани ровать (\ \). Таким образом, СТрОКа '\ \n' представляет символ обратной косой черты, за которым следует строчная буква n. Однако, поместив символ r перед пер- вым апострофом cтpoKoBoro значения, вы можете указать, что это и сырая" строка, в которой символы не экранированы. Поскольку символы обратной косой черты часта используются в реryлярных вы- ражениях, удобно передавать методу re. compile () "сырые" строки, а не вводить дополнительные символы обратной косой черты. Ввести r' \d\d\d\d\d\d\d\d\ d\d' HOMHoro проще, чем' \ \d\ \d\ \d \ \d\ \d\ \d \ \d\ \d\ \d\ \d', ./ 
196 rЛова 7 что выражение \d означает "цифровой символ", а выражение \d\d\d\d\d\ d\d\d\d\d  реryлярное выражение, соответствующее шаблону корректно- ['о телефонноrо номера): »> phoneNumRegex = re.compile(rf\d\d\d\d\d\d\d\d\d\d') Теперь в переменной phoneNиrnRegex содержится объект Regex. пO.tK too,.e,t,.., 06.ек,аll 'е,ех Метод search () объекта Regex ищет в переданной ему строке любые сов. падения с реryлярным выражением. В случае отсyrствия в строке совпадс. ний с реryлярным выражением он возвращает значение None. Е(:ли совпа- дения обнаружены, то метод search () возвращает объект Match. Объекты Match имеют метод group (), который возвращает найденные соответствия шаблону, (О том, что такое rруппы, будет сказано ниже.) В качестве нриме- ра введите в интерактивной оболочке следующие команды. »> phoneNumRegex = rе.сошрilе(r'\d'd\d\d'd\d'd\d'd\d') »> шо = рhоneNumRegех.sеаrch('Мой номер: 41S5554242. ') »> рrint('НайденНblЙ тепефонНblЙ номер: ' + шо.grоuр(» Найденный телефонньм номер: 4155554242 Здесь то  это не более чем произвольное имя, выбранное в качестве ти повоrо имени переменной для работы с объектами Ма tch. Возможно, этот пример поначалу покажется вам слишком сложным, но он значительно ко- роче представлснной ранее проrpаммы isPhoneNumЬer.py, хотя делает то же самое. В этом примере мы передаем желаемый шаблон методу re. compi le ( ) и сохраняем результирующий объект Regex в переменной phoneNumRegex. Затем мы вызываем метод search () для переменной phoneNurnRegex и пер даем ему строку, в которой хотим найти соответствия шаблону. Результат поиска сохраняется в переменной то. В данном при мере нам известно, что искомый образец будет найден в строке, и поэтому мы знаем, что метод search () вернет объект Match. Зная, что переменная то содержит объект Match, а не нулевое значение None, мы можем вызвать для нее метод groиp () , возвращающий найденное соответствие. Передача функнии print () выра- жения то . group () обеспечивает вывод получеШlOrо соответствия шаблону, Т.е. отображение телефонною номера 4155554242. 
Поиск ПО шаблону с ПОМОЩЬЮ реrулярных выаженнйй 197 nOllltlro.". про"му", ПО.,",, ,oorвeTa... реryл.рно.у .",,,,.енн. Процедура использования реryлярных выражений включает несколько шаrов, каждый из которых довольно прост. 1. Импортируйте модуль regex С помощью инструкции import re. 2. Создайте объект Regex с помощью функции re. compi le ( ) . (1 le забы вайте о том, что необходимо использовать "сырую" строку.) 3. Передайте строку, в которой хотите ВЫIЮЛНИТЬ поиск, методу search () объекта Regex. Этот метод возвращает объект Match. 4. Вызовите метод group () объекта Мatch, который вернет строку, содер- жащую фактический найденный текст, соответствующий данному ре... ryлярному выражению. Прuмечаuuе Вся'ЧеС1СU поощряя са.м'оcmОЯтeJlЪUое въtnолuеиие вами 'кода nPUltte/Joo в иитфа",тuв- нoU оБОJlО'Ч",е, я ре1Сом,e1tдую uаРяду с этим uспол'ЬЗоватъ пРедЛа2аемые в Инmepжте тестер'Ы реzyлярu'Ых въфажеиuй, "'omojJые дают тО'ЧuуюкаРтUlIУ соответствия реZYЛЯрUЪtХ въtpаЖe1tuй фРаzмеuта.м ooeaeuuozo вами текста. Лu'Чuо я пojJeкo-м,eи- дова.л бы вам воспол'ЬЗоватъся mecтepoM. ",оторый предлаzaemся 'На caurne h t tp: 11 regexpal. сот/. Друrие возможные wабпоны реryпярных выражений Теперь, Korдa основные Шal'И по созданию и нахождению объектов pcry лярных выражений с помощью Python вам уже известны, вы вполнс roтопы испытать более мощные возможности поиска 110 ша6лонам. 'oJДtlH.e (рупп , по.о",,,. "рyrлыж ,,,o6o,, Предположим, вы хотите отделить территориальный код от осталыюй части телефонноrо номера. 1(о6авление круrлых скобок ПрИ80ДИТ к созда нию rрупп В реryлярных выражениях: (\d\d\d)  (\d\d\d\d\d\d\d). Послс этоrо вы можете использовать метод group () объекта совпадения для захва та совпавшею текста, соответствующеrо только одной rруппе. Псрвый набор КРУl'Лых скобок в строке реryлярноrо ВЫР<lЖения будет rpYIl ной 1. Второй набор будет IРУППОЙ 2. Передавая целые числа 1 или 2 методу group () объекта совпадения, вы мож<-"Те избирательно захватывать различ lIые части совпаВШеl'О текста, Если методу group () передается О или вообще lIичеrо не передается, то он возвращает весь найденный текст, COOTBeтCTBY ющий шаблону, Введите в интерактивной оболочке следующие команды. 
198 rЛQВQ 7 >>> phoneNwuRegex = re.OOIIIpile(r' (\d\d\d)(\d\d\d\d\d\d\d) ') »> ао = рhоneNUВRegex.sеаrch('НЬй номер: 4155554242.') »> mo.group(l) '415 ' »> mo.group(2) '5554242' »> mo.group(O) '4155554242' »> mo.groupO '4155554242' Если вам нужно ИЗWlечь сразу все rРУl1ПЫ, используйте метод groиps () (обратите внимание на показатель множественноrо числа в имени мето- да  букву s). >>> ао. groups О ( , 415', '5554242 I ) »> areaCode, mainNU8ber = mo.groups() »> print(areaCode) 415 »> print(mainNuaber) 5554242 Поскольку метод то . groups () возвращает кортеж, состоящий из несколь- ких значений, вы можете использовать трюк с rрynповым присваиванием, коrда каждое значение присваивается отдельной переменной, как в строке areaCode, mainNиmbe r = то. g roиps () приведенноrо выше кода. Крyrлые скобки имеют в реryлярнbIX выражениях особый смысл, но что если вам нужно находить в тексте сами скобки? Ведь, например, в телефон- ных номерах круrлые скобки часто используются для выделения террито-- риальноrо кода. В подобных случаях символу ( или ) должен предшество- вать символ обратной косой черты. Введите в интерактивной оболочке следующие команды. >>> phoneNwllRegex = re.OOIIIpile(r' (\(\d\d\d\» (\d\d\d\d\d\d\d) ') > > > ао = phоneNшlRegex. еепсЬ ('НЬй номер:  (415) 5554242.') »> mo.group(l) , (415) , >>> mo.group(2) '5554242' Экранированные символы \ ( и \) в "сырой" строке, передаваемой ме- тоду re. compile ( ) , означают соответствие фактическим символам крyrлых скобок. 
Поиск по шаблону с помоЩЬЮ реryлярных выражений 199 'ы60р аЛ6rер.аr...6IХ (рупп t пО.ОЩ61О канала Символ I называется кана.ло.м. Вы можете использовать ero всеrда, Korдa хотите находить соответствие одному из нескольких аJII.тернативных BЫ ражений. Например, реryЛЯРIIОМУ выражению r' Batman I Tina Fey' будут со-- ответствовать как строка 'Babnan', так и строка 'Tina Fey'. Если в исследуемой строке содержатся обеэти строки, то в объекте Мatch будет возвращена та из них, которая встретится первой. Введите в интерак тивной оболочке следующие команды. »> heroRegex = re.сощрilе (r'ВatmanITina Fey') >>> шоl = heroRegex. search ( 'Ваtшan and Tina Fey.') >>> шоl. group () 'Batman' »> ш02 = heroRegex.search('Tina Ьу and Ваtman. ') >>> шо2. group () 'Tina Fey' ПjJuечаuие Для поиска всех совпадений с шаблоно.м .мОЖ1l0 UСnОЛ'Ь.10ваrпъ .метод Eiпdal1 (), котор'ый обсуждается в разделе "Метод Eiпda 11 () ". Для выбора альтернативных вариантов при поиске совпадений можно использовать канал. Предположим, например, что вы заинтересованы в поиске соответствий любой из строк 'Batman', 'Batmobile', 'Batcopter' и 'Batbat'. Поскольку в начале каждой из них содержатся символы Bat, было бы неплохо иметь возможность задать этот префикс только один раз. Это можно сделать с помощью круrлых скобок. Введите в интерактивной оболочке следующие команды. »> batRegex - rе.сошрilе(r'Ваt(шanlшОЬilеlаорtеrIЬаt) ') >>> 1110 = batR8gex.search('BatmObile потеряп капесо') >>> шо. group () I Batmobile' >>> шо.grоuр(l) 'mobile' Вызов то. group () возвращает весь совпавший с шаблоном текст, Т.е. строку' Batmobile' , тоrда как вызов то. group (1) возвращает лишь часть совпавшеrо текста, соответствующую первой rруппе в круrлых скобках, Т.е. 'mobile'. Используя символ канала и rруппирование с помощью Kpyr лых скобок, вы можете указать несколько альтернативных шаблонов для поиска соответствий с помощью реryлярноrо выражения. 
200 rЛQВQ 7 Если требуется поиск соответствий самому символу канала, то в pery лярном выражении ему должен предшествовать символ обратной косой черты: \ 1. YIlOJtlH.e .е06.JtneЛ6.0i ""n61 ,...оло. , nOllО"61О '0Ilp.."1I6.0'0 ,нtI"" Иноrда возникает необходимость в шаблонах, содержащих необязателъ- Ilые символы или rpynпы символов. Это означает, что реryлярное выраж ние должно найти соответствие, Независимо от TOro, содержит ли оно H который фраrмент текста. Символ? указывает на то, что предшествующая ему rруппа является необязательной частью поисковоro шаблона. Введите в интерактивной оболочке следующие команды. »> batRegex = re.СОЩРil&(r'Ваt(wо)?aan') »> ао1 = batRegex.search('Тb& Adventures of Вatllan') >>> шо1. group () 'Batman' >>> ао2 = batRegex.search('Тbe Adventures of Вatwoman') >>> ао2. group () 'Batwoman' Часть (wo)? реryлярноrо выражения означает, что шаблон wo является необязательной rруппой. Реryлярному выражению будет соответствовать текст, в котором 110ДСТрОка wo либо вообще не встречается, либо встреча- ется только один раз. Именно поэтому данному реryлярному выражению соответствует как слово 'Ба twoman ' , так и слово 'Ва tman ' . Используя ранее приведенный пример телефонноrо номера, вы можете составить реryлярное выражение, находящее номера, которые MOryт содер- жать, а MOryт и не содержать территориальный код. Введите в интерактив- ной оболочке следующие команды. »> phoneRegex = re.coapile(r'(\d\d\d)?\d\d\d\d\d\d\d') »> шо1 - рhоneRegex.sеаrch('Мой номер: 41SSSS4242') >>> JlЮ1. group () '4155554242' »> ао2 = рhоneRegex.sеarch('Мой номер: S554242') »> 802. group () '5554242' Считайте, что символ? имеет следующий смысл: "Искать соответствие тексту, в котором rРУПllа. предшествующая вопросительному знаку, BCTpe чается нуль или один раз". 
Поиск по шаблону с помощью реryлярных выражений 201 Если вам необходимо находить соответ(твие самому вопросительному знаку, то в реryлярном выражении ему должен предшествовать символ об.- ратной косой черты: \ ? У"tlзt1н.е (oorвe,т.. (руппе t...оло., по""р..ще." .УЛ6 .л. .еt"ОЛ6"0 РIlЗ, t пО.ОЩ61О з.е3ll0.". Символ * ("звездочка") означает "совпадение с нулевым или большим количеством экземWlЯрОВ", Т.е. rрynпа, предшествующая звездочке, может встречаться в тексте любое количество раз подряд. Она может либо вообще отсуктвовать, либо повторяться снова и снова. Вновь вернемся к примеру с Batm.an. »> batR8gex - re.соmрilе(r'Ваt(wo)*шan') >>> аоl - batReq&x. search ( 'Тh& Adventures of Ваtшan') »> mol.groupO 'Batman' »> ао2 - batReqex.search('The Adventures of Batwoun') >>> mo2.group() 'Batwoman' »> mоЗ = Ьа tRegex. search ( 'Тh& Adventures of BatW01f01fowoman') »> mоЗ.grоuр() 'Batwowowowoman' в случае слова 'Batm.an' часть (wo) * реryлярноrо выражения соответству- ет нулевому количеству (т.е. отсyrствию) экземпляров rруппы wo в строке; в случае слова' Batworoan' часть (wo) * совпадает с одним экземпляром wo; а в случае слова 'Ва twowowoworoan' часть (wo) * совпадает с четырьмя экземпля- рами rрynпы wo. Если вам необходимо находить соответствие самому символу звездочки, то в реryлярном выражении ему должен предшествовать символ обратной косой черты: \ * . УКtlЗt1н.е tOO".,t".. ОДНО.У .л. неt"ОЛ6".. по.,орен... (руппы t пО.ОЩ61О п".ttl Если символ * в реryлярном выражении означает "совпадение с нулевым или большим количеством экземпляров", то символ + ("плюс") означает "совпадение с одиночным или большим количеством экземпляров". В отли- чие от символа звездочки, который не требует появления предшествующей rруппы в исследуемой строке, rруппа, предшествующая плюсу, должна поя- виться в строке хотя бы один раз. Такая rруппа не является необязательной. 
202 r лова 7 Введите в интерактивной оболочке следующие команды и сравните полу ченные результаты с результатами из предыдущеrо раздела, относящимися к реryлярным выражениям со звездочкой. »> batR8g_x - re.аошрilе(r'Ваt(wо)+шan') »> шо1 = batRegex.search('Тhe Adventures of Batwoman') >>> шо1. group () 'i3atwomaп, »> 802 - batReq_x. search ( 'The Adventures of Ваtwоwоwо_ошan') >>> ш02. group () 'Batwowowowoman' >>> шоЗ = batRegex.search('Тhe Adventures of Ваtman') »> шоЗ == None True Реryлярное выражение Bat (wo) +man не найдет соответствия в строке 'The Adventures of Batman', поскольку плюс требует наличия по крайней мере ОДНОI'О экземпляра подстроки wo. Если вам необходимо находить соответствие самому символу плюса, то в реryлярпом выражении ему должен предше(:твовать символ обратной ко-- сой черты: \ +. 'КII3I1н.е (001leT"... опреllе.е..о.у"о...е".у по.торен.' (ру..", ( .0.ОЩ61О Ф.rур.",х (ко60к Если у вас имеется rрynпа, которую вы хотите повторить определенное количество раз, укажите за ней число необходимых повторений в фиrypных Сlюбках в своем реryлярном выражении. Например, реryлярному выраже- нию (На) (3} будет соответствовать строка 'НаНаНа', но не строка 'НаНа I , по-- скольку В последнем случае I'рynпа (На) повторяется BcerO лишь два раза. Вместо одноrо числа вы можете указать диапазон, записав в фиrypных скобках значения минимальноrо И максимальноrо числа допустимых по- вторений, разделенные запятой. Например, реryлярному выражению (На) {3, 5} будут соответствовать строки 'НаНаНа', 'НаНаНаНа ' и 'НаНаНаНаНа 1. Как первое, так и второе из этих чисел в фиryрных скобках MOryт опу- скаться, оставляя неоrpаниченными минимальное и максимальное количес- тва повторений. Например, реryлярному выражению (На) {З, } будут соот- ветствовать три и более экземпляров rруппы (На), тоrда как реryлярному выражению (На) ( , 5} будут соответствовать от нуля ДО пяти экземпляров. Фиryрные скобки позволяют запи(:ывать реryлярные выражения в более компактном виде. Следующим двум реryлярным выражениям соответству- ют идентичные шаблоны. 
Поиск по шаблону с помощью реryлярных выражений 203 (На) {3) (На) (На) (На) Этим двум реryлярным выражениям также соответствуют идентичные шаблоны. (На) {3,5) ( (На) (На) (На)) I ( (На) (На) (На) (На) ) I ((На) (На) (На) (На) (На)) Введите в интерактивной оболочке следующие команды. »> haRegex = re.сошрilе(r' (На)(3}') »> 801 = haRegex. search ( 'НаНаНа 1) >>> шо1. group () 'НаНаНа' »> ш02 = haRegex.search('Ha') »> ш02 == None True Эдесь реryлярное ВЫРC:lЖение (На) {3 I совпадает со строкой 'НаНаНа', 110 не со строкой 'На I . Поскольку оно не соответствует строке 'На', метод search () возвращает значение None. Жадный и неж:адный виды поиска Поскольку реryлярному выражению (На) {3, 51 MOryr соответствовать три, четыре или пять вхождений На В строке 'НаНаНаНаНа ' , вас может удив лять, почему вызов объектом Match метода groиp () В предыдущем приме ре с фиryрными скобками возвращает строку 'НаНаНаНаНа ' , а не ее более короткие 110ДСТрОКИ. В конце концов, строки 'НаНаНа' и 'НаНаНаНа ' также представляют (обой действительные соответствия реryлярному выраже нию (На) {З, 51. Реryлярные выражения Python по умолчанию являются жад'НьtМu в том смысле, что в lIеоднозначных ситуациях они бyдyr пытаться COOTBeтCTBO вать как можно более длинной строке. Нежадиая версия фиrypных скобок, которая пытается соответствовать самой короткой из возможных строк, характеризуется наличием вопросительноrо знака сразу за закрывающей фиrypной скобкой. Введите в интерактивной оболочке следующие команды и обратите вни мание на ра:JЛИЧИЯ между жадной и нежадной формами фиryрных скобок при выполнении поиска в одной и той же тестируемой строке. 
2м r лава 7 »> qreedyHaRegex = А. compi1e (r' (На) {3, S} , ) > > > 1101 .. qreedyНaReqex. _earch ( 'НаНаНаНаНа' ) »> 1I01.groupO 'НаНаНаНаНа' »> nonqreedyHaReqex = rе.сошрilе(r' (Ha){3,S}?') »> 11102 .. nonqreedyHaReqex. _earch ( 'НаНаНаНаНа I ) >>> 11102. qroup () 'НаНаНа' Обратите внимание на то, что в реryлярных выражениях ВОIIроситель ный знак может использоваться в двояком смысле: указывать на нежадный поиск и обозначать необязательную rруппу. Эти две el'o функции никак между собой не связаны. Метод findall () В дополнение к методу search () объекты Regex имеют метод findall ()" Если метод search () возвращает объект Match первоrо совпадения, найден- Horo в тестируемой строке, то метод findall () возвращает строки каждом из найденных совпадений. Чтобы увидеть, как метод search () возвращает лишь объект Match для первом найденноrо вхождения совпадающеrо тек- ста, введите в интерактивной оболочке следующие команды. >>> phon8NuDlRsgex .. re.compile(r'\d\d\d\d\d\d\d\d\d\d') »> 110 = рhоneNumRegex._еarch('СО8ЫЙ: 41SSSS9999 Рабочий: 212SSSOOOO') >>> 110. qroup () '4155559999' с дрyrой <:тороны, метод findall () возвратит не объект Match, а список строк, 'Колъ cк()jJo 8 рееу.ляjmо.м 8ъtpажеиuu отсутствуют еруnn'Ь'. Введите в инте- рактивной оболочке следующие команды. >>> phoneNUIIIR8gex ·  re. cOlllpile (r ' \d\d\d \d\d\d \d\d\d\d') * не ИИ88'l' :rpvnn »> рhоneNumRegех.findall('СО'l'оаый: 41SSSS9999 Рабочий: 212SSSOOOO') [' 4155559999', '212555OOOO'] В случае же nалu'Чuя rрупп в реryлярном выражении метод findall () воз- вратит список кортежей. Каждый кортеж представляет найденное cooт ствие, а ero элементы  совпавшие строки для каждой rруппы в реryлярном выражении. Чтобы увидеть, как работает метод findall () , введите в инте- рактивной оболочке следующие команды (обратите внимание на то, что 
Поиск по шаблону с помощью реryлярных выражений 205 теперь в компилируемом реryлярном выражении имеются rРУПIIЫ, заклкr ченные в круrлые скобки). »> phon.NumReq.x ·  х-е .cODIpil. (r' (\d\d\d)  (\d\d\d)  (\d\d\d\d) ') #имн'1' 1"РУIUUol »> рhоneNuшRвqех.findall('СО'1'СВЫЙ: 41SSSS9999 Рабочий: 212SS50000') [('415', '555', '1122'), ('212', '555', '0000')] Подведем краткое резюме работы метода findall ( ) . 1. Будучи вызванным для реryлярноrо выражения, не содержащеrо rpynII, например \d\d\d\d\d\d\d\d\d\d. метод findall () возвращает список совпавших строк, такой как ['415555 9999', '2125550000']. 2. Будучи вызванным для реryлярноro выражения, содержащеl'О rрynпы, например (\d\d\d)  (\d\d\d)  (\d\ d\d\d), метод findall () возвращает список кортежей строк (по одной строке для каждой rpYl1l1bI), такой как [('415', '555', '1122'), ('212', '555', '0000')]. Симвопьные кпассы Из предыдущих примеров вам уже известно, что сокращение \d может представлять любую цифру. Таким образом, \d  это сокращенное обозна чение реryлярноrо выражения (О 1112 131 4 1 51 617 18 1 9) . Аналоrичные сокра- щения существуют для мноrих друrих символьных классов (табл. 7.1). Та6JIица 7.1. Сокращенн..е обоэначеНИII распроараненных символьных классов Сокращение \d \0 \w \w \s \8 Предста......... CllМIIOJI" Любаll цифра в диапазоне от О до 9 ЛlOбой СИМ80n, не IIВnIlIOЩИЙС. цифрой в диапазоне от О АО 9 Люба. буква, цифра ИJlИ CНМ80Jl подчеркиваНИII (так называемые сло.арные CНМ8OJlы) Любой симвм, не .8JlllIOЩИЙСII буквой, цифрой ИIIИ СИМВОJlОМ подчеркивани. Симвм пробела, табуJlllЦИИ ИJlИ новой строки (ток иазываемые пробепьные СИМВОJlЫ) Любой симвм, не 1I1J1111ОЩИЙСII СИМВОJlОМ пробела, таБУJlllЦИИ иnи новой СТРОКИ Символьные классы (клаССbl символов) удобно использовать дЛЯ KOM пактной записи реryлярных выражений. Классу символов [05] будет со- ответствовать любая одиночная цифра в диапа:юне от О до 5; такая запись rораздо короче, чем запись (О 111 2 1 з 1 4 15) . Введите в интерактивной оболочке следующие команды. »> XDa.Regex - x-e.compil.(r'\d+\.\w+') »> XDa.Reqex.findall('12 drumшer., 11 piper., 10 lord., 9 ladie., 
206 r лава 7  8 Dlaid8, 7 swans, 6 чвезе, 5 ring8, 4 Ьirdз, 3 hens, 2 doW8,  1 partridqe') ['12 drиmrners', '11 pipers', '10 10rds', '9 ladies', '8 maids', '7 swans', '6 geese', '5 rings', 14 birds 1, '3 hens', '2 doves', '1 partridge'] Реryлярному выражению \d+\s\w+ будет соответствовать текст, содер- жащий одну или несколько цифр (\d+), за которыми следует пробельный символ (\з), за которым следует один или нес.колько словарных символов  буква, цифра или символ подчеркивания (\w+). Метод findall () возвращает все совпавшие строки шаблона реryлярноrо выражения в виде списка. Соэдание собственных симвопьных кпассов ИНOI'да возникает необходимость в с..опоставлении реryлярноI'О Bыpa жения с символами из определенноrо набора, для KOToporo сокращенные символьные классы (\d, \w, \s и т.д.) оказываются слишком широкими. Вы можете создать собственный символьный клас.с, используя квадратные скобки. Например, символьному классу [aeiouAEIOU] будет соответствовать любая rласная, будь то в нижнем или верхнем реrистре. Введите в интерак тивной оболочке следующие команды. »> vowelRegex - rе.сошрilе(r'[аеiоuAEIОU)') >>> vowelReqex.findall('RoboCop eat8 ЬаЬУ food. ВАВУ FOOD.') ['о', 'о', 'о', 'е', 'а', 'а', 'о', 'о', 'Д', 'О', 'О'] в классы также можно включать диапазоны букв и цифр, используя де- фис. Например, классу [azAZO9] будуr соответствовать все буквы в ниж- нем и верхнем реrистрах, а также цифры. Обратите внимание на то, что в пределах квадратных скобок обычные символы реryлярных выражений не интерпретируются как таковые. Это означает, что перед символами ., *, ?, ( и ) не следует ставить символ обрат ной косой черты. Например, классу :O5.] будут соответствовать цифры от О до 5 и точка. Вы не должны записывать этот класс как [o 5 \ . ] . Поместив сразу за открывающей квадратной скобкой класса символ "крышка", или циркумфлекс (Л) ,можно создать u.uвертUр08ан'Н:ый CШtво.л:ьн'Ый 'КJЩСС. Инвертированному символьному клас(,,'У будет соответствовать любой символ, не принадлежащий исходному классу. Например, введите в инте- рактивной оболочке следующие команды. »> oOn8onantRegex = re.сошрilе(r' [AaeiouAEIOU) ') »> cOn8onantRegex.findall('RQboCop eat8 ЬаЬу food. ВАВУ FOOD.') ['R', 'Ь', 'с', 'р', I " 't', 's', I " 'Ь', 'Ь', 'у', , " If', 'd', '.', , " '8', '8', 'У', , " 'F', 'О', '.'] 
Поиск по шаблону с помоЩЬЮ реrУЛЯРНblХ Вblражений 207 Теперь вместо rласных реryлярному выражению будут соответствовать все символы, не являющиеся rласными. Симвоп крыwки И знак доппара Вы также можете использовать символ" ("крышка") в начале реryляр-- HOI'O выражения для указания 1'01"0, что соответствие рсryлярному Bыpa жепию должно находиты:я в nачале тестируемоro текста. Аналоrичным 0& разом можно поместить знак доллара ($) в конце реryлярноrо выражения для указания Toro, что строка должна заканчиваться данным шаблоном pe ryлярноrо выражения. Еще можно использовать символы" и $ совместно, для тою чтобы показать: данному реryлярному выражению должна соответ- ствовать вся строка, Т.е. совпадения с ним лишь какой-то подстроки будет недостаточно. Например, реryлярному выражению r' "Hello' будут соотвеТ<:твовать строки, начинающиеся СО слова' Hello' . Введите в интерактивной оболоч- ке следующие команды. »> beqinsWithHello = re.соmpilе(r'ЛНеllо') »> beqinsWithHello.search('Hello world!') <sre.SREMatch object; span(O, 5), matchIHello'> > > > beqin_Wi thHello. _earch ( 'Не _aid he1l0. ') == Ном True Реryлярному выражению r' \d$' будут соответствовать строки, ;)аканчи. вающиеся любой цифрой в диапазоне от О до 9. Введите в интерактивной оболочке следующие команды. »> endsWithNumber = rе.сощрilе(r'\d$') »> endsWithNumber._earch( 'Your number i_ 42') <sre.SREMatch object; span(16, 17), match12'> >>> endsWithNumber._earch( 'Your number i_ forty two.') == None True Реryлярному выражению r'" \d+$' будут соответствовать строки, KOT<r рые начинаются и заканчиваются одной или несколькими цифрами. Вве- ДИте 8 интерактивной оболочке следующие команды. »> wholeStrinqIsHum = rе.сошрilе(r'Л\d+$') »> wholestrinqI_Num._earch('1234567890') <sre.SREMatch object; span(O, 10), match'1234567890'> »> wholeStringIsNum. search( '12345xyz67890 1) == Ноne True »> wholeStrinqI_Num._earch('12 34567890') == None True 
208 r лава 7 Последние два вызова метода search () в предыдущем примере дeMOH стрируют необходимость совпадения с реryлярным выражением всей cтp<r ки, если в нем используются одновременно символы л и $. Вам нужно лишь твердо запомнить, что символ л относится к началу строки, а символ $  к концу. rрупповой симвоп Символ. ("точка") в реryлярных выражениях называется epynn08ЪLМ cuм- волом и представляет собой сокращенную форму записи символьноrо клас. са, совпадающеro с любым одиночным символом, за исключением символа новой строки. Например, введите в интерактивной оболочке следующие команды. »> atRegвx · r..сощрil.(r'.аt') >>> atReqex.findall('Тhe cat in the hat _at оп the flat IIUlt. ') [ 'cat '1 'hat 1, 1 sat', 'lat', 'mat'] Вспомните о том, что символу точки соответствует только одиночный произвольный символ, что И объясняет, почему в качестве совпадающеrо текста в слове flat была выбрана только подстрока lat. Если вы хотите искать соответствие самой точке, то в реryлярном выражении ей должен предшествовать символ обратной косой черты: \.. Ук",,,,,.е (00f8em... .",60_, ,екау ( 110110""'" ко_6.""".. иro.к",.е'1I0.к,,и Иноrда желательно, чтобы реryлярному выражению соответствовало все и вся. Предположим, например, что вы хотите найти совпадение со строкой I First Name:', за которой следует любой текст, за которым следует строка' Last Name:', а за ней  вновь любой текст. для представления этоro "любоrо текста" можно использовать комбинацию "точказвездочка" (. *). Вспомните о том, что символ. означает "любой одиночный символ, за ис ключением символа новой строки", а символ * означает "нуль или более вхождений предыдущеrо символа". Введите в интерактивной оболочке следующие команды. »> nameReqвx = rе.сощрil.(r'Fir_t Наше: (.*) La_t Наше: (.*) ') >>> 110 = l1aIIeReqex. _earch ( I Fir_t Hue: Al La_t Н_.: Sw.iqart') >>> lIo.qroup (1) 'Al' >>> lIo.qroup(2) 'Sweigart' 
Поиск по шаблону с помощью реryлярных выражений 209 Комбинация . * работает в режиме жадnоzо поиска: она вcelДa будет CTpt.>" миться захватить как можно больше текста. Чтобы заставить ее работать в нежадной манере, дополните ее вопросительным знаком: . *? Как и в слу- чае фиrypных скобок, вопросительный ЗНак указывает Python на необходи мость использования lIежадиоео поиска. Чтобы увидеть, чем различаются жадная и нежадная версии реryлярноro выражения, введите в интерактивной оболочке следующие команды. »> nonqreedyRegex · re.сошрilе(r'<.*?>') »> 1110 .. nOng'reedyRegex. вearcb ( ,<То serve un> for dinn.r. >' ) >>> 110. qroup () ,<То serve таn> , »> g'r.edyR8gex = re.compile(r'<.*>') >>> IDO · gre.dyRegex._earch('<To _erve un> for dinn.r.>') >>> 1110. group () '<То serve man> for dinner.>' в общих чертах смысл обоих реryлярных выражений можно выразить следующими словами: "Найти открывающую уrловую скобку, за которой следует произвольный текст, завершающий(:я закрывающая уrловая скоб- Ка". Однако в строке' <То serve тап> for dinner. >' имеюп'я два возможных варианта совпадения для закрывающей УI:ЛОВОЙ скобки. В нежадной версии реryлярноrо выражения Руthоп оrраничивается самой короткой из воз- можных строк: '<То serve тап> ' . в жадной версии PythOI1 стремится к тому, чтобы найти соответствие в виде как можно более длинной строки из всех возможных: ,<То serve тап> for dinner . >' . Уко,"..е 'OOf8ern... ,...аll. .о.о. ирок. , 110.0"..10 ro.к. Комбинации "точказвездочка" будет соответствовать все, за исключе нием символа новой строки. Передав методу re. compi le () при ero вызове константу re. DOTALL в качестве BToporo apryмeHTa, можно установить ре- жим, при котором точке соответствует также символ новой строки. Введите в интерактивной оболочке следующие команды. »> noN.wlineReqex = re.сошрilе('.*') »> noN.wlineReqex._.arch('Serve the public tru_t.\nProt8ct the  innocent. \nUphold th. law.'). g'roup () 'Serve the pиblic trust.' >>> n.wlineReqex = re.compile('.*', юе.DOТALL) »> n.wlineReqex._earch('Serve the public tru_t.\nprotect the  innocent. \nUphold th. law. I ) . qroup () 'Serve the pиblic trust.\nProtect the innocent.\nUphold the law.' 
218 r лава 7 Реryлярному выражению noNewlineRegex, при создании KOToporo методу re . cornpile () не передавался apryмeнт re. DOTALL, будет соответствовать весь текст, но только до первоrо символа новой строки, тоrДа как реryлярному выражению newlineRegex, при создании котороro методу re. соrnpНе () была передана константа re. DOTALL, будет соответствовать весь текст, включая символ новой строки. Именно поэтому вызов newlineRegex. search () В03Bpa щает полную строку вместе с ее символами новой строки. СвоДкасимвояовреryя.рныхвыра_ений в этой l'Лаве вы I10знакомились со мнш'ими элементами нотации pel'Y лярных выражений, 110:')ТОМУ имеет смысл привести их полный перечень. . ?  соответствует отсутствию или одиночному вхождению IlреДlUе ствующей rРУIlПЫ. . *  соответствует отсутствию или произвольному количеству вхожде- ний предшествующей rpynпы. . +  соответствует одиночному или большему количеству вхождений предшествующей rpynпы. . {n}  соответствует в точности n вхождениям предшествующей rpynпы. . {n,}  соответствует n или более вхождениям предшествующей rpуппы. . { , т}  соответствует отсyrствию или вплоть до т вхождений предше- ствующей rруппы. . { п, т}  соответствует не менее чем n и не более чем т вхождениям предшествующей rруппы. . {n, т} ? , или *?, или +?  выполняет нежа,дный поиск вхождений ПреД шествующей rруппы. . Л spam  означает, что строка должна начинаться символами spam. . зрат$  означает, что строка должна заканчиваться символами spam. . .  соответствует любому символу, за исключением символа новой строки. . \d, \w и \5  совпадают с одиночным цифровым, словарным и I1p<r бельным символами соответственно. . \0, \W и \S  совпадают с произвольным одиночным символом, не яв ляющимся цифровым, словарным и пробельным соответственно. . [аЬс]  совпадает с любым одиночным символом из числа тех, KOT<r рые указаны в квадратных скобках (например, а, Ь или с). . [ЛаЬс]  совпадает с любым одиночным символом, не относящим(я к числу тех, которые указаны в квадратных скобках. 
Поиск по шаблону с помощью реryлярных выражений 211 Иrнорирование реrистра при поиске соответствий Обычно при установлении соответствия между реryлярным выражением и текстом учитывается, в каком реrистре записаны буквы. Например. сле- дующим реryлярным выражениям будут соответствовать разные строки. »> reqexl = rе.сощрilе('RoЬоСор') >>> reqex2 = re. COJIIрilе ( 'ROвосор' ) »> rеqвхЗ = re.compile('robOcop') »> reqвx4 = re.COJIIpile('RobooOp') Однако иноrда вас интере(:ует лишь сам факт совпадения букв, незави- симо от их реrистра. Можно установить режим, в котором реrистр букв иrнорируется, передав методу re. compile () при ero вызове константу re. IGNORECASE или re. 1 в качестве BTOporo apryмeHTa. Введите в интерак тивной оболочке сле/\ующие команды. »> rObooop. re.compile(r'robocop', re.I) »> rObocop. .earch ( 'RoboCop i& part 1II8J1, part machine, а11  сор. ') .qroup() 'RoboCop' »> rObocop.&earch('ROВOCOP protect& tbe innocent.') .group() 'ROBOCOP' »> rObocop. &earch ( I М, why doe& your proqr8Jlllinq book ta1k about  robocop &0 DlUch?') . qroup () 'robocop' Замена строк с помощыо метода sub () Реl'Улярные выражения MOryт не только находить заданные образцы тек- ста, но и заменять их новым текстом. Объекты Regex имеют метод sub ( ) , который принимает два арl'}'Мента. Первый apryMeHT  это строка, которая должна 1I0ДСТаоляться вместо найденных СОВПадений. Второй apryMeHT  это строка реryлярноrо выражения. Метод sub () возвращает строку. к Koт<r рой применены замены. Введите в интерактивной оболочке следующие команды. »> namesReqex = re.COJIIpile(r'Agent \w+') >>> nam.esReqex.&ub('CENSORED', 'Aqent Alice qave the secret  docwaent& to Aqent ВоЬ. ') 'CENSORED gave the secret documents to CENSORED.' 
212 rлава 7 Иноrда возникают ситуаЦИИ, Korдa совпавший с реryлярным выражени ем текст сам используется в качестве части подстановочноrо текста. Для этою можно использовать в первом apryмeHTe при вызове метода sиb () об- ратные ссылки \1, \2, \3 и Т.д., которые имеют с.ледующий СМI>lС.л: "Вставить в под(тановочный текст rpyппы ], 2, 3 и так далее". Предположим, например, что вы хотите скрыть имена секретных areH тов, отображая в них лишь первые буквы. Для этоrо можно ВОСfюльзоваТI.- ся реryлярным выражением Agent (\ w) \w* и передать методу sub () в каче- стве первоrо apryмeHTa строку r' \ 1 * * * * , . Ссылка \ 1 в этой строке будет за меняться тем текстом, который будет захвачен I'РУIШОЙ 1, T.{. rРУllllОЙ (\w) реryлярноrо выражения. »> agentNam.8Regex - r..сошрil.(r'Agent (\w)\w*') »> agantNam.8Regex.8ub(r' \1**** ' , 'Agent Alioe told Agent Carol  that Agent Ev. kn." Agent ВОЬ n8 а doubl. agent.') А**** told С**** that Е**** knew В**** was а doub1e agent.' Работа СО СЯО_НЫМИ реryпярными выра_ениями с реryлярными выражениями леrко работать в случае простых TeKcT<r вых шаблонов. Однако сопоставление с более с.ложными текстовыми ша& лонами может требовать построения длинных и запутанных реryлярных выражений. Один из способов внесения ясности в подобных (итуациях за- ключается в использовании режима работы метода re . compile ( ) , при Кото- ром пробелы и комментарии в строке реryлярноrо выражения иrнорируют- ся. Чтобы включить этот "мноrословный режим", следует передать методу re . compile () константу re . VERBOSE в качестве второю apryмeHTa. 1Ьrда вместо сложною для восприятия реryлярною выражения наподобие phoneRegex = re.compile (r' ((\d{3) 1\ (\d{3} \})? (\5 I  I \.) ?\d{3} (\5 I  I \. ) \d {4 } (\5* (ext I х I ext. ) \s* \d{ 2,5} ) ?} '} можно использовать развернутое в нескольких строках и снабженное комментариями реryлярное выражение следующеrо вида, понять смысл которою значительно леrче: phoneRegex = re.compile(r'" ( (\d{3}\\(\d{3}\})? (\5 \  1\. ) ? \d{3} (\5 I  I \.) \d{4} (\5*(extlxiext.)\5*\d{2,5})? ) l' " re.VERBOSE) i территориальный код # раздепитель # первые 3 цифры # разделитель # последние 4 цифры # доОавочный номер 
Поиск по шаблону с ПОМОЩЬЮ реryлярных выражений 213 Обратите внимание на использование в предыдущем примере синтак сиса с тройными апострофами (. · · ) для создания мноrострочноrо текста, что позволило растянyrь определение реryлярноro выражения на нссколь-- ко строк, блаroдаря чему читать ero стало HaмHoro леrче. Для комментариев в пределах строки реryлярноro выражения действyюr те же правила, что и в случае обычноrо кода Python: символ # и все содер-- жимое до конца строки иrнорируются. Кроме TOro, дополнительные пр<r белы в мноrострочном реryлярном выражении не считаются частью TeK cтoBoro шаблона, соответствие которому ищется. Это по:шоляет записать реryлярное выражение таким обра:JOМ, чтобы оно леrче читалось. Комбинация констант re. IGNORв:CASE, re. DOТALL и re. VERВOSE Что если вы захотите использовать режим re. VERBOSE для включения комментариев в свое реryлярное выражение, но одновременно иметь воз- можность иrнорировать реrистр с помощью константы re. IGNORECASE? К сожалению, метод re. compile () принимает лишь одиночное значение в качестве BToporo apryMcHTa. Это оrраничение можно обойти, объединяя константы re. IGNORECASE, re. ООТА11 и re. VERBOSE с помощью символа KaHa ла ( I ), который в данном контексте имеет смысл nобumoвоzо оператора ИЛИ. Следовательно, если вы хотите, чтобы реryлярное выражение иrнориp<r вало реrистр и символам точки в нем соответствовали бы также символы но- вой строки, вызывайте метод re. compile () примерно следующим образом: »> someRegexValU8. re.compile('foo', re.IGNORECASE I re.DOТALL) А вот пример объединения всех трех опций во втором apryMeHTe: »> sошeRegeХVаluе = re.compile('foo', re.IGNORECASE I re.DOTALL I re. VERВOSE) Этот синтаксис HeMHoro старомоден и происходит от ранних версий Python. Детальное рассмотрение работы побитовых операторов выходит за рамки книrи, но вы сможете получить более подробную информацию по этому вопросу, посетивсайт http://nostarch.com/aиtomatestиff/. Во втором apryмeHTe можно передава1'Ь и друrие опции; они не используются широко, и при желании вы сможете найти более подробную информацию о них са- мостоятельно. 
214 r лава 7 Проект: И3ВJ1ечение теяефонных номеров и адресов эяектронной почты Предположим, вам предстоит заняться рутинпой работой  пайти все телефонные номера и адреса электронной почты, которые содержатся на ДЛИННОЙ веб--странице или в документе 6ольшоrо размера. l<::СЛи прокручи вать страницу вручную, то на такой поиск у вас может уйти MHOI'O времсни. 110 если бы у вас была проrрамма, способная выполнять поиск тслефон пых номеров и электронных адресов в буфере обмена, то можно было бы просто нажать комбинацию клавиш <Ctrl+A> для выделения Bcel'o текста и комбинацию клавиши <сн'I+С> для копирования выделеННОfО текста в буфер, а затем выполнить проrpамму. Такая I1pOrpaMMa моrла бы заменить НilXодящийся в буфере текст найденпыми телефонными номерами и aдpe сами электронной почты. ВСЯКИЙ раз, коrда вы приступаете к новому проекту, возникает соблазн сразу же браться за написание кода. Однако в подавляющем большинс.тве случаев лучше не спеШИТI. и оценить общую картину. Я рекомендую всеrда начинать с составления BblCOKoypoBHel\OrO плана '1'01'0, что должна делать проrрамма. На данном этапе не <:тоит задумываться о фактическом коде  об этом можно будст побеспокоиться позже. Приступая к ра(юте, оrраllИЧИ вайтесь "широкими мазками". I lаl1ример, вот что должна делать ваша проrрамма, предназначснная для извлсчения телефонных номеров и адресов электронной почты: . получать текст из буфера обмена; . находить в тексте все телефонные номера и адреса электронной по'пы; . IIсреносить найденный текст в буфер обмепа. Вот тепсрь уже можно подумать над тем, как реализовать все это в виде кода. Код должен выполнять следующие операции: . использовать модуль pyperclip для копирования и вставки строк; . создавать два рСI)'ЛЯрНЫХ выражения, первое из которых COOTBeтCTBY ст телефонным номерам, а второе  адресам электронной почты; . паходить все совпадения, а не только первое, для обоих реryлярных выражений; . аккуратно форматировать найденпые строки, преобразуя их в одну строку для вставки о буфер; . отображать соответствующее сообщение, если искомые соответствия в тексте не бblJlИ обнаружсны. Этот СIlИ<:ОК служит CBoero рода дорожной картой проекта. В процессе написания кода вы сможете сконцеlприровать внимание на каждом этапе по отдельности. Любой из этих этапов вполне выполним и выражается в терминах TOro, 'по вы уже умеете делать с помощью Python. 
Поиск по шаблону с помощью реryлярных выражений 215 ш", ,. '0111"".' per,..p"o,O ."р".'''.. /111. II0"'К" re.,фо""..х "о"'ро. Прежде BCeI'O, необходимо создать реryлярное выражение для поиска телефонных номеров. Создайте новый файл, введите в Hero следующий код и сохраните ero в файле phoneAndEтail.py. #! python3 # phopeAndEmail.py  Находит телефонные номера и # адреса электронной почты в буфере обмена. import pyperclip, re phoneRegex = re.compile(r'" ( (\d{311 \ (\d{31\))? (\511\.)? (\d{31) (\5 I  I \.) (\d{ 41) ( \ s * (ext I х I ext . ) \ s * ( \ d { 2 , 5} ) ) ? ) , I " re . VERBOSE} # территориальный код # разделитель # первые 3 цифры # разделитель # последние 4 цифры # добавочный номер # тооо: Создать реrулярное выражение для адресов электронной # почты. # тооо: Найти соответствия в тексте, содержащемся в # оуфере обмена. # тооо: Скопировать результаты в оуфер обмена. Комментарии ТООО (ЧТОСДЕЛАТЬ) всеro лишь обозначают "скелет" npOI'paMMbI. Впоследствии на их месте будет находиться фактический код. 1елефонный номер начинается с территориальноro Кода, который не является обязательным, в СIЯ3И с чем за соответствующей ему rpynпой сле- дует вопросительный знак. Поскольку территориальный код может иметь ровно три цифры (т.е. \d{ 3}) или ровно три цифры в круrлых скобках (т.е. \ (\d{ 3} \) ), ати две альтернативы следует соединить символом канала. Вы также можете добавить в реryлярное выражение комментарий # терри ториальный код, напоминающий о том, с чем должно совпадать реl'УЛЯРlюе выражение (\d{3} 1\ (\d{3} \}}? В качестве разделителя rрупп цифр в телефонном номере MOI'yr ИСПОЛIr зоваться пробел (\ s), дефис () или точка ( . ), поэтому данные компонеll ты реryлярноrо выражения также должны быть соединены символами канала. В следующих трех компонентах нет ничеro сложноrо: три цифры, за которыми следует друroй разделитель, а затем еще четыре цифры. П<r следняя часть  это необязательный добавочный номер, состоящий из 
216 r лава 7 произвольноrо количества пробелов, за которыми следует буквенное об<r значение ext, х или ext., а затем  и сам добавочный номер, содержащий от двух ДО пяти цифр. Ш", 2. '0311"".е pery..p"o,o ."р".е".. /111. "О.'К" "дреео. J.епро""оj no.r" Вам также необходимо иметь реl'Улярное выражение для поиска адресов электронной IIОЧТЫ. Добавьте в ПрOl'рамму новый код, выделенный ниже полужирным шрифтом. #! python3 # phoneAndEmail.py  Находит телефонные номера и # адреса электронной почты в буфере обмена. import pyperclip, re phoneRegex = re.compile(r'" ( пропущенный KOД # Создание peI'YnярНОl"о lIblpuения дn_ адресо& М8JC'l'pОННОЙ поч'1'Ы. emailRegex = rе.сощрilе(r' , , ( о [azAZO9. ,+]+ # ИМR пользователя . @  #CИN8cm@ . [azAZO9.]+ # ИМR домена (\.[azAZ]{2,4}) # ост&nьная чаC'l'Ь адреса ) I I " re.VERВOSE) # ТООО: Найти соответствия в тексте, содерхащемся в # буфере обмена. # тооо: Скопировать результаты в буфер обмена. Часть адреса, содержащая имя пользователя О, включает один или б<r лее символов, которыми MOryr быть любые из следующих символов: буквы в верхнем или нижнем реrистре, цифры, точка, символ подчеркивания, знак процента, знак "плюс" и дефис. Все эти символы можно указать в виде символьноrо класса: [azAZO9.  %+). Имя домена отделяется от имени пользователя символом @ .. Домен- ное имя. представляется с помощью более узкоrо класса, включающеrо только буквы, цифры, точку и дефис: [azAZO9. ]. А последняя, так назы- ваемая часть "dot-cotn" (с технической точки зрения представляющая до.мен верхnеео уровnя) , фактически может содержать только точку и любой текст. Эта часть может включать от двух ДО четырех символов. Формат адресов электронной почты определяется мноrими, подча(: причудливыми, правилами. Данному реryлярному выражению будут соот- ветствовать не все корректные адреса электронной почты, но ero будет 
Поиск по шаблону с помощью реryлярных выражений 217 Достаточно почти для всех видов электронных адресов. с которыми вы M<r жете столкнyrься. Ша, 3. nО"'К .teЖ ,о."аllе"". . "кт, ,ко".ро.а".о. . 6уфер ..е"а 'Iеперь, Korдa у вас уже есть реryлярные выражения для поиска телефон ных номеров и адресов электронной почты, можно поручить модулю re выполнить утомительную работу по поиску в буфере обмена всех строк, c<r ответствующих составленным реryлярным выражениям. Методруреrсliр. paste () получит строковое значение текста. хранящеrося в буфере обмена, а ме-rод findall () вернет список кортежей. Добавьте в проrрамму новый код. выделенный ниже полужирным шрифтом. #! рythопЗ # phoneAndEmail.py  Находит телефонные номера и # адреса электронной почты в буфере обмена. import pyperclip, re phoneRegex = re.compile(r" I ( лропущенный KOД * DоиоlC соотв.тствий В 'l'8JCC'1'B, coдepIC8IЦ8МСЯ в * бvфере обмена. text - .tr(pyperclip.pa.t8(» О Utch8. .. [] Ofor qroup& in phoneRegex. findall (text) : phoneNum - ''.join([group.[l], grouр.[З], qroup.[S]]) if group.[8] != ": phoneNum +- · х' + qroup. [8] шаtc:he.. арреnd (phoneNwa) Ofor qroup. in emailRegex.findall(text): u tc:he. . append (qroup. [ О] ) i ТООО: Скопировать результаты в буфер обмена. Для каждоrо совпадения создается один кортеж. и каждый кортеж со- держит строки для каждой rруппы в реryлярном вьrражении. Не забывайте о том. что rрynпе О соответствует все реryлярное выражение. поэтому то, что вам нужно,  это rруппа с индексом О в кортеже. Найденные совпадения с реryлярным выражениям сохраняются в списке тatches. Поначалу этот список пуст ..' Далее следуют два цикла for. В слу чае адресов электронной почты достаточно IIрисоединять к списку та tches rРУПI1У О каждоrо найденноrо совпадения .. В случае же телефонных HOM ров мы не можем оrраничиться только этим. Поскольку проrpамма ищет 
218 rлава 7 телефонные номера, формат которых может быть различным, то, прежде чем присоединять их к списку, их нужно привести к единому стандартному формату. В переменной phoneNum содержится строка, скомпонованная из rрупп 1, З, 5 и 8 совпавшеrо текста 8. (Этими rруппами являются террито- риальный код, первые три цифры, последние четыре цифры и добавочный номер.) Ша, 4. 06"11."'''.' ,oll,all'''.' . ОIl"У"РОКУ RJI. ко""ро.а".. . 6уф.р 06.."а Теперь, коrда адреса электронной почты и телефонные номера coxpa нены в переменной matches, их необходимо скопировать в буфер обмена. Функция pyperclip. сору () принимает одиночное строковое значение, а не список строк, поэтому для перемеllНОЙ matches вызывает(я метод join (). Чтобы упро(тить проверку работы проrраммы, выведем все найденные совпадения на экран. А если ни телефонных номеров, ни мресов электрон ной почты в тексте не найдено, будет выведено соответствующее сообщение. Внесите в проrрамму следующие изменения. #! руthоnЗ # phoneAndEmail.py  Находит телефонные номера и # адреса электронной почты в буфере обмена. пропущенный KOД for groups in emailRegex.findall(text): matches.append(groups[O]) * Копиро.ание pe8YnЬ'l'a'l'o. 8 буфер обмена. if 18n(aatch8.) > о: pyperclip.oopy('\n'.join(aatch88» рrint('СJCОПИРОваио 8 буфер обмена: ') рrint('\n'.jоin(шаtchе.» 81.8: print ( 'ТenефоННЪ18 номера и адреса ЭЛ8хтроНИОЙ t!> ПОЧ'1'bl Н8 обнар}'1l8НЫ.') ..."0.,,.... .ро'рам.. в качестве примера откройте свой браузер на странице контактов сайта No StaI'Ch Press по адресу http://www.nostarch.com/contactus.htm. нажмите комбинацию клавиш <Ctl'l+A> для выделения вcero текста на странице, а затем комбинацию клавиш <Ctl'l+C> для копирования этоrо текста в буфер. Выполнив проrрамму, вы должны получить примерно с.ледующий вывод. Скопировано в буфер обмена: 8004207240 
Поиск по шаблону с помощью реryлярных выражений 219 4158БЗ9900 4158БЗ9950 info@nostarch.com media@nostarch.com academic@nostarch.com help@nostarch.com Иllе. OrHOC.ren6HO е031111Н.. IIНllnО,..,Н",Х про,р".. Распознавание образцов текста (и их замена с помощью метода sub ()) имее'f' множество возможных областей применения, например: . нахождение URL-адресов веб-сайтов, начинающихся с префикса http:// или https: //; . унификация дат, приведенных в различных форма'f'ах (например, 1/21/2015, 01212015 и 2015/1/21), пyrем их замены датами, представ ленными <: помощью выбранноrо С'f'андарТllоrо формата; . удаление конфиденциальной информации, такой как номера социаль-- ных страховок или кредитных карт; . исправление типичных опечаток, таких как наличие нескольких про- белов между словами, случайное повторение слов или наличие не- скольких восклицательных знаков в конце предложения. Это так pa:r дражает!! ! РеЗlOме Ilесмотря на то что компьютер способен очень быстро выполнять тек- стовый поиск, он нуждается в конкретных указаниях относительно Toro, что именно необходимо найти. Реryлярные выражения позволяют точно определить символьные шаблоны для поиска. В действительности некото- рые текстовые процессоры и электронные таблицы предоставляют сред- ства поиска и замены, использующие механизм реryлярных выражений. Поставляемый вместе с Python модуль re обеспечивает компиляцию объектов реryлярных выражений, так называемых объектов Regex. В част- ности, эти объекты имеют следующие методы: search ()  поиск одиночных совпадений с реryлярным выражением, findall р  поиск всех :К:Jемпля- ров совпадений и sub ()  поиск с заменой тек<:та. В этой 1'JlaBe синтаксис реryлярных выражений был рассмотрен далеко не 110ЛНО<:ТЬЮ. Более подробную информацию по этой теме вы сможете получить, обратившись к официальной документации Python по адресу http://docs .python. оrg/З! library/re .html. Вам также будет полезно про- читать практическое руководство по работе с реryлярными выражениями, доступное по адресу http://www.regularexpressions.info/. 
220 r лава 7 Теперь, КOI'да вы уже приобрел и определенньrй опыт манипулирования текстовыми строками и научились работать с текстовыми шаблонами, мы можем перейти к уrлубленному рассмотрению методов чтения и записи данных в файлы, сохраняемые на жестком диске вашеrо компьютера. Контропьные вопросы 1. С помощью какой функции создаются объекты реryлярных выраже- ний (объекты Regex)? 2. Почему при создании объектов Regex часто используются "сырые" строки? 3. Что возвращает метод search ()? 4. Как получить из объекта Match фактические строки, соответствующие шаблону реryлярноrо выражения? 5. Что именно представляет в объекте реryлярноrо выражения, создан- ном на основе строки r' (\d\d\d)  (\d\d\d\d\d\d\d) ',rpуппа о? Iруппа 1? Iруппа 2? 6. В синтаксисе реryлярных выражений круrлые скобки и точки имеют особый смысл. как бы вы указали в реryлярном выражении, что сим- волы круrлых скобок и точки сами являются объектом поиска? 7. Метод findall () возвращает список строк ИЛИ список кортежей строк. В каких именно случаях возвращается тот или иной тин значений? 8. Что означает символ I в реryлярных выражениях? 9. Какие две функции выполняет символ 1 в реryлярных выражениях? 10. В чем разница между символами + и * в реryлярных выражениях? 11. В чем разница между записями {3} и {З, 5} в реryлярных выражениях? 12. Что означают сокращенные символьные классы \d, \w и \s в реryляр- ных выражениях? 13. Что означают сокращенные символьные классы \О, \W и \8 в реryляр- ных выражениях? 14. Как сделать реryлярные выражения нечувствительными к реrистру? 15. Чему обычно соответствует символ. ? Чему он соответствует, если ме- тоду re. сотрНе () в качестве BToporo apryмeHTa передана константа re.DOTALL? 16. В чем разница между сочетаниями символов. * и . *11 17. Как записать символьный класс, которому соответствуют все цифры и буквы в нижнем реrистре? 18. Если nиmRegex  re. compile (r' \d+' ) , то что вернет вызов numRegex. sиb('X', '12drшmnеrs, l1pipers, fiverings, 3hens'}? 
Поиск ПО шаблону с ПОМОЩЬЮ реryлярных выражений 221 19. Что стаНО8ИТСЯ возможным при передаче константы re. VERВOSE в ка- честве BTOporo apryмeHTa при вызове метода re . СОПlpНе ( ) ? 20. Как бы вы записали реryлярное выражение, которому СООТ8етствуют числа с запятой в качестве разделителя между каждыми тремя цифра- ми? Этому 8ыражению должны соответствовать следующие числа: . '42' . '1,234' . '6,368,745' и не должны соответствовать следующие: . '12,34,567' (только две цифры между занятыми); . '1234' (отсутствуют запятые). 21. как бы вы записали реryлярное выражение, которому соответствуют всс полные имена, включающие фамилию Nakamoto? Можно предполо- жить, что имя, предшествующее фамилии, всеrда состоит из одноro слова, начинающеrося с большой буквы. Этому реryлярному выраже- нию должны соответствовать следующие имена: . 'Satoshi Nakamoto' . 'A1ice Nakamoto' . 'RoboCop Nakamoto' и не должны соответствовать следующие: . I satoshi Nakamoto' (имя не начинается с большой буквы); . 'Mr. Na kamoto' (в предшествующем слове имеется небуквенный символ); . 'Nakamoto' (имя отсутствует); . 'Satoshi nakamoto' (фамилия Nakamoto не начинается с большой буквы). 22. Как бы вы записали реryлярное выражение, совпадающее с преДJlO- жениями, которые начинаются с одноrо из слов Alice, ВоЬ и Carol; вторым словом является одно из слов eats, pets и throws; третьим словом является одно из слов apples, cats и baseballs; и которые за- канчиваются точкой? Это реryлярное выражение должно быть не- чувствительным к реrистру. Ему должны соответствовать следующие предложения: . 'Alice eats apples.' . 'ВоЬ pets cats.' . 'Carol throws baseballs.' . 'Alice throws Apples.' . 'ВОВ EATS CATS.' 
222 r лава 7 и не ДОЛЖНЫ соответствовать следующие: . 'RoboCop eats apples.' . 'ALICE THROWS FOOTВA11S.' . 'Carol eats 7 cats.' Учебные проекты Чтобы закрепить полученные знания на практике, напишите ПрОI'рам- мы для предложенных ниже задач. 06"ару..".е ,.л.".,,, пароле;; Напишите функцию, которая использует реryлярные выражения для проверки TOI'O, что переданная ей строка представляет собой сильный пароль. Сильными считаются пароли, которые состоят по крайней мере из восьми символов, содержат символы в верхнем и нижнем реrистрах и включают 110 крайней мере одну цифру. Вер'''. фУ"КЦ.. strip () I ",,,О..,,lOlIIа. ре"..р..,е ."ра.е".. Напишите функцию, которая принимает строку и делает то же, что и строковый метод str ip () . [ели ей не переданы никакие друrие apryMeHTbI, кроме строки, то данная функция должна удалить из строки начальные и K<r нечные пробельные символы. В противном случае из строки должны быть удалены символы, переданные функции в качестве BToporo apryмeнтa. 
ЧТЕНИЕ И ЗАПИСЬ ФАЙЛОВ Переменные  отличное средство хранения данных во время выполнения проrраммы, но если вы хотите, что бы данные существовали и после ее выполнения, то co храните их в файле. Можно рассматривать содержимое файла как одну строку, размер которой способен исчис- ляться rиrабайтами. Из этой rлавы вы узнаете о том, как использовать Pytl10n для создания, чтения и сохранения файлов на жест ком диске. Файпы и пути доступа к НИМ Файл имеет два ключевых свойства: имя файла (обычно записываемое в ви- де одноrо слова) и путъ доступа к файлу. Пyrь определяет, rде именно в ком- пьютере располarается файл. Например, в моем ноутбуке, работающем под управлением Windows 7, есть файл с именем projects.docx и пyrем доступа с:\ Userslp,sweigartlpocumeпts. Часть имени файла, которая следует за последней точ- кой, называется расш,uреиие.м имени файла (для краткости часто roворят рас- ш.иpeuue фаила или просто расширение) и указывает на тип файла. Файл project. docx  это документ W01ll, а U5ers, asweigart и Docuтeпts  это названия папок (также называемых ката.лоеами). Папки MOIyr содержать файлы и дрyrие пап- ки. Например, файл projесt.dосхсодержится в папке Docuтeпts, которая содер- жится в папке asweigart, которая, в свою очередь, содержится в папке Users. Этот способ орraнизации папок проиллюстрирован на рис. 8.1. Часть с:\ пyrи к файлу  это корневая папка, которая содержит все осталь- ные папки. В Windows корневой является папка c: которая также на..1ы- вается диском с:. В 05 Х и Linиx корневой является папка /. В этой книrе для корневой папки используется обозначение с:\ в стиле Windows. Если вы выполняете при меры в интерактивной оболочке 05 Х или Linиx, то ис пользуйте вместо этоrо обозначение /. 
224 r лава 8 с:\ L L L Users asweigart Docитeпts L pro j ect . docx Рис. 8. J. Расположение файла 8 иерархии папок Дополнительные тома, такие как диски DVD или U5Вфлешки, будут отображаться по-разному в разных операционных системах. В Windows они отображаются в виде корневых дисков с дрyrими буквенными обозна- чениями: D: Е:\ и т.Д. В 05 Х они отображаются в виде новых папок в папке jVolитes, в Liпuх  в виде новых папок в папке jтnt (от aнrл. тoиnt). Кроме Toro, имсйте в виду, что, в то время как в Windows и 05 Х имена файлов,и папок нечувствительны к реrистру, в Linux они зависят от реrистра. И,пОЛ.,08аНllе обратной 1(0'0. .,ер'" 8 Wiatlows 111(<<0. .ерт.. 8 05 Х 11 Liпиx В Windows пути к файлам записывают, разделяя имена папок обратной косой чертой (О. В то же время в 05 Х и Linux разделителем служит косая черта (/). Если вы хотите, чтобы ваши nporpaмMbI работали во всех опсра- ционных системах, пишите свои сценарии на PytllOn так, чтобы обрабаты- вались оба случая. К счастью, это очень просто делается с помощью функции 05. ра th. join (). Если вы передадите ей строковые значения имен файлов и папок в своем пyrи доступа, то вызов 05 .path. join () возвратит строку, в которой пyrь доступа к файлу указан с использованием корректной версии раздели- теля. Введите в интерактивной оболочке следующие КОМаНДЫ. >>> iш.роrt о. »> 0..path.join('U8r', 'bin', 'враа') 'usr\\bin\\spam' я выполняю все примеры в интерактивной оболочке Windows, поэтому BbI30B05.path.join('usr', 'bin', 'зрат') вернул мне строку 'usr\\bin\\ 5рат' . (Обратите внимание на то, что символы обратной косой черты np<r дублИрО8аны, поскольку каждый из них нуждается в экранировании друrим символом обратной косой черты.) Е(:ли бы я вызвал эту функцию в 05 Х или [jnux, то была бы возвращена строка' U5r /bin/ зрат' . 
Чтение и запись файлов 225 Функция 05 .path. join () оказывается полезной при создании строк для имен файлов. Эти строки будут передаваться ряду функций, используемых при работе с файлами, с которыми вы познакомитесь в данной I'лаве. На- пример, в следующем примере имена файлов из заданноrо списка прис диняются К имени папки. »> myFi18. - [' accounts . txt', 'cletail. . сВУ', ' invi t8. docx' ] »> for filenaшe in шуFilеs: print(05.path.join('C:\\Users\\asweigart', filename)) C:\Users\asweigart\accounts.txt C:\Users\asweigart\details.csv C:\Users\asweigart\invite.docx Те.,..... ра60... к"",.о, Каждая проrpaмма, которая выполняется на компьютере, имеет meкуц4UЙ раБCfЧUЙ каmaлоz (current wOl'kil1g direcLory  r.w<l). Предполarается, что лкr бые имена файлов или пути, которые не начинаются с указания корневой папки, заданы относительно текущеrо рабочеrо каталоrа. Для получения значения текущеro рабочеrо каталоrа в виде строки используется функция 05. getcwd ( ) , а для ero изменения  функция 05. chdir ( ) . Введите в интерак- тивной оболочке следующие команды. »> iJllport о. >>> o..qetcwdO 'C:\\Python34' »> 0..chdir('C:\\Window.\\Sy.tem32') >>> o..qetcwd() 'С: \\Windows\\System32 , дecь в качестве текущеrо рабочсrо каталOl'а устанавливается папка C:\Python34, поэтому имя файла projecl.docx ссылается на файл C:\Python34\ project.docx. Если мы заменим текущий рабочий каталоr каталоюм с:\ит,ndоw, то имя файла project.docx будет интерпретироваться как полное имя с:\ Windows'фrojесt. docx. При попытке перейти внесуществующий каталоr PytllOn выведет соо& щение об ошибке. »> о.. chdir ( 'С: \ \Тhi.FolderDoesNotExi..t' ) Traceback (most recent call last): File "<pyshell#18>", line 1, in <module> os.chdir('C:\\ThisFolderDoesNotExist') FileNotFoundError: [WinError 2] The system cannot find the file specified: 'С: \\ThisFolderDoesNotExist , 
226 r лава 8 При.мечаnue Несмоmря на mo чmо более совремеин'ы.м эививaлe1f,mо.м тер.мина каmа.лое явл.я.еmся тер,м,ин "папка", в качестве стандартноео исполъзуеmся тер,м,ин "текущий рабо-- v " ( Р "-1  ,. " ) " Р  " чии каталое или п осто раиО'Ч.иu кamалое ,а не meкущая аиО'Ч.ая папка . А6,ОlllOrиwе . оrио'.rе.....е пyr.1l0ayпa Существуют два способа определения пyrи доступа к файлу: . абсолюmныЙ nутъ, который Всеrда начинается с имени корневой папки; . оmносumeJtънъtu. путъ, который задается относительно текущеl'О рабо- чеrо каталоrа проrраммы. Существуют также папки, обозначаемые одним (.) ИЛИ двумя (..) симво- лами точки. Это не реальные папки, а специальные имена, которые MOryr использоваться при задаНИИ пyrей. Одиночная точка является сокращен ным обозначением, имеющим смысл "данная папка". Двойная точка имеет смысл "родительская папка". На рис. 8.2 IIоказан пример расположения папок и файлов. Если в каче-- crBe текущеrо установлен рабочий каталоI' C:'{Jacoп, то относительные пyrи к друrим папкам и файлам задаются так, как показано на рисунке. Относительные пути Текущий рабочий ............... каталоr Ьасоп .. \ . \ Абсолютные пути С: \ С: \ С: \Ьасоп t L fiZ: paт . txt spaт. txt . \fizz С: \bacoп\fizz . \fizz\spaт. txt C:\bacoп\fizz\spaт.txt . \spaт. txt C:\bacoп\spam.txt eggs ..\eggs С: \eggs L spaт. txt .. \eggs\spaт.txt С: \eggs\spam.txt spam. txt . . \spaт. txt С: \spam. txt Рис. 8.2. Относительные пути доступа к попкам и файлом 8 рабочем коталоrе С:\Ьасо" Имя. \ в начале относительноrо пyrи является необязательным. Напри мер, пyrи . pam.lxl и spam.txl ведут к одному и тому же файлу. 
Чтение и заПИСь файлов 227 'Ollfa".e "О.... "аnок , "ОМОЩ61О фу "к".. 08 . zaaJcedirs ( ) Ваши проrраммы MOryr создавать новые папки (каталоrи) с помощью функции 05 .makedirs () . Введите в интерактивной оболочке следующие ко- манды. >>> ill\port 08 »> о..шakеdir8('С:\\deliСiоus\\wаlnut\\wаfflе.') В результате будет создана не только папка C:\delicious, но и расположен- ная в ней папка walnиt, а также расположенная в папке C:'rJelicioиs\walnut пап- ка waffles. Таким образом, функция 08 .makedirs () будет создавать ВСС необ- ходимые промежуточные папки, l'арантируя существование полноI'О пути. Соответствующая иерархия папок показана на рис. 8.3. с:\ L L L delicious walпut waff1es Рис. 8.3. Результат роботы фуикции os. makedirs ('C:\\delicious\\walпut\\waffles') Модуяь 08 . path Модуль os.path содержит множество полезных функций для работы с именами файлов и путями до<:тупа. Например, вы уже и(:пользовали функ- цию 05. path. j oin ( ) , создающую корректные пути для любой операционной системы. Поскольку 05. path  ЭТО модуль, содержащийся в модуле 05, для er() импортирования достаточно выполнить ин(:трукцию import 05. Всякий раз, Korдa вашей проrрамме необходимо работать с файлами, папками или путя- ми доступа, вы можете обращаться для справки к коротким примерам, при- веденным в этом разделе. Полная документация модуля 05.path приведена на сайте Python по адресу http://docs.python .org/3/1ibrarY/05 .path. html. Ilpu.мечанuе Для ООлЪШU1t(:тва nor.tteдy'lO'lJ&Ux nfJ1.utePoB, nриведеи'Н:ых в Э1nOМ разделе, вам nотре- буется .lltодулъ ОБ, nD.'mюму ue забывайте u.м,nортироваmъ еео в uа'Чале ",аждоео с-це- пария 1.1. при ",аждом перезапуске IDLE. В nроти8ио.llt случае будет в'Ыведеuо сообще- ние об ошибке NameError: пате 'os' is пot defiпed. 
228 r лава 8 06ра60nrа a6'OlllOr.6IX " orнo,,,reIl6"6I. пут'. Модуль 05. ра th предоставляет ФУНКЦИИ для возврата абсолютноl"O пути по заданному относительному пyrи и проверки '1'01"0, является ли путь абсо- лютным. . выовB os . ра th. ab5path (ра th) возвращает строку абсолютноro пути ар- ryMeHTa. Это простой способ преобразования относительноrо пути в абсолютный. . Вызов os.path.isab5 (path) возвращает значение True, если apryMeHT  абсолютный путь, и Fa15e, если apryMeHT  относительный путь. . Вызов os.path.relpath (path, start) возвращает строку относитель- Horo пyrи от start к path. Если путь start не предоставлен, то в качестве пеrо используется текущий рабочий каталоr. Испытайте работу этих функций в интерактивной оболочке. »> 08.path.abspath(' ,') 'C:\\Python34' »> 08.path.abspath(' .\\Script.') 'C:\\Python34\\Scripts' »> 0..path.i8ab.('. ,) False »> о. .path. i.8b. (08 .path. 8bspath (' . ' ) ) True Поскольку в момент вызова функции 05. path. аЬ5ра th () текущим раб<r чим каталоrом был с: \Python34, папка. \ представляет аБсолютный пyrь 'с: \ \Python34 ' . Прu.мечанue тк как файл:ы и папки в вашей системе, вероятUl!е всеео, будут отли'Чатъс.я от моих. в'Ы 'не сможете воспроизвести все npuмefrы в этой елаве в moм виде, в каком они npuводятс.я. Тем nе мeuee nоn'Ытайтесъ их в'Ыnолнитъ, иСnОJtъзуя папки па свор"", комnъюmepe. Выполните в интерактивной оболочке следующие команды. »> o..path.relpath('C:\\Window.', 'С:\\') 'Windows' >>> o..path.relpath('C:\\Window.', 'C:\\spa\\egq.') , . . \ \ , . \ \Windows I »> о.. qetcwd () 'C:\\Python34' 
Чтение и запись файлов 229 Вызов 05.path.dirname (path) возвращает строку, содержащую всю часть пути, которая предшествует последней косой черте в apryмeHTe раш. Вызов 05. path. ba5ename (ра th) возвращает строку, содержащую всю ту часть пути, которая следует за lю(:ледней косой чертой в apryMeHTe path. Имя папки и базовое имя пути I10казаны на рис. 8.4. C:\Wiпdows\Systeт32\calc.exe 1 11 I Имя папки Базовое имя Рис. 8.4. Базовое имя указывается за последнеЙ КОСОЙ чертой в обозиачении пути и совпадает с именем файла. Имя папки  это вся часть пути, которая предшествует последней косоЙ черте Например, введите в интерактивной оболочке следующие команды. »> path = 'с: \\Window.\\Sу.t8aЭ2\\саlс.8хе , >>> 08.path.ba88n8JII8(path) 'calc.exe' >>> 08. path. dirn8J118 (path) 'С:\\Windоws\\SуstеmЗ2' Если вам нужны как имя папки, так и базовое имя, достаточно вызвать функцию 05.path. split (), которая возвращает кортеж, включающий обе эти строки. »> calcFi18Path = 'C:\\Window8\\SY8t8m32\\calo.exe' »> 0..path.8plit(calcFilePath) ('С:\\Wiпdоws\\SуstеmЗ2' I 'calc.exe') Заметьте, что тот же кортеж можно получить, вызвав функции 05. path. dirname () и 05. path. basename () и поместив возвращаемые ими значения в кортеж. >>> (08.path.dirn8Jlle(calcFi1ePath), 08.path.ba88name(calcFilePath» ('С:\\Windоws\\SуstеmЗ2', 'calc.exe') Однако если вам нужны оба значения, то вызов o5.path.5plit () является более коротким. Обратите внимание на то, что возвращаемое значение функции 05. path. 5рН t () н,е представляет собой список строк, соответствующих каждой из папок пути по отдельности. Для получения Taкoro списка следует исполЬ3<r вать строковый метод spl i t () совместно с разделителем 05. 5ер. Вспомни те, что персменная 05. 5ер содержит ту версию косой черты (прямой или 
230 r лава 8 обратной), используемой в качестве разделителя имен папок, которая соот- ветствует установленной на компьюrере операционной системе. Например, введите в интерактивной оболочке следующие команды. »> calcFilePath.split(o_.path._&p) ['С:', 'Windows', 'SуstеmЗ2', 'calc.exe'] На компьютерах, работающих под управлением 05 Х и Linux, в начале возвращенноro списка будет указана пустая строка. »> '/usr/bin'.split(08.path._&p) [' " 'usr', 'bin'] Строковый метод 5pli t () обеспечивает возврат списка всех частей пyrи. Если вы передадите ему разделитель 05. path. 5ер, то он сработает в любой операционной системе. ОпР'I'Л'.'" ра_'Р08 ф"'n08 " "'1.,.".0,0 папок Научившись работать с путями доступа к файлам, можете при ступить к сбору информации о конкретных файлах и папках. Модуль 05.path предо ставляет функции, позволяющие находить размеры файлов, выраженные в байтах, и определять, какие файлы и папки содержатся в заданной папке. . Вызов функции os. path. get5ize (path) возвращает выраженный в бай тах размер файла, указанноro в apryмeHTe path. · Вызов 05.1istdir(path) возвращает список строк с именами всех файлов с пyrем доступа, указанным в арryмеите path. (Обратите вни мание на то, что эта функция содержится в модуле 05, а не в модуле os.path.) Вот что я получаю, КOI'Да выполняю эти функции в интерактивной об<r лочке. »> 08.раth.gеtsizе('С:\\Windоw_\\Sу_t8aЗ2\\саlс.ехе') 776192 »> 08.1i_tdir('С:\\Window_\\Sу_t8aЗ2') ['0409', '12520437.срх', '12520850.срх', '5U877.ax', I aac1ient. dll' , пропущеннь KOД 'xwtpctui.dll', 'хwtрwЗ2.dll', 'zhCN', 'zhHK', 'zhTW', 'zipfldr .dll' ] Как видите, на моем компьютере ПРОI'рамма calc. ехе имеет размер 776192 байта, и в папке С:\Wiпdоws\sуstеmЗ2 содержится множество 
Чтение и запись ф айлов 231 файлов. Если бы потребовалось найти суммарный размер всех файлов, Ha ходящихся в этой папке, то для этоI'О Я Mor бы воспользоваться функциями 05.path.get5ize() иоs.li5tdir(). »> totalSize · О »> for filename in o..li.tdir('C:\\Window.\\Sy.tem32'): totalSize · totalSize + о..раth.gеt.izе(о..раth.jоin('С:\\Window.\\Sу.temЗ2' , filename) ) »> print(totalSize) 1117846456 По мере TOI'O как в цикле перебираются имена всех файлов, содержа- щихся в папке с: \WiпdОWS\SУ5tеmЗ2, значение переменной totalSize каж дый раз увеличивается на размер очереДНОI'О файла. Заметьте, как я исполь- зую функцию 05. path. j oin () для присоединения имени папки к текущему имени файла при Вblзове функции оз .path.get5ize (). Целочисленное зна- чение, возвращаемое функцией оз .path .getsize (), добавляется к текущему значению переменной totalSize. По завершении цикла выводится значе- ние totalSize, отображающее суммарный размер содержимоrо папки с: \ WiпdОW5\SУ5tеmЗ2. nро..рк" QlllfI080."".. "". Мноrие функции Python завершаются аварийно с выдачей сообщения об ошибке в случае, если предоставленный им путь не существует. Модуль 05. ра th представляет функции, позволяющие проверить, существует ли за данный путь и соответствует ли он файлу или папке. . Вызов 05 .path.exi5t5 (path) возвращает значение True, если файл (или lIапка), на который ссылается apryMeHT, существует, и значение Fa15e, с(:ли он не существует. . Вызов 05 .path. i5file (path) возвращает значение True, е<:ли заданный apl')'MeHToM путь существует и является файлом, и значение Fa15e в противном случае. . Вызов 05. path. i5dir (path) возвращает значение True, если заданный apryмellToM путь существует и является папкой; иначе  Fa15e. Вот что я получаю, KOl'дa выполняю эти функции в интерактивной обо- лочке. >>> o..path.exi.t.('C:\\Window.') True >>> o..path.exi.t. ('С:\ \.OJIIemadeupfolder') 
232 rлава 8 False >>> о. .path. i.dir (' с: \ \Window.\ \Зу.t8I132') True »> 0..раth.i.filе('С:\\Window.\\Sy.teшЗ2') False »> 0..раth.isdir('С:\\Window.\\Sу.t8Ш32\\calс.ехе') False »> 0..раth.i.filе('С:\\Window.\\Sу.t8Ш32\\calс.ехе') True Чтобы определить, подключен ли в данный момент к компьютеру DVD или флешдиск, можно воспользоваться функцией 05. path. exist s () . На- пример, если бы я хотел проверить, подключен ли флешдиск D: \ на моем Wiпdоwsкомпьютере, то я Mor бы это сделать с помощью следующей ко- манды. »> os.path.exi_t.('D:\\') False Ой! Похоже, я забыл подключить свою флешку! Чтение и запись файпов Научившись уверенно работать с папками и относительными путями, вы сможете задавать расположение файлов для операций чтения и записи. Функции, рассматриваемые в нескольких последующих ра.зделах, будут при- меняться к простым текстовым файлам. Простые meкcmoвш фашыl содержат лишь базовые текстовые символы, не сопровождающиеся информацией о шрифте, размере и цвете текста. В качестве примера про<:тых текстовых файлов можно привести файлы с расширением .txt или файлы сценариев Python с расширением .ру. Эти файлы MOryr быть открыты (: помощью при- ложения Notepa<l в Windows или приложения TextEdit в OSX. Ваши np<r l'paмMbl Moryr леrко читать содержимое простых текстовых файлов и об- рабатывать их как одиночные строковые значения. Друrим типом файлов являются двоичuш (бинарные) файлы, такие как файлы документов, создаваемых текстовыми процессорами, РDF-файлы, а также файлы изображений, электронных таблиц и выполняемых np<r rpaмM. Открыв двоичный файл в приложении Блокнот или TextEdit, вы увидите бессмысленный набор странных символов (рис. 8.5). 
Чтение и запись файлов 233 ",2'':;; .... .' . ,. о..,''' О" :. ;';..:< :". . j:=f lа" Ф.йл Пр.... Формат ВII,II (пр..... MZ1) L" j' .iIii'е.... ._@....... .. ртоgraш cannot Ье М1 in DOS mode. $ aKtJ..-с:«..С«..:с:«...YI,I:«,,.YhJЬ«,,С«"ОЕ:"-У%о!l.. .«..- уты!Ji«..-У1)у«..Jl.У1'1Jlr<....у<r«..RichC«.. РЕ dt ФЙ[J Р " с!', fJ т. 0.2 + + ,  0.11  .то, @t а + + + Tc Т p.' n.  fJ IL 1-- 8 11, d  @ 1Db-- @ .text Й + fJ  . .rdata ДЛ  + <t-- @ @.data ЪN о. N $. @ A.pdata cd n. f rt @ @.rзтс . Р. ( ш. @ @.reloc I L JJ J .11 @ BTa[ Ya[Jr tIO[JA kЮ[JМ +а[JЩ a[Jr ;'a[Jp "а[Jь @ю[J--- +аfJЩ ЯЯ[J!I +а[JЩ €a[! +а[JЩ Sa[J+ Оа[Jб ,a[J@ Я [n.. sЯ[JV +а[JЩ SНЕLL32.dП SНL\\I.dП gdiр1us.dП ADVАРI32.dП пtdП.DLL ОLЕАUТ32.dП UхТhemе.dП о1e32.dП СОМCТL32.dП КERNEL32.dU USER32.dU RPCRТ4.dП \\'INММ.dП \o-'ERSION.dU GDI32.dП ms\'crt.dU , H<lItlЦ JI.Lk  А;ЕО"йr, Н..!'''. JI.Lu r.К". LK 11)7. нК.LLб L нК) ИХ' Не ЪD9-I)7. O...ir, 3ЙJJ.LЕt H<IlJtE YМ нКl.!' Ai'P р Р(Л r H!i! LН!Т1us Рис. 8.5. Windоws-проrpамма са/с.ехе, открытая 8 приложеНlfИ Блокнот Поскольку различные типы двоичных файлов должны обрабатываться по-разному, мы не будем пытаться в этой книrе непосредственно читать и записывать "сырые" двоичные файлы. К счастью, МНОl'ие модули упрощают работу с двоичными файлами. с одним из них, модулем shel ve, вы познако- митесь чyrь позже. В Python операции чтения/записи файлов выполняются в три этапа: 1) вызовите функцию open ( ) , которая возвратит объект File; 2) вызовите метод read () или wr i te () для объекта File; 3) закройте файл, вызвав метод clase () для объекта File. OrKp6l",e фlll.II , "0_0,,,'10 фу"к".. ореп ( ) Чтобы открыть файл с помощью функции apen ( ) , вы передаете ей стро- ку, содержащую пyrь к файлу, который хотите открыть, причем это может быть абсолютный или относительный путь. Функция open () возвращает объект File. Создайте текстовый файл heUo.txt с помощью приложения Notepad или TextEdit. Введите Hella, warld! и сохраните файл в своей пользователь- ской папке. Затем, если вы используете Windows, введите в интерактивной оболочке следующую команду: »> helloFil. ('с:\\User8\\ватапапкапользавателя\\ hello. txt ' ) 
234 r лава 8 Если вы используете 05 Х, введите в интерактивной оболочке следую- щую команду: »> helloFile = open(I/User8/вашапапкапользователЯ/hеllо.txt') Подставьте вместо заменителя вашаnаn1(аn()л'ЬЗоватl'.л.я реальное имя своей папки. Например, на своем компьютере с Windows я зареrистриро- ван как пользователь asweigart и поэтому использую пyrь 'C:\\Users\\asweigart\\ hello.txt'. Обе приведенные выше команды открывают файл в режиме "чтения простоro текста" или, для краткости, "в режиме чтения". Если файл откры- вается в режиме чтения, то Python позволяет лишь читать данные из файла; вы не сможете записать данные в файл или каким-либо образом изменить ero содержимое. Режим чтения  это режим по умолчанию для файлов, открываемых в Python. Но если вы не хотите 1I0лаrаться на установки по умолчанию, то можете явно указать этот режим, передав функции open () (TpOKOBoe значение 'r' В качестве BToporo apryMeHTa. Поэтому вызовы open('/Users/asweigart/hello.txt', 'r') иореп('/Usеrs/аswеigаrt/hеllо. txt I } делают одно и то же. Вызов open () возвращает объект File. Этот объект представляет файл на вашем компьютере и является просто еще одним типом значений в Рytlюп, во MHoroM таким же, как списки или словари, с которыми вы уже знакомы. В предыдущем примере Bbl сохранили объект File в переменной helloFile. Теперь всякий раз, коrда вы захотите прочитать или записать данный файл, вам достаточно будет вызвать соответствующий метод для объекта File, со- xpaHeHHoro в переменной hel1oFile. Чrе""е ,одер."мою ф".." Имея объект File, можно при ступить к чтению данных из Hero. Если требуется прочитать все содержимое файла в виде одноrо строковоro зна- чения, используйте метод read () объекта File. Продолжим работу с объ- ектом File файла hello.txt, сохраненным в переменной helloFile. Введите в интерактивной оболочке следующие команды. »> helloContent = helloFile.read() »> helloContent 'Hello, world!' Если вы хотите рассматривать содержимое файла как одну большую строку, то метод read () возвращает (:троку, хранящуюся в этом файле. 
Чтение и запись файлов 235 Возможен и друrой подход, Korдa вы используете метод readlines () для получения из файла списка СТРОК08ЫХ значений, по одной (TpOKe для каж- дой строки текста. Например. создайте файл sonnet29. txt в той же папке, в которой находится файл hello.txt. и введите в нею следующий текст. When, in disgrace with fortune and men's eyes, 1 all alone beweep ту outcast state, And trouble deaf heaven with ту bootless cries, And look ироп myself and curse ту fate, В процессе ввода текста не забывайте разрывать строки, нажимая клави- шу <Enter>. Затем введите в интерактивной оболочке следующие команды. »> _onnetFile .. ореn ( , _onnet29. txt' ) »> _onnetFile.readline_() [When, in disgrace with fortune and men's eyes,\n', , 1 all alone beweep ту outcast state,\n', And trouble deaf heaven with ту bootless cries,\n', And look upon myself and curse ту fate, ') Обратите внимание на то, что каждое из строковых значений, за исклю- чением последней строки файла. заканчивается СИМВОJlОМ новой с.троки, \n. Во мноrих случаях работать со списком строк проще, чем с одним длин- ным строковым значением. 3""." . ф,,;;л Python позволяет записывать содержимое файла аналоrично тому. как это делается при "записи" строк на экран с помощью функции print (). Од- нако запись в файл, открытый в режиме чтения, невозможна. Вместо этоrо файл должен быть открыт в режиме записи простоrо текста или добавле- ния простоrо текста (далее для краткости  "в режиме записи" и "в режиме добавления" соответственно). В режиме записи содержимое существующеrо файла удаляется, и новые данные записываются "с чистоrо листа" аllалоrично тому, как в процессе операции присваивания старое значение переменной заменяется новым. Чтобы открыть файл в режиме записи, следует передать методу apen () стро- ку 'w' в качестве BToporo apryMeHTa. С друrой стороны. в режиме добавле- ния новый текст добавляется в конец существующеrо файла. Эту операцию можно рассматривать как нрисоединение HOBoro значения к хранящемуся в переменной списку, а не полную перезапись содержимоrо переменной. Чтобы открыть файл в режиме добаВJIения. следует передать методу open ( ) строку' а' В качестве BToporo apryмeнтa. 
236 r лава 8 Если файла с именем, переданным методу apen ( ) , не существует, то как в режиме записи, так и в режиме добавления будет создан новый, пустой файл. Прежде чем вновь открывать файл после выполнения операций чтения или записи, ero предварительно нужно закрыrь с помощью метода close ( ) . Сведем все это воедино. Введите в интерактивной оболочке следующие команды. »> baconFile = open('bacon.txt', 'w') »> baconFile.write('Hello, world!\n') 13 »> baconFile.clo.8() »> baconFile = open('bacon.txt', 'а') »> baconFile. wri t8 ( 'Васоп i. not а vegetab18. ' ) 25 »> baconFile. alO.8 О »> baconFile = open('bacon.txt') >>> content = baconFile.readO »> baconFile.clo.8() »> print(cont8nt) Hello, world! Васоп is nat а vegetable. Прежде Bcero Mbl открываем файл bacon.txtB режиме записи. Поскольку файла bacoп.txt пока что не существует, PythOll создает ero. В результате вы- зова метода wri te () для OTKpbIToro файла и передачи ему CTpoKoBoro apry' мента' НеНа, warld! /n' осуществляется запись строки в файл и возвра- щается количество записанных символов, включая символы новОй строки. Затем мы закрываем файл. Чтобы ДОIJОЛНИТЬ новым текстом содержимое существующеrо файла, а не заменить только что записанную строку, мы oTKpblВaeM файл в ре. жиме добавления текста. Мы записываем в файл строку' Bacan is not а vegetable. ' и закрываем ero. Наконец, чтобы вывести содержимое фай' ла на экран, мы открываем файл в установленном по умолчании режиме чтения, вызываем метод read () , сохраняем результирующий объект File в переменной content, закрываем файл и выводим на экран ero содержимое. Обратите внимание на то, что метод wr i te () не записывает автоматиче- ски символ новой строки в конце строки, как это делает функция print (). Этот символ вы должны добавлять самостоятельно. Сохранение переменных с помощыо модупя shel уе Используя модуль shelve, можно сохранять переменные в двоичных файлах-хранилищах. БJlаrодаря этому впоследствии проrрамма может восстановить значения переменных, читая данные с жесткOI'О диска. 
Чтение и запись файлов 237 С помощью модуля shel ve можно добавить в проrрамму возможности Save (Сохранить) и Ореп (Открыть). Например, выполнив проrрамму и введя ряд конфиrypационных параметров, вы сможете сохранить их в файле xpa нилища и зarpузить при последующем запуске проrраммы. Введите в интерактивной оболочке следующие команды. »> iaport _helve »> _helfFile = _hеlve.open('шydata') »> cat_. ['Zophie', 'Pooka', 'Simon'] »> _helfFile['cat_'] = cats »> _helfFile.clo_e() Чтобы иметь возможность читать и записывать данные с помощью мо- дуля shel Уе, прежде вcero еro необходимо импортировать. Вызовите метод she 1 ve. open () и передайте ему имя файла, а затем сохраните возвращен- ное значение в переменной. Доступ к хранилищу осуществляется по клю- чу, как при работе со словарями. Закончив работу, вызовите метод close ( ) . В данном случае значение сохраняется в переменной shelfFile. Мы соз- даем список cats и записываем ero в хранилище с помощью инструкции shel fFile [ 'cats'] = cats, которая сохраняет впеременной shelfFile спи- сок в виде значения, ассоциированноrо с lUlючом 'cats'. Затем мы вызыва- ем метод close () для переменной shelfFile. Выполнив предыдущий код на Wiпdоwsкомпьютере, вы увидите в Teкy щем рабочем каталоrе три новых файла: тydata.bak, тydata,dat и тydata.dir. На компьютере, работающем под управлением 05 Х, будет (о:щан только один файл тydata. db. Описанные двоичные файлы содержат данные, которые вы сохранили в хранилище. Точный формат хранения данных в этих двоичных файлах для вас не имеет значения; вам достаточно знать лишь то, что именно де- лает модуль shel ve, а не как он это делает. Данный модуль освобождает вас от всех забот, связанных с орrанизацией хранения данных проrраммы в файлах. Проrрамма может использовать модуль shel ve для последующеro откры- тия файлов хранилища и извлечения из них данных. Хранилища не нужда- ются в открытии в режиме чтения или записи  как только хранилище ()'f-- крыто, вы можете выполнять оба типа операций. Введите в интерактивной оболочке следующие команды. »> _helfFile = _hеlve.ореn('шydata') »> typ8(_helfFile) <class 'shelve.DbfilenameShelf'> »> _helfFile['cat_'] [ 'zophie 1, 'Pooka 1, I Simon 1] »> _helfFile.clo_e() 
238 r лава 8 Здесь мы открываем файлы хранилища для проверки TOI'O, что данные были корректно сохранены. Команда shelfFile [ 'cats'] возвращает тот же список, который был сохранен ранее, что подтверждает корректность c<r хранения данных, а метод close () закрывает хранилище. Как и словари, хранилища имеют методы keys () и values () , извлекаю- щие из хранилища коллекции ключей и значений, подобные спискам. П<r скольку возвращаемые этими методами коллекции лишь подобны спискам, а не являются истинными списками, для 1'01"0 чтобы получать их в виде списков, их следует передавать функции list () . Введите в интерактивной оболочке следующие команды. >>> _helfFile = _helve. open ('шydata') »> li_t(_helfFile.key_()) ['cats' ) »> li_t(_helfFile.value_()) [['Zophie', 'Pooka', 'Simon')) »> _helfFile.clo_e() Формат простоrо текста удобно использовать для создания файлов, которые вы будете читать в текстовом редакторе наподобие Norepad или TextEdir.. Если же вы хотите сохранять данные из своих проrрамм на языке Python, то используйте модуль shelve. Сохранениепеременныхспомощью функции pprint .pfonnat () Вспомните, как в разделе "Красивая печать" в rлаве 51'0ВОРИЛОСЬ о том, что функция pprint. pprint () обеспечивает "красивый" вывод содержимоrо списка или словаря на экран, тоrда как функция pprint .pformat () просто возвращает тот же текст в виде строки. Эта строка не только отформати- рована так, что ее удобно читать, но и представляет собой синтаксически правильный код Python. Предположим, у вас есть словарь, сохраненный в переменной, и вы хотите сохранить эту переменную и ее содержимое для будущеrо использования. Применив функцию pprint. pformat () , вы получи- те строку, которую можно записать в .ру-файл. Этот файл будет вашим соб- ственным модулем, который вы сможете импортировать всякий раз, коrда захотите ИСIlользовать хранящуюся в нем переменную. Например, введите в интерактивной оболочке следующие команды. »> iшport pprint »> cat_ = [{'namе': 'Zophie', 'де_с': 'chubby'}, ('паше':  'Pooka', 'де_с': 'fluffy')] »> pprint.pformat(cat8) 
Чтение и запись файлов 239 "[{'desc': 'chubby', 'пате': 'Zophie'}, {'desc': 'fluffy', 'пате': , Pooka ' } ] " »> fileObj .. open('DlyCat8.py', '1") »> fileObj.writ8('cats = ' + ррrint.рfоrшаt(саt8) + '\n') 83 »> fileObj.clo.8() Здесь мы импортируем модуль pprint, чтобы иметь возможность исполь- зовать функцию pprint.pformat (). у нас есть список словарей, сохранен- ный в переменной cats. Чтобы сохранить возможность обращения к спи- ску, хранящемуся в переменной cats, даже после Toro как будет закрыта обо- лочка, мы используем функцию pprint. pformat () , возвращающую список в виде строки. Получив данные, хранящиеся переменной cats, в виде строки, мы сможем леrко записать их в файл, который мы назовем туСам.ру. Модули, импортируемые с помощью инструкции import, сами являются не более чем обычными сценариями Python. После Toro как строка, воз- вращаемая pprint. pformat () , будет сохранена в ,ру-файле, этот файл станет модулем, который может быть импортирован подобно любому друrому мо- дулю. А поскольку сценарии Python сами по себе являются пр<><:тыми тексто- выми файлами с расширением .ру, ваши ПрОl'раммы на Python MOryт даже I'енерировать друrие Руthоп-проrраммы. Впоследствии эти файлы MOryт импортироваться в сценарии. »> iDlport DlyCats >>> DlyCat8.cats [{'пате': 'Zophie', 'desc': 'chubby'}, {'name': 'Pooka', 'desc': 'ПиНу' }] »> myCats.cat.[O] {'пате': 'Zophie', 'desc': 'chubby'} »> DlyCat..cat.[O] ['nаше'] 'zophie' Преимуществом создания .ру-файлов (в отличие от сохранения пере мен- ных с помощью модуля shel ve) является то, что, поскольку они представля- ют собой обычный текстовый файл, ero содержимое можно читать и изме- нять с помощью обычноrо TeKcToвoro редактора. Однако ДЛЯ большинства приложений сохранение данных с использованием модуля shelve является более предпочтительным способом сохранения переменных в файле. Запи- сывать в файл в виде простоrо текста можно только данные элементарных типов, такие как целые числа и числа с плавающей точкой, строки, списки и словари. Объекты же File, например, не MOryт быть закодированы в виде текста. 
240 r лава 8 Проект: rенерация файпов Сllучайных зкэаменационных 6Иllетов Предположим, вы читаете reоrpафию rруппе из 35 студентов и хотите провести контрольную работу на знание столиц штатов 8 США. Увы, 0& становка в вашем классе такая, что вы не можете быть уверены в том, что студенты не будут списывать друr у дрyra. Вы хотели бы составить экзамена ционные билеты таким образом, чтобы вопросы в них располаraлись в ('.лу чайном порядке, блarодаря чему каждый билет будет отличаты-я от дрyrоro, и это затруднит списывание ответов. Разумеется, составлять такие билеты вручную  задача утомительная и к тому же отнимающая MHoro времени. К счастью, вы уже освоили некоторые возможнО{ти Python. Вот примерный план действий, которые должна выполнять проrрамма: . создать 35 различных билетов; . создать по 50 вопросов с множественным выбором для каждоro биле- та, расположив их в случайном порядке; . предоставить правильный ответ и три случайно выбранных непра вильных ответа на каждый вопрос, располаrая их в случайном порядке; . записать билеты в 35 текс.товых файлов; . записать ключи ответов в 35 текстовых файлов. Это означает, что код должен будет выполнять следующие операции: . сохранять названия штатов и их столиц в словаре; . вызывать методы open () , write () и close () для текстовых файлов, в которых хранятся билеты и ключи ответов; . использовать функцию random. shuffle () для рандомизации (располо- жения в случайном порядке следования) вопросов и вариантов MHO жественноro выбора. Ша, '. Сохране".е lIан".,х 6..ero8 8 '.О8аре Первый шar состоит в том, чтобы создать "скелет" сценария и напол нить ero данными билета. Создайте файл raпdoтQyizCnтerator.py и введите в Hero следующий текст. #! руthопЗ i randomQuizGenerator.py  Создает экзаменационные билеты с t вопросами и ответами, расположенными в случайном порядке, # вместе с ключами ответов. о import random # Данные билета. Ключи  названия штатов, а значения  столицы. О capitals = {'Alabama'; 'Montgomery', 'Alaska': 'Juneaи" 'Arizona': 'Phoenix', 'Arkansas'; 'Little Rock', 'California'; 
Чтение и запись файлов 241 'Sacramento', 'Colorado': 'oenver', 'Соппесt icut ': 'Hartford', 'Delaware': 'Dover', 'Florida': 'Tallahassee', 'Georgia': 'Atlanta', 'Hawaii': 'Honolulи" 'Idaho': '80i5e', 'Illinois': 'Springfield', 'Indiana': 'Indianapolis', 'Iowa': 'Оев Moines' , 'Kansas': 'Topeka', 'Kentucky': 'Frankfort', 'Louisiana': 'Baton Rouge', 'Maine': 'Augusta', 'Maryland': 'Annapolis' , 'Massachusetts': 'Boston', 'Michigan': 'Lansing', 'Minnesota': 'Saint Paul', 'MiS5issippi': 'Jackson', 'Missouri': 'Jefferson City', 'Montana': 'Helena', 'Nebraska': 'Lincoln', 'Nevada': 'Carson City', 'New Hampshire': 'Concord', 'New Jersey': 'Trenton', 'New Mexico': 'Santa Fe', 'New York': 'Albany', 'North Carolina': 'Raleigh', 'North oakota': 'Bismarck', 'Ohio': 'Columbus', 'Oklahoma': 'Oklahoma City', 'Oregon': 'Sa1em', 'pennsylvania': 'Harrisburg', 'Rhode Island': 'Providence', 'South Carolina': 'Columbia', 'South Dakota': 'Pierre', 'Tennessee': 'Nashville', 'Texas': 'Austin', 'Otah': 'Salt Lake City', 'Vermont': 'Montpelier' , 'Virginia': 'Richmond', 'Washington': 'Olympia', 'West Virginia': 'Charleston', 'Wisconsin': 'Madison', 'Wyoming': 'Cheyenne'} # rенерация 35 файлов билетов. . for quizNum in range(35): # тооо: Создать файлы билетов и ключей ответов. # TODO: Записать заrоловок билета. # TODO: Перемешать порядок следования штатов. # тооо: Орrанизовать цикл по всем 50 штатам, # создавая вопрос для каждоrо из них. Поскольку эта nporpaMMa должна располаrать вопросы и ответы в слу- чайном порядке, вам понадобится импортировать модуль random ., чтобы использовать el'o функции. Переменная capitals . содержит словарь, в K<r тором штаты CIIIA иrрают роль ключей, а значениями являются названия столиц штатов. А поскольку вы хотите создать 35 билетов, код, который бу- дет фактически reнерировать файлы билетов и ключей ответов (на данном этапе отмечен комментариями ТООО), должен бьrrь помещен в цикл for, выполняющий 35 итераций .. (Это число можно изменить для rенерации любоro заданноrо количества билетов.) Ш",2. COIlf"""e ."..08 6"..108 " "ере.'."8""'" 80"1'0'08 А теперь настал черед приступить к замене комментариев TODO реаль- ным кодом. Код в цикле будет повторен 35 раз  110 одному разу на каждый билет, в связи с чем вам достаточно сосредоточивать внимание в цикле каждый раз 
242 rлава 8 только на одном билете. Прежде Bcero, необходимо создаТI. фактический файл билета. Он должен иметь уникальное имя, а также содержать некий стандартный зarоловок с пустыми полями для имени, даты и класса, кото- рые будут заполняться студентами. Далее вам нужно будет IIОЛУЧИТЬ список штатов, расположенных в случайном порядке, который впоследствии мож- но будет использовать для создания вопросов и ответов 1< каждому билету. Добавьте в файл rаndoтQиiz.Generаtor.рууказанные ниже строки кода. #! python3 # randomQиizGenerator.py  Создает экзаменационные билеты с # вопросами и ответами, расположенными в случайном порядке, # вместе с ключами ответов. пропущенный KOД # rенерация 35 файлов билетов. for quizNum in range(35): # Создание фай,п08 БИnИ08 И 1UJJOЧ8Й 0'1'88'1'08. О quizFile = open('capital.quiz%..txt' % (quizNwa + 1), '1") О 8n81'erKeyFile - оpen (1 capital.quiz answer.%.. txt' % (quizNwa + 1), '1")  * Зanиоь зarroп08ка билиа. . quizFile. 1'ri t8 ( 'Имя: \n \nДa'1'a: \n \nКypc : \n \n I ) quizFile.1'rite«' I * 15) + 'Пр08ерка зlUUlИJl CI'1'OJJИЦ lI'1'а'1'08 (Випи %.)' % (quizNuDI + 1» quizFile.1'rit8('\n\n') * П8р8М811И88НИе ПopядlCа спед08ания CI'1'OJ1ИЦ 1I'1'a'1'08. 8tate. = li.t(capital..key.(» о randoJD. shuffle (.аteз) # тооо: Орrанизовать цикл по всем 50 штатам, # создавая вопрос для каждоrо из них. Файлы будут иметь имена capitalsquiz<N>.txt, I'де <N>  это уникальный номер билета, который берется из переменной цикла qui zNuт. Ключи от- ветоВ для файлов capitalsquiz<N>. txt будут храниться в тек<.:товых файлах capitalsquizanswers<N>.txt. При каждом прохождении цикла вместо замести. теля %s в строках' capitalsquiz%s. txt' и 'capitalsquizanswers%s. txt' бу- дет подставляться значение (quizNum + 1), поэтому файлами l1epBoro из соз- даваемых билетов и ключа ответа будут capitalsquiz.1. txl и capitalsquiz.aпswers 1. txt. Эта файлы будут создаваты-я вызовами функции open () в инструкциях О и . с передачей им строки 'w' в качестве ВТОрОI'O apryмeHTa для открытия файлов в режиме записи. Инструкции wri te () . создают заl'OЛОВОК билета с полями, которые будут заполняться студентами. Наконец, с помощью функции random. shuffle () ., которая случайным образом переynорядочивает список любых переданных ей значений, создается рандомизированный список всех штатов США. 
Чтение и запись файлов 243 Ша, 3. COJllaH"e .ap"aHrOB OrBero. Теперь необходимо crенерировать варианты ответов для каждоrо вопро- са, предоставляя возможность выбора одноrо из ответов, обозначенных буквами от А до О. Вам понадобится создать еще один цикл for  он будет rенерировать содержимое для каждоrо из 50 вопросов билета. Далее будет третий, вложенный цикл for, предназначенный для rенерации вариантов множественноrо выбора для каждоrо вопроса. Дополните имеющийся код, как показано ниже. #! руthопЗ # randomQuizGenerator.py  Создает экзаменационные билеты с # вопросами и ответами, расположенными в случайном порядке, # вместе с ключами ответов. пропущенный KOД * Ор:rаНИ8ация цима по 8оем 50 штатам * с созданием 80npоса для К8ЖДо:rо из них. for que.tionNum in range(50) : * Попучение npавипьНblX и нenpaвипьных отве'1'О8. О correctAn."er - capital. [.tate8 [questionNwa)) . wrongAn.wer. = li.t (capi tal. . val ue. () ) о del "rongAn.wer.[wrongAn.wer..index(correctAnswer)) О wrongAn.wer. = randoВl..ample(wrongAn.wer., 3) О an."erOption. = wrongAn.wer. + [correctAn.wer) О randoJa. .huffle (answerOption.) * 'l'ODO: Записа 8apиaR'1'W ВОпроСО8 И 0'1'88'1'08 * 8 фвйn esипе'1'а. * 'l'ODO: Зanиса JCJDIN 0'1'88'1'а 8 ф8Йn. Корректный ответ можно леrко получить  он хранится в виде значе- ния в CJюваре capitals е. Данный цикл итерирует по штатам, содержа- щимся в перемешанном списке штатов, от states [О] До states [49], находит каждый штат в capitals и сохраняет название ero столицы в переменной correctAnswer. Со списком возможных неправильных ответов дело обстоит несколько сложнее. Вы сможете ПOJIYЧить еro, продублировав все значения из словаря capitals е, удалив правильный ответ е и выбрав три случайных значения из этоrо списка е. Функция random. sample () упрощает этот выбор. Ее пер- вый apryмeнт  это список, из Koтoporo вы хотите выбирать значения; вто- рой apryмeнт  это количество значений, которые вы хотите выбрать. Пол- ный список вариантов ответа представляет собой сочетание трех непра- 
244 r лааа 8 вильных ответов справильным .. Наконец, ответы следует перемешать ., чтобы правильный ответ не Bcerдa соответствовал варианту Н. Ш", 4. 3"""" 'ОllеР."IIО'О . ф".л., 6"nе,о. " КЛIO,е. о,.е,о. Теперь все, что осталось сделать,  это записать вопрос в файл билета, а ответ  в файл ключей ответов. Дополните код, как 110казано ниже. #! python3 # randomQuizGenerator.py  Создает экэаменационные билеты с # вопросами и ответами, расположенными в случайном порядке, # вместе с ключами ответов. пропущенный KOД # Орrанизация цикла по всем 50 щтатам # с созданием вопроса для каждоrо иэ них. for questionNum in range(50): пропущенный KOД # Запись варианто. .ОПРОСОВ и ответов . фaйn 6иnета. quizFi18.writ8('%8. выберете monицу lI'1'aTa %8.\n' % (qu88tionNwD + 1, 8tat8. [qu88tionNum]) ) о for i in range (4) : О quizFi18.writ8(' %8. %.\n' % ('AВCD' [i], an8w8rOption8 [i] ) ) quizFi18.writ8('\n') # Зanис. a ОТ.8та . ф8Йn. О an8werКeyFi18.writ8('%8. %8\n' % (qu88tiOnNum + 1, 'AВCD'[an8w8rOption8.index(00rreotAn8w8r)]» quizFi1e. 01088 () an8werhyFil8. 01088 О Цикл for, перебирающий целые числа от О До 3, записывает варианты ответов в список answerOptions.. В выражении 'АВСО' [i] . строка 'АВСО' трактуется как массив с элементами 'А', 'В', 'С' и 'О' , выбираемыми на со- ответствующей итерации ЦИкла. В последней строке. выражение answerOptions. index (correctAnswer) находит целочисленный индекс правильноro ответа среди случайно рас- положенных вариантов, а вычисление выражения 'АВСО' [answerOpt ions. index (correctAnswer) ] дает буквенное обозначение правильноrо варианта ответа, подлежащеrо записи в файл ключа ответа. Ниже показан примерный вид содержимоrо файла capitalsquizl.txt, хотя, разумеется, вопросы и варианты ответов в вашем файле будут выrлядеть иначе, в зависимости от результатов вызова функции random. shufПе (). 
Чтение и запись файлов 245 Имя: Дата: Курс: Проверка знания столиц штатов (Билет 1) 1.Выберите столицу штата West Virginia. А. Hartford В. Santa Fe С. Harrisburg О. Charleston 2. Выберите столицу штата Colorado. А. Raleigh В. Harrisburg С. Denver О. Lincoln опущено Соответствующий текстовый файл capitalsquiz.answersl.txt будет выrля- деть примерно так. 1. D 2. С З. А 4. С опущено Проект: буфер обмена дnя работы с нескояькими значениями Предположим, вам пред<:тоит yrомительная работа по заполнению ряда форм на веб-странице или мноrочисленных текстовых полей в проrpaмме. Буфср обмена обеспечивает экономию времени, избавляя вас от необходи- мости 1I0BTOpHOI"O ввода одноro и тoro же текста. Вместе с тем в Hel'O можно копировать только один фраrмент TeKLTa за один раз. Если же у вас имеется несколько рааличных тек(:товых ФраrмеllТОВ, ПОДJIсжащих копированию и вставке, то вам приходит(я каждый раз заново выделять и копировать одни и те же фраrменты. Однако можно написать проrрамму на языке Python, которая будет отслеживать различные фраrмеllТЫ текста. Мы назовем этот "мноrоза- рядный" буфер ("multiclipboar"d") тсЬ.руш (поскольку "mсЬ" короче, чем "шullkliрЬоar"d"). Расширение .руш означает, что Python не будет отобра- жать окно терминала в процессе выполнения проrpаммы. (Более подробно об этом читайте в ПРИЛОЖСIIИИ Б.) 
246 r лава 8 Проrрамма будет сохранять каждый фрarмент копируемоrо в буфер тек- ста с использованием сиоеrо ключевою слова. Например, е<:ли вы ВЫIЮЛ- ните команду ру тсЬ. pyw save spam, то текущее содерЖИМое буфера обмена будет сохранено с ключевым словом spam. Впоследствии этот тек<:т можно будет вновь зarрузить в буфер обмена с помощью команды ру тсЬ. pyw spam. А если пользователь забудет, какие ключевые слова соответствуют тем или иным текстовым фрarмептам, то он сможет выполнить команду ру тсЬ. pyw 1 ist для копирования списка всех ключевых слов в буфер обмена. Вот что делает данная проrpамма: . проверяет apryMeHT командной строки для ключевOI'О слова; . е<:ли этот apryмeHT  save, то содержимое буфера обмtна сохраняется с данным ключевым словом; . если этот арryмент  list, то все ключевые слова копируются в буфер обмена; . в противном случае текст, соответствующий ключевому слову, копиру- ется в буфер обмена. Это означает, что код должен выполнять следующие действия: . читать apryмeHTЫ командной строки из переменной sys. argv; . выполнять операции чтения и записи в буфер обмена; . сохранять и заrружать текст в файл хранилища. Если вы работаете в Windows, то вам будет леrко выполнить этот сцена- рий из окна Run (Выполнить), создав пакетный файл тrh.batco следующим содержимым: @pyw.exe C:\Python34\mcb.pyw %* Ша, '. Ko_eнrap.. " "aapoiKa .жра".....а Начнем с создания каркаса сценария, содержащеr() некоторые коммен- тарии и базовые настройки. Создайте следующий код. #! python3 # mcb.pyw  Сохраняет и заrружает фраrменты текста # в буфер обмена. О # Использование: ру.ехе mcb.pyw save <ключевоеслово>  # Сохраняет буфер обмена в ключевое слово. # ру.ехе mcb.pyw <ключевоеслово>  # Заrружает ключевое слово в буфер обмена. # ру.ехе mcb.pyw list  # 3аrружает все ключевые слова в буФер # обмена. 
Чтение и запись файлов 247 о import shelve, pyperclip, sys о mcbShelf  shelve.open ('тсЬ') # тооо: Сохранить содержимое буфера обмена. # TODO: Сформировать список ключевых слов и заrрузить # содержимое. mcbShelf. close () Общепринятой практикой является размещение общей информации о порядке использования проrраммы, оформленной в виде комментариев в начале файла.. Если вы вдруr забудете, как выполнить сценарий, взrля- ните на комментарий. Затем импортируются необходимые модули .. Для копирования и вставки текста потребуется модуль pyperclip, а для чтения apryMeHToB командной строки  модуль sys. Также потребуется модуль shelve: всякий раз, коrда пользователь захочет сохранить НОВЫй фраrмент находящеrося в буфере обмена текста, вы сохраните ero в файле хранили- ща. Далее, если пользователь захочет поместить текст обратно в буфер, вы откроете файл хранилища и заI'рузите ero в проrрамму, Имя файла храни- лища будет содержать префикс тсЬ .. Ш",2. 'Ollt"... 'ОIl'Р...О'О 6уф.р" ОМ''''', ""о"."ру'.ою , оlO...... tJlО.О. Проrрамма выполняет различные действия в зависимости от 1'01"0, чеrо хочет пользоватеJlЬ: сохранить текст, ассоциируя ero с ключевым словом, заrрузить текст в буфер или вывести список всех имеющихся ключевых ('лов. Рассмотрим первый с.лучай. ДОП()}Jните код, как показано ниже. #! руthопЗ # mcb.pyw  Сохраняет и заrружает фраrменты текста # в буфер обмена. пропущенный KOД # Сохранение содержимоI'O буфера оБИ8на. О if len(.y..argv) .... 3 and .y..argv[l] .lower() .. '.аув': . DlcbShelf[.y..argv[2]] .. pyperclip.pa.teO elif len(.y..argv) == 2: О # Сформировать список ключевых слов и заrрузить содержимое. mcbShelf.close() Если первым apryMeHToM командной строки (который всеrда будет иметь индекс 1 в списке sys. argv) ЯВJIЯется 'save' О, то вторым aployмeHToM 
248 r лава 8 ЯWJяется ключевое слово для текущеrо содержимоrо буфера обмена. Ключе- вое слово будет использоваться в качестве ключа для хранилища mcbShelf, тоrда как значением будет текст, находящийся в данный момент в буфере обмена .. Если предоставлен только один apryMeHT командной строки, то вы пред- полаrаете, что это либо строка' list', либо ключевое слово для заrрузки содеРЖИМОJ'О в буфер обмена. Этот код вы реализуете lIозднее. А пока что оставьте в этом месте кодасоответ(:твующий комментарий TODO.. Ш", 3. Сn.,ок KIlIO".WX ело. . J""YJK" 'Oll"...oro, "ccoq..pO."HHOro t KIlIO".". '.0.0. Ilаконец, реализуем два оставшихся пункта: зarpузка в буфер обмена TeK ста, ассоциированноrо с определенным ключевым словом, и вывод списка всех доступных ключевых слов. Дополните код, как показано ниже. #! python3 # mcb.pyw  Сохраняет и заrружает фраrменты текста # в буфер обмена. пропущенный KOД # Сохранение содержимоrо буфера обмена. if len(sys.argv} == 3 and sys.argv{l] .lower() == 'save': mcbShelf[sys.argv[2]] = pyperclip.paste() elif len(sys.argv) == 2: * Формирование cnиска КJDI)Чеаых с.пов и зaжpvзка СОДepDlМOl'O. О if &y..arqv[l].lower() == 'li.t': О руреrсliр.cqpy(.tr(li.t(шdbShelf.keys(»» elif .y..argv[l] in mcbShelf: О pyperclip. cqpy (шсЬShеlf [.У.' arqv[ 1] ] ) mcbShelf.close(} Если имеется только один арryмент командной строки, то прежде вcero необходимо проверить, является ли ИМ строка' list' .. Если это действи- тельно так, то в буфер обмена копируется строковое представление списка ключей хранилища .. Пользователь может вставить этот список в окно oт KpblToro TeKcтoBoro редактора и прочитать ero. В противном случае можно полaraть, rro apryMem командной строки яв- ляется ключевым словом. Если это ключевое слово существует в виде ключа хранилища mcbShelf, можно заrpузить соответствующее значение в буфер обмена .. Вот и все! В зависимости от установленной на компьютере операцион- ной системы эта проrрамма может запускаться Шrразному. Детали запуска ПрОI'рамм в различных операционных системах описаны в приложении Б. 
Чтение и запись файлов 249 Вспомните проrpамму парольной защиты, сохраняющую пароли в сл<r варе, которую мы создали в rлаве 6. Обновление паролей требовало изме- нения исходноro кода проrраммы. Это далеко не идеальный вариант, по- скольку пользователи не MOryr чувствовать себя комфортно, если для об- новления проrpаммы им приходится самостоятельно вносить изменения в код. Кроме Toro, всякий раз, коrда приходится изменять исходный код I1pOrpaMMbI, существует риск случайноrо внесения в нее новых ошибок. ('.о- храняя данные для проrpаммы не в коде, а в дpyroM месте, вы облеrчаете использование проrраммы друrими людьми и снижаете вероятность появ ления в ней новых ошибок. РеЗlOме Файлы ОРl'анизуются в папки (друroе название  каталоrи), и их расп<r ложение описывается пyrями доступа. Каждая проrpамма, выполняющая ся на вашем компьютере, имеет текущий рабочий каталоr, что позволяет указывать пyrи относительно текущеrо расположения вместо Toro, чтобы всеrда задавать полный (или абсолютный) путь. Модуль 05. path содержит множество функций, предназначенных для манипулирования пyrями досту- па к файлам. Ваши проrраммы имеют возможность непосредственно взаимодейстВ<r вать с содержимым текстовых файлов. Функция open () позволяет OTKpЫ вать эти файлы для чтения их содержимоI'О в виде одной длинной строки (с помощью метода read () или в виде списка строк (с помощью метода readline5 () ). Функция open () может открывать файлы в режиме записи или присоединения для создания новых текстовых файлов или добавления TeK ста в конец существующих файлов соответственно. В предыдущих rлавах вы использовали буфер обмена в качестве сред- ства, позволяющеrо вставлять в проrрамму rотовый текст, а не вводить eI'o вручную. Теперь же вы научились читать необходимые проrрамме данные непосредственно с жесткоrо диска, что является большим дости' жением, поскольку хранить данные в файлах I'ораздо надежнее, чем в бу- фере обмена. Из следующей rлавы вы узнаете о том, как обрабатывать сами файлы, Т.е. выполнять такие операции, как копирование, удаление, переименова- ние, перемещение файлов и мноrие ДРУI'ие. Контропьные вопросы 1. Относительно чеrо задается относительный пyrь? 2. С чеrо начинается абсолютный путь? 3. Каково назначение функций 05. getcwd () и 05. chdir ()? 
250 4. 5. 6. 7. 8. 9. r лава 8 Что собой представляют папки . и . .? Какие части пути С:\Ьасоn\еggssраm.tхtпредставляют имя папки и базо- вое имя? Назовите три возможных значения apryMeHTa, задающие режим o крытия файла, которые MOryт передаваться функции open ( ) . Что происходит при открытии существующеrо файла в режиме запи си? Чем ра:шичаюп:я методы read () и readlines ()? Какую структуру данных напоминает орrанизация хранилища, созда- BaeMoro с помощью модуля shelve? Учебные проекты Чтобы закрепить полученные знания на практике,напишите проrрам- мы для предложенных ниже задач. Р"сш.рен.е возможносте. 6уфер" .е.", Р""'."'''''ОIO н" Р"'ОТУ , не,"о.,,,,,.,, зн",ен".." Расширьте возможности проrраммы для работы <: не(:колькими значени ями через буфер обмена таким образом, чтобы она допускала использова- ние apryMeHTa командной строки ВИда delete <КЛ1Очевоес.лово>, обеспечи вающеrо удаление ключевоro слова из хранилища. Затем добавьте apryмeHT командной строки delete, позволяющий удалить все ключевые слова, пporp".M" II"d L;b$ Создайте проrрамму Mad Libs, которая читает текстовые файлы и пре доставляет пользователю возможность добамять собственный текст в лю бом месте файла, rде встречаются слова ADJECTIVE (прилаrательное), NOUN (существительное), ADVERB (наречие) и VERB (rлаrол). Например, содержи мое TeKcToBoro файла может иметь следующий вид: ТЬе ADJECTIVE panda walked to the NOUN and then VERB. А nearby NОИN was unaffected Ьу these events. Проrрамма найдет вхождения перечисленных слов и предложит пользо вателю заменить ИХ. Введите имя прилаrательное: silly Введите имя существительное: chande1ier 
Чтение и запись файлов 251 Введите rлаrол: screamed Введите имя существительное: pickup truck в результате будет еоздан следующий текстовый файл: The silly panda walked to the chandelier and then screamed. А nearby pickup truck was unaffected Ьу these events. Результаты должны выводиться на экран и сохраняться в новом TeKCTO вом файле. по",к , пОМОЩ61D pery..p""X ."ра.е",,;; Напишите проrрамму, которая открывает все файлы с расширением. txt, находящиеся в папке, и выполняет поиск строк, соответствующих предо ставленному пользователем реryлярному выражению. Результаты должны ВblВОДИТЬСЯ на экран. 
VПРАвпЕНИЕ ФАйпАМИ в предыдущей rлаве вы научились создавать и записывать на жесткий диск новые файлы в Python. Но ваши проrрам- мы MOryr реорrанизовывать и файлы, которые уже суще- ствуют на жестком диске. Возможно, вам приходилось рабо- тать с папками, хранящими десятки, сотни и даже тысячи файлов, которые нужно было копировать, переименовы- вать, перемещать или сжимать вручную. Задачи подобноrо рода MOryr быть самыми разнообразными, включая, например, следующие: . создание копий всех РDF-файлов (и moJlЪ"'О РIН'-файлов) во всех под папках заданной папки; . удаление ведущих нулей из имен всех файлов наподобие spaтOOl.txt, spaт002.txt, sратООЗ.tхt и так Далее, сохраненных в некоторой папке в количестве, исчисляемом сотнями; . сжатие содержимоrо нескольких папок в один ZIР-файл (что может быть использовано для создания простейшей системы для создания резервных КОПИй файлов). Подобные рутинные задачИ так и просятся, чтобы их автоматизировали с помощью Python. Проrpаммируя свой компьютер для выполнения Taкoro рода задач, вы леrко превратите ero в расторопноrо, безошибочно функ- ционирующеrо офисноrо клерка. Начав работать с файлами, вы вскоре поймете, насколько удобно иметь возможность непосредственно видеть, какое расширение (.txt, .pdj, .jрgи др.) имеет тот или иной файл. В операционных системах 05 Х и Lint1x обо- зреватель файлов в большинстве случаев автоматически отображает pac ширения имен. В случае же Windows расширения имен файлов по YMOk чанию MoryT скрываться. Чтобы отобразить их, выберите ПУНКТЫ меню ПускПанель управленияОформление и персонализацияПараметры папок. Пере- йдите в открывшемся окне на вкладку Вид и снимите флажок Скрывать расши- рения для зареrистрированных типов файлов в разделе Дополнительные параметры. 
254 r лава 9 Модупь shutil Модуль shutil (от аllI'Л. "shell utilities"  утилиты командной оболочки) содержит функции, позволяющие копироваТI>, перемещать, переименовы вать и удалять файлы с IIОМОЩЬЮ проrpамм на PytllOll. Прежде чем исполь зовать эти утилиты, необходимо выполнить инструкцию import shutil. Ко""ров"н", ф"..08 " п""ок Модуль shutil предоставляет функции для копирования как файлов, так и целых папок. Вы:юв shut il. сору (исходный путь, путь  назначения) приведет к КОПИр<r ванию файла, расположение KOToporo определяется пyrем ИСХОДНЫЙ путь, В папку, определяемую пyrем путьназначения. (Параметры исходныйпуть и путьнаэначения являются строками.) Если apryMeHT путьназначения это имя файла, то оно будет использовано 8 качестве HOBoro имени скопи- pOBaHHoro файла. Эта функция возвращает строку, (одержащую пyrь к CK<r пированному файлу. Чтобы посмотреть, как работает функция shutil. сору (), введите в ИIIТC'" рактивной оболочке следующие команды. >>> import .hutil, о. »> 08.chdir('C:\\') О >>> shutil. сору ( 'С: \\spam. txt', I С: \\deliciou. ' ) 'C:\\delicious\\spam.txt' О >>> .hutil.copy('egq..txt', 'C:\\delicious\\eqq.2.txt') 'C:\\delicious\\eggs2.txt' Первый вызов функции shutil. сору () копирует файл C:\fpaт.lxt В папку C:VJelicious. Возвращаемым значением является пyrь к скопированному фай- лу. Обратите внимание на то, что в качестве объекта назначения указана папка., а имя копии файла совпадает с именем исходноrо файла spaт. txt. Второй вызов shutil. сору () . также выполняет копирование файла C:. txt в папку C:VJeliciou..s, но копии файла присваивается имя eggs2. txt. В то время как функция sh и til. сору () копирует одиночный файл, функ- ция shutil.copytree () копирует папку вместе со всеми папками и файлами, которые в ней содержатся. В результате вызова shutil. copytree (исходный путь, путьназначения) папка, находящаяся в расположении исходный путь, копируется вместе со всеми находящимися в ней файлами и под' папками в раПlOложение путьназначения. Параметры исходныйпуть и путьназначения являются строками. Функция возвращает строку, пред- ставляющую путь к копии папки. 
Управление файлами 255 Введите в интерактивной оболочке следующие команды. »> iшроrt _hutil, оз »> оз.chdir('С:\\') »> зhutil.соруtrее('С:\\Ьаооn', 'C:\\baconbackup') 'C:\\baconbackup' в результате вызова shutil. copytree () создается новая папка baconbackup с таким же содержимым, как и содержимое исходной папки Ьасоn. Теперь у вас есть резервная копия вашей бесценной папки Ьасоn. п.р.....,."". " "'Р'".'''08''''''' фаЙЛ08 " """ОК Вызов функции shutil.move (исходныйпуть, путьназначения) пере- Мещает файл или папку из расположения исходныйпуть В расположение путьназначенияи возвращает строку, представляющую абсолютный пyrь к новому расположению. Если l1араметр путь  назна чения представляет папку, исходный файл пе-- ремещается в папку назначения и сохраняет свое текущее имя. Например, введите в интерактивной оболочке следующие команды. »> iшроrt _hutil »> зhutil.ШОV8('С:\\Ьасоn.txt', 'C:\\egqs') 'C:\\eggs\\bacon.txt' в предположении, что папка eggs существует в каталоre c: вызов shutil. тоуе () в словесной формулировке означает следующее: "Переме(ТИ1Ъ файл C:'/Jacon. txt в папку C: "(:ggs" , Если в папке C:"(:ggs уже существует файл Ьасоп, txt, то он будет заменен. Поскольку таким образом можно очень леl"КО случайно потерять нужный файл, вы должны проявлять определенную осторожность, коrда используе- те функцию move ( ) . Параметр путь  назна чения также может задавать имя файла. в следую- щем примере исходный файл перемещает(:я и переименовывастся. »> _hutil.шоvе('С:\\Ьасоn.txt', 'C:\\egqs\\new bacon.txt') 'C:\\eggs\\newbacon.txt'  Эта строка кода имеет следующий смысл: "Переместить файл C:'.pacon.tx/ в папку C:"(:ggs и присвоить перемещенному файлу bacoп.txt новое имя nеш Ьасоn. txf, Оба предыдущих примера работают в предположении, что в каталоrс с:\ существует папка eggs. Но если это не так, то функция move () переименует файл Ьасоn. txt в файл eggs. 
256 r лава 9 »> Shutil.шove('С:\\Ьасоn.txt', 'C:\\eggs') 'с: \\eggs , Здесь функция move ( ) , не найдя папку eggs в каталоre c: npедполаrает, что путь назначения ДОJlжен обозначать имя файла, а не папки. Поэтому текстовый файл bacon.txt переименовывается в файл eggs (текстовый файл, но без расширения. txt)  вероятно, вразрез с вашими намерениями! Разrля- деть подобную ошибку в проrрамме очень трудно, поскольку вызов move ( ) может беспрепятственно сделать то, чею вы совершенно не ожидали. Это еще одна из причин, по которым работа с функцией move () требует осто- рожности. Наконец, задаваемые в качестве nyти назначения папки должны суще ствовать, иначе PythOIl сreнерирует исключение. Введите в интерактивной оболочке следующую команду. »> 8hutil.шоve('spаш,txt', 'C:\\d088 not ехi8t\\egg8\\haш') Traceback (most recent call last):   File "C:\Python34\lib\shиtil.py", line 521, in move os.rename(src, realdst) FileNotFoиndError: [WinError 3] The system cannot find the path specified: 'spam.txt' > 'c:\\doesnotexist\\eggs\\ham' В процессе обработки вышеприведенноro исключения возникает друroe исключение. Traceback (most recent call last): File "<pyshell#29>", line 1, in <modиle> shиtil.move('spam.txt', 'c:\\does поt exist\\eggs\\ham') File "C:\Python34\lib\shиtil.py", lIne 533, in move copy2(src, real dst) File "C:\Python34\lib\shutil.py", line 244, in сору2 copyfile(src, dst, fo11ow symlinksfollow symlinks) File "C:\Python34\lib\shutil.py", line 108,in copyfile with open(dst, 'wb') as fdst: FileNotFoиndError: [Errno 2] No such file or directory: 'c:\\does not exist\\ eggs\\ham'  Python ищет eggs и haт в каталоrе doe.snoCexist. Поскольку найти несуще- ствующие каталоrи ему не удается, он не может псреместить файл spaт.txt по указанному пyrи. 
Управление файлами 257 В".о'.р"'.о, УII".'..' ф".nо. . п"пок Если для удаления одиночною файла или одиночной пустой папки мож но воспользоваться функциями, содержащимися в модуле 05, то для уда- ления папки вместе со всем ее содержимым следует использовать модуль 5hutil. . Вызов 05. unlink (путь) удаляет файл, расположенный 110 указанному пyrи. . Вызов 05. rmdi r (путь) удаляет папку. расположенную по указанному пути. Эта папка должна быть пуста. Т.е. не должна содержать никаких друrих папок и файлов. . Вызов 5hutil. rmtree (путь) удаляет папку, расположенную по указан ному пyrи. вместе со всеми содержащимися в ней друrими папками и файлами. Испольэуя эти функции в своих проrpаммах, соблюдайте осторожность! Часто имеет смысл предварительно запустить версию проrраммы, в KOT<r рой эти вызовы "закомментированы" . а контроль тою, какие файлы будут удаляться, осуществляется с помощью функции print (). Ниже приведен фраrмент проrраммы на языке Python. предназначенный для удаления файлов с расширением. txt, но из-за допущенной опечатки (выделена п<r лужирным шрифтом) удаляющий файлы с расширением. пct. import 05 for filename in 05.1i5tdir(): if filename.endswith(' .rxt'): 05.unlink(filename) Если бы У вас были важные файлы, имена которых заканчиваются pac ширением . пct, то все они были бы безвозвратно удалены. Вместо этоrо сле довало бы сначала запустить проrрамму с измененной версией этоrо фрar- мента. import 05 for filename in 05.1istdir(): if filename.end5with(' .rxt'): #05.unlink(filename) print(filename) Теперь вызов 05 . unlink () не будет выполняться, поскольку он включен в текст комментария ("закомментирован"), и в силуэтоrо Python проиrнори- рует ero. В таком случае вместо фактическою удаления файла будет лишь выведено имя файла, подлежащеrо удалению. Предварительно выполнив эту версию проrраммы, вы сразу же обнаружите, что в проrрамме имеется 
258 r лава 9 ошибка, из-за которой она будет ошибочно удалять не текстовые файлы с расширением. txt, а файлы с расширением. rxt. Убедившись в том, что проrрамма работает так, как запланировано, уда- лите строку print (имяфайла), а также символ комментария в строке с ин- струкцией 05. иnlink (имя файла), После этоrо вновь запустите проrрамму для удаления файлов. COxp".,'re реJер..ые ко".. УII".е..ых Ф"'.О. . п"пок , пOItlO",,,O _оду.' seпd2trash Поскольку встроенная функция Python shиtil. rmtree () необратимо удаляет файлы и папки, ее использование связано с определенным ри- ском. HaмHoro лучший способ удаления файлов и папок предлаrает модуль send2tra5h от независимых разработчиков. Этот модуль можно установить, выполнив команду pip install send2trash в окне терминала. (Более под- робная информация о Ilорядке установки модулей, разработанных сторон- ними компаниями, приведена в приложении А.) Модуль send2trash HaMHoro безопаснее в использовании, чем обычные функции Pyt}lOn, выполняющие операцию удаления, поскольку он отправ. ляет удаляемые файлы и папки в корзину компьютера или в специальную корзину, а не удаляет их безвозвратно. Если из-за ошибок в проrрамме будут удалены файлы, которые вы не собирались удалять, но при этом исполь:ю- вался модуль send2trash, то впоследствии у вас будет возможность восстано- вить их из <:пециальной корзины. После Toro как вы установите модуль send2trash, введите в интерактив- ной оболочке следующие команды. »> iшроrt send2tra_h »> baconFile. open('bacon.txt', 'а') * cosдает файп >>> baconFile. wri t8 ( 'Васоn is not а veqetable. ') 25 »> baoonFile.close() »> send2tra_h.send2trash('bacon.txt') Вообще roворя, целссообраано Bcerдa использовать функцию send2trash. 5end2trash () для удаления файлов и папок. Однако, несмотря на то что от- правка файлов в специальную корзину оставляет вам возможность их по- следующеrо восстановления, размер свободноrо дисковоrо пространства при этом не увеличивается, как в случае безвозвратноrо удаления файлов. Если у вас возникает потребность в освобождении ДИСКОВОI'О пространства, используйте для удаления файлов и папок функции модулей 05 и shиtil. Об- ратите внимание на то, что функция send2trash () может лишь отправлять файлы в корзину, но не извлекать их из нее. 
Управление файлами 259 Обход дерева KaTaпoroB Предположим, вы хотите переименовать все файлы, находящисся в не- которой папке, а также во всех ее подпапках. Следовательно, вам необходи мо выполнить обход Bcero дерева каталоrов, обрабатывая при этом каждый файл. К сожалению, написание соответствующей проrраммы  задача не- тривиальная, и потому Python предоставляет функцию, орrанизующую этот процесс вместо вас. Рассмотрим папку C:'ylelicioиs и все ее содержимое (рис. 9.1). с:\ L delicioиs cats [ catnaтes.txt zophie.jpg walnиt L L waffles bиtter. txt spam. txt Рис. 9. 1. Пример попки, содержащей три друrие папки и четыре файла Ниже приведен при мер проrpаммы, в которой для обхода дерева катал<r rOB, представлеНIIОro на рис. 9.1, используется функция 05. walk ( ) . import 05 for folderName, 5ubfolder5, filenames in os.walk('C:\\delicious'): рriпt('Текущая лапка  , + folderName) for subfolder in 5ubfolder5: рriпt('ПОДПАПКА ПАПКИ I + folderName + 1. , + subfolder) for filename in filename5: рriпt('фАЙЛ В ПАПКЕ' + folderName + 1. '+ filename) print ( , , ) 
260 r лава 9 Функции 05. walk () передается единственное строковое значение  пyrь к папке. Вы можете использовать функцию 05. walk () в цикле for для обхо- Да дерева каталOl'ОВ во MHorOM примерно так, как функцию range () можно использовать для перебора всех целых чисел, принадлежащих некоторому диапазону. Однако, в отличие от функции range () , функция 05. walk () воз- вращает три значения на каждой итерации цикла: 1) строку, содержащую текущее имя папки; 2) список строк, представляющих имена папок, которые содержатся в текущей папке; 3) список строк, представляющих имена файлов, которые содержатся в текущей папке. (Под текущей папкой я подразумеваю папку, используемую в текущей итерации цикла. Применение функции 05. walk () не приводит к смене те- кущеrо рабочеrо каталоrа проrраммы.) Подобно тому как допускается выбрать имя переменной i в коде for i in range ( 1 О) :, можно выбирать имена перемеНIIЫХ для трех вышеперечис- ленных значений. Обычно в качестве таковых я использую соответственно имена folderпame, sиbfolders и filename.5. Если вы выполните эту проrрамму, то ее вывод будет выrлядеть так. 'Текущая папка  C:\delicioиs ПОДПАПКА ПАПКИ C:\delicioиs: cats ПОДПАПКА ПАПКИ C:\delicious: walnиt ФАЙЛ В ПАПКЕ C:\delicioи5: spam.txt 'Текущая папка  C:\delicioиs\cats ФАЙЛ в ПАПКЕ C:\delicioиs\cats: catnames.txt ФАЙЛ В ПАПКЕ C:\delicious\cats: zophie.jpg 'Текущая папка  C:\delicioиs\walnиt ПОДПАПКА ПАЛКИ C:\delicioиs\walnиt: waffles 'Текущая папка  C:\delicioиs\walnиt\waffles ФАйЛ в ПАЛКЕ C:\delicioиs\walnиt\waffles: bиtter.txt. Поскольку функция 08. walk () возвращает списки строк для переменных 5иbfolder и filename, можно использовать эти списки в их собственных циклах for. Замените вы30вы функции print () собственным пользователь- ским кодом. (Или же удалите цикJIы for, если вам не нужен какой-то один из этих циклоВ или оба цикла.) 
Управление ф айлами 261 Сжатие файпов с ПОМОЩblO модуп. zipfile Вероятно, вы знакомы с ZIР-файлами (имеющими расширение .zip), в ко- торых В сжатом виде может храниться содержимое мноrих друrих файлов. В результате сжатия файла ero размер уменьшается, что немаловажно при передаче файлов через Интернет. А поскольку один ZIР-файл может coдe жать множество файлов и папок, эта возможность очень удобна для упаков ки нескольких файлов в один. Этот единственный файл (так называемый архU8'Н:ЫЙ файл) можно, например, присоединить к сообщению электрон- ной 1I0ЧТЫ. Ваши PythОI1проrраммы MOryr как создавать, так и открывать (или расnа- "'О8'ШJШnЪ) ZIР-файлы с помощью функций из модуля zipfile. Предположим, у вас имеется ZIР-файл exaтple.zip, содержимое KOToporo представлено на рис. 9.2. cats [ catnames.txt zophie.jpg spam. txt Рис. 9.2. Содержимое файла examp/e.zip Можете скачать этот файл по aдpecyhttp://nostarch.com/aиtomatestиff/ или просто использовать один из ZIР-файлов, которые уже имеются на ва- шем компьютере. Чrение 1"-ф"..о. Чтобы прочитать содержимое ZIР-файла, прежде Bcero необходимо соз- дать объект ZipFile (обратите внимание на использование прописных букв "Z" и "F" в имени объекта). Объекты ZipFile концептуально напоминают объекты Fi 1 е, возвращаемые функцией open ( ) . с которой вы познакоми- лись в предыдущей rлаве: они представляют собой значения, посредством которых проrрамма взаимодействует с файлами. Для создания объекта ZipFile следует вызвать функцию zipfile. ZipFile ( ) , передав ей строку с именем .ziр-файла. Обратите внимание на то, что zipfile  это имя модуля РytlЮIl, а ZipFile ()  имя функции. Введите в интерактивной оболочке следующие команды. »> import zipfile, 08 >>> 08. chdir ( 'с: \\ 1) t пepeй.rи . папку, оодержащYJO 8Хашрlе. zip 
262 r лава 9 »> exampleZip = ziрfilе.ZiрFilе('ехашрlе.ziр') »> exampleZip.naaeli8t() [ 'зрат. txt', · cats/', 'cats/ catnames. txt " 'cats/zophie. jpg' ] >>> spaIILInfo = exaвapleZip. чеtinfо ( , 8pa1D. txt' ) »> spaIILInfo.file 8ize 13908  »> spaIILInfo.oampre88 8ize 3828  О »> 'Са.'NЙ файл . %8 раза меныIQ'' % (round(spaIILInfo.file8ize  / spaIILInfo.coapres8 8ize, 2» 'Сжатый файл в 3.63 раза меньше!' »> eZip.alose() Объект ZipFile имеет метод namelist (), который возвращает список строк для всех файлов и папок, содержащихся в ZIР-файле. Эти строки можно передать методу getinfo () объекта ZipFile, который возвратит объ- ект Ziplnfo, содержащий информацию о данном файле. Объекты Ziplnfo имеют собственные атрибуты, такие как filesize и compresssize, KOT<r рые содержат соответственно целочисленные значения размера исходноrо и OKёtToro файлов, выраженные в байтах. В то время как объект ZipFile представляет весь архивный файл, объект Ziplnfo хранит полезную инфор- мацию относительно отдельною файла в сжатом архиве. Команда. рассчитывает степень сжатия файла example. zip пyrем деле- ния размера ИСХОДНОI'О файла на размер сжатоrо файла и выводит эту ин- формацию, используя строку форматирования с описателем формата %з. ИI.."'''", ф"'1I0. ", Z,p."px"." Метод extractall () объектов ZipFile извлекает Все файлы и папки И:J ZlP-файла в текущий рабочий каталоr. »> iшроrt zipfile, 08 »> o8.chdir('C:\\') * перейти 8 папку, сод8р*аЩуа ехашрlе.ziр »> exampleZip. zipfile.ZipFile('example.zip') О >>> exampleZip.extractall () »> exampleZip.cl08e() После выполнения этоrо кода содержимое файла exaтple.zip будет из- влечено в каталоr C: Методу extractall () можно передать имя папки в качестве необязательноrо параметра, позволяющеrо извлекать файлы в папку, не являющуюся текущим рабочим каталоrом. Если переданная MeT<r ду extractall () папка не существует, то она будет создана. Например, если вызов. заменить вызовом exampleZip.extractall ('с: \ \delicioиs,), код из- влечет файлы из файла exaтple.zip во вноВь созданную папку С: "(ielicious. 
Управление файлами 263 Метод extract () объектов ZipFile извлекает одиночный файл из ZIp файла, Продолжите выполнение примера в интерактивной оболочке. »> exampleZip.extract('_pam.txt') 'C:\\spam.txt' »> exampleZip.extract('_pam.txt', 'C:\\some\\new\\folders') 'C:\\some\\new\\folders\\spam.txt' »> exampleZip.clo_e() Передаваемая методу extr act () строка должна соответствовать одной из строк в списке, возвращаемом методом namelist (). Методу extract () TaK же можно передавать необязательный второй параметр, обеспечивающий возможность извлечения файлов в папку, не являющуюся текущим рабочим каталоrом. Если заданная вторым параметром папка не существует, PytllOn создаст ее. Метод extract () возвращает абсолютный путь, по которому был распакован данный файл. Создан.е Z".фа'.08 . 1I06аиен.е 8 Н.Х H08WX фаiiЛ08 Чтобы (оздать собственный ZIР-Файл, необходимо открыть объект ZipFile в режuмезаnиси посредством передачи строки 'w' в качестве BT<r рО1'О apryMeHTa. (Это аналоrично открытию TeKcToBol'O файла в режиме заIlИСИ путем передачи строки 'w' методу apen () в качестве BToporo пара метра. ) Коrда вы передаете пyrь методу wri te () объекта ZipFile, PytllOl1 сжима ет файл, расположенный по указанному пути, и добавляет ею в ZIР-файл. Первый apryмeHT метода write ()  это строка, содержащая имя добавляе MOI'O файла. Второй apryмeHT  это параметр типа сжатия, сообщающий компьютеру о том, какой алюритм следует использовать для сжатия фай лов; вы всеrда можете установить для этоro параметра значение zipfile. ZIPDEFLATED. (Этому значению соответствует алroритм сжатия без nотеръ, который хорошо работает со всеми типами данных.) Введите в интерактив ной оболочке следующие команды. »> import zipfile »> newZip = zipfile.ZipFile('new.zip', 'w') »> newZip.write('spam.txt', сошрres_type=ziрfilе.ZIРDЕFLAТЕD) »> newZip.close() Данный код создает новый ZIР-файл new.ир, включающий сжатое содер- жимое файла spam.txt. 
264 r лава 9 Имейте в виду, что, как и в случае обычной записи файлов, все существу- ющее содержимое ZIР-файла в режиме записи удаляется. Если вы хотите просто добавить файлы в существующий ZIР-файл, псредайте строку 'а' в качестве BToporo параметра методу zipfile. ZipFile () , чтобы открыть ZIP- файл в режиме присосдинения. Проект: пере именование файпов с заменой американскоrо формата дат европейским Предположим, начальник перебросил вам по электронной почте тыся- чи файлов с просьбой переименовать их с заменой американскоro формата дат (mm-м-rrrт) в их именах европейским (M-MM-rrrr). Выполняя это порученис вручную, вы можете потратить на Hero целый день! Не лучше ли написать проrpамму, которая сделает всю работу за вас? Вот что должна делать эта проrрамма: . выполнять в текущем рабочем каталоre поиск всех файлов, имена ко- торых содержат дату в американском формате; . при нахождении каждоrо TaKoro файла переименовывать ero, меняя местами обозначения даты и месяца, чтобы привести стиль ука:щния даты в соответствие с европейским форматом. Это означает, что код должен выполнить следующие действия: . создать реryлярное выражение для распознавания образцов текста, соответствующих дате, заданной в американском стиле; . вызвать функцию 05 .lis tdir () для создания списка всех файлов, со- держащихея в рабочем каталоrе; . орrани:ювать про(мотр всех имен файлов в цикле, определяя, со- держат ли они даты, с помощью соответствующеrо реryлярноrо вы- ражения; . если в имя файла входит дата, изменить ero с помощью функции shutil.move (). Приступая к работе над данным проектом, откройте новое окно в фай- ловом редакторе и сохраните ею в файле renaтeDates.py. Ша, ,. 'OJДII".e pery.'p"OtD 8..ра.,".' /111' .о.,ка lIar, указа""..х 8 а_ер.ка",ко_ фор_аrе в первой части проrpаммы потребуется импортировать необходимые модули и создать реryлярное выражение, (пособное распознавать даты в формате MM-,lUJ.-rrrr. Комментарии ТОНА будyr напоминать о проrрамм- ном коде, который еще предстоит написать. Использование общепринятоro 
Управление файлами 265 обозначения ТООО (ЧТО СДЕЛАТЬ) дЛЯ этих комментариев упрощает их поиск путем нажатия комбинации клавиш <Ctl'l+F> при работе в IDLE. BBe дите в файл следующий код. #! руthопЗ # renameDate5.py  Переименовывает файлы, имена которых включают # даты, указанные в американском формате (ммддrrrr), приводя # их в соответствие с европейским форматом дат (ддммrrrr). о import 5hиtil, 05, re # Создание реrулярноrо выражения, которому соответствуют имена # файлов, содержащие даты в американском формате. О datePattern  re.compile(r'"""'(.*?) # весь текст перед датой ((Oll)?\d) # одна или две цифры месяца ((ОI112IЗ)?\d) # одна или две цифры числа ((19120)\d\d) # четыре цифры rода (.*?)$ # весь текст после даты О """, re. VERBOSE) # TODO: Орrанизовать цикл по файлам в рабочем каталоrе. # тооо: Пропустить файлы с именами, не содержащими дат. # TODO: Получить отдельные части имен файлов. # тооо: Сформировать имена, соответствующие европейскому стилю # указания дат. # ТОDO: Получить полные абсолютные пути к файлам. # ТООО: Переименовать файлы. Из этой rлавы вы уже знаете о том, что для переименования файлов мож- но использовать функцию shиtil.move (), арryментами которой служат ис- ходное и НОВое имена файла. Поскольку эта функция содержится в модуле shиtil, ero необходимо импортировать.. Однако, прежде чем переименовывать файлы, НУЖНО идентифициро- вать те из них, которые подлежат переименованию. Переименовывать сле- дует файлы, в именах которых содержатся даты, например spaт441984.txt или 01.03.2014eggs.zip, тоща как имена таких файлов, как littlebrother.epub, не содержащие дат, можно иrнорировать. Для распознавания этоrо шаблона можно использовать реryлярные выражения. Импортировав модуль re в начале файла, вызовите функцию re . сотрНе () для создания объекта Regex .. Передача этой функции KOH станты re. VERBOSE в качестве BToporo apryMeHTa . разрешает использовать пробелы и комментарии в строке реryлярноrо выражения для повышения ее удобочитаемости. 
266 r лава 9 Строка реryлярноrо выражения начинается символами л (. *?), которым соответствует любой текст в имепи файла, предшествующий дате. Iруппе ( (О 11) ?\d) соответствует цифровое обозначение месяца. Первой цифрой может быть как О, так и 1, так что реryЛЯРIIОС выражение совпадет как с 06о:шачением 12 в (Jlучае декабря, так и с обозначением 02 8 случае февра ля. Кроме 1'01'0, эта цифра задана как необязательная, поэтому, например, апрель будет раСlIознан независимо от TOI'O, как он обозначен: 04 или 4, Обозначениям дней соответствует rруппа ( (О 111213) ?\d), которая с.ледует аналоrичной лоrике; 3, 03 и 31  каждый из этих вариантов является ДOIIY стимым для обозначения дней. (Если вы отличаетесь наблюдательностью, то заметите, что данному реryлярному выражению будут соответствовать и некоторые недопустимые даты, такие как 4312014, 2292013 или 015 2014. При работе с датами следует учитывать множество топких моментов, которые MOryт леrко ускользнyrь от вашеl'О внимания. Одпако для большин ства простых случаев это реryлярное выражение может считаться вполне приемлемым для нашей проrpаммы.) Несмотря па то что 1885  корректное обозначение rода, вы можете оrраничиться юдами, относящимися к хх И XXI столетиям. Тем самым вы И:Jбавитесь от с.лучайноrо пере именования тех файлов, в именах которых встречаются цифровые обо:Jначения, лишь нохожие на даты, такие как 10-10-1000.txt. Части (. *?) $ реryЛЯРНОI'О выражения соответствует любой текст, KOT<r рый следует за датой в имени файла. Ш",2. Иllе",нФнк"qн. ,,,ае. н.е" ф"'.08, '00'8еrа8УIOЩНХ 11"'''. После этою проrрамма должна просмотреть в цикле строки имен фай лов из списка, возвращенноI'О функцией 05 .listdir () , для их сравнения с реryлярным выражением. Любые файлы, имена которых не включают дату, должны иrнорироваться. Для имен, содержащих дату, совпавший с шаБЛ<r 110М текст должен быть сохранен в нескольких переменнЬ1Х. Замените пер- вые три комментария ТООО в вашей проrрамме следующим кодом. #! python3 # renameDates.py  Переименовывает файлы, имена которых включают # даты, указанные в американском формате (ммддrrrr), приводя # их в соответствие с европейским форматом дат (ддммrrrr). пропушенный KOД t ОрI'aНИЗ8ЦИЯ цикnа по файлам . рабочем 1C&'l'aJIOI'e. for amerFilename in o..li_tdir('.'): шо - datePattern..earch(amerFilename) 
Управление файлами 267 # IlponyCJC Ф&ЙJ10. с именами, Не содержащими да'1'. О if 110 == None: . continue о # Ilолучение o'1'дeпнwx час'1'8Й имен файло.. beforePart . 1I0.group(1) DlonthPart · mo.group(2) daypart = шо.grоup(4) yearpart .. 1D0.qroup(6) afterPart - 1D0.qroup(8) пропущенный KOД Если метод search () возвращает значение None ., значит, строка имени файла, содержащаяся в переменной amerFilename, не соответствует pery лярному выражению. Инструкция continue . Иl'норирует оставшуюся часть цикла и осуществляет переход к следующему имени файла. В противном случае строки, соответствующие отдельным rруппам в pe ryлярном выражении, сохраняются в l1еременных beforePart, monthPart, day?art, yearpart и afterPart .. Эти строки будут использоваться при BЫ полнении следующеro шаr'а для формирования имен файлов с датами в ев- ропейском формате. Для поддержания сквозной нумерации rрупп попробуйте прочитать ре-- ryлярное выражение с caMoro начала, ведя отсчет посредством прибавле- ния единицы всякий раз, коrда вам встречается открывающая круrлая скоб- ка. Не думайте о коде и просто наметьте каркас реryлярноrо выражения. Возможно, так вам будет леrче визуализировать rpуппы. datePattern'" re.compile(r""""(l) (2 (З) )  (4 (5) )  (6 (7) ) (8) $ """, re.VERBOSE) # весь текст перед датой # одна или две цифры месяца # одна или две цифры числа # четыре цифры rода # весь текст после даты Здесь числа от 1 до 8 представляют rpуппы в реryлярном выражении, K<r торое вы написали. ПреДLтавление структуры реryлярноro выражения с ис- пользованием лишь крyrлых скобок и номеров rрупп поможет вам понять ero смысл, прежде чем вы перейдете к созданию остальной части проrраммы. Ш", 3. Фор..ро."н.е но.о,о ..ен. ф".." . "'ре..е.о."н.е ф,,;;.о. Последнее, что осталось сделать,  это конкатенировать строки, co храненные в переменных на предыдущем шаrе, для I1риведения даты к 
268 rпaBa 9 европейскому формату, в соответствии с которым число предшествует ме- сяцу. Замените три оставшихся комментария ТООО кодом, как показано ниже. ft! руthоnЗ ft renameDates.py  Переименовывает файлы, имеиа которых включают ft даты, указанные в американском формате (ммддrrrr), приводя # их в соответствие с европейским форматом дат (ддммrrrr). пропущенный KOД * Формиро_ание имен, COO'l'Se'l'C'1'_YJQIIOa европeйcJcому 0'l'И.JII) * ука$8НИJII да'l'. О euroFilename = beforePart + dayPart + ,, + lDonthPart + ,' + yearpart + aft8rPart * По.пучение по.пнwx aCScOJDOll'НWX n}"1'8Й IC файпаи. ab81forkinqDir = o..path.abspath('. ') amerFilename . 08 .path. join (ab.WorkinqDir, amerFilename) euroFilename - 0..path.join(ab81forkingDir, 8UroFilename) * Переимено_ание файпо_. о print( 'ЗаиеНR8М ИМR "%8" именем "%."...' % (amerFi18name, euroFilename» О *.hutil.lDоve(ашеrFilепamе, euroFilename) * раСКОММ8Нтиро8атъ * поспе _ыпопнения ft 'l'8С'l'иро_аниR Конкатенированная строка сохраняется в переменной eиroFilename ... Затем исходное и новое имена файлов, сохраненные в переменных amerFilename и eиroFilename, передаются функции shиtil.move () для окон. чательноrо переименования файла .. В данной версии проrраммы вызов shиtil.move () отключен с помощью комментария и имена файлов, подлежащих переименованию, просто выво. дятся на экран .. Запуск проrраммы в таком режиме позволяет еще раз убе- диться в корректном переименовании файлов. После этою можно удалить символ комментария в строке с вызовом shиtil.move () и вновь выполнить проrрамму для фактическоrо переименования файлов. Иllе. о",0,.rеЛ6ИО ,03ll"И.. "и"лоr""И6lХ "porp".. Необходимость в персименовании БОЛЬUlOrо количества файлов может возникать по целому ряду друrих причин: . для добавления стандартною префикса в начале имен файлов; напри- мер, файл eggs. ехе переименовывается в файл spa1l'ceggs. txt; 
Управление файлами 269 · для преобразования дат в именах файлов из европейскоrо формата в американский; · для удаления ведущих нулей из имен таких файлов, как spaт0042.txt. Проект: создание резервной копии папки в виде ZIР-файяа Предположим, вы работаете над просктом, файлы KOTOpOro хранятся в папке C:l/s.lsPythonBook. Вас волнует сохранность результатов вашей работы, и вам хотелось бы периодически создавать "моментальные снимки" про- екта, сохраняя всю папку в одном ZIРфайле. Для вас было бы желательно хранить различные версии проекта в файлах с именами, содержащими номер резервной копии, который увеличивается всякий раз, коrда соз- дается новый ZIР-файл, например AlsPythonBookl.zip, AlsPythonBook2.zip, АlsРуthопВооkЗ.z.iр и т.д. Это можно было бы делать и вручную, 110 такой под- ход чреват тем, что номера ZIР-файлов MOryr быть случайно перепутаны. lЪраздо проще написать проrрамму, которая будет выполнять эту рутинную работу вместо вас. При ступая к работе над данным проектом, откройте новое окно в фай- ловом редакторе и сохраните ero в файле backup1bZip.py. Шаr 1. Опреllеле".е ..е"., KOrOpOe ,леllУет .р.,.о." Z/Р.фаjлу Код этой проrраммы будет помещен в функцию backupToZip (). Это унростит ero копирование и вставку в друrие проrраммы на Python, нужда- ющиеся в этой функциональности. В конце проrраммы эта функция будет вызываться для создания резервной копии содержимоrо папки. Введите следующий код. ft! руthопЗ ft backupToZip.py  Копирует папку вместе со всем ее содержимым ft в ZIРфайл с инкрементируемым номером копии в имени файла. о import zipfile, 05 def backupToZip(folder): ft Создание резервной копии Bcel'o содержимоI'О папки "folder" # в виде ZIРфайла. folder  os.path.abspath(folder) # убедиться в том, что ft задан абсолютный путь ft к файлу # Определить, какое имя файла должен использовать этот код, # исходя из имен уже существующих файлов. 
270 r лава 9 о numЬer = 1 О while True: zipFilename = os.path.basename(folder) + I , + str(numЬer) + '.zip' if not os.path.exists{zipFilename): break number = number + 1 о # TODO: Создать ZIРфайл. # тооо: Обойти все дерево папки и сжать файлы, содержащиеся # в каждой папке. print ( I rOTOBO. 1) backupToZip('C:\\delicious') Мы начинаем с элементарных вещей: добавляем "мarическую" строку за пуска PytllOn (с символами #!), описываем назначение проrpаммы и импор- тируем модули zipfile и 05 .. Далее определяется функция backupToZip ( ) , принимающая Bccro один парамстр  folder. Этот параметр  строка, содержащая пyrь к папке, ре- зервную копию содержимOI'О которой следует создать. Сначала функция определяет имя, которое следует npисвоить создаваемому ZIР-файлу; затем она создает сам файл, совершает обход содержимоrо папки folder и добав ляет все ее подпапки и файлы в ZIР-файл. Включите в исходный код KOM ментарии TODO для этих шarов как напоминание о том, что необходимо сделать в дальнейшем .. В первой части ПрОl'раммы, ответственной за присвоение имени ZIP файлу, используется базовое имя абсолютноrо пути к папке. Если резерви руется содержимое папки C:VJelicious, то именем ZIР-файла будет deliciQUsN. zip, rде N = 1 при первом запуске проrpаммы, N = 2  при втором и т.Д. Вы можете определить, каким должно быть значение N, проверив, существуют ли уже файлы deliciousl.zip, delicious2.zip и Т.д. Значение N xpa пится в переменной пшnbеr . и инкрементируется в цикле, в котором с п<r мощью функции 05.path.exi5t5 () проверяется существование файлов.. Как только обнаружен несуществующий файл, цикл прерывается, ПОСКОЛlr ку нам уже известно, какое имя следует присвоить новому ZIР-файлу. Ш", 2. COJII"""e 1108010 l"ф";;.,, Следующим шаroм является создание ZIР-файла. Дополните проrpамму новым кодом, как показано ниже. #! python3 # backupToZip.py  Копирует папку вместе со всем ее содержимым # в ZIРфайл с инкрементируемым номером копии в имени файла. 
Управление файлами 271 пропущенный KOД while True: zipFilename = os.path.basename(folder) + ' , + str(number) + '.zip' if not os.path.exists(zipFilename): break number = number + 1 . Создание ZIРфайла. рrint('Соада"ся файл %з.... % (zipFilenaDle)) О backupZip = zipfil..ZipFile(zipFilename, 'w') # Тооо: Обойти все дерево папки и сжать файлы, содержащиеся # в каждой папке. print ('rOTOBO.') backupToZip('C:\\delicious') 1еперь, КОl'да имя НОВОI'O ZIР-файла сохранено впеременной zipFilename, можно вызвать функцию zipfile. ZipFile () для фактическоrо создания ZIP файла.. Не забудьте передать ей строку 'w' в качестве BToporo apryмeHTa, чтобы открыть ZIР-файл в режиме записи. Ш", 3. Обход дере." K",,,.oro. .1I0'"..ен.е "'Rep.."oro . 1"-ф,,'. Наконец, для обхода всех файлов и подпаrюк, содержащихся в данной IIапке, используется функция оз. walk (). Дополните ПрОI'рамму новым ко- дом, выделенным ниже полужирным шрифтом. #! руthопЗ . backupToZip.py  Копирует лапку вместе со всем ее содержимьш . в ZIРфайл с инкрементируемым номером копии в имени файла, лролущенный KOД * Обход BCВI'O дерева палки и сжатие файлов, оодержащихся . в каждой палКе. О for foldername, 8ubfolders, filename8 in оз. walk (folder) : рrint('Добаапение файлов из папки %8...' % (foldername)) * Добааит в ZIРфайл екущую папку. О backupZip.write(foldername) * Добавить в ZIРфайл все файлы из данной папки. О for filename in filename8: neWВa8e / 08.path.basename(folder) + ' , if filenam..8tart8with(newBa8e) алd filename.endswith('.zip') continu. t Не создаваь резервные копии * ZIРфaй.nов backupZip.write(08.path.join(foldernam., filename)) 
272 r лава 9 bactupZip.clo.8() print ( 'rOTOBO. ' ) backupToZip('C:\\deliciou5') Функцию 05. walk () можно использовать в цикле for .. и на каждой ите- рации ЦИJUIа она будет возвращать имя текущей папки, а также имена всех подпапок И файлов, содержащихся в данной папке. В теле цикла for папка добавляется в ZIРфайл .. Обход всех файлов, имена которых содержатся в списке filenarne5 ., осуществляется во вло- женном цикле for. Каждый из них, за исключением ранее созданных ZIP- архивов, добавляется в ZIР-файл. Выполнив эту проrрамму, вы получите примерно следующий вывод. Создается файл delicioи51.zip... Добавление файлов из папки C:\delicious... Добавление файлов из папки C:\delicious\cat5... Добавление файлов из папки C:\deliciou5\waffles... Добавление файлов из папки C:\delicioи5\walnut... Добавление файлов из папки C:\delicioиs\walnut\waffle5... rOTOBO. в результате BToporo вызова проrpaммы все файлы' содержащиеся в пап- ке C:Vl.eliciиus, будут архивированы в ZIР-файле d.elicious2.zip и т.Д. ИII'. or.o,,,,,..,,O ""11"".. """.О"'."61Х "ро",,_ Вы сможете использовать обход дерева каталоrов и добавление файлов в сжатые zIp-архивы в целом ряде друrих проrрамм. Например, можно на- писать проrраммы для выполнения следующих задач: . обход дерева каталоrов и архивирование лишь файлов с определен- ными расширениями, например . txt или .ру. и никакими друrими; . обход дерева каталоrов и архивирование всех файлов, за исключени- ем тех, которые имеют расширение .tхtили .ру; . поиск в дереве каталоrов папки, содержащей наибольшее количество файлов, или папки, занимающей наибольший объем дисковоrо про- странства. PeSIOMe Даже если вы опытный пользователь, вы, вероятнее вcero. работаете с файлами вручную с помощью мыши и клавиатуры. Современные файловые менеджеры упрощают работу с небольшим количеством файлов. Но ино- 
Управление файлами 273 rда возникают задачи, для выполнения которых с помощью проводника по- требуется несколько часов работы. Модули 05 и 5hutil предлаrают функции, позволяющие осуществлять ко- пирование, перемещспие, переименование и удаление файлов. Удаляя фай- лы, вы, возможно, захотите восполь;юваться модулем send2tra5h, который предоставляет возможность не удалять файлы безвозвратно, а перемещать их в корзину. При написании проrрамм, предназначенных для обработки файлов, целесообразно использовать символы комментария для отключе- ния кода, выполняющеrо непосредствешюе копирование (перемещение, переименование, удаление) файлов, добавляя вместо Hero вызовы функции print () , которые позволяют убедиться в том, что nporpaMMa делает именно то, что вам необходимо. Операции подобноrо рода приходится выполнять не только над фай- лами, хранящимися в данной папке, но и над файлами, хранящимися во вложенных папках, а также в папках BToporo и всех последующих уровней вложенности. Функция 05. walk () может выполнить обход всей структуры папок вместо вас, позволяя сосредоточить все внимание на том, что долж- на сделать nporpaмMa в отношении хранящихся в этих папках файлов. С помощью модуля zipfile можно сжимать и извлекать файлы, хра- нящиеся в .ziр-архивах. В сочетании с предоставляемыми модулями 05 и shutil функциями для обработки файлов модуль zipfile упрощает упаков- ку нескольких файлов, хранящихея в любой папке на вашем жестком диске. Такие .ziр-файлы rораздо леrче выrружать на веб-сайты или пересылать в виде вложений в сообщения электронной почты, чем множество отдель- ных файлов. В предыдущих I'лавах вам предлаraлся rотовый код, который достаточно было просто копировать. Однако при разработке cotkTBeHHbIx nporpaMM вы столкнетесь с тем, что они не щ:сrда правильно работают с nepBoro раза. Следующая rлава посвящена некоторым модулям Руthоп, облеrчаю- щим анализ и отладку IIporpaмM, которые nOMOryr вам быстрее добиться их правильной работы. Контрояьные вопросы 1. Чем отличаются функции 5hutil. сору () и 5hutil. copytree ()? 2. Какая функция используется для lIереименования файлов? 3. Чем отличаются функции для удаления файлов, предлarаемые модуля- ми 5end2tra5h И 5hutil? 4. Метод cl05e () имеют как объекты ZipFile, так и объекты File. Какой метод объектов ZipFile эквивалентен методу open () объектов File? 
274 r лава 9 Учебные проекты Чтобы закрепить полученные знания на практике, напишите проrрам мы для предложенных ниже задач. Вы60РОЧНО' ко".ро."н.. Напишите проrрамму, выполняющую обход дерева каталоrов с целью отбора файлов с заданным расширением (например, . pdf или . j pg). Скопи- руйте эти файлы из их текущеrо расположения в новую папку. Уд"л.н.. н,нужных фа..о. Не так уж редки ситуации, коrда несколько ненужных файлов или папок orpoMHoro размера занимают существенную часть дисковою пространства. Для освобождения места на жестком диске наилучший эффект будет достиr- нyr в том случае, если удалить самые крупные из ненужных файлов. Однако сначала их необходимо найти. Напишите проrрамму, которая обходит дерево папок, выполняя поиск исключительно больших по своим размерам папок и файлов,  скажем, та- ких, размеры которых превышают 100 Мбайт. (В(:помните, что размер фай ла можно определить с помощью функции os.path.getsize () из модуля os.) Выведите абсолютные пyrи доступа к этим файлам на экран. J""олн.н.. "РО"У'КО. . .ум."""." ф"..о. Напишите проrрамму, которая ищет в папке все файлы с именами, c<r держащими заданный префикс, такими как spaтOOl.txt, spaт002.txt и Т.д., и обнаруживает любые пропуски в нумерации файлов (например, имеются файлы sратООl.tхtи spaт003.txt, но OTcyrcTBYeт файл spaт002.txt). Проrрам- ма должна изменять имена файлов с большими номерами таким образом, чтобы ликвидировать имеющиеся про пуски. В качестве дополнительноrо задания напишите дрyryю проrрамму, спо собную создавать пропуски в нумерации файлов, тем самым создавая CB<r бодные позиции для добавления новых файлов. 
от ПАДКА Тех знаний, которые вы к этому времени успели приобре сти, вполне достаточно для Toro, чтобы приступить к напи санию более сложных проrpамм. Однако вы должны быть rотовы К тому, что в вашем HporpaMMHoM коде будут нстре-- чаться ошибки, найти которые не так уж и просто. В этой I'лаве обсуждаются инструменты и методики, нозволяющие ЭКОНОМИТI. время и усилия, затрачиваемые на обнаружение и устранение лоrических ошибок в проrраммах. Перефразируя расхожую шутку ПРOl'раммистов, я сказал бы так: "Про- rраммирование на 90 процентов состоит из написания кода. Остальные 90 процентов приходятся на отладку". Ваш компьютер сделает лишь то, что вы ему прикажете; оп не способен исполнять ваши 1UJ,м,ерe1tШl, читая мысли, и нуждается в соответстнующем проrраммном коде, ответственность за IЮДI'ОТОВКУ KOTOpOI'O возлаraется на проrраммистов. Однако даже ПрОфсССИОПaJlЫIЫС lIрOl'раммисты иноrда д<r пускают серьезныe проrраммные ошибки, поэтому пс стоит падать духом, если в работе вашей НрOl'раммы обнаруживаются неполадки. К счастью, существует целых ряд инструментов и методик, с помощью которых вы сможете точно опрсделить, что имснно делает ваш код и в кa ком месте проrраммы чтото пошло не так, как надо. Во--первых, мы pac смотрим протоколирование операций и утверждсния  два средства, облеl'чающие раннее обнаружснис ошибок, Вообще I'оворя, чем раньше об- наружена ОlПибка, тем проще ее исправить. Во--вторых, вы позпакомитссь с отладчиком. Отладчик  это средство IDLE, обсспсчивающее пошаrовое ВЫllOлпеllие lIpOrpaMMbI, по одпой ин струкции за раэ, что дает возможность иш:псктировать значения перемен пых и отслеживать их изменсния в ходс вынолнения проrраммы. При этом lIpOrpaMMa выполняется значительно медленнее, чем обычно, но зато вы получаете возможность наблюдать за реальными значениями переменных, а не оценивать их на основе умозаключений, анализируя исходный код. 
276 rлава 10 Возбуждение искпlOчениi Python возбуждает (reнерирует) исключение всякий раз, коrда предпри' нимается попытка выполнить недопустимый код. Из rлавы 3 вы узнали о том, как обрабатывать исключения PythOIl с помощью инструкций try и except, позволяющих избежать преждевременноrо прекращения работы проrраммы при возникновении исключительных ситуаций, которые вы предвидели. Но у вас есть возможность возбуждать в своем коде собствен- ные исключения. Возбуждение исключения о:шачает для компьютера сле- дующее: "Прекратить выполнение кода данной функции и передать управ- ление инструкции except". Исключения возбуждаются с помощью инструкции raise, синтаксис ко. торой включает следующие элементы: . ключевое слово raise; . вызов функции Exception (); . строка с описательным сообщением об ошибке, передаваемая функ- ции Exception ( ) . Например, введите в интерактивной оболочке следующую команду. »> raia8 ЕхоерtiСn('ЭТО сообщение об о-ибке.') Traceback (most recent са!1 !ast): File "<pyshell#O>", line 1, in <module> raise Ехсерtiоп('Это сообщение об ошибке.'} Exception: Это сообщение об ошибке. в отсyrствие инструкций try и except, охватывающих инструкцию raise, которая rенерирует исключение, выполнение проrраммы завершается aвa рийно с выводом соответствующеro сообщения. Часто обработка исключения, которое может возникнyrь в функции, вы- полняется не самой функцией, а вызывающим ее кодом. ПО:iТОМУ инструк- ция raise нередко встречается в теле функции, а связанные с ней инструк- ции try И except  в коде, вызывающем эту функцию. Например, откройте новое окно в файловом редакторе, введите в Hero следующий код и сохра- ните проrрамму в файле boxPrint.py. def boxPrint(symbo!, width, height}: if !en(symbo!} != 1: О raise Ехсерtiоп('Переменная symbol должна быть  односимвольной строкой.'} if width <= 2: О raise Ехсерtiоп('Значение width должно превыmать 2.') if height <= 2: О raise Ехсерtiоп('Значение height должно превыmать 2.') print(symbol * width} 
Отладка 2" for i in range(height  2}: print(symbol + (' , * (width  2}) + symbol} print(symbol * width) for sym, w, h in (('*',4, 4), ('0',20, 5), ('х', 1, З), ('ZZ', 3, З)}: try: boxPrint(sym, w, h} О except Exception as err: О рriпt('Возникло исключение: ' + str(err)} Здесь мы определили функцию boxprint () , которая принимает параметр symbol, определяющий символ или rруппу символов, а также параметры width и height, и использует заданный символ (символы) для создания не- БОЛЬШОl'О изображения, ширина и высота KOTOpOro определяются осталь- ными двумя l1араметрами. Полученная прямоуrольная фиrypа выводится на консоль. Предположим, мы хотим, чтобы параметр symbol Mor быть только оди- ночным символом, а значения параметров width и height превышали 2. для этоrо мы добамяем инструкции Н, которые возбуждают исключения, если эти требования не удовлетворяются. Впоследствии, коrда вызывается функ- ция boxPrint () с различными арryментами, конструкция try /except обрабо- тает недопустимые apl'YMeHTbI. В этой проrрамме в инструкции except используется и(ключение в форме Exception as err О. В случае возврата объекта Exception функцией boxPrint () ... инструкция except сохранит ero в переменной err. Затем объект Exception можно преобразовать в строку посредством передачи ero функции str () для вывода сообщения об ошибке в понятной для пользова- теля форме .. В результате выполнения nporpaMMbI boxPrint. ру вы долж- ны получить следующий вывод. **** * * * * **** 00000000000000000000 о о о О о О 00000000000000000000 Возникло исключение: 'Значение width должно превышать 2. Возникло исключение: Переменная symbol должна быть одно символьной строкой. Применение инструкций try и except обеспечипает элеraнтную обрабо..... ку ошибок, исключающую возможность неконтролируемоrо аварийноro за- вершения nporpaмMbI. 
278 rЛQВQ 10 Попучение обратной трассировки стека вызовов в виде строки Коrда возникает ошибка, Python предоставляет весьма ценную информа- цию о ее природе в виде так называемой обратной трассировки Сте1Са въtз0вов. Эта информация включает текст сообщения об ошибке, номер строки в ис ходном коде, ЯWJяющейся причиной неполадок, и последовательность вы- зовов функций, I1риведшую к ошибке. Эта последовательность называется Сте1СО./Ч вшооов. Откройте новое окно в IDLE, введите приведенный ниже код проrрам- мы и сохраните ero в файле eпvrExample.py. def зраm () : bacon ( ) def bacon(): raise Ехсерtiоп('Это сообщение об ошибке.') spam() Выполнив проrpамму eпvrExample.py, вы получите следующий вывод. Traceback (most recent call last): File "errorExample.py", line 7, in <module> spam() File "errorExample.py", line 2, in spam Ьасоn () File "errorExample.py", line 5, in bacon raise Ехсерtiоn('Это сообщение об ошибке.') Exception: Это сообщение об ошибке. На основании информации о стеке вызовов можно yrверждать, что ошибка возникла в строке 5, Т.е. в коде функции bacon () . Эта функция была вызвана в строке 2, Т.е. в коде функции spam(), которая, в свою очередь, бьта вызвана в строке 7. В тех случаях, коrда одна и та же функция может вызываться в нескольких местах проrраммы, с.тек вызовов позволяет опре-- делить, какой именно вызов l1рИВОДИТ к ошибке. Python отображает стек вызовов всякий раз, Коrда возбужденное исклю- чение остается необработанным. Но ero также можно получить в виде (тра- ки, вызвав функцию traceback. forma t  ехс ( ) . Эта функция приroдится вам в тех случаях, коrда вы хотите обработать ИСJUIючение, но одновременно с этим получить информацию о стеке вызовов. Прежде чем вызывать эту функцию, необходимо импортировать модуль traceback Python. 
Отладка 279 Например, вместо Toro чтобы просто позволить проrрамме завершить- ся аварийно сразу же после возникновения исключения, можно записать информацию о стеке вызовов в журнал отчетов и предоставить проrpамме воаможность дальнейшеrо выполнения. Впоследствии, коrда вы будете 1"0- товы приступить К отладке, вы сможете обратиться к записанной в журнале информации. Введите в интерактивной оболочке следующий код. >>> iDlport traceback »> try: rai_e Exception ( 'э.rо сообщение об ошибке. I ) except: errorFile · ореn ('errorInfo. txt' , 'w') errorFile..rite(traceback.format8Xc(» errorFile.clo&e() print( 'Ииформация о снке 8Ы80808 бып& 8anисана 8 файл  errorInfo . txt. ' ) 113 Информация о стеке вызовов записана в файл errorInfo.txt. Число 113  это значение, возвращенное методом wri te () , которое равно количеству символов, записанных в файл (ВJUIючая символы новой строки). Записанная в файл error/nfo. txt информация должна выrлядеть примерно так. Traceback (most recent call last): File "<pyshell#l1>", Нпе 2, in <module> Exception: Это сообщение об ошибке. УтвеР)I(дения Уmвef)ждеиue это профилактический механизм, позволяющий убедить- ся в I1равильности выполнения кода проrраммы. Этот механизм основан на выполнении проверок с помощью инструкций assert. Если критерий проверки не выдержив'аt.'Тся, то возникает исключение AssertionError. Ин- струкция assert имеет следующий синтаксис: . ключевое слово assert; . условие (т.е. выражение, вычисление KOTOpOl'O дает значение True или False); . запятая; . строка, которая отображается в том случае, если условие оказываt..'Тся ложным. 
280 rлова 10 в каче<:тве примера введите в интерактивной оболочке следующий код. >>> podВayDoorStatu& = 'открыто' > > > a&&8rt podВayDoorSta tU& .. ' OТIepbl'l'O', 'Дверь Ж'РУSО80ro О'1'сеха  дo.mrнa нахОди'1'Ь . СОС'1'ОRНИИ "открыто".' >>> podВayDoorStatu& · 'Мне -М18, Дейв. SOIOOIo, R ничеro Н8  мory сд..пать. ' , >>> a&&ert podВayDoorStatu& == 'OTIepbl'l'O', 'Двер18 :t'pYS080r0 О'1'сеха  дo.mrнa нахОди'1'18CJ1 8 СОC'rОRНИИ "открыто".' Traceback (most recent call last): File "<pyshell#10>", line 1, in <module> assert podBayDoorStatиs == 'открыто', 'Дверь РРУЗ0ВОРО отсека должна находиться в состоянии "открыто".' AssertionError: дверь РРУЗ0ВОРО отсека должна находиться в состоянии "OT крыто". Здесь мы УLтанавливаем для переменной podBayDoorStatus значение 'OT крыто' И рассчитываем на то, что данная переменная всеrда будет иметь именно это значение. Предположим, что выполнение этоl'О условия имеет существенное значение для 1'01"0, чтобы код работал так, как ожидается. По- этому мы добавляем утверждение, которое позволяет УДОLТОвериrься в том, ЧТО в lIеременной podВayDoorSta tus действиrельно хранится значение 'OT крыто' . в данном случае дЛЯ TOI'O чтобы сразу увидеть, rде возникает сбой, если утверждение не выполняется, мы включаем в операцию присваивания строку сообщения 'Дверь I'PY30BOI'O отсека должна находиться в состоянии "открыто" . ' . Допустим, что впоследствии мы совершаем досадную ошибку, при сваи- вая переменной podBayDoorStatus дрyrое значение, но не замечаем этоrо orpexa среди множества строк кода. Утверждение обнаружит эту ошибку и недвусмысленно информирует нас о том, что именно произошло. В переводе на обычный язык утверждение сообщает нам следующее: "Я убеждаюсь в том, что данное условие выполняется; в противном случае можно утверждать, что rдe-тo в проrрамме скрывается ошибка". В отличие от исключений ваш код не должен обрабатывать инструкции assert с помо- щью конструкции try/except. Если утверждение оказывается ложным, то ваша проrрамма должна завершиться аварийно. Такой способ paHHero вы- явления потенциальных ошибок сокращает промежуток времени, отделяю- щий момент возникновения первопричины ошибки от момента, коrда вам впервые становится известно о наличии ошибки в ПрОI'рамме. Тем самым сокращается объем кода, который пришлось бы написать для выявления фраrментов OCHOBHoro кода, 110рождающих ошибку. Утверждения предназначены для нахождения ошибок проrраммиста, а не пользователя. Для обнаружения ошибок, которые допускают элеrантное (или, как rоворят, амортизированное) восстановление работоспособноrо 
Отладка 281 СОСТОЯНИЯ проrраммы, таких как ввод пользователем некорректных дaH ных или lIевозможность обнаружить указанный файл, используйте возбуж- дение исключений, а не инструкции a55ert. И'"О."О."".' р.'ржд.".. . .porptl_., ..."'Р,IOIII'. ра60ry ,..1Офор" ПреДIIОЛОЖИМ, требуется создать проrpамму, имитирующую работу све- тофора. В качестве структуры данных, предстаВJIяющей сю'налы свето- фора на перекрестке, выбираем словарь с ключами 'n5' и 'ew' для ламп, ориентированных вдоль направлений "северюl''' и "востокзапад" COOT ветственно. Каждому из этих ключей MOryr соответствовать значения в виде строк' green', 'yellow' и 'red' (красный). Соответствующий код бу дет выrлядеть так. market 2nd = {'ns': 'green', 'ew': 'red') mission 16th = {'ns': 'red', 'ew': 'green'} Эти две переменные отвечают за перекрестки, образуемые парам и улиц "Mal'ket Stl'eet2nd Street" и "Mission Street16th Stl'eet". Приступая к про- екту, целесообразно написать функцию swi tchLights () , которая будст пере- ключать еветофор, принимая указанный словарь в качестве apryMeHTa. Первое, что может прийти вам на ум,  это то, что функция switch Lights () Bcero лишь должна поеледовательно переключать цвета сиrналов ( одноrо на друrой: за любым значением I green' (зеленый) должно следовать значение' yellow' (желтый), которое будет заменяться значением' red' (красный), в свою очередь сменяющимся значением' green' и Т.д. КОД, pea лизующий эту идею, может выrлядеть примерно так. def switchLights(stoplight): for key in stoplight.keys(): if stop1ight[key]  'green': stop1ight[key]  'yellow' elif stoplight[key)  'yellow': stoplight[key] = 'red' elif stoplight[key] == 'red': stoplight[key]  'green' switchLights(market2nd) Возможно, вам уже удалось заметить проблему, связанную с этим KO ДОМ, но все же давайте представим, что вы уже написали остальную часть кода ПрOl'раммы имитации светофора, насчитывающую тысячи строк, И указанная проблема ускользнула от вашеrо внимания. Коrда вы запустите 
282 rлаВQ 10 проrpамму, она не потерпит крах, чеrо нелЬ.зя будет сказать о ваших ВИРТУ- альных автомобилях! Поскольку, как мы предположили, остальная часть проrpаммы уже напи- сана, вы не имеете ни малейшеrо представления о том, в каком ее разделе может скрываться ошибка. Возможно, она затаилась в коде, имитирующем движение машин, или, может быть, в коде, имитирующем действия вирту- альных водителей. Прежде чем вы доrадаетесь, что следы ошибки ведут к функции swi tchLights (), может пройти немало времени. Но если бы в процессе написания функции switchLights () вы добавили yrверждение, проверяющее, что в любой момент времени по ",райnеи. .мере одии cuеuал светофора ",раcu'Ый, то поместили бы в конце функции следую- щий код. assert 'red' in stoplight.values(), 'Ни один из сиrиалов не является красным! ' + str(stoplight) Проrрамма, содержащая это утверждение, завершится аварийно с выда- чей следующеI'О сообщения об ошибке. Traceback (most recent саll last): File "carSim.py", Нпе 14, in <module> switchLights(market2nd) File "carSim.py", line 13, in switchLights assert 'red' in эtорlight.vаluеs(}, 'Ни один из сиrналов не является красным! ' + str(stop1ight) О АssеrtiопЕrrоr:Ни один из сиrналов светофора не является красным! {'ns': 'yell0W', 'ew': 'green'} Здесь очень важна строка AssertionError О. Несмотря то что решение, ПРИВОДЯlцее к аварийному завершению работы проrраммы, далеко не иде- ально, оно позволяет незамедлительно известить вас о нарушении прове- ряемоrо условия: ни в одном из направлений не rорит красный сиrнал, а это означает, что движение разрешено одновременно по двум пересекаю- щимся проезжим частям. Заблаrовременно обнаруживая этот факт уже на ранних стадиях выполнения проrраммы, вы обеспечиваете значительную ЭКОНОМИЮ своих усилий по ее отладке в будущем. Откn",.'".' yr..рq.".й Механизм утверждений можно отключить, указав в командной строке флаr o при запуске Python. Эта мера вполне уместна, коrда этап написа- ния и отладки проrраммы завершен и вы не хотите, чтобы работа проrрам- мы замедлялась за счет выполнения профилактических проверок с помо- ЩЬЮ утверждений (хотя в большинстве случаев наличие yrверждений не 
Отладка 283 оказывает существенноrо влияния на скорость выполнения проrраммы). Утверждения предназначены для проrрамм, находящихся на стадии раз работки, но не для rOToBoro продукта. К тому времени, Korдa вы переда дите проrрамму в эксплуатацию потребителям, она должна быть свободна от ошибок и не должна нуждаться в проверке соблюдения оrраничений. Более подробно о том, как запустить с флarом o проrpамму, которая, как ожидается, уже отлажена, речь идет в приложении Б. Протокояирование Если вы уже ис.пользовали инструкции print () для вывода значений интересующих вас. lIеременных в ходе выполнения проrраммы, то може те считать, что с одной из форм nроmtЖолuроваnия вы уже знакомы. Cpeд ства протоколирования позволяют получать ценнейшую информацию о том, что именно и в какой последовательности происходит в ПРOI'рамме. Модуль logging в PyttlOI1 упрощает создание и ведение журнала отчетов на основе ПОДl'отовленных вами сообщений. Сообщения протоколирования включают описание Toro, коrда именно проrрамма достиrла вызова функ ции протоколирования, а также список значений указанных вами перемен. ных в данный момент времени. С ДРУl'ОЙ стороны, отсутствие сообщения свидетельствует о том, что данная часть кода была пропущена и не выпол- нялась. И,,,ОЛ6Jо.,,н.е -оду.. loggiпg Чтобы активизировать модуль logging для вывода сообщений Проток<r лирования на экран в процессе выполнения проrраммы, скопируйте приве- денный ниже код в начало своей проrpаммы (но после "мarической" строки запуска PytllOIl, начинающейся символами #!). import logging logging.basicConfig (level=logging. DEBUG, format=' % (asctime)s  %(levelname)s  %(message)s') Детали Toro, как все это работает, для вас несущественны, но вам будет полезно знать, что Python каждый раз, коrда протоколирует событие, соз- дает объект 1ogRecord, в котором хранится информация о данном событии. Функция basicConfig () модуля logging позволяет конкретизировать, какую именно информацию об объекте 1ogRecord и в какой именно форме вы хо- тите видеть. Предположим вы написали функцию для расчета факториала числа. Ha пример, факториал числа 4 равен произведению 1 )( 2 )( 3)( 4, Т.е. 24. Факт<r риал числа 7 равен 1 )( 2 х 3 х 4 х 5 )( б )( 7, или 5040. Откройте новое окно в 
284 rлава 10 файловом редакторе и введите приведенный ниже код. В этом коде содер- жится ошибка, но вы дополнительно предусмотрите несколько сообщений, предназначенных для записи в журнал, которые помоryт вам выяснить, rде именно чт<yfо идет не так, как нужно. Сохраните проrрамму в файле factoriaLLog. ру. import logging logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s  %(levelname)s  %(message)s') lоggiпg.dеЬug('Начало проrраммы') def factorial(n): lоggiпg.dеЬug('Начало factorial(%s%%)' % (n)) total = 1 for i in range(n + 1): tota1 *= i logging.debug('i = , + str(i) + " total '+ str (total) ) logging.debug('KoHeu factorial(%s%%) I % (п)) return tota1 print(factorial(5)) logging.debug('KoHeu проrраммы') в этом коде для вывода протоколируемой информации используется функция logging. debug ( ) . Она вызывает функцию basicConfig () , которая выводит строку информации. Формат вывода будет соответствовать тому формату, который задан в функции basicConfig () , и включать сообщения, переданные функции debug (). Вызов print (factorial (5)) является частью исходной проrраммы, поэтому результат будет отображаться даже в том случае, если средства протоколирования отключены. Результирующий вывод будет иметь примерно следующий вид. 2015O523 16:20:12,664  DEBUG  Начало проrраммы 20150523 16:20:12,664  DEBUG  Начало factoria1 (5) 20150523 16:20:12,665  DEBUG  i О, total = О 20150523 16:20:12,668  DEBUG  i 1, total О 20150523 16:20:12,670  DEBUG i 2, total О 20150523 16:20:12,673  DEBUG  i 3, tota1 О 2015052З 16:20:12,675  DEBUG  i 4, total О 2015052З 16:20:12,678  DEBUG  i 5, total О 2015052З 16:20:12,680  DEBUG  Конец factoria1(5) О 2015052З 16:20:12,684  DEBUG  Конец проrраммы Функция factorial () возвращает значение О для факториала числа 5, что неверно. В цикле for значение переменной total должно умножаться 
Отладка 215 на числа от 1 до 5. Однако сообщения, отображаемые функцией 10gging. debug ( ) , указывают на то, что начальным значением переменной i является О, а не 1. Поскольку результатом умножения любоrо числа на О всеrда будет О, оставшаяся часть итераций также дает неверный результат для nepeMeH ной total. Сообщения журнала отчетов являются теми самыми "хлебными крошками", идя ПО которым, вам будет леrче выяснить, в каком месте np<r rраммы чт<rто пошло не так, как надо. Замените строку for i in range (n + 1): строкой for i in range (1, n + 1): и вновь выполните npOl'paMMY, Результат должен выrлядеть примерно так. 20150523 17:13:40,650  DEBUG  Начало проrраммы 20150523 17:13:40,651  DEBUG  Начало factoria1(5) 20150523 17:13:40,651  DEBUG   1, t ot а 1 1 20150523 17:13:40,654  DEBUG  i 2, tota1 2 20150523 17:13:40,656  DEBUG  i 3, tota1 6 20150523 17:13:40,659  DEBUG  i 4, total 24 20150523 17:13:40,661 DEBUG  i 5, total 120 20150523 17:13:40,661  DEBUG  Конец factorial(5) 120 20150523 17:13:40,666  DEBUG  Конец ;-;роrраммы Теперь вызов factorial (5) возвращает корректное значение 120. Соо& щения протоколирования показывают: именно то, что происходит в ци кле, непосредственно IIрИВОДИТ к ошибке. как видите, вызовЫlоgging .debug () выводят не только передаваемые им строки, но и временные метки, а также слово DEBUG. Не ...IIM",.,e О,.III1КУ , 110_0"..10 .""РУК".. priп t ( ) Подход, основанный на импортирован ии модуля logging и вызове функ- цииlоggiпg.ЬаsiсСопfig(lеvеl=lоgging.DЕВUG, format = '%(asctime)s  : (levelname) s  % (message) s' ) , может выrлядеть несколько rpомоздким. Не исключено, что у вас возникнет желание использовать вместо Hcro обыч- ные вызовы print (), однако не поддавайтесь этому искушению! 3акончив отладку, вы потратите массу времени на удаление из кода вызовов print () .1.ЛЯ каждоrо сообщения. Более 1'01'0, при этом не исключено, что вы слу- чайно удалите полезные вызовы print ( ) , никак не связанные <: сообщения- :IofИ протоколирования. Работа с сообщениями протоколирования удобна тем, что вы можете предусмотреть в своей проrpамме столько сообщений, сколько пожелаете, а 8последствии в любой момент сможете отключить их, добавив единственный вызов logging. di sable (logging . CRITICAL) . в от- .1ичие от функции print () , модуль logging упрощает переключение между режимами отображения и сокрытия сообщений протоколирования. 
286 r лава 1 О Сообщения протоколирования предназначены для I1роrраммистов, а не для пользователей. Пользователя не интересует содержимое слова- ря, за которым вы хотите наблюдать в процессе отладки; для подобных целей используйте сообщения протоколирования. Для вывода сообще- ний, предназначенных для пользователя, таких как "Файл не найден" или "Недопустимый ввод; пожалуйста, введите число", следует исполь:ювать вызовы print (). Ведь не захотите же вы лишить пользователя доступа к полезной информации после Toro, как отключите вывод сообщениЙ жур- нала отчетов. У,о..'" Kp.r.."o,,,, ,006щ.".. В системе протоколирования так называемые уровни критичности (уров- ни журналирования) сообщений позволяют классифицировать сообщения по степени важности. Существует пять уровней критичности сообщений протоколирования, описанных в табл. 10.1 в порядке возрастания их важ- ности. На каждом уровне сообщения MOryr выводиться с использованием различных функций протоколирования. Та6nмца 10.1. Уровни критичности сообщений в Руthоп Уронн.. ФунКЦИ. протоковировани. Описани. DEBUG logging . debug ( ) Самый ни ,кий уровень. Испопьэуетсl Дnl вывода подробных сведении техническоrо характера. Обычно вы 'аинтересованы в этих сообщеНИIХ В процессе диаrНОСТМРОВОНИI пробnем INFO logging. info () ИспоnьэуетCl Дnl ,описи информации об обычных соБЫТИllх, ПРОИСХOДlщих в проrpaмме, или Дnll подтверждеНИI HOpмOllbHoro ходо роботы проrраммы WARN ING logg ing . wa rning ( ) ИспоnьзуетСI дnя индикации потенциanьно опасных ситуации, которы. не препlIТСТВуюТ работе проrраммы, но мoryт привести к пому , будущем ERROR logging. error () ИспоnыуетСIДnI ,аписи информации об ошиБК8, котораl привела к тому, что проrромма не cмarna IbIПОnНИТЬ некоторые деИСТВИI CRITICA1 logging. cri tical () Наивысший уровень. ИспопыуетСIДnllиндикацни фатальных ошибок, которые привели иnи MOryт привести к полному прекращениlO работы проrраммы Ваши сообщения передаются этим функциям в виде строк. Приведен ные описания  это не более чем рекомендации. В конечном счете ЛИшь вы решаете, к какой катеrории следует отнести то или иное предупреждающее сообщение. Введите в интерактивной оболочке следующие команды. 
Отладка 287 >>> illport loqqinq »> logqinq.ba8icConfiq(level-loqqinq.DЕВUG, fоrшаt=' %(asctille).  %(lвvelname)s  %(1I888аЧ8)8') »> loqqinq. debuq ( 'О'l'Л8ДочиаR информации. ' ) 20150518 19:04:26,901  DEBUG  Отладочная информация. »> lоqqinq.infо('Работает модуль logqinq. ') 20150518 19:04:35,569  INFO  Работает модуль logging. »> lоqginq.wаrninq('Риск получеНИR сообщения об ошибке. ') 20150518 19:04:56,843  WARNING  Риск получения сообщения об ошибке. »> lоqqinq.8rror('произошnа ошибка. ') 20150518 19:05:07,737  ERROR  Произошла ошибка. »> lоqqinq.сritiсаl('Восотаиоanение работоспособности nporpaммw  не80ЗИОЖНО ' ) 20150518 19:05:45,794  CRITICAL  Восстановление работоспособности про rраммы невозможно Преимуществом использования уровней критичности сообщений яв- ляется То, что они позволяют задавать rраничный приоритет сообщений, подлежащих отслеживанию. Передача значения 10gging. DEBUG в качестве именованноrо apryмeHTa функции basicConfig () приведет к тому, что 01'0& ражаты:я будут сообщения всех уровней критиtШо(:ти (из которых DEBUG  самый низкий уровень). После Toro как разработка I1porpaMMbl пройдет определенные этапы, вас будет интересовать лишь вывод информации об ОlUибках. В этом случае вы (:можете задать для функции basicConfig ( ) в качестве apryMeHTa, определяющеrо уровень критичности сообщений, значение 10gging. ERROR. В результате будут отображаться лишь сообщения катеroрий ERROR и CRITICAL, а сообщения катеrорий DEBUG, INFO и WARNING будyr иrнорироваться. OrUIO.'II'" "pOrOKO."pO."II". Завершив отладку nporpaмMbI, вы, вероятно, захотите избавиться от на- зойливых сообщений, захламляющих экран. Вам не придется удалять все вызовы функций протоколирования вручную, поскольку вместо вас это может сделать функция logging. disable (). Вам остается лишь передать ей желаемый уровень критичности ошибок, и она подавит вывод сообщсний, относящихся К этому И более низким уровням. Следователr>но, если вы за- хотите полностью отключить средства протоколирования, вам буд<.->т доста- точно добавить в свою проrpамму вызов logging. disable (logging. CRITICAL) . Например, введите в интерактивной оболочке следующие команды. »> import logqinq »> logqinq.ba8icConfiq(level-loqqinq.INFO,  format=' % (a8ctiJlle) 8  % (levelnaDle) 8  % (1IIe88aqe) 8 1) >>> logqinq.critical ('I(pИ'1'ИЧ8СКая ошибка! Кpm.ич8СICU OIIИбха!') 
288 rлава 10 20150522 11:10:48,054  CRITICAL  Критическая ошибка! Критическая ошибка! »> loqqinq.di_able(loqqinq,CRITICAL) >>> loqqing. cri tical ( 'Критическая OIIIИбка! Критическая ошибка! ') >>> loqging. error ( , Ошибка! Ошибка!') Поскольку вызов logging. disable () отключает все следующие за ним ин- СТРУКlии протоколирования, вы, вероятно захотите добавить ero 8 свою проrрамму за строкой import logging. Блаrодаря этому вы сможете быстро находить этот вызов, чтобы при необходимости "закомментировать " или "раскомментировать" ero ДJШ аКТИВИзации или отключения (:редств прото- колирования. 3а""" ,оо6",ен,,' "poroKo."po8aH". 8 фаl. .урна.а Вместо Toro чтобы отображать сообщения протоколирования на экране, их можно записывать в текстовый файл журнала отчетов (жарr. лое-файл). Для этоrо следует воспользоваться функцией logging .basicConfig (), кото- рая принимает именованный apryмeHT filename. .import logging lоggiпg.ЬаsiсСопfig(filenaше='ауProqraaLoq.txt', level=logging.DEBUG, format='%(asctime)s  %(levelname)s  %(message)s') Здесь сообщения протоколирования сохраняются в файле тyProgramLog. txt. Какими бы полезными ни были сообщения протоколирования, они мо- ryr захламлять экран и затруднять чтение вывода проrраммы. Запись этих сообщений в файл журнала позволяет освободить экран, а ознакомитЬ(:я с тек(:том сообщений можно будет уже после завершения выполнения про- rpaMMbI. для открытия файла журнала отчетов можно воспользоваться лю- бым текстовым редактором, таким как Блокнот или TextEdit. Отпадчик IDLE Отлаичu'К  это средство IDIE, которое предоставляет возможность по- шаrовоro выполнения проrраммы по одной строке за раз. При этом отлад- чик выполняет одну строку кода, а затем переходит в состояние ожидания, пока не получит команду продолжить выполнение. Выполнение проrрам- мы под управлением отладчика позволяет наблюдать за значениями перс-- менных в любом заданном месте проrpаммы на протяжении ее срока сущес- твования. Orладчик  нсзаменимое средство для обнаружения лоrических ошибок 8 проrрамме. 
Отладка 289 Чтобы активизировать отладчик IDLE, щелкните на пунктах меню Debug  Debugger (Отладка Отладчик) в окне интерактивной оболочки для открытия окна Debug Control (Управление отладкой), как показано на рис. 10.1. Коrда откроется окно Debug Control, установите все четыре флажка, Stack (Стек), Locals (Локальные переменные), Source (Исходный код) и Globals (rлобальные переменные), для отображения полноrо набора разделов от- ладочной информации. Всякий раз, кш'да вы будете запускать проrрамму из окна файловоrо редактора при открытом окне Debug Contral, отладчик бу дет приостанавливать выполнение проrраммы перед первой инструкцией и отображать следующую информацию: . строку кода, которая будет выполнена следующей; . список всех локальных lIеременных и их значения; . список всех rлобальных переменных и их значения. r,-'''''' ;:;, .-::--'- .-  DёugСontroI (.""I LI.  . . I .,.. J ". .1. ", ! ,'. : J;;i StlCk ! SoLlI(R j...' .....:.:...J.:J....::_.j ';; Lo"ls ;'. Glob.Is (Non) ..J Loc.ls Non , ..) ..J Рис. 10. 1. Окно Debug Contro/ Позже вы увидите, что в списке rлобальных переменных присутствуют такие имена, как as Ьиiltiлs, doc, file и друrие, которые вы не определяли. Эти переменные PythOl1 автоматически устанавливает вся- кий раз, коrда вы запускаете проrpамму. Их рас<м()трение выходит за рамки данной книrи, и вы можете смело их иrнорировать. Проrрамма будет оставаться в состоянии ожидания до тех пор, пока вы не щелкнете на одной из следующих пяти кнопок в окне Debug Control: Go, Step, Over, Out или Quit. 
290 rЛQВQ 10 Кнопка 'О Щелчок на кнопке Go (Выполнить) запускает обычный процесс ВЫПОk пения ПрOl'раммы до ее полноrо завершения или до тех пор, КOI'да будет достиrнyrа mО'ч/Ка остаnова. (О точках останова речь пойдет далее.) Если вы завершили отладку и хотите, чтобы проrрамма продолжила выполнение в обычном режиме, щелкните на кнопке Go. Кнопка 51ер Щелчок на кнопке Step (Шаr с заходом) приводит К выполнению следую- щей строки кода и приостановке проrраммы. Если значения локальных и l'Лобальных переменных изменились, то их список в окне Dcbug COI1tl'ol обновится. Если следующей строкой кода является вызов функции, то OT ладчик выполнит "шаr с заходом", Т.е. выполнит вызов функции и приоста новится на первой строке ее кода. Кнопка Over Щелчок на кнопке Over (Шаr с обходом) при водит к выполнению сле дующей строки кода, как и при щелчке на кнопке Step. Но если следующей строкой кода является вызов функции, то отладчик выполнит "Шаr с обхо дом", Т.е. будет выполнен не только вызов функции, но и весь ее код, и пос ле возврата из функции дальнейшее выполнение приостановится. Напри мер, если следующей строкой кода является вызов функции print () , то вас интересует лишь вывод переданной ей строки на экран, а не сам код функ ции. Поэтому обычно кнопкой Over пользуются чаще, чем кнопкой Step. Кнопка Ои' IЦелчок на кнопке Out (Шаr с выходом) приводит К выполнению строк кода в обычном режиме до тех пор, пока не будет выполнен возврат из те- кущей функции. Если перед этим вы выполнили шаr с заходом в функцию с помощью кнопки Step, а теперь просто хотите продолжить выполнение инструкций вплоть до возврата из функции, щелкните на кнопке Out для выполнения "шаrа с выходом" из текущеrо вызова. Кнопка Ои;' Если вы хотите полностью прекратить отладку без дальнейшеrо выпол- нения остальной части проrраммы, щелкните на кнопке Quit (Выход), что приведет к немедленному прекращению выполнения проrраммы. Если же вы хотите возобновить работу проrраммы в обычном режиме, то отключи- те отладчик, повторно выбрав пункты меню DebugDebugger. 
Отладка 291 ОrЛ"IIК" пpOrp"MM6I /111- Ulоже",,_ '''''л Откройте новое окно в файловом редакторе и введите следующий код. рriпt('Введите первое слаrаемое:') first = inpиt () рriпt('Введите второе слаrаемое: ') second = inpиt ( ) рriпt('Введите третье слаrаемое: ') third = inpиt() print('Cyммa равна' + first + second + third) Сохраните текст ПрOl'раммы в файле buggyAddiпgPrograт.py и выполните ее сначала при отключенном отладчике. Вы должны ПОЛУЧИ1'Ь следующий вывод. Введите первое слаrаемое: 5 Введите второе слаrаемое: 3 Введите третье слаrаемое:: 42 Сумма равна 5342 Проrрамма не завершилась аварийно, однако результат явно неверный. АКтивизируйте окно Debug Control и вновь выполните проrрамму, на этот раз под управлением отладчика. Нажмите клавишу <F5> или выберите пункты меню RunqRun Module (предварительно активизировав отладчик ( помощью пунктов меню DebugqDebugger и установив все четыре флажка в окне Debug Control), что приведет к запуску ПрОl'раммы и ее приостановке на строке 1. Отладчик всеrда приостанавливается на той строке кода, которая будет ВЫIIOЛНЯТЫ::Я следующей. Окно Debug Control примет вид, показанный на рис. 10.2. Щелкните на кнопке Over один раз для выполнения первоrо ны:юва функции print (). В данном случае необходимо использовать кнопку Over, а не Step, поскольку в "заходе" в код функции print () нет необходимости. Содержимое окна Debug Control обновится, и теперь оНО будет отображаТI. информацию, относящуюся к строке 2, а сама строка 2 в окне файловою ре- дактора будет подсвечена (рис. 10.3). Тем самым вы сможете леrко опреде- лить, какая именно строка кода выполняется в данный момент. Вновь щелкните на кнопке Over, чтобы выполнился вызов функции inpиt (); при этом на все то время, пока IDLE будет ожидать вашею ввода в окне интерактивной оболочки 8 ответ на вызов функции inpиt ( ) , кнопки в окне Debug Control деактивизируются. Введите 5 и нажмите клавишу <Enter>. После этоrо кнопки в окне Debug Control вновь активизируются. 
292 rлава 10 . . jt Debug Conbol . J 1 J Jti1St1Ck oJ2:P ?:,e l  .2' [О; LOCIII ":.;:.:"':> ?{:(.:. '..,.. :-<,.<><:.';'"!. (":.: > ';l;;t(rBi" 1" Source ;;; Globlll bugg)'Addingprogr.m,PJ"l: < modult> О # 1; ;i;; I! I l I I I I I i I ,,,''""" ..... ....... ""'_''_"'''__'''_'''''''_.., ................L LOCIIs Nont , ....J Globlls builtin. doc filt Iolder < module 'bu,ltins' (built,in» Nont '5:\\'.' Project'l, \',\ ру\\ \\buggyAddingProgram.py' < ,1155 '}rozen.importlib,Builtinlmporter" J neml'..... 'main...: .....P"klge None  None ) Рис. 10.2. Внд окна Debug Coпtro/ при пеР80начальном запуске про- rpаммы под управлением отлодчика f,'C_ ..' ","," ..<',.'..>., ,. . ба:  : о... ; Ф". Q\I& I ,.J bdr.  Saura .",",",,-..:.. .';..,  !v Lo(»S __ 01,*_ bugg';'AddIngPnIgf,"'.l; '\ medult)O 'i3 fiiiil ЪCI> .tun(l. I4l'1ea. "K( I.I .t':.:t!;;:;t..t"'J"Z .. ...tp..,."'; . s.;J ;  'i;';Ci;; :. .. Е'" FOnNt '" opoiom ......... ...., LOaIS . ;П' (' ') .. ' IP] 1т (.i'.iat (' .. .".- .. ') second = ;',pt () .! I:;t ( . . .., : --." с ) third  :r.1=ut () (,".! : :it ( , . .... + firt + sec.,nd + third) Но.. GlotI.S tluiti"l  1"I'Юd\,1t bUI".r'l ':b'ln:' dc( Ыe... ....otdv ..... ':'.:...\ P':.tf<I' .;,';.Ьu9ФДddi"gPf09""" р,' oJс18и '.frol.lmpoftlф 8uilttn&.npoltu'" ...оцМ..... m.ln ............. None Ln; 1 (01 Н .....)I:8'!: None Рис. 10.3. Вид окиа Debug Сопtrol после щелчка на кнопке Over 
Отладка 293 Продолжая щелкать на кнопке Over, введите 3 и 42 в качестве следующих ДI}УХ слаl'аемых, пока отладчик не достиrнет строки 7, Т.е. последнеrо BЫ зова функции print () в ПрOl'рамме. В этот момент окно Debug Control долж но выrлядеть так, как 110казано на рис. 10.4. Как можно увидеть в разделе Globals, первая, вторая и третья переменные содержат строковы е значения , 5 1, '3' и '42', а не целочисленные значения 5, 3 и 42. При достижении п<r следней строки кода вместо сложения трех чисел выполняется KOHкaTeHa ция указапных строк, что приводит К ошибке. (jc.. , I ' ; , i;; 5tlck ..? 1ep ._r_! .?ut:Q.uJ r.; LOClls buggyAddingProgrlm,py:7: < modulR> О C;;=-iС ii), j Source '" Globals 'bdb',runO, lin 431: exc(cmd, glob.ls, locels) .-  ...: ;'1J!'::j'.:.-r:::::UХ.:J ; ...J Loc.ls Non. .1 ! ..... Glob.ls builtinl doc fjll!.... loadRr <modulR 'builtlns' ibuilt.in» Nona '5:\\ \\Projws\",\ \ ру" \ \o,buggyAddingProgram.py' . ,1'55 'Jrozn.importlib,Builtinlmpollar'> I .J ...nam!".... .....m8In.....' .jIlclclgL Nona spac Nona fir5t '5' sacond '3' third '42'  Рис. 10.4. Вид окна Debиg Coпtro/ при достижении последней строки кода. Перемениые принимают строковые значения, что приводит к ошибке Пошaroвое выполнение проrpаммы с помощью отладчика чрезвычайно информативно, однако замедляет работу проrраммы. Во мноrих случаях вам хотелось бы, чтобы проrрамма выполнялась в обычном режиме, пока не достиrнет определенной строки кода. Можно сконфиrypировать отлад чик для TaкOI'O режима работы, используя точки останова. 
2М rлава 1 О То.к" ona"о.. Точка остапова, у(тановленная для определенной строки кода, сообща- ет отладчику, что по достижении данной строки выполнение nporpaMMbl должно нриостановиться. Откройте новое окно файловоrо редактора и введите следующую nporpaMMY, имитирующую подбрасывание монсты 1000 раз. Сохраните nporpaмMY в файле coinFlip.py. import random heads = О for i in range(l, 1001): О if random.randint(O, 1) 1: heads = heads + 1 if i == 500: О print ( I Полпути пройдено!') рriпt('Орел выпал' + str(heads) + ' раз.') Вызов random.randint (О, 1). в половине случаев будет возвращать зна- чение О, а в половине  1. Этот факт можно использовать для имитации подбрасывания монеты с равными вероятностями выпадения "орла" и "решки", причем в нашем случае "орлу" соответствует значение 1. Выпол- нив nporpaMMY без отладчика, вы очень быстро получите примерно следую- щий вывод. Полпути пройдено! Орел выпал 490 раз. Если вы будете выполнять проrрамму под управлением отладчика, то к тому моменту, коrда npOI'paMMa закончит выполнение, вам придется щел- кнуть на кнопке Over не одну тысячу раз. Если вас интересует, сколько раз выпала решка, коrда nporpaMMa выполнилась наполовину, Т.е. при совер- шении 500 "подбрасываний" монеты из 1000 возможных, можете просто задать точку останова в строке print ( 'Полпути пройдено! ') .. Чтобы задать TOtlKY останова, щелкните правой кнопкой мыши на этой строке в окне файловоrо редактора и выберите во всплывающем меню пункт Set Breakpoint (Задать точку останова), как показано на рис. 10.5. Задавать точку останова для строки с инструкцией if не следует, по- скольку данная инструкция выполняется на каждой итерации цикла. Если задать точку останова в коде инструкции i f, то отладчик будет прерывать выполнение только в тех случаях, коrда поток управления будет входить в тело этой инструкции. Строка, для которой задана точка останова, подсвечивается в файловом редакторе желтым цветом. Коrда вы запускаете проrрамму под управлени- ем отладчика, ее выполнение начинается с состояния ожидания в нервой 
Отладка 295 строке. Но если вы щелкнете на кнопке Go, то проrpамма станет ВblПОЛНЯТЬ инструкцию за инструкцией до тех пор, пока не достиrнет строки, дЛЯ KOT<r рой задана точка останова. Далее можете щелкать на кнопках Go, Over, Step и Out, чтобы продолжать выполнение в соответствующем режиме. L_ coinFIip.py - S:\Projects\1I)'\colnFflp.py (Э';5.11) Eile Edit F,Qrmat Bun Qptions. indow, J:ielp random heads = О i rarJg,,, (1, 1001): random.randint(O, 1) head5 = heads + 1 '. i == 5.00: print (", ,'"...-::, -,;:..:';,::,:, I' \ + tr (heads (,- I-'':i '&fOJI8 ..] 1: print (' .....:;, l -;" '. ""'''''.....''f(; ';""'.-'.""kfF Clear Вreakpoint Ln: 7Co 31 Рис. 10.5. Задание точки останова Чтобы удалить точку останова, щелкните правой кнопкой мыши на COOT ветствующей строке в окне файловоrо редактора и выберите в открывшем- ся меню пункт Clear Breakpoint (Отменить точку останова). Желтая подсветка строки исчезнет, и в будущем выполнение проrраммы на ЭТОй строке не будет прерываться. РеЗlOме Утверждения, ИСIUlючения, протоколирование и отладка  незаменимые инструменты, предназначенные для обнаружения и устранения лоrических ошибок в проrраммах. Механизм утверждений, обеспечиваемый инструк цией assert в Python,  отличное средство реализации "профилактических проверок" , обеспечивающее раннее обнаружение потенциальных ошибок, если не ВЫIЮЛШIЮТСЯ необходимые условия. Утверждения прсдназначены для выявления только тех ошибок, при возникновении которых проrрам- ма не будет пытаться восстановить работоспособное состояние и должна лишь быстро завершиться аварийно. В противном случае следует исполь- зовать исключения. Исключения можно перехватывать и обрабатывать (: помощью инструк- ций try и except. Модуль logging, обеспечивающий возможность учета уровней критичности ошибок при записи сообщений о них в текстовый 
296 rлава 10 файл журнала, предоставляет неплохие возможности для наблюдения за состоянием ПрOl'раммы во время ее выполнения и I'ораздо более удобен в использовании, чем функция print (). Отладчик обеспечивает пошаl'овое выполнение I1porpaMMbl по одной инструкции за раз. Альтернативной возможностью является выполнение IIporpaMMbl с обычной скоростью с возможностью ее прерывания отлад чиком при достижении строк КОДа, для которых заданы точки останова. Используя отладчик, можно наблюдать за значениями любых переменных во время выполнения ПрОI'раммы на протяжении вcero срока ее существо вания. Использование перечисленных средств и методик отладки облеrчит вам написание правильно работающих проrpамм. Непреднамеренное внесение лоrических ошибок в код  неотъемлемый атрибут работы проrраммиста, от KOToporo не помоrает полностью избавиться даже мноrолетний опыт проrраммирования. KOHTpOnbHbl8 BonpOCbI 1. Напишите инструкцию assert, которая возбуждает исключение AssertionError, если :шачение переменной эрат представляет собой целое число, меньшее 10. 2. Напишите инструкцию assert, которая возбуждает исключение AssertionError, если переменные eggs и bacon содержат одинаковые LТрОКИ, при этом реrистр букв не учитывается (например, они содер- жат строки I hello I И 'hello' или же строки' goodЬye' и I GOODbye' , K<r торые также считаются одинаковыми). 3. Напишите инструкцию assert, которая всеrда возбуждает ип<люче- ние AssertionError. 4. Какие две строки кода должна иметь ваша проrрамма для TOro, чтобы вызывать функцию logging. debug () ? 5. Какие две строки кода должна иметь ваша проrpамма для TOro, что бы вызывать функцию logging. debug ( ) , записывающую сообщение в файл журнала prograтLog. txt? 6. Назовите пять уровней критичности сообщений. 7. Какую строку кода вы можете добавить для Toro, чтобы отключить все сообщения протоколирования в своей проrpамме? 8. Почему для отображения одноrо и Toro же сообщения лучше испол зовать сообщения протоколирования, чем вызов функции print ( ) ? 9. Чем различаются команды, выполняемые после щелчков на кнопках Step, Over и Out в окне Debug Control? 
Отладка 297 10. Коrда отладчик прервет выполнение nporpaMMbl, если вы щслкнете на кнопке Go в окне Debug Control? 11. Что такое точка останова? 12. Как задать точку останова для строки кода в IDIE? Учебный проект Чтобы закрепить полученные знания на практике, реализуйте предл<r женную ниже задачу. Or."IIK" "porfICJ_6I, II."'IIPYIOIII" 1I01l6p"'''.''IIII' .011',61 Привсденная ниже nporpaMMa предназначена для имитации иrры в yra дывание Toro, какой стороной будет обращена вверх упавшая после 1I0Д брасывания монета. В распоряжении иrрока имеются два варианта ОТВета (это простая иrра). Однако в nporpaмMe содержатся лоrические ошибки. Запустите nporpaMМY несколько раз для тою, чтобы обнаружить ошибки, препятствующие ее правильной работе. import random guess = " while guess not in ('орел', 'решка'): рriпt('Уrадайте результат подбрасывания монеты! .Введите орел или ретка:') gиess = input () toss = random.randint(O, l} # о  решка, 1  орел if toss == gиess: print ( 'Есть! 1) else: print('YBbl! Попробуйте снова!'} guesss = input () if toss == guess: print ( 'Есть! I ) else: print('HeT. Вам действительно не везет в этой иrре.') 
АВТОМАТИЧЕСКИЙ С&ОР ДАННЫХ ВИНТЕРНЕТЕ в те редкие пуrающие минуты, коrда остаешься без WHi, отчетливо осознаешь, насколько то, что ты делаешь с IЮм<r щью компьютера, связано с Интернетом. Я часто замечаю, что по устоявшейся привычке меня так и тянет про верить электронную почту, прочитать сообщения друзей в Твит тере или ответить на вопросы типа "Были ли у Кертвуда Смита l'Лавные роли дО TOI'O, как ero приrласили ПIЯТЬСЯ в ориrинальном фильме РобlЖОn, выпущенном в 1987 rоду?"1 Поскольку значительная часть работ, выполняемых с помощью компькr тера, связана с выходом в Интернет, было бы замечательно, если бы ваши nporpaMMbI моrли обеспечивать это подключение. Авто,м,атический сбор даn- nых в иnтерnете (жарr. веб-скраnиnz)  это термин, обозначающий исполь зование проrраммы для зarрузки и обработки содержимоrо из Интернета. К примеру, Google выполняет множество таких nporpaMM, индексируя веб- страницы для своей поисковой системы. В этой rлаве вы познакомитесь с несколькими модулями, которые облеrчают автоматический сбор данных с веб-страниц с помощью Python. Ниже приведен перечень этих модулей. . webbrowser. Поставляется вместе с Pytholl и предназначен для OТKpЫ тия браузера на определенной веб-странице. . Requests. Заrружает файлы и веб-страницы из Интернета. . Beautiful Soup. Предназначен для синтаксическоrо анализа кода HTML  языка, на котором написаны веб-страницы. . Selenium. Запускает веббраузер и управляет ero работой; способен заполнять формы и имитировать щелчки мышью в браузере. I Orвeт  нет. 
300 rЛQва 11 Проект: nporpaMMa map't.py с модупем webbro"ser ФуНКЦИЯ open () модуля webbrowser запускает браузер с использованием указанноrо URL-aдpeca. Введите в интерактивной оболочке с.ледующие ко- манды: »> iшроrt w8bbrowaer »> w8bbrowaer.open('http://inventwithpython.com/') В бра узе ре откроется веб-страница, расположенная по URL-адресу http://inventwithpython. сот/. Это почти единственное, что может делать модуль webbrowser. Тем не менее функция ореп () позволяет делать интерес- ные вещи. Например, вставка lIочтовоrо адреса в приложение Google Maps через буфер обмена  довольно хлопотная задача. Вы можете сэкономить на этом некоторое время, написав простой сценарий, который будет авто- матически запускать карту в вашем браузере, используя содержимое буфера обмена. Блаroдаря этому вам останется лишь скопировать адрес в буфер об- мена и запустить сценарий, который и заrpузит карту. Вот что должна делать такая проrpамма: . получать почтовый адрес из командной строки или буфера обмена; . открывать браузер на странице Google Maps, соответствующей yкa занному адресу. Это означает, что ваш код должен выполнять следующие операции: . читать apryмeнты командной строки из списка sys . argv; . читать с.одержимое буфера обмена; . вызывать функцию webbrowser . open () для открытия браузера. Orкройте новое окно файловоrо редактора и сохраните ero в файле map/t.py. Ш", ,. О"Р'II'.'''.' U'I."IIP"" Руководствуясь инструкциями, приведенными в приложении В, настрой- те файл map/t.py таким образом, чтобы при ero запуске, например, с помо щью команды С:\> mapit 870 Valencia St, San Francisco, СА 94110 сценарий использовал в качестве почтовоrо адреса apryмeHTЫ командной строки, а не содержимое буфера обмена. Если же apryмeнты командной стро- ки не заданы, то проrрамма будет знать, что в этом случае нужно использо- вать буфер обмена. 
Автоматический сбор данных вИнтернете 301 Прежде BCeI'O необходимо определить, какой URLaдpec следует исполь- зовать для указанноrо почтовоrо адреса. Если вы заrрузите в браузер стра- ницу http://maps . google. сот/ и выполните поиск по интересующему вас по- чтовому адресу, то URL-aдpec, отображаемый в адресной строке браузера, будет выrлядеть примерно так: https://www.google.com/maps/place/870+Valencia+st/@37.7590311, 122.421509б,17z/dаtа=!3m1!4bl!4m2!3m1!1s0х808f7е3dаdс07аЗ7: Охс8БЬОЬ2ЬЬ9ЗЬ73d8 URL-aдpec содержит почтовый адрес, но, кроме Hero, включает также дополнительный текст. Веб-сайты часто вставляют дополнительные дан- ные в URL-адреса, которые используются для отслеживания при вы чек посетителей или адаптации сайта к запросам пользователей. Если вы ис- ключите эти данные и попытаетесь выполнить в браузере переход по упро- щенному адресу https: / /www. google. com/maps/place/870+Valencia+St+San+ Francisco+CA/, то обнаружите, что вам по-прежнему доставляется нужная страница. Следовательно, нам достаточно настроить проrрамму на страни- цу 'https: / /www.gооglе.сот/тарs/рlасе/вашаадреснаясока', rде ваша адресная строка  это почтовый адрес, в соответствии с которым должна быть открыта карта. Ш", 2. 06р"60nr,, "P,.,.'II108 КО."II11110. "рок. Введите следующий код. #! python3 # mapIt.py  Запускает карту в браузере, извлекая почтовый адрес # из командной строки или буфера обмена. import webbrowser, sys if len(sys.argv) > 1: # Получение почтовоrо адреса из командной строки. address = , '.join(sys.argv[1:]) # TODO: Получить почтовый адрес из буфера обмена. Первая строка сценария  "мarическая" (начинается символами # ! ). Да- лее мы импортируем модули webbrowser (необходим для запуска сценария) и sys (необходим для чтения возможных apryMeHToB командной строки). Впеременной sys. argv хранится список, включающий имя файла проrpам- мы и apryмeHTЫ командной строки. Если этот список содержит не только имя файла проrpаммы, то вызов len (sys. argv) возпратит значение, боль- шее 1, указывающее на то, что в командной строке действительно предо- ставлены арryменты. 
302 rлава 11 Обычно apryMeHTbI командной строки разделяются пробелами, но в дан- ном случае мы желаем интерпретировать все apryMeHTbI как одну строку. Поскольку sys . argv  это СПИСОК строк, ero можно передать методу j oin ( ) , который возвратит результат в виде одной строки. Имя проrраммы в этой строке для нас не представляет интереса, поэтому мы передаем методу j oin () не весь список sys. argv, а ero срез sys. argv [1: ] , тем самым исклю- чая ненужный нам элемент списка. Результирующая строка сохраняется в переменной address. Если для запуска проrраммы использовать команду Taкoro вида: mapit 870 Va1encia St, San Francisco, СА 94110 то впеременной sys. argv будет содержаться следующий список: ['maplt.py', 1870', 'Valencia', 'St, " 'San', 'Francisco, , 'СА', '94110'] в то время как 8 переменной addre s s будет содержаться строка '87 О Valencia St, San Francisco, СА 94110'. Шаr 3. 06работка 'Оllер...оro 6уфера 06.."а . ,ап"к 6рорера Добавьте в проrpамму код, выделенный ниже полужирным шрифтом. #! python3 # maplt.py  Запускает карту в браузере, извлекая почтовый адрес # из командной строки или буфера обмена. import webbrowser, sys, pyperc1ip if len(sys.argv) > 1: # Получение почтовоrо адреса из командной строки. address = I '.join(sys.argv[l:]) 8188: * Ilo.nyч8ние ПОЧ'l'О801'O a,qpeca на буфера обмена. addr888 = pyperclip.pa8t8() w8ЬЬrOW88r.open('httpа://www.gоog18.oam/шар8/р1аое/' + addr8aa) Если apryMeHTbI командной строки не предоставлены, проrpамма пред- полаrает, что адрес хранится в буфере обмена. Мы можем получить содер- жимое буфера обмена с помощью функции pyperclip. pas te () и сохранить ero в переменной address. Наконец, для запуска браузера с URL-адресом Google Maps вызывается функция webbrowser. open () . Несмотря на то что некоторые из проrрамм, которые вы напишете, сэ- кономят вам MHoro часов, выполняя вместо вас трудоемкие задачи, не стоит 
Автоматический сбор данных вИнтернете 303 недооценивать nporpaMMbI, позволяющие сэкономить хотя бы несколько секунд вашеro времени каждый раз, Korдa вы выполняете даже такие np<r стые задачи, как отображение интересующеl'О вас места на карте Google. В табл. 11.1 сравниваются шarи, которые приходится выполнять для отоб- ражения карты с помощью и без помощи nporpaMMbl mllp/t.py. Та6nмца 11.1. Попучеиие карты местности с помощыо и без помощи nporpoммы map't.py Попучение KQpnoI8PY'lflYlO 8ЫД8J1ение адреса Копирование адреса Открытие браузера Переход по адресу httр://шарs .google.com/ Щ8J1'40К в попе Д/lII ввода адреса Вставка адреса Нажатие клавиши <Enter> ИCIКNIltlO8ClНие nPOrPaММIOI mapl,.py Выдепение адреса Копирование адреса Запуск проrpаммы шар!t . ру Вы сами можете оценить, насколько автоматизация этой задачи позв<r ляет СЭКОIЮМИТЬ ваши усилия И время. Иllе" OrHO,,,re.tHO '0111""". """.о,,,.,,,.,х "ро',о_ Коль скоро известен нужный URLaдpec, модуль webbrowser избавит от необходимости самостоятельно открывать браузер и переходить на нуж ный веб-сайт. Вы можете встроить эту функциональность в друrие проrрам мы для решения следующих задач: . открытия всех ссылок, Ilриведенных на странице, в отдельных вклад ках браузера; . открытия браузера на странице с проrнозом поroДЫIlО вашему реrиону; . открытия нескольких сайтов социальных сетей, которые вы реryляр но посещаете. Эаrруэка файяов И3 Интернета с помощыо модупя Requests Модуль Requests упрощает заrрузку файлов из Интернета, позволяя не задумываться о таких сложных вопросах, как ошибки сети, проблемы noд ключения и сжатие данных. Этот модуль не поставляется вместе с Python и нуждается в предварительной установке. Выполните в командной строке команду pip in.tall request.. (Подробное описание процесса установки модулей, разработанных третьими сторонами, ПРИ8едено в приложении А.) 
304 rлава 11 Необходимость в написании модуля Requests была продиктована тем обстоятельством, что модуль Python urllib2 СJIИШКОМ сложен в использова нии. Фактически вам следует наrлухо вымарать маркером весь этот абзац. Забудьте вообще о том, что я коrдалибо упоминал о модуле urllib2. Е(ли вам нужно заrрузить информацию из Интерllета, используйте для этоro ис ключителыю модуль Requests. Вашим следующим шarом должно быть выполнение простоrо теста, КО- торый позволит убедитьс.я в корректности установки модуля Requests: »> import requests Оп:утствие (:ообщений об ошибке будет свидетельствовать о том, что установка модуля Requests была успешной. 311rpY3KII ..6."р"..,," п"'рв..том фУНК".. requests . get (J Функция requests. get () принимает строку с URLaдpecoM, по которо- му должна осуществляться зarрузка. Вызвав функцию type () для значения, возвращеННОl'О вызовом requests .get (), вы увидите, что этот вызов возвра щает объект Response, в котором содержится ответ с.ервера на ваш запрос. Мы еще поrоворим об объекте Response, а пока что введите в интерактив ной оболочке следующие команды, предварительно убедившись в том, что компьютер подключен к Интернету. »> iщport requests О>>> res ... requests.get( 'http://www.gutenberg.org/  cache/вpub/1112/pg1112 . txt' ) »> type (res) <class 'requests.models.Response'> О>>> res.statuscode -- requests.codes.ok True »> len (res . text) 178981 »> print(res.text[:250]) The project Gutenberg EBook of Romeo and Juliet, Ьу William Shakespeare. This eBook is for the use of апуопе anywhere at по cost and with almost по restrictions whatsoever. Уои тау сору it, give it away or reuse it under the terms of the proje По указанному URLpccy находится веб-странип.а, содержащая полный текст lIьссы"Ромео и Джульстта", который предоставляется бесплатно в рамках IIроскта "Iyrенберr" О. Проверить успеШНОСТI. выloлненияя запр<><:а можно с помощью атрибyrа sta tus  code объекта Response. 
Автоматический сбор данных вИнтернете 305 Значение requests. codes. ok этоrо атрибута rоворит о том, что запрос был успешно выполнен О. (Кстати, в протоколе HТfP успешному запросу ("ОК") соответствует код (остояния 200.) Возможно, вам уже приходилось сталкиваться с кодом состояния 404 ("Not Found"  "Не найдено"). В слу чае успешноrо запроса заrpуженная страница сохраняется в виде строки в переменной text объекта Response. В этой переменной хранится полный текст пьесы в ВИДе одной длинной строки; результат вызова len (res. text) rоворит о том, что эта СТрОКа содержит более 178000 символов. Наконец, вызов print (res. text [ : 250] ) отображает лишь первые 250 символов. Про.еРКII ОШllбок Как вам уже известно, объект Response имеет атрибут statuscode, cpaв нение KOToporo со значением requests. codes. ok позволяет (:удить о том, была ли заrрузка успешной. Однако для такой проверки существует более простой способ, который состоит в том, чтобы вызвать метод raise  for. status () для объекта Response. Данный метод возбуждает исключение, если в процессе заrрузки файла произошла ошибка, и не совершает никаких действий в случае успешной заrрузки. Введите в интерактивной оболочке следующие команды. »> r8S = r8quеsts.gеt('httр://inventwithруthоn.сош/  Н.С)'Щ.С.У"" C!l'p8НJllIfa') »> r8s.raise fortatus() Traceback (most recent call 1ast): File "<руэЬеll#13В>", line 1, in <modu1e> res.raise for status() File "С:\РуthопЗ4\liЬ\sitерасkаgеs\rеquеsts\mоdеls.ру", line 773, in raise for status raiseHTTPError(httperrormsg, response=self) requests.exceptions.HTTPError: 404 Client Error: Not Found Метод raise  for  status ()  ЭТО эффективный СIIОСоб rарантированной остановки проrpаммы в случае неудачной зarpузки. Конечно же, это непл хо, ведЬ вы сами заинтересованы в том, чтобы при возникновении неоЖИ данных ошибок выполнение проrpаммы было llрекращено. Если же Hey дачная заrрузка не является критическим препятствием для ДaJIьнейшеrо выполнения IIporpaMMbI, можно обернуть строку кода raiseforstatus () конструкцией try/except, чтобы обработать эту ошибку, не допуская ава. рийной остановки проrpаммы. import requests res : requests.get('http://inventwithpython.com/  неС.YIЦествующая страница' ) try: 
306 rлаВQ 11 res.raiseforstatus() except Exception as ехс: рriпt('Возникла проблема: %s' % (ехс)) в результате данноrо вызова метода raiseforstatus () проrрамма вы- ведет следующую информацию: Возникла проблема: 404 Client Error: Not Found Целесообразно всеrда вызывать метод raisefor status () после функ- ции requests. get () . Это позволяет убедиться в том, что зarрузка действи- тельно была осуществлена, прежде чем предоставить проrрамме возмож- ность ДaJIьнейшеrо выполнения. Сохранение эаrруженных файпов на жестком диске в этом разделе будет рассказано о том, как сохранить веб-страницу в файле на жестком диске с помощью стандартной функции ореп () и ието- да wri te ( ) . Однако есть некоторые нюансы. Прежде Bcero, необходимо открыть файл в режиме двоичной записи, передав функции ореп () строку 'wb' в качестве BToporo apryмeнтa. Даже если страница представляет собой простой текст (как, скажем, заrpуженный ранее текст "Ромео и Джульет- ты"), для поддержки кодировки Unicode необходимо записывать двоичные данные, а не текстовыс. Кодировка Unicode Рассмотрение кодировки Unicode выходит за рамки донной книrи. Для получения более подробной информации по зтому вопросу можете воспользоваться следую- щими ресурсами вИнтернете: . )ое' оп Software: The Abso/ute M;n;mum Every Software Developer Abso/utely, Pos;- tive/y Must Kпow Аbou, Un;code and Character Sets (No Excusesl): http://www . joelonsoftware.cam/articles/Unicode.html . Pragmatic Unicode: http://nedЬatchelder . com/text/unipain. ьш Записать веб-страницу в файл можно с помощью цикла for по возвращае- мому значению метода i ter  content () объекта Response. »> iщроrt requests »> res = r8quеsts.get('http://1f1Пf.gutenberg.оrg/  cache/epub/1112/pg1112 . txt' ) >>> res.raiseforstatus () 
Автоматический сбор данных вИнтернете 307 > > > playFile = оpen ( 'RoDLeoAndJuliet. txt', '.Ь I ) »> for сЬunk in re8.itercontent(lOOOOO) : playFile.write(chunk) 100000 78983 »> playFile.close() Метод itercontent () возвращает порции содержимоrо на каждой CTa дии цикла. Каждая порция данных  ЭТО данные байтовоrо типа, и оам предоставляется возможность указать, сколько байтов должна содержать каждая порция. В общем случае одна сотня тысяч байтов  ЭТО вполне 1I0Д ходящий размер, ПОЭТОМУ мы передаем функции i ter  conten t () значение 100000 в качестве арryмепта. Теперь в рабочем каталоrе существует файл RoтeoA пdjuliet. txt. Обрати те внимание на то, что ЭТО имя, под которым файл сохраняется на жест ком диске, отличается от имени файла, используемоrо вебстраницей (pgll12.txt). Заrрузкой содержимоro веб-страниц управляет модуль Requests. Как только страница заrружена, она становится простыми данными в Ba шей проrрамме. Даже если соединение с Интернетом нарушится после за rрузки страницы, ее содержимое останется в вашем компьютере. Метод wr i te () возвращает количество байтов, записанных в файл. В предыдущем при мере первая порция включала 100000 байт, так что для оставшейся части файла понадобилосьтолько 78983 байта. Ниже приведен перечень отдельных стадий процесса заrрузки и coxpa нения файла. 1. Вызов функции requests. get () для заrрузки файла. 2. Вызов функции ореп () С apryмeHToM 'wb' для создания HOBOro файла в режиме записи двоичных данных. 3. Цикл по возвращаемому значению метода i t е r  со n ten t () объекта Response. 4. Вызов метода write () на каждой итерации для записи содержимоrо файла. 5. Вызов метода close () для закрытия файла. Это все, что вам следует знать о модуле requests! Возможно, использ вание цикла for и метода itercontent () покажется вам несколько более сложным по сравнению с технолоrией, основанной на применении цепоч ки вызовов ореп () /wri te () /close () , которую мы использовали для записи теКСТовых файлов. Однако такой подход rарантирует, что модуль Requests не будет потреблять слишком MHoro памяти даже в случае заrpузки крупных файлов. Более подробную информацию относительно дрyrих возможностей модуля Requests можно найти на сайте http://requests .readthedocs .org/. 
308 rлава 11 HTML Прежде чем приступить к анализу веб-страниц, следует познакомиться с основами HTMI.. Вы также узнаете о том, как воспользоваться мощными средствами разработки, предоставляемыми браузером, которые значитель- но упростят процесс веб-скрапинrа. Ре,ур,,,, дл. ",учен". НТМ! Язык rипертекстовой разметки (Hypertext Markup Lапguаgе  HTML)  это формат, используемый для записи веб--страниц. В этой rлаВе предпо лаrается, что вы уже владеете некоторыми навыками работы с HTMI.., но если вы нуждаетесь в практическом руководстве для начинающих, можно порекомендовать следующие сайты: . http://htmldog.com/guides/html/beginner/ . http://www.codecademy.com/tracks/web/ . https://developer.mozilla.org/enUS/learn/html/ IРllтк"е tвeдeH". по ЮМL Если до этоrо вы сталкивались с НТМI..докумснтами лишь эпизоди чески, то вам будет полезно ознакомиться с приведснным ниже кратким обзором основ HTML. НТМLайл  это обычный текстовый файл с pac ширением .html. Текст в таких файлах окружается mezaмu (дескрипторами). Каждый Ter представляет собой слово, заключеННОе в yrловые скобки. Теrи сообщают браузеру, как следует форматировать веб-страllИЦУ. Начальный и конеЧный (открывающий и закрывающий) теrи MOryт окружать некоторый текст, вместе с которым они образуют э.лe.мeum. Текст (или в'Нутрениее НТМLсодержu.м,ое)  это содержимое, заключен- ное между открывающим и закрывающим теrами. Например, следующий НТМLдокумент отображает в браузере фразу Здравствуй, мир!, в которой слово Здравствуй выделено полужирным шрифтом: <strопg>Здравствуй</strопg>, мир! Вид этой НТМL-разметки в браузере показан на рис. 11.1. Открывающий Ter <strong> указывает на то, что заключенный между этими теrами текст должен отображаться .полужирным шрифтом. За- крывающий Ter </ strong> обозначает, rде заканчивается полужирный текст. В HTML существует множество TerOB. Некоторые из них имеют допол- нительные свойства в виде атрибутов, записываемых в yrловых скобках. Например, Ter <а> содержит тек(:т, создающий rиперссылку. URL-aдpec, на 
Автоматический сбор данных вИнтернете 389 который с(:ылается этот текст, определяется атрибутом href. Вот Соответ- ствующий пример: <а hrеf="httр://iпvепtwithруthоп.соm">Книrи по Python</a>, бесплатно предоставляемые на сайте Эла. r'i i,..,dr-:':"!Jr! со .' f;;@;I/.;5:.;Pr',j@ctS/PY/I"d@x.I':\' :J.::Jравств)'й. шр! ц ч,QI:iI: Рис. J '. '. Фраза ЗдРавствуй, мир!, визуализированная в браузере Вид этой НТМL-разметки, визуализированной в браузере, показан на рис. 11.2. i. t ; In:lt. "ФJ";'" Х с: fIIe;ij/;fPI'C'j@ct>ipy(i"d@x,llt",1 (.\ vt t1 r3  k ИllfП по P y tllOI1. беСП.1атно пре.:Iоста8.1яемые на сшlrе 'Эла. Рис. 11.2. Ссылка, визуализированная в браУЗ8ре Некоторые элементы имеют атрибут id, который используется в ка- честве уникалыюrо идентификатора элемента на странице. В своих про- I'paMMax вам придется часто выполнят!> поиск элемента по ero атрибyry id, поэтому определение ДаННоrо атрибута с использованием инструментов разработчика, предоставляемых браузером, является одной из самых рас- пространенных задач при написании I1pOrpaMM для веб-скраllинrа. Про,.отр "'XOIIHOrO HTIIL-KOIIO "'."РОН",,'" Так или иначе вам понадобится просматривать исходный НТМL-код веб-страниц, с которыми будут работать ваши проrраммы. Чтобы это сде- лать, щелкните правой кнопкой мыши (или выполните щелчок мышью 
310 rлаВQ 11 при нажатой клавише <Ctrl> в OS Х) на любой всб-страпиП,е в своем брау зере и выберите в (у)'крывшемся контекстном меню пупкт Просмотреть код (View Source) или Просмотр кода страницы (View page source), как показано на рис. 11.3. Таким образом вы сможете просмотреть текст, который в дей ствительности получает ваш браузер. Браузеру известно, как отображать. или, как roворят. визуа.лuзuровamъ. веб-страницу па основе I1ТМI4-кода. Вestullers I . :., . ,...... 1tIeМak&r'sGuldetolhe ., ",. lOmbIe Apocalypse " '0" }_ ,"olr.', .'..,.0....",. ...э;. j ['PYTHONi В , 08,.'D,! 1 :. >.,}. ,....... ",.- pe::-. 3:': ::.оч::.....IТI::I .3"" "d !"""i!'...1't'_ ;' ' с ) , , '..у,; " .' ) "_ : .  , :'lt;.. ;):''.I.:,"'::T ..;.; . . - 1] t.  "".t ! 1"1 -;I r . (. r.; SUr,.., р,..,. "'- if. ... -:c,_",.:httpo.' , ' )t "l.}::':: с -.\,}-,.; ,'i\'JV,l)оst",'СI,,',:оп'1 Щ " 13t g CIЗ  '," 0.1>.1 , ," --::. t I : -:.::: ,,.-tt ,"-" ,. ; t!..,:. ",'::'. i:)("'g/ 1 ):(.I / .;<1'1-t::1" ... (": F---'," er"'" "' '. ",r. ",,.. >..; .:.;.;i;':: E:rl ;J ,т..:'  ;1:!\. '. -'. 1 \ ::;."C.r::t17_.TYDe" '::'',:еп1;::::I't€:уt. J,tтl.1 ':').:1P'5t=,.tf .jI/. .' t 1: 1.; '.1.10 Stdl'ch Press' ,'т:: t::'€:> '.т..::.Э" ,.,;_,.. ..... :..i ..-:=.f'(.:.п:I1--=hТуре" ..(.:tе(;t='I.tе-хt/"tml; Сl(.:н"':::еt::-.tf.i3",:> <::,. "l--: :.. ,:""\'t-=-,-.:t/...1\/d>:,"':"pt"j' I!<! [0::'-:-::'[ trv{if ('indow,CloudFlare) {var CloudFlare= [ {"e,'bose: ", р: 1457666483, Ьус: 0, c.,oJlid: .. С f" ,bag2: 1, mirage2: 0, oracle: е. patll s :{cloudflare;";cdn сgi/пехр/dоkЗV=161ЗаЗа185/.,.tоk:.d7е3а5а139184g1еа8cdb6983c3069..,peto k:Кblf0ЬfЬd31dс8lа8ЬЗ2dfБЬ9еI8аI140328а6dБ-1458141163 18130", Zane: "nostarch . со"," ,I'ocket: "О., apps: {"gakey": {.ua.: .UA 5e17625 Рис. 11.3. Просмотр исходноrо кода ве6страницы я настоятельно рскоменяую вам ИССJlедовать 1 П'П.код понравИВПlихся сайтов. ЕсJlИ нри просмотрс НТМLкода вам не все будет понятно, не orop- чайтесь. Чтобы писать простые проrpаммы для ве{Н:крапинrа. не нужно быть искусным мастером HTML  в конце концов, речь не идет о создании 
Автоматический сбор данных вИнтернете 311 собственноrо веб-сайта. Вам достаточно лишь знать, как орraнизовать сбор данных с существующих сайтов. О,кр"""е ОКНII "н"ру.енro. рllЗрll60"'"КII . 6Рllузере Кроме исходноrо кода веб--страницы, можно просматривать HTML- разметку страницы с помощью инструментов разработки, предоставляе- мых браузером. В браузерах Chrome и Iпtеrпеt Explorer для Windows ин- струменты разработчика уже установлены, и для начала работы с ними вам остается лишь нажать клавишу <F12> (рис. 11.4). Повторное нажатие клавиши <F12> приводит к закрытию окна инструментов разработчика. В браузере Chrome вам предоставляется дополнительная возможность д ступа к инструментам разработчика пyrем выбора пунктов меню Настройка и управление Google Chrome (кнопка в конце адресной строки)t::>Дополнительные инструментыt::>Инструменты разработчика. В OS Х для этоrо следует нажать ком- бинацию клавиш <X+Option+I>. -;;  :.;:: ar.;" "oe +- с (J ",." . '". !...: .!..;< ,lhww.nos!arch.::om С, ' " ....  :-'I-I":I' ! I .. ... I ___,О ,. а, . I '" I ."...,. ...... ".,..,..  .... ..:... --.; зr-j More u!inV;J .:r.,""':.I1'1 ;:!:-"'.1!1!: 1..'. '1181  Y11IO" '......1:.'.... Pyll10n Cr..h Cour.. .5 3 ::JUI( ')C.MI1Sn'S& Q!.'JE: {О pf.').;!:"al"1f"lI".!J i P).tfIOn. I ш,сЬ, а""..', 'WhOdul':;t  trto ::i1j.lb! э;I-i!'. in'rod"tln ,h. :00'.." ...: . (" . ;; t$!'\I'J:t startrl 31,:";З . .<.''" а"::iМР(lI1.э!'1t::i9ltjj - :',';7.." 'S'riJсtlrtи t!'НЦ1h Ule rl\etIM о' "" !ТIj;orj "о:...&! I .... Арот  ""'" -, " ') >« ",,/f!;  .. С. r 1/ l' . '1'; "'"..:t\,< I'' .:: . ":::-:. ,., :I'IH . '.,< \ l'  .' .:. х . '9 .. 'v . .::. :;; rI :., .;.1. .. ,.:' .,.; \lo:t...,;:.;1 '>_1 Й ..; ":,-::: :r,:,:,a:.:':- .Z ....:!"'!1 .:; ..E-.o! -  .C ..;...-:- ..:. .:' ..;:  ,;",\..' :''' E-S... .!l.e-.,:;' .3  B -- !I<! '::'';'  . .-;..: .>: r,:,.  : :;., , (,E ca "Щ ",'d,....;57 ..i..6 .- tB 0:-''';''. : .=>#. ''''', , -: ':: ,,> lE "":' i :. .' <.. , ....):.: :' : .... Р" '.- '9 .!'\ ,jt' ....: lB -:=.' :j е - <5;. ,у,  ."" "'" ;ш. =::.T;;;  j.(i:-t';;, J:: .:. r 6:i.:';-;' "7..41\:' ;. '.:' _ ':.:I ::i !=Iп:::.h.:' 6:. 5 : :y)r...1C :.п!E;tL'::"..1J_ .31;:. ; ..:..:,:). 2.::  : Рис. 11.4. Окно инструментов разработчика в браузере Chrome в браузере Mozi11a Firefox для открытия окна Инспектор можно нажать комбинацию клавиш <Ctrl+Shift+C> в Windows и Lillux или комбинацию клавиш <X+Option+C> в OS Х. в обоих случаях окна инструментов разра- ботчика будyr выrлядеть почти так же, как в Сhroше. В браузсрс Safari следует открыть окно Preferences (Установки) и YCTaн вить флажок Show Develop menu in the menu bar (ПОКа3ывать меню инструментов 
312 rлава 11 разработчика в строке меню) панели Advanced (Дополнительно). После это- 1"0 можно вызвать окно инструментов разработчика, нажав комбинацию клавиш <X+Option+I>. Активизировав или установив инструменты разработчика в своем брау- зере, можете щелкнyrь правой кнопкой мыши в любой части ве6-страницы и выбрать в открывшемся контекстном меню пункт Inspect Element (Исследо- вать элемент), чтобы выделить НТМL-размстку, ответственную за данную часть страницы. Такая возможность придется вам кстати, коrда вы присту- пите к синтаксическому анализу (парсинry) НТМL-документов в своих про- rраммах для автоматическоrо сбора данных вИнтернете. Не пытайтесь испопЬЗ0вать реryпярные выражения дnя НТМL-парсинrа Казалось бы, что может быть ффективнее применения реryлярных выражений, если речь идет о нахождении определенных HTML-лемеНТ08 в строке. Однако я катеrорический противник TaKoro подхода. HTML-РОЗМ8тка может осуществляться множеством самыХ раэ.nичных способов, Кaжд1Jму из которых будет cootbetCT-О8ать действительный НТМL-документ, и любые попытки охватить все возможные 8ариан ты с помощью реryЛЯрНIIIХ 8ыражений потребуют больwих усилий И будут чреsaты ошибками. Вероятность появления оwибок уменьwится, если использовать модуль Beautiful Soup, специOJ'lЬНО разработанный дnя НТМL-парсинrа. Дополнительную арryментацию против испопьзования реryлярных выражений АЛЯ парсинrа НТМL-разметки можио найти по адресу http://stackoverflow. соm/а/1732454/18931б4h "'ПОЛ'308"нне .Hnpy.ell108 р"з,,,60,,,.,,,, RЛ. по.,,,,, иrlIl-.е.енro8 Как только ваша проrpамма заrрузит веб..страницу с помощью модуля Requests, вы будете иметь в своем распоряжении ее НТМL--содержимое в виде одной строки. Ваши дальнейшие действия заключаются в том, чтобы определить, какая часть НТМL--содержимоrо соответствует объекту вашеrо интереса. И здесь вам На IJОМОЩЬ придyr инструменты разработчика, предостав- ляемые браузером. ПреДllОЛОЖИМ, 8Ы хотите написать проrpамму, осущест- вляющую сбор данных о проrнозах поroды на сайте http://weather.gov/. Прежде чем браться за написание кода, проведите небольшой эксперимент. Если вы посетите этот сайт и выполните поиск 110 ZIP-KOДY (почтовому ин- дексу) 94105, то сайт откроется на странице, представляющей информацию о поrоде для этой местности. 
Автоматический сбор данных вИнтернете 313 Что если ва(: интересует сбор температурных данных по местности с данным ZIР-кодом? Щелкните правой кнопкой мыши в соответствующем месте страницы (или щелкните мышью при нажатой клавише <Сопtrоl>, если работаете в 05 Х) и выберите в открывшемся контекстном меню пункт Inspect Element (Исследовать элемент). Это приведет к открытию окна инструментов разработчика, в котором будет отображаться НТМL-код, от- ветственный за создание данной части страницы. На рис. 11.5 показано окно инструментов разработчика, открытое на НТМL-коде, отображающем температуру. ,!y; ] . 7-0ау Forecast 10" . .. 'I'f% .. с ,forecos1.weat'l."90V -:.;..;" .(" ;,'-;' "'!.- : ';':,.::.:: \:. .:'J.:. о fI'!!! о,. -,7J 5, Ф. NATIONAL WEATHER SERVICE НО"" FOII:CAST РАЯ WБOIНtR v.l:АТ"'А SAfE1Y IЧFORМАТIOI4 CEIm:R ,.,ws 5tAROI AIЮUТ Lоиtfortlеl.tЬ, Clti. 61.or%llII.:o" ао Un...oonaЫy COId ........."'...1. .... с........ ond ...t8m U,5, "''''Utlh WodnиdlУ А pow..... cold 'ont has ulht8td in ......нonebly cdd l,m",.8Iu,fS 10 ch. С.nI,aI and ..,t,fII U S for.iiП)o thI& w"" F'rHllIIg t8mptr1tures \\О. drJt into tht Онр SOUIh IIКI tc tht f.tId.A1IInIic thtouglt ':.:tdPnd8y п-oming 1'bis t\iI tt drmaging to иn8lt1\. 'leQ8lallOI'I '........OUI Frost Мi80nи вnd 'rtN. v".mingI "е in tlect   Current Condltlons "'-1IWOI I 59°F ....."'" 65.. \'/ind SOn<I ,!Д ........... IIД .,.,.,.... rFt1I.C: ......... IIД . ;...,.,.,c.:JO*",_ S'IN FRAНCI5CO DOWNTOWN (SfOC1) LaI э., !?'О56т Lon 122.ue.s=w [.1..... 1508 11- ip.",:?,:'.<!.-':"':_ :'::.:"::'E :::'.. 47;:J .:::.I ''''iI HI;r.li I.:,::;; Q. EIe'ncnts NetI'lo'Ork 5otlfc Tlffldlnl Profills Resourlto Au6ib ConнIe: " ,"=. о. . !) t..,,."tI!X\" e....s'. pt. ._. .......:s- pt. ..:JIQ't t.. tl!.'\'.II'.II:'ipt. .........!&.-:йiDt..t:..i "а11 ir.'...$".jpt, .,,., ,:_. 1".QI!!S t . l - o( 111'11:1 !:cl."t о", . !t;'le"cllsPll1ilnOMJ' . . а1' : .,  -'::11 - \Jll -= '1!C1t .se.:tJ.cn cuent -.:cnаJ.t1cns' '.' id1\- '., .'.,1..' .11 __:::  -'".. ..e;8":':;'CC:.:::;> -:'\J'_,,-,'cr.aitll'; ,. ..(11,' .;al.'dj'.-\J1.l ,;u'.eM.(..Ii=.1t1on, . ...,J". <:1,-'"ne-thid.1.lt.  ., <:I,.. 'J а: " 011. .hI1"'- . . ,1l<.' :, ..:o.-e'"c\'-:.....,,,t. .r..., . j;:' .. 5t)1es' CcmpuIIId E1mt Lnt8*! -, .l"ent.stle { :. :.; . j .., '" -_ ... ",т. -,1" .s; : .(. : .ItU .tnt.c:tadltions Р  I"'I;;' -'_I':' ",(. I.р.....". ,:41';' "a.l..' .. :11'. 'l"H" t;nt.thi"..,;1.lt .':-11,. ".:11'. .:].H..anl!.th1-d.t.st......:ri,. . . !J..: :li'. :11',,-' .c'''.st 'SoI!.:,..to.. .< '111'. . hlmI bod)' dlv.иnlf1 Clt\o.ct"ttr-tonttnt сР.dtf".'ull.WfJInt-с;опcИthИlS .Io;.M,4hi1d4Irst dlv.dIY.tI'" .rl'ec'5t"U.,,,,t. c.:';(t..:-.!t.. :';' 11 : ::: ;;;. Cid; I:: :; -.e4 .. .1I'Y"е(.:.t-..:....I"'mt- .:.1l 1". f Рис. 11.5. Инспектирование элемента, в котором храннrСII код, ответственный за отображение температуры, с помощью инструментов разработчика Окно инструментов разработчика позволяет увидеть, что за отображе- ние температуры отвечает следующий код: <р class="myforecastcurrent lrg">59°F</p>. Это именно то, что вы искали! Нетрудно заметить, что ин. формация о температуре содержится в элементе <р>, которому присвоен класс myforecastcurrentlrg. Теперь, коrда вы уже знаете, что именно He обходимо искать, модуль BeautifulSoup поможет вам найти искомый эле- мент в строке. 
314 rлава 11 Синтаксическиii анапиз HTML с помощыо Beautiful Soup Beautiful Soup  это модуль, предназначенный для извлечения инфор- мации из НТМL-cтраницы (причем roраздо более приспособленный для этой цели, чем реryлярные выражения). Имя модуля Beautiful Soup  Ьв4 (от "Beautiful Soup, версия 4"). Для установки этоro модуля необходимо BЫ ПОЛНИть команду pip install beautifulsoup4 в командной строке. (Более подробные инструкции относительно установки модулей, разработанных третьими сторонами, приведены в приложении А.) Несмотря на то что при установке этоrо модуля используется имя beautifulsoup4, он импортируе ся с помощью инструкции import bs4. Приведенные в этой rлаве примеры использования модуля Beautiful Soup охватывают синтаксический анализ (т.е. исследование и идентифика цию отдельных элементов) НТМLайла, хранящеrося на Жестком диске. Откройте новое окно в файловом рсдакторе IDLE, введите приведенный ниже текст и сохраните ero в файле exaтple. htтl. Этот файл также достуйен на сайте http://nostarch . com/automatestuff/. <! Это файл примера example.html. > <html><hеаd><titlе>Заrоловок вебсайта</titlе></hеаd> <body> <р>Заrрузите мои книrи по <strong>Python</strong> на моем сайте <а href=''http://inventwithpython.com''></a>.</p> <р Сlаss="slоgап">Простой подход к изучению Python!</p> <р>Автор <span id="аuthоr">Эл Свейrарт</sрап></р> < /body></html > Как можно видеть, даже простой НТМLфайл включает MHOI'O ра:шич ных TeroB и атрибyrов, количество которых быстро увеличивается при переходе к сложным веб.сайтам. К счастью, модуль Beautiful Soup значи тельно упрощает работу с НТМL-разметкой. COIAIIHlle 06.еКТll Вeauti:f'ulSoup НII о,но.е НТМl Вам потребуется вызвать функцию bs4. BeautifulSoup () , передав ей строку, которая содержит НТМLкод, подлежащий анализу. Функция Ьв4. BeautifulSoup () возвращает объект BeautifulSoup. Введите в ИlIтерактив ной оболочке следующие Команды, предварительно убедившись в том, что ваш компьютер подключен к Интернету. »> import reque8t8, Ь84 »> re8 = reque8t8.qet('http://no8tarch.aoa') »> re8. rai88 for  8и tU8 () 
Автоматический сбор AaHHblX вИнтернете 315 »> noStarchSoup = bs4.ВeautifulSoup(res.text) »> type(noStarchSoup) <class 'bs4.BeautifulSoup'> Этот код сначала заrружает основную страницу веб--сайта No Starch Press с помощью вызова requests. get () , а затем передает атрибyr text oт вета функции Ьв4 . BeautifulSoup () . Возвращенный этой функцией объект BeautifulSoup сохраняется в переменной noStarchSoup. Вы также можете заrрузить НТМL-файл со cBoero жесткоrо диска, пере- дав функции bs4 . BeautifulSoup () объект File. Введите в интерактивной оболочке следующие команды (предварительно убедившись в том, что в рабочем каталоrе находится файл ехатрю. htтl). >>> 8X8J11.pleFile = open ( 'exaaple. htll1l' ) »> ехашрlеSоup = Ьs4.ВеаutifulSоup(ехашрlеFilе) >>> type(exaapleSoup) <class 'bs4.BeautifulSoup'> Получив объект BeautifulSoup, вы можете использовать ero Мt.'1'оды для поиска отдельных частей HTML-докумеНl'а. ПОН'К Jле_ен,,, , ПО_ОЩ61О _етод" select () Для получения элемента веб-страницы из объекта BeautifulSoup можно вызвать метод select () и передать ему строку CSS-cелектора, выбирающеrо искомый элемент. Селекторы напоминают реryлярные выражения: в обоих случаях задается шаблон поиска, но в случае селекторов поиск осуществля- ется в НТМL-страницах, а не в обычных текстовых строках. Полное обсуждение синтаксиса СSS-селекторов выходит за рамки дан- ной книrи (хорошее практическое руководство по использованию селекто- ров вы найдете на сайте http://www.nostarch.com/automatestuffresources). поэтому ниже мы оrраничимся лишь кратким их обзором. Примеры наи- более часто используемых селекторов нриведены в табл. 11.2. Та6nица 11.2. Примеры CSS-С8пекторов Сen8кrор,передаеаемыА методу select () Соответствие 'div' '#author' , . notice' 'div span' 'div > span' Все ал.менты <div> Элем.нт, атрнбут id KOToporo им.ет значение author Все алементы, атрибут class которых имеет значение notice Вс. ал.менты <sраП>,вnож.нны.ввnементы <di v> Вс. ал.м.нты <span>, вложенны. непосредственно в JЛементы <di v >, без каких бы то ни было ал.ментов между ними 
316 rлава 11 (}кон/ча1luе там.. 11.2 С.,.8КТОР, передаваемый методу se18ct () 'input[пame] , СоответстВИ8 'input[type="button"] , Все элементы <input>, имеlOщие атрибут пате, независимо от era значеНИI Все элементы <input>, имеlOщие атрибут type со значением button Ра.зличные селекторы MOryr с.очетаться, обра:JУЯ сложные шаблоны. На- пример, вызову soup. select ( 'р #author') будет соответствовать любой эл мент, атрибут id Koтoporo имеет значение author, при условии, что этот элемент вложен в элемент <р>. Метод select () возвращает список объектов Tag, представляющих в Beautiful Soup НТМL-элементы. Этот список будет представлять по одному объекту Tag для каждоrо найденноrо совпадения в НТМL-коде объекта BeautifulSoup. Объекты Tag можно передавать функ ции str () для отображения TeroB HTML, которые они представляют. Значения типа Tag имеют атрибyr attrs, преДСl'авляющий все HTM1.- атрибуты данноrо Tera в виде словаря. Используя предыдущий файл exaтple. html, введите в интерактивной оболочке следующие команды. >>> import Ь.4 »> 8xamp18Fi18 - open('examp18.html') »> examp18Soup . Ь.4.ВеаutifulS0uр(ехащрleFi18.read(» > > > 818111. = exup18S0Up. .818ct ( , 'author' ) >>> type (818111.) <class 'list'> »> 1en(818111.) 1 »> type(818111.[0) <class 'bs4.element.Tag'> »> 818111.[0).q8tT8xt() 'Эл Свейrарт' »> .tr(818111.[0) '<span id="а\lthоr">Эл СверТ'айт</sрап>, »> 818111.[0) .attr. { 'id': 'author' I Этот код извлекает элемент с id="author" из нашеrо примера HTML разметки. Вызов select ( '1tauthor' ) возвращает список всех элементов, удо- влетворяющих данному условию. Мы сохраняем этот (:писок объектов Tag в переменной elerns, и значение len (elems) указывает на то, что в списке име- ется только один такой элемент. Вызов метода getText () для этоrо элемеН- та возвращает содержащийся в нем текст, Т.е. внутреннюю НТМIJРазметку. Текст элемента  это содержимое, заключенное между ero открывающим и закрывающим тсrами. В данном случае это строка' Эл СвейТ'арт I . 
Автоматический сбор данных вИнтернете 317 При передаче функции str () элемента она возвращает строку, включаю- щую открывающий и закрывающий теrи вместе с текстом элемента. Haк нец, переменная attrs предоставляет словарь, содержащий имя атрибyrа, , id 1, и ero значение, 'author'. Также можно извлечь из объекта BeautifulSoup все элементы <р>. Введи- те в интерактивной оболочке следующие команды. »> pElems = examp!eSoup.8e18ct('p') »> 8tr(pElem8[0) '<р>Заrрузите мои книrи по <strong>Python</strong> на сайте <а href=''http://inventwithpython.com''></a>.</p>' >>> рЕ!8II18 [О) . getText О 'Заrрузите мои книrи по python на моем сайте.' »> 8tr(pElem8[1) '<р сlаss="slоgап">Простой подход к изучению Python!</p>' >>> рЕ 181118 [ 1] . getText () 'Простой подход к изучению Python! ' »> 8tr(pE18ll18[2]) '<р>Автор <span id="аuthоr">Эл СвеЙI'арт</sрап></р>' »> рЕ!8II18 [2) . q8tText() 'Автор Эл Свейrарт' На этот раз вызов метода select () предоставляет СIIИ(ОК с тремя сов- падениями, который мы сохраняем в переменной pElems. Последователь- но передавая функции str() элементы pElems[O], pElems(l] и pElems(2], мы отображаем каждый из этих элементов в виде строки, а вызов метода getText () для элементов позволяет отобразить их текстовое содержимое. ПолучеНllе AIIHHWX 113 "'pll6yro. Iле.е",II Метод get () объекта Tag упрощает доступ к значениям атрибyrов соот- ветствующеrо элемента. Метод принимает строку с именем атрибyrа и воз- вращает cro значение. Используя файл exaтple.html, введите в интерактив- ной оболочке следующие команды. »> import Ьв4 >>> 80UP == Ьв4. ВeautifulSoup (open ( 'example. htJD1' » »> 8panElem = 8oup.8elect('span') [О) >>> str (apanEl8ll1) '<span id="аuthоr">Эл СвеЙI'арт</sрап>, »> apanElem. q8t ( , id ' ) 'author' »> врanE!еш. qet ( 'несущес'l'В)'ЩИЙ c') == None True  »> spanElem.attr8 {'id': 'author'} 
318 r лава 11 Здесь мы используем метод select () ДЛЯ нахождения элементов <span> и сохранения первоrо из них в lIеременной spanElem. Передав методу get () имя атрибута, 'id t , мы получаем соответствующее значение, I author'. Проект: кнопка "Мне повезет" поисковика 600g18 Выполнив поиск в Google, я никоrда не начинаю сразу же просматри вать все полученные ссылки ОДНа :Ja дрyroй. Вместо этоrо я щелкаю средней кнопкой мыши (или левой кнопкой мыши при нажатой клавише <Ctrl» на нескольких первых ссылках для открытия каждой из них на новой вкладКе, чтобы просмотреть их позже. Поскольку поисковиком Google я пользуюсь довольно часто, описанный порядок поиска  открытие браузера, поиск по заданному поисковому термину и ПОСJIедующие щелчки средней кнопкой мыши на нескольких ссылках  оказывается достаточно трудоемким. Было бы неплохо, ес.ли бы Я Mor просто ввести поисковый термин в комаlЩIlОЙ строке, а компьютер автоматически открыл бы браузер с первыми несколь- кими результатами поиска, размещенными на отдельных вкладках. Давайте напишем сценарий, реализующий ЭТО пожелание. Вот что должна делать Такая I1porpaмMa: . получать из apryMeHToB командной строки ключевые слова, по кот<)- рым должен быть выполнен поиск; . извлекать страницу с результатами поиска; . размещать каждый результат на отдельной вкладке браузера. Это означает, что ваш код должен выполнять следующие действия: . читать apryMeHTbI командной строки из списка sys. argv; . извлекать страницу с результатами поиска с помощью МОДУЛЯ Req uests; . находить ссылки на каждый результат поиска; . вызывать функцию webbrowser . open () для открытия браузера. Откройте новое окно в файловом редакторе и сохраните ero в файле lucky.py. ШII' ,. Полученне "р'у.енfO' КО.IIНДНОЙ ,Т РОК. . Зllпро, пОНСIlО'ОН СТРllН."'" Прежде чем приступить к написанию кода, необходимо определить URL адрес страницы с результатами поиска. Взrлянув на адресную строку брау зера после выполнения поиска в Google, вы увидите там URIjaдpec вида https: / /www.gооglе.соm/sеаrсh?q=поисковыйтЕрмин.. Модуль Requests мо- жет заrрузить эту страницу, после чеrо вы сможете использовать Beautiful Soup для нахождения ссылок на результаты поиска в HTML. Наконец, вы 
Автоматический сбор данных вИнтернете 319 используете модуль webbrowser для открытия этих ссылок в отдельных вкладках браузера. Введите в созданный файл следующий код. #! руthопЗ # lucky.py  Открывает несколько результатов поиска с помощью # Google. import requests, зуз, webbrowser, Ьз4 рriпt('rуrлим...') * отображается при заrрузке страницы Google res = requests.get('http://google.com/search?q=' + , '.join(sys.argv[l:])) res.raiseforstatus() # ТООО: Извлечь первые несколько найденных ссылок. # ТООО: Открыть отдельную вкладку для каждоrо результата. Поисковые термины будут предостааляться пользователем с помощью apryMeHToB командной строки при запуске проrpаммы. Эти apryмeHTЫ бу дут сохраняться в виде строк в списке зуз. argv. Ш", J. ПОН'К .еех peJY.""'O' А теперь настал черед использовать модуль Beautiful Soup для Извле- чения нескольких первых ссылок на результаты поиска из заrруженноrо НТМLдокумента. Однако как определить, какой селектор следует исполь- зовать для выполнения этой работы? Например, вы не можете просто ото- брать все теrи <а>, поскольку в полученном НТМI_документе будет присyr- ствовать множество ссылок, не представляющих для пас интереса. Вместо ЭТОI'О вы должны инспектировать страницу с результатами поиска с по мощью инструментов разработчика, предоставляемых браузером, чтобы определить (електор, который отберет лишь нужные вам ссылки. Используя для поиска в Google строку Beautiful SQUp в качестве поисково- 1"0 термина, вы можете открыть окно инструментов разработчика и иссле- довать некоторые из элементов, содержащих ссылки. Эти элементы MOryr выrлядеть невероятно устрашающе, например так. <а href="/url?sa=t&ampirct=j&amp;q=&amp;esrc=s&amp;source=web&amp; :d=1&amp;cad=rja&amp;uact=B&amp;ved=OCCgQFjAA&amp;url=httр%ЗА%2F .:2Fwww.сrummу.соm%2Fsоftwаrе%2FБеаutifulSоuр%2F&аmр;еi= :HBVUXDD9КVyAShmYDwCw&amp;usg=AFQjCNHAxwplurFOBqg5cehWQEVKiTuLQ&a :!\p;sig2=sdZu6WVIBIVSDrwhtworМA" onmousedown="return rwt(this,' ',' " ", '1', 'АFQjСNНАхwрlurFОБqg5сеhWQЕVКiТuLQ', 'sdZuБWVIВIVSDrwhtwоrМА', 'JCCgQFjAA',' ',' ',event)" datahref=..http://www.crummy.com/software/ EeautifulSoup/"><em>BeautifulSoup</em>: We called him Tortoise :есаизе he taught из.</а> 
320 rnaBQ 11 Пусть вас не смущает вид элемента. Вам достаточно опредслить лишь шаблон, который яwшется общим для всех ссылок. Однако в этом :лементе <а> нет ничеrо, что позволило бы леrко отличить ero от элементов <а>, не ИМеющих никакоrо отношения к поиску. Добавьте в llporpaммy код, выделенный ниже полужирным шрифтом. #! руthопЗ # lucky.py  Открывает несколько результатов поиска с помощью # Google. import requests, зуз, webbrowser, bs4 Пропущенный KOД f ИзВJIечеИИ8 первых несКOJJЪJCИX найденных ссыпок. 80UP = Ье4. ВeautifulSoup (res . text) . ОтlCpW'1'И8 О'l'дem.ной вкпa,цJCИ дпя: кurдoro peaYJlЬ'l'a'l'a. linkElems · soup.select('.r а') Однако, оrлядевшись в окрестностях элемента <а>, вы заметите элемент наподобие <hЗ class="r">. Просмотр оставшейся части I-IТМI..-кода позв ляет предположить, что класс r используетСЯ исключительно для ссылок на результаты поиска. Вам необязательно знать, что собой представляет CSs. класс r и что он делает. Вы просто будете использовать ero в качестве марке- ра элемента <а>, который ищете. Вы можете создать объект BeautifulSoup из HTMLoТeKcTa заrруженной страницы, а затем использовать селектор , . r а' ДЛЯ нахождения всех элементов <а>, которые вложены в элемент, имеющий С5S-класс r. Ш"r 3. Qr"pw'IIe 6р"узер" Р. "".Aoro 11' ре',.""'о, пOll'"'' Наконец, нам необходимо, чтобы проrрамма открыла в браузере отдель ные вкладки ДЛЯ каждоrо из результатов поиска. Добавьте в конце nporpaM мы следующий код. #! руthопЗ # lucky.py  Открывает несколько результатов поиска с помощью # Google. import requests, sys, webbrowser, Ьз4 пропущенный KOД # Открытие отдельной вкладки для каждоrо результата. linkElems = soup.select('.r а') nwaOpen - IВin(S, len(linkElems» 
Автоматический сбор данных вИнтернете 321 for i in range (numOpen) : webbrowser.open('http://google.coa' + linkEl&ms[i].get('href'» По умолчанию вы открываете с помощью модуля webbrowser новые вкладки для пяти результатов поиска. Однако пользователь Mor выполнить поиск, дающий менее пяти результатов. Вызов soup. se lect () возвращает список всех элементов, соответствующих селектору , . r а', поэтому коли чество открываемых ВКЛадок либо будет равно 5, либо будет определяться длиной указанноrо списка (в зависимости от Toro, что меньше). Встроенная функция Python min () возвращает наименьшее из передан ных ей целых или вещественных чисел. (Также существует встроенная функция шах ( ) , которая возвращает наибольшсе из чисел, которые были ей переданы.) Можно использовать функцию min () для Toro, чтобы выяснить, содержит ли СIlИСОК менее няти ссылок, и сохранить количество ссылок, подлеЖащих открытию, в переменной nurnOpen. После этоro можно ВЫПОk нить цикл for, вызвав функцию range (nurnOpen). На каждой итерации цикла вы открываете новую вкладку в браузере с по-- мощью вызова webbrowser . open () . Обратите внимание на то, что значение атрибута href в возвращеНIIЫХ элементах <а> не содержит начальную часть U RLaдpeca http://google.com, поэтому вы должны конкатснировать ее со строкой значения атрибyrа h re [. Теперь вы можете сразу открыть псрвые пять результатов поиска, выпол- неШ-lOrо, скажем, для ПОИСКО80rо термина Python prograттing tutarials, введя в командной строке команду lucky python progranuning tutorials! (Более Iюдробная информация о простом способе запуска проrрамм в разлИlНых операционных системах приведена в приложении Б.) ИАе. OT.ot.reIl6.0 tOJAII.". II.IIlIor,,'.6IX пРО"II"" Преимуществом описанноrо подхода к выполнению НОШ' ка является то, что он позволяет леrко открывать ссылки в новых вкладках для Toro, чтобы просмотреть их ПОЗДнее. Проrрамма, автоматически открывающая сразу несколько ссылок, ПОМОЖL>Т вам сэкономить время, ВЫIlОЛНЯЯ следующие операции: . открытие страниц всех заинтересовавших вас товаров после выпол- нения поиска на <:айте какоrо-либо интернет-маrазина, например Ашazоп; . открытие ссылок на все обзоры, посвященные одному и тому же пp<r дукту; . открытие рсзультирующих ссылок на фотоrpафии по<:ле выполнения поиска на каком-либо фотосайте, таком как Flickr или Iшgur. 
322 rлава 11 Проект: эаrруэка всех комиксов на сайте XKCD в блоrах и на друrих реryлярно обновляемых веб-сайтах часто есть за- rлавная страница с последней публикацией и кнопка Previous (Предыдущая), которая ПрИ80ДИТ к предыдущей публикации. Предпоследний пост также снабжен аналоrичной кнопкой previous и Т.д., что обеспечивает возмож- ность последовательноrо прос.мотра публикаций на данном сайте в направ- лении от последней к первой. Если вы хотите скопировать содержимое сайта, чтобы прочесть ero позднее в автономном режиме, то можете вруч- ную пройтись по всем страницам и сохранить их. Но это занятие довольно скучное, поэтому поручим nporpaMMe выполнить эту работу вместо вас. XKCD  это популярный веб-комикс с сайтом, структура KOToporo впи- сывается в эту схему (рис. 11.6). На заrлавной странице по адресу http: / / xkcd.com/ есть кнопка Prev (Предыдущий), щелкая на которой пользователь может переходить к предыдущим комиксам. Заrpузка всех комиксов вруч- ную длилась бы целую вечность, но вы можете написать сценарий, кото- рый выполнит эту работу за пару минут. Вот какие действия должна выполнять ваша проrрамма: . заrружать rлавнуlO страницу XKCD; . сохранять изображение комикса, отображаемоrо на данной странице; . выполнять переходы по ссылке Previous Comic (Предыдущий комикс); . повторять описанные действия, пока не будет ДОСТИПI}Т первый комикс. I .... .  1\ П   n . I 16 ют А ; :1: 1 CAtII ТEU.. PМtSICS, 1'00 :r IJORICED OUТ7JfE ktNl<S '''' CQl\Nn.IМ NIC.S АМ) IlSJImVIТY. AL  . TOOk. А LDТ М ПfIМ<ING. EkJТ 1)ffS PtN::E  fIWER $ 1JOfAN А S\oIIS6 ""ТОЛ' CIFFЮ!:. il 1k9E's PWП'V OF' 1IМЕ' fOR "tИ,*-\NG our НERE. 1;'VE RU'ltR.lVE\> ЖIEQМ """" IN r.WO ..... ..."  ....  O .- '":'.::' fW E1'ERt.JIJY, R!)IЦ.Y. ANOТНEн $OrIE шн Nt'W ROU RX.I.OWED FRDМ ТНЕ IA$Т 'н '" 5fI'IPЦ: P1mI:RfI. о d.. o о о : . о o..Q t> о О А "'\1'1-'1)4&  5u OF  fIID ENouGtI 5М'Е, :I \м МLE. 1\\ 8UtLD А Щ"'VIВ. ;: i:Ч:;;i;t:"Н "EIOi' N&i'PA. o,r . SТ0N6 IS Ч tIEItr \'1"IRRrQI rJf 1к. N. ONE OIW t sт.-.m:o l.IМt.IO OOWN  of RCUS. о о о o - ...... ; ..:: ..:. : . ..: .:...::: ........ -..- .. .- -ь. SURt', IrS Roats .....SТE:AD OF' ШaRIafY, &п"'В Т14!:. Рис. 11.6. ХКСО  "веб-комикс о романтике, сарказме, математике и языке" 
Автоматический сбор данных вИнтернете 323 Это означает, что ваш код должен выполнять следующие операции: . заrружать страницы с помощью модуля requests; . находить URIaдpec изображения комикса для страницы, используя возможности Beautiful Soup; . заrружать и (охранять изображение комикса на жестком диске, ис пользуя метод itercontent (). . находить URLaдpcc ссылки Previous Comic и повторять действия. Откройте новое окно в файловом редакторе и сохраните ero в файле downloadXkcd.py. ш", ,. npoeKrllpO'''.IIe про,р"..'" Открыв в браузере окно инструментов разработчика и ПРОИНСllектиp<r вав элементы (траницы, вы обнаружите следующее: . URL-aдpec файла изображения комикса предоставляется атрибyrом href элемента <img>; . элемент <img> вложен в :лемент <div id="comic">; . кнопка Prev имеет НТМI,-атрибут rel со значением prev. . С кнопкой Prev nepBoro комикса связан URLaдpec http://xkcd. сот/#, указывающий на отсyrствие дрyrих предыдущих страниц. Введите в подrотовленный ранее файл следующий код. #! python3 # down1oadXkcd.py  3аrружает все комиксы хксо. import reque5t5, 05, Ь54 url = 'http://xkcd.com' # начальнь URLадрес os.makedirs('xkcd', existok=True) # сохраняем комикс в # папке. /xkcd while not url.endswith('#'):  тооо: 3аrрузить страницу. .;. тооо: Найти URI.аДрес изображения комикса.  тооо: 3аrрузить изображение. = тооо: Сохранить изображение в папке ./xkcd.  тооо: Получить URLадрес кнопки Prev. rint ('rOTOBO. 1) у вас будет персменная url с начальным значением 'http://xkcd.com' , которое будет реryлярно обновляться (в цикле for) значением URL-aдpeca ссылки на кнопку Prev текущей страницы. На каждом шаrе цикла вы заrру жаете комикс, находящийся по адресу, который хранится впеременной url. Вы будете знать, что цикл следует завершить, коrда встретится значение ..:.::1, заканчивающееся (имволом '#'. 
324 rлова 11 Файлы изображений будут заrружаться в папку xkcd, находящуюся в Te кущем рабочем каталоrс. Вызов os .rnakedirs () rарантирует существование этой папки, а именованный apryмeHT exist  ok=True предотвращает возбуж- дение исключения в том случае, если эта папка уже существует. ОСТaJll>Ная часть КОДа  это просто комментарии, описывающие оставшуюся часть проrраммы. ШII,2. 311'РРКII ,,'.аРIIН",,'" Приступим к реализации кода для заrрузки страницы. Добавьте в про- rpaMМY код, выделенный ниже полужирным шрифтом. #! руthопЗ # downloadXkcd.py  Заrружает все комиксы хксо. import requests, os, bs4 url = 'http://xkcd.com' # начальный URLадрес os.makedirs('xkcd', exist ok=True) # сохраняем комикс в   папке ./xkcd while not url.endswith('#'): * ЗаrpУ8ка страницы. рrint('Заrpужаеся страница %в...' , url) res · requests.qet(url) r8s.rais8forstatuB() soup . bs4.B8autifulSoup(res.text) # Тооо: Найти URLадрес изображения комикса. # тооо: Заrрузить изображение. # тооо: Сохранить изображение в папке ./xkcd. # TODO: Получить URLадрес кнопки Prev. print (IrOTOBO. 1) Здесь прежде Bcero выводится значение url, информирующее пользова- теля о том, по какому URL-aдpecy сейчас будет осуществляться зarpузка; для последующей заl'РУЗКИ изображения используется функция request. get ( ) модуля requests. Как вcerдa, если в процессе заrрузки что-то пойдет не так, вы немедленно сможете вызвать метод raise  for  status () объекта Response для возбуждения исключения и прекращения работы проrраммы. В пр тивном случае нужно будет создать объект BeautifulSoup на основе текста заrруженной страницы. 
Автоматический сбор данных вИнтернете 325 Шаr 3. ПО"'1l . JarpYJlla .Jо6ражен". ком.",а Добавьте в nporpaMМY код, выделенный ниже полужирным шрифтом. #! руthопЗ # downloadXkcd.py  Заvружает все комиксы хксо. import reque5t5, 05, bs4 пропущенный KOД f ПОИСК tЛU.а,цреса иеобрu8НИJI ХOМИJCса. comioElem. 8oup.8elect('_comic iшq') if comicElem == [): print('He удалось найти изображение ХOМИJCса. ') е188: comicUrl = comicElem[O).get('8rc') _ Эаrpуеиь иеображение. рrint('Эаrpужаеся иеобрв-ение %8... I % (comicVrl» А8 = reque8t8.get(comicUrl) re8.rai8efor8tatu8() # TODO: Сохранить изображение в папке ./xkcd. # тооо: Получить URLадрес кнопки Prev. print ( I rOTOBO. I ) Как показывают результаты инспектирования rлавной страницы XКCD с помощью инструментов разработчика, элемент <img> для изображения K микса помещен в элемент <di v>, атрибyr id KOToporo имеет значение сооос. Поэтому для извлечения нужноrо элемента <img> из объекта BeautifulSoup следует использовать селектор' #сооос img I . Некоторые страницы XKCD имеют специальное содержимое, не явля- ющееся простым файлом изображения. Никаких сложностей это не пред- ставляет  вы просто пропускаете эти страницы. Если селектор вообще не найдет никаких элементов, то вызов 50ир. select (' #comic img') вернет пу- стой список. В этом <:лучае nporpaMMa может просто вывести сообщение об ошибке и продолжить выполнение, опустив зarpузку изображения. В противном случае селектор возвратит список, содержащий один эле- мент <img>. Вы можете получить значение атрибyrа src этоrо элемента <img> и передать ею функции requests. get () для заrpузки файла изображе- ния комикса. 
326 rЛQва 11 Ш", 4. 'охр"..... .,06р"...". . по.,,, np'A"'AYIII'ro "о."",,, Добавьте в проrрамму код, выделенный ниже полужирным шрифтом. #! руthопЗ # downloadXkcd.py  Заrружает все комиксы ХКСО. import rеquезtз, оз, Ьз4 ПрОПущеннЫЙ KOД f Сохранение иеображеНИR в пanке ./xkod. imaqeFile . open(08.path.join('xk.cd' ,o8.path. Ьа8еnаше(oomiсUrl», 'wb') for chunk in r88.iter content(lOOOOO): imaqeFile.write(Chunk) imageFile.clo8e() * Пo.nучени8 URLaдp8ca lCНопlCИ Prev. prevLink = 8oup.8elect('a[r81-"pr8v")') [О] url = 'http://xkod.CIOIII' + prevLink.qet( 'href') print ( I rOTOBO. ' ) К этому моменту файл изображения комикса хранится в переменной res. Вам следует записать данные изображения в файл на жестком диске. Функции ореп () необходимо передать имя локальноrо файла изображе- ния. Переменная comicUrl будет иметь значение наподобие' http: 11 imgs. xkcd. com/comics/heartbleedexplanation.png', которое, как вы, наверное, заметили, во мноroм напоминает путь к файлу. И действительно, вы може- те вызвать функцию os.path.basename () с comicUrl в качестве apryмeнтa. и она возвратит лишь последнюю часть URI" I heartbleed  explana tion. png' . эту часть можно использовать в качестве имени файла при сохранении и бражения на жестком диске. Полученное имя можно соединить с именем вашей папки xkcd с помощью функции os.path.join () (ваша проrpамма ис пользует в обозначениях пyrи символы обратной косой черты при работе в Windows и косой черты при работе в 05 Х и linux). Получив имя файла, вы можете вызвать функцию open () для открытия HOBoro файла в режиме I wb' (запись двоичноrо файла). Как уже rоворило<ъ, для (охранения заrруженных файлов с помощью модуля Requests следует орrанизовать цикл по возвращаемому значению метода iter  content () . Содержащийся в цикле код записывает порции дан- ных изображения (не более 100000 байт в каждой порции) в файл, после чеro вы закрываете файл. Теперь изображение сохранено на вашем жест. ком диске. 
Автоматический сбор данных вИнтернете 327 Затем селектор' а [rel="prev"] , выбирает элемент <а>, атрибyr rel кото- poro имеет значение prev, и атрибyr href этоrо элемента используется для получения URL-aдpeca предыдущеrо комикса, который сохраняется в пере- мен ной url. После этоro ЦИIUI while вновь начинает весь процесс зarpузки для Данноrо комикса. Вывод nporpaмMbl выrлядит так. Заружается страница http://xkcd.com... Заружается изображение http://imgs.xkcd.com/comics/phonealarm.png... Заружается страница httр://хkсd.соm/lЗ58/... Заrружается изображение http://imgs.xkcd.com/comics/nro.png... Заружается страница http://xkcd.com/1357/... Заружается изображение http://imgs.xkcd.com/comics/free speech.png... Заrружается страница httр://хkсd.соm/135б/...  Заrружается изображение http://imgs.xkcd.com/comics/ orbitalmechanics.png... Заrружается страница http://xkcd.com/1355/... Заrружается изображение http://imgs.xkcd.com/comics/ airplane message.png... Заrружается страница httр://хkсd.соm/lЗ54/... Заrружается изображение http://imgs.xkcd.com/comics/ heartbleedexplanation.png... пропущенный KOД Этот проект представляет собой неплохой пример nporpaмMbI, которая может автоматически выIIлнятьb переходы по ссылкам для сбора больших объемов данных в Интернете. О друrих возможностях модуля Beautiful Soup можно узнать из ero документации, которая находится по адресу http://www.crummy.com/software/BeautifulSoup/bs4/doc/. ИАе" оrио,,,rеn6ИО tОJАIIИИ. IIИllnоrИ9И6lХ ироrpll" Заrрузка страниц и переходы по ссылкам составляют основу мноrих про- rрамм, собирающих данные вИнтернете. Анмоrичные им npOl'paмMbI мо- ryr выполнять также (ледующие задачи: . резервное копирование Bcero сайта путем обхода всех ero ссылок; . копирование в(ех сообщений, опубликованных на форуме; . дублирование каталоrа товаров для продажи через интернет- маl'3.ЗИИ. Модули requests и BeautifulSoup сослужат вам отличную службу при условии, что вы можете определить URL-aдpec, который необходимо пере- дать функции request s. get () . Однако иноrДа сделать это довольно неле1'- ко. Кроме 1'01"0, может оказаться так, что сайт, навиrацию по которому вы хотите выполнить с помощью своей проrраммы, требует предварительно- 1"0 выполнения процедуры входа. Средством, которое предоставит вашим 
328 rЛQВQ 11 проrраммам больше возможностей ДЛЯ решения таких сложных задач, Я& ляется модуль SeleniuJll. Управпение 6рауэером с помощыо модуп. Selenium Модуль SеlепiuПl предоставляс'r Pytllon возможность непосредственно управлять браузером пyrем IIроrраммной имитации щелчков на ссылках и прохождения процедуры входа, как если бы сам пользователь взаимодей ствовал со страницей. Этот модуль обеспечивает rораздо более rибкие ме- тоды взаимодействия с вебстраницами, чем модули Requests и Beautiful Soup. Но поскольку модуль Selenium запускает браузер, он работает нем но- ro медленнее, и орrанизация фоновой обработки с ero помощью требует больших хлопот, если, скажем, вам нужно Bcero лишь зarpузить некоторые файлы из Интернета. Процедура установки модулей, разработанных сторонними лицами, опи сана в приложении А. 3апуек 6раузера, yпpa...eMoro моду.е. Se/eпiиm Для работы с при мерами в этом разделе вам понадобится браузер Firefox. Это и есть браузер, которым вы будете управлять. Если Firefox у вас еще не установлен, можете бесплатно заrрузить ero, посетив сайт http: / / get firefox. сот/. Импортирование модулей для Selenium выполняется не совсем стандарт- Ным способом. Вместо команды import selenium необходимо выполнить ко- манду from selenium import webdriver. (Объяснение истинных причин тoro, почему ДЛЯ установки МОДУЛЯ SelепiUIl1 выбран подобный способ, выходит за рамки книrи.) После этою вы сможете запустить браузер Firefox с помощью Selenium, Введите в ИJперактивной оболочке следующие команды. »> froDl 8eleniwa iJDport "ebdriver »> brO"8er - webdriver.Firefox() »> type(brow8er) <class 'selenium.webdriver.firefox.webdriver.WebDriver'> »> bro"8er.get('ht://invent"ithpython.OODI') Вы увидите, что вызов webdriver. Firefox () приводит К запуску браузе- ра Firefox. Вызов функции type () с передачей ей значения, возвращенною вызовом webdriver. Firefox (), показывает, что типом данных это('() значе- ния является WebDriver. Л п()(:ледующий вызов метода browser.get ('http: / / inventwithpython.com') перенаправляет браузер на страницу http: // inventwi thpython. сот/. Окно вашеro браузера должно выrлядеть примерно так, как показано на рис. 11.7. 
Автоматический сбор данных вИнтернете 329 . . ". .i.": '.:"':".'2!.::и-_.':,:,. ."  . ''''''''' r......-"PI'thon.Сomi ," + . :..,"" ,7,':Iii!ii!I .,'. '!. ,)i'iI..ae vent with Python   M , , .  .._._............. ... ".,... J.5.O ShOII FlIt fdC SIIIII Dnvg opboos ...оn..... .... Python 3.5.0 (v3.5.0:374f501f45б7, sep 13 201 02:27:37) [МSC v.1900 б4 bit (AМDб4)] оп win3 Туре "copyright", "сrеditз. or "1iсеnзе()" fo ore information. »> 5elenium webdriver »> Ьrоwзеr = webdriver.Firefox() »> Ьrоwзеr.gеt(' »> .... ) Рис. 11.7. Выполнение вызовов webdriver. Firefox () и get () в IDLE приводнт к запуску 6раузера Firefox По"ек элементо. н" "р""""е Объекты WebDri ver имеют ДОВОЛЬНО большое количество методов, пред" l-tазначенных для поиска элементов на странице. Эти методы условно де- лятся на Две rруппы: findelement * () и findelernents * (). МетОДЫ rpуп" пы findelernent* () возвращают одиночный объект WebElement, который представляет первый из найденных на странице элементов, соответствую- щих запросу. Методы rруппы findelernents* () возвращают список объек" то в WebElement  *, который представляет все элементы, соответствующие запросу. В табл. 11.3 ноказано несколько примеров вызова методов find element* () и findelements* () для объекта WebDriver, coxpaHeHHoro в пе- ременной browser. Табnица 11.3. Примеры ИСПОПЬЭО8аНИI методов об'Ьекта WebDri ver Дnl поиска !леМ8НТОВ Во,вращаемый 06ьект (список 06ьеКТОВ) W.bIlement Им. метода browser.find element Ьу class пamе(имя) Ьrоwsеr.fiпd=еlеrnеntsЬусlаssпamе(имя) browser.find element Ьу css sеlесtоr(селектор) Ьrоwsеr.fiпd=еlemепtsЬусsssеlесtоr(селектор) browser.find element Ьу id(id) browser.find=elementsbyid(id) Элементы, имеющие С55. lCЛасс с указанным именем Элементы, соответствующие указанному селектору С55 Элементы, соответствующие указанному значению атрибутаid 
330 rлово 11 Oк01l: l taииe тafiя. 11.3 И.. метода browser.find elernent Ьу link text(TeKcT) browser.find=elernentsbylinktext(TeKcT) browser.find elernent Ьу partial link text(TeKcT) browser.findelernents Ьу partiaI link text (текст)      browser.find elernent Ьу пarnе(имя) Ьrоwsеr.find=еlеrnепtsЬуnarnе(имя) browser.find elernent Ьу tag пarnе(имя) Ьrоwsеr.fiпd=еlеrnепtsЬуtаgnarnе(имя) Воавращaeмwi о6ьект (СПIlCOК 06мкr0в) weыement Элементы <а>, ПOJIноаыо совпадающие с указанным текстом Элементы <а>, содержащие указанный тека ЭлеМ8нты,СОАеР*Dе атрибут с указанным нменем Элементы с указанным нмен.м T.ra (реrистр н. УЧИТЫ80.теl)i нменам I а ' и , А' будет eooТleтcr8080ТЬ тer <а> За исключением rруппы методов *  Ьу  tag паrnе ( ) , apryMeHTbl всех м(...... ТОДов нечувствительны к реrистру, Если элементы, являющие(:я объектом поиска метода, отсутствуют на странице, модуль selenium возбуждает ис ключение NoSuchElernent. Если аварийное завершение работы проrpаммы в результате возбуждения исключения не входит в ваши планы, добавьте в код инструкции try и except. Получив объект WebElernent, вы сможете найти более полную инфор мацию о нем, читая атрибуты или вызывая методы, приведенные в табл. 11.4. Табnица 11.4. Атрибуты и MeTOДbI об\8КТО WebElemen t Атрибут Иllи метод Описани. tagnarne gеtаttriЬutе(имя) text clear() is  di splayed ( ) is  enabled () is  selected () location Имя Terai например, 'а' в случае !лемента <а> Значение атрибута с указанным нменем АПI AaHHoro !Аементо Т.ка, еодеРJI(QЙClВ мемеН18; например, 'hello' в случае !л.мента <span>hello</span> YAOIIleY Т8ка, введенный в текаовом пол. иnи текаовой облаCJИ Возвращает знач.ни. True, .сли мемент видимый, инач.  False Возвраща.т знач.ние True Д/II м.меНТОВВ80да, еслн м.мент аКТИlИзирован. иначе  False Возвращает значение True АПI флажка или переключатеЛI, .сли !л.мент выбран. иначе  False CIIовар.. с ключами 'х' и 'у' АПI позиции !лем-нто на аранице 
Автоматический сбор данных вИнтернете 331 Например, откройте новое окно в файловом редакторе и введите сле- ДУЮЩИЙ код. from 8e1enium import webdriver brOW8er = webdri ver. Fir8fox () brow8er.qet('http://inventwithpython.coa') иу: е1еа = brow8er.find e1ement ьу с1а88 nаше('Ьооkсоver') print ( 'Найден эпемен'l' <%8> с даннwи Именем кпасса l' % (elem.taqname» except: print('He удалось найти эпемен'l' с даннwи именем класса.') Этот код открывает Firefox и пере направляет ero по заданному URL- адресу. На открывшейся странице выполняется поиск элементов с классом 'bookcover' , и если такой элемент обнаружен, то на экран выводится имя Tera, определяемое атрибyrом tag пате. В противном случае выводится дру- rое сообщение. Для данной проrраммы вывод будет таким: Найден элемент <img> с данным именем класса! Мы нашли элемент с именем класса' bookcover' и именем Tera 'img'. Щелчок н" "р"н""е Объекты WebElement, возвращаемые методами findelement* () и find elements * () , имеют метод click () , имитирующий щелчок мышью на эле- менте. Этот метод можно использовать для перехода по ссылке, выбора с помощью переключателя, щелчка на кнопке Submit (Отправить) или ини- циирования любоl'О друrоrо действия, которое может быть запущено щелч- ком на элементе. Ilапример, введите в интерактивной оболочке следующие команды. »> from 8818nium import webdriver »> brow88r · W8bdriver.Firefox() »> brow8er.qet('http://inventwithpython.cam') »> 1inkE1em · brow8er.find e1ement ьу link teхt('Чи'l'а'l'Ь онпайн') >>> type (linkE1em)     <class 'seleniиm.webdriver.remote.webelement.WebElement'> >>> 1inkE1em. c1ick () * перей'1'и по ссыпке "Чи'l'а'l'Ь онпайн" 
332 rлава 11 В Данном случае мы открываем Firefox на странице h t tp : / / inventwithpython. сот/, получаем объект WebElement для элемента <а> с тек- стом Чuтат'ь о'Н.Лайи, а затем имитируем щелчок На этом элементе. Все про- исходит так, как если бы вы сами щелкнули на ссылке, заставляя браузер открыть соответствующую страницу. 311nOnHeHlle " отпр"вк" ФОР. Отправка нажатий клавиш при нахождении фокуса ввода в текстовом поле сводится к нахождению на странице элемента <input> или <textarea>, соответствующсrо данному полю, и последующему вызову меТОДа send  keys ( ) . Например, введите в интерактивной оболочке следующий код. »> from 8eleniuz import webdriver »> brow8.r . w8bdriver.Firefox() »> brow8.r.qet('http://gmail.com') »> emailElem . browBer.find elem.nt Ьу id('Email') »> emailEl.m.8end kеУ8('фИКТИВНЫЙ адрес email@gmail.com') »> pa88wordElem =bro"8er.find elSment ьу id('Pa88"d') »> pa88"ordElem.8.nd key8 (' 12345')   »> pa88wordElem.8uЬmIt() Если только служба Gшаil не изменила идентификаторы id текстовых полей Username (Имя пользователя) и Passworcl (Пароль) с момента выхода в свет данной Кllиrи, этот код заполнит указанные текстовые поля пред ставленным текстом. (Для проверки значения id вы всеrда можете вое. пользоваться средством инспектирования элсментов, предоставляемым браузером. ) Вызов метода submi t () для любоro элемента будет иметь тот жс эффект, что и щелчок На кнопке Submit (Отправить) формы, содержащей данный элемент. (Вы также моши вызвать emailElem. subrnit () , и код выпол. нил бы то же самое.) Ornpll.KII КОДОВ ,ne"HIIII6HWX КЛII.IIШ SelcniUln включает модуль для обработки нажатий клавиш, которые нельзя ввести в виде строки, функционирующий во мноroм подобно экра- нированным символам. Соответствующие значения хранятся в модуле selenium. webdr i ver. соттоп. keys. Поскольку это довольно длинное имя, ro- раздо проще выполнить в начале проrраммы инструкцию from sеlепiшn. webdriver. common. keys import Keys. После этоrо вы сможете использовать имя Keys везде, rде обычно вы использовали бы запись selenium.webdriver. соттоп. keys. 
Автоматический сбор данных вИнтернете 333 Та6nица 11.5. Часто иcnаnьэуемые "еременные модуn...lеnium.webdrivеr.сошmоn.kеУ8 Атри'Ут Keys.DOWN, Keys.UP, Keys.LEFТ, Keys . RIGHT Keys.ENTER, Keys.RETURN Кеуз.НОМЕ, Keys.END, Keys.PAGE DOWN, Keys.PAGEUP  Keys.ESCAPE, Keys.BACKSPACE, Keys.DELETE Onисаи... l<JIаlИWИ со ар8llками l<JIаlИWИ <Enter> и <R.tum> l<JIаlИWИ <Ноте>, <End>, <PageDown> и <Pag.Up> l<JIаlИWИ <Ек>, <Backspace> и <D.lete> Keys.Fl, Keys.F2, . . Keys.F12 Кеуз.ТАВ l<JIаlИWИ от <F 1> АО <F2> l<JIаlИwa <ТаЬ> Например, если курсор в данный момент не находится в текстовом поле, нажатие клавиш <Ноше> и <End> выполняет прокрyrку в начало или конец страницы соответственно. Введите в интерактивной оболочке следующий код и обратите внимание на то, как вызовы метода send  keys () приводят К прокрyrке страницы. »> from 8elenium import webdriver »> from sеlеnium.webdriver.сошmоn.kеУ8 import КеУ8 >;> > bro,,8er · "ebc:iri ver . Firefox () »> bro''8er.gвt('http://n08tarch.com') »> htmlElem = bro"8er.find element Ьу tag name('html') >>> htmlElem.8end key8 (КeY87END) # ПроRpУ'l'U В конец >>> htmlElem. 8snd:key8 (КеУ8. НОМЕ) * npoRPy'l'I<& В начало Ter <html>  основной в НТМL-файлах: все содержимое НТМLайла за ключено между теrами <html> и </:-tt.!'!tl>. Вызов browser. findelementbytag пате ( , h tml') обеспечивает удобную возможность отправки кодов клавиш целой всб-странице. Это приrодится вам, если, например, новое содержи Мое заrружается сразу же, как только вы прокручиваете страницу до конца. Щ'ЛЧКII НII кнопКIIХ 6,IIУI'," Модуль Selenium также может имитировать щелчки на различных кноп ках браузера с помощью следующих методов: . browser.backO  щелчок на кнопке Back (Назад); . browser.forwardO  щелчок на кнопке Forward (Вперед); . browser.refreshO  щелчок на кнопке Refresh (Обновить)/Rеlоаd (Пе-- резаrрузить) ; . browser.quitO  щелчок на кнопке Close Window (Закрыть окно). 
334 rлава 11 Поnучен"е 1I0nоnн"rеn6НОЙ "нфор.""." О .Ollyne 5elen;1I8 Модуль Selenium способен делать rораздо больше, чем здесь описано. ОН может изменять сооkiефаЙJIЫ браузера. получать моментальные сним- ки веб-страниц и выполнять пользовательские сценарииJаvaSсriрt. Чтобы узнать больше об этих возможностях. посетите сайт с документацией по SеlенiuП1  http://seleniumpython. readthedocs .org/. РеЭlOме Большинство рyrинных задаЧ связано не только с обработкой файлов, хранящихся на вашем компьютере. Возможность проrраммной заrрузки веб-страниц выводит вашу проrрамму на просторы Интернета. Модуль Requests упрощает процесс заrрузки веб-страниц, а с помощью модуля BeautifulSoup вы, имея лишь самые поверхностные знания базовых поня. тий HTML и селекторов, сможете выполнять синтаксический анализ заrру. женных страниц. Однако для полной автоматизации задач, связанных с Интернетом, вам необходимы возможности непосредственноro управления браузером, K торыс обеснсчивает модуль Sellenium. Этот модуль позволяет автоматизи- ровать процедуру входа на сайт и заполнение форм. Поскольку браузеры являются самым распространенным средством отправки и получения ин- формации через Интернет, описанные возможности обязательно нужно включить в свой арсенал проrpаммиста. Контропьные вопросы 1. Вкратце опишите различия между модулями webbrowser, Requests, Beautiful Soup и Selenium. 2. Объект KaKOI'o типа возвращает функция requests. get ( ) ? Каким об- разом можно получить доступ к заrруженному содержимому в виде CTpoKoBoro значения? 3. Какой метод модуля Requests позволяет проверить успешноcrь зarpузки? 4. Как получить код состояния НТТР из ответа на запрос модуля Req uests? 5. Как сохранить в файле ответ на запрос модуля Requests? 6. Какая комбинация клавиш предназначена для открытия окна инстру- ментов разработчика, предоставляемых браузером? 7. Как можно просмотреть (в окне инструментов разработчика) HTML- код KOHKpeTHoro элемента на веб-странице? 8. Какая строка с..електора найдет элемент, атрибyr id KOToporo име- ет значение main? 
Автоматический сбор данных вИнтернете 335 9. Какая строка СSS-селектора найдет элементы, относящиеся к CSS классу highlight? 10. Какая строка СS8-селектора найдет псе элементы <di v>, каждый из ко- торых вложен вдруrой элемент <div>? 11. Какая строка СSS-селектора найдет элемент <button>, атрибут value KOToporo имеет значение favorite? 12. Предположим. у ва(: имеется сохраненный в переменной spaт объект Tag Beautiful Soup для элемента <div>Hello world! </div>. Как полу- чить строку I Hello world! ' из объекта Tag ? 13. Как сохранить все атрибyrы объекта Tag Beaut.iful Soup в переменной linkElem? 14. Инструкция ilnport selenium не работает. как правильно импортиро- вать модуль Selenium? 15. В чем суть различия между методами findelement * () и find elements  * ()? 16. Какие Методы объектов WebElement модуля Selenium имитируют щелч- ки мышью и нажатия клавиш? 17. Чтобы отправить с Помощью модуля SeleniulI1 форму. можно вызвать метод sendkeys (Keys.ENTER) для объекта WebElement кнопки Submit. но какой для этоrо существует более простой способ? 18. Как сымитировать щелчки на кнопках браузера Forward, Back и Refresh с помощью модуля Selenium? Учебные проекты Чтобы закрепить полученные знания на практике, напишите проrрам- мы для предложенных ниже задач. nporp".." ДЛR о,nр"'"'' Jле",ронноj но.,.. НЗ ко."нднон е1рО"" Напишите проrрамму. которая принимает адрес электронной почты и строку текста в командной строке, а затем, используя модуль Selenium, вхо- дит В вашу учетную запись электронной почты и отправляет строку сообще- ния по указанному адресу. (Возможно, для этой проrраммы целесообра:шо завести отдельную учетную запись электронной почты.) Для вас это был бы отличный шанс дополнить свои проrраммы возможностью рассылки уведомлений. Вы также мOI'JIИ бы написать анало- rичную проrрамму для отправки сообщений из учетных записей Facebook или Твиттера. 
336 rлава 11 311rpYJ'''K "з06РII.ен". ,,3 IIн,ерне", НаНИlIIите ПРОI'рамму, которая перенаправляет браузер на какой-либо фотосайт, такой как Flickr или Imgur, осуществляет поиск фотоrрафий определенной катеrории, а затем заrружает все результирующие изображе- ния. Вы моrли бы написать проrрамму, способную работаТI с любым фото сайтом, предоставляющим (peДCTBa поиска. 11204811 "2048"  это наэвание простой иrры, в которой иrрок перемещает плит ки с помощью клавиш управления курсором на клавиатуре. Кш'да две плит. ки с одипаковыми цифрами соприкасаются, они сливаются в одну. Напиши те проrрамму, которая открывает иrру на сайте https: / /gabrielecirulli. github.io/2048/, а затем отправляет коды клавиш управления курсором, со-- ответствующих перемещениям в направлениях вверх, вправо, вниз и ВЛево, автоматически номерживая иrpовой процесс. 'еР"ф"КII,,". ""лок Напишите проrрамму, которая припимает URI-aдpec веб-страницы, а эа- тем пытается заrрузить каждую страницу, на которую на данной странице имеется ссылка. Проrрамма должна номечать флаrом те страницы, при по- пытке открытия которых получен код состояния 404 "Страница не найде- на", и выводить информацию о разорванных связях на экран. 
РА&ОТА с ЭЛЕКТРОННЫМИ ТА&ЛИЦАМИ EXCEL Ехсеl  мощная проrpамма ДЛЯ работы с электронными таб- лицами в операционной системе Windows, пользующаяся широкой популярн()(тью. Задействуя модуль openpyxl, ваши проrраммы на PytJЮIl MOryr читать и изменять файлы элек тронных таблиц Excel. Необходимость в этом может возни кать сплошь и рядом. В качестве примера можно привести копирование данных из одной таблицы в друryю, ПРОСМОТр тысяч строк таблицы с целью выбора лишь оrраниченноrо их числа и внесения изме-- l-\ений на основании определенных критериев или же анализ сотен таблиц бюджета отделов, ЧТобы найти отделы с дефицитом баланса. Все это при меры тех рутинных задаЧ, неминуемо возникающих при работе с электрон ными таблицами, которые Python может выполнять вмес.то вас. Ехсеl распространяется компанией Мiсrоsоft как коммерчсский IIpO дукт, однако существуют аналоrИЧНЫе бесплатные ПрОI'раммы, IIреДЛal'ае-- мые для операционных систем Windows, 08 Х и Linux. Такие нриложспия, как LibreOffice Саlc и OpenOffice Calc, спос.обны работать с принятым в Ехсеl форматом . xlsx ДЛЯ файлов электронных таблиц, а это означает, что модуль openpyxl может работать и с электронными таблицами указанных приложений. Эти IIporpaMMbI доступны для зarpузки на сайтах https: / /www. libreoffice.org/ и http://www. openoffice .org/ соответственно. Даже если приложение Excel установлено на вашем компьютере, может оказаться так, что с упомянутыми проrpаммами вам будет леrче работать. При этом следу ет заметить, что все снимки экрана, приведенные в этой rлаве, cooтвeтcTBY ют проrрамме Exce12010, работающей под управлением Windows 7. 
338 rЛQВQ 12 Документы Excel Прежде Bcero обратимся к некоторым базовым определениям. Л:окумент электронной таблицы Excel называется рабачеи КUИ20й. Рабочая КIIиrа хра- нится в файле с расширением .xlsx. Каждая книrа может содержать любое количество листов (также называемых рабач'UМ,и листами). Лист, просматри- ваемый пользователем в данный момент (или последНИЙ из тех, которые просматривались, прежде чем была закрыта nporpaMMa Excel), называется активu'ыМ, листом. Лист состоит из столбцов (адресуемых с помощью букв, начиная с А) и стрО1( (адресуемых с помощью чисел, начиная с 1). Прямоуrольная об- ласть, образуемая пересечением столбца и строки, называется я:чеикоu. Каждая ячейка таблицы может содержать числовое или текстовое значе- ние. Совокупность ячеек вместе с содержащимися в них данными образует рабочий лист. Установка модуля openpyxl Модуль OpenPyXL не поставляется вместе с PytllOn, поэтому еro нужно установить самостоятельно. Следуйте приведенным в приложении А ИН- струкциям по установке модулей, разработанных сторонними компания- ми; имя модуля  openpyxl. Чтобы протестировать корректность установки, введите в интерактивной оболочке следующую команду: > > > illlport openpyxl в случае корректной установки модуля выполнеНИе этой инструкции не должно сопровождаться выводом сообщения об ошибке. Также не за- бывайте импортировать модуль openpyxl перед началом работы с примера- ми в этой rлаве, иначе вы получите сообщение об ошибке NarneError: пarnе 'openpyxl' is not defined. При подrотовке этой книrи ИСПОЛЬЗ0валась версия модуля Ореп PyXL 2.1.4, однако команда разработчиков реryлярно выпускает новые вер- сии модуля. Вы не должны беспокоиться по этому поводу, поскольку новые версии будут поддерживать обратную совместимость с кодом на Python, ис- пользуемым в примерах l . Если у вас установлена более новая версия и вы хотели бы знать, какие новые возможности в ней доступны, обратитесь к полной документации к модулю OpenPyXL, которую можно найти на сайте http://openpyxl.readthedocs.org/. 1 В процессе подrотовкн перевода книrи ИСПОЛЬЗ0валась версия модуля OpenPyXL 2.1.7, несовместимая с используемой автором версией в отношении построения диаrрамм (см. да- лее).  Прllмеч. ред. 
Робота с электронными таблицами Ехсеl 339 Чтение документов Ехсеl в этой rлаве мы будем использовать в примерах электронную таблицу exaтple.xlsx, сохраненную в корневой папке. Вы можете либо самостоятель- но создать этот файл, либо заrрузить cro на сайте http://nostarch.com/ automatestuff/. На рис. 12.1 ПОI<aЗaНы вкладки трех заданных по умолчанию листов с именами Листl, Лист2 И ЛистЗ, которые Ехсеl автомаТИ'lески пред ставляет во вновь создаваемых рабочих книrах. (Количество открываемых по умолчанию листов может быть различным в зависимости от используе-- мой операционной системы и приложения электронной таблицы.) : 10 :11 12 13 14  :С, . ...;...g!_.}.g?::-:-[!!? .:.. rOTO'O  .4  :.-::);:.'' ). ;j';;-:.з Рис. 12.1. Вкладки листов рабочей книrи, расположенные в левом нижнем уrлу окна Ехсе' Содержимое листа Лист! из файла примера представлено в табл. 12.1. Та6nица 12.1. Рабочий лна example . X18X А В С 1 05.04.201513:34 Яблоки 73 2 05.04.20153:41 Вишни 85 3 06.04.2015 12:46 r руwи 14 4 08.04.20158:59 Апельсины 52 5 10.04.2015 2:07 Яблоки 152 6 10.04.201518:10 Банаиы 23 7 10.04.20152:40 Земляника 98 Имея в своем распоряжении файл с примером, ра(смотрим способы ма- нипулирования электронными таблицами с помощью модуля openpyxl. Откр",т"е документов Extel , 80МО"'61О моду.. ОрепРухх, Импортировав модуль openpyxl, можно использовать функцию openpyxl. :'oadworkbook (). Введите в интерактивной оболочке следующие команды. 
340 rЛQва 12 »> import openpyxl »> _Ь = openpyxl.load wcrkbооk('ежamрlе.хlsх') »> type(wb)  <сlазз 'openpyxl.workbook.workbook.Workbook'> Функция openpyxl.load  workbook () принимает имя файла в качестве apry- мента и возвращает значение типа Workbook. Объект Workbook представляет файл Excel, подобно тому как объект File представляет открытый тексто- вый файл. Вспомните: для 1'01"0 чтобы можно было работать с файлом example.xlsx, он должен находиться в текущем рабочем Каталоrе. Вы можете определить, какая именно папка является текущим рабочим каталоrом, импортировав модуль 03 и использовав функцию 03. getcwd ( ) , тоrда как для смены рабоче- 1"0 каталоrа можно использовать функцию 03. chdir ( ) . Полу,ен"е '."'КII л",то, р"60,е. КН"'" ДЛЯ получения списка листов, входящих в состав рабочей книrи, следует вызвать метод get  3heet  narne 3 () . Введите в интерактивной оболочке сле- дующие команды. > > > import openpyxl > > > _Ь = openpyxl. load workbook ( 'example. xlsx' ) >>> wb.get sheet naJDeе () [' Лист!', 'Лист2', 'ЛистЗ' 1 »> sheet = _Ь. get sheet ьу nlШ8 ( 'JlиС'1'З' ) »> sheet    <Worksheet "ЛистЗ"> >>> type (sheet) <class 'орепрухl.wоrkзhееt.wоrkshееt.Wоrkshееt'> >>> sheet. ti tle 'ЛистЗ' >>> anotherSheet = .Ь. get acti ve sheet () »> anotherSheet   <Worksheet "Лист!"> Каждый лист представляется объектом Worksheet, который можно 1I0ЛУ чить, передав строку с именем листа методу getsheetbynarne () объекта workbook. Наконец, вызвав метод getactivesheet () объекта Workbook, мож но получить активный лист2. Активный лист  это лист, нахол;ящийся в на- чале списка, КоrДа открывается рабо1lая книrа Excel. Коль скоро у вас есть объект Worksheet, вы сможете получить ero имя из атрибyrа ti tle. 2 В настоящее время функция get act i ve sheet () считается устаревшеА. и вместо нее рекомендуется НСПOllьзовать атрибут active объекта Workbook.  l1рu.uеч. pecJ. 
Работа с электронными таблицами Ехсеl 341 П.у,енне .,еек р"60'"Х лнtто. Имея в своем раСlIоряжении объект Worksheet, можно получить доступ к объекту CelllIo cro имени. Введите в интерактивной оболочке следующие команды. »> import openpyxl »> _Ь . openpyxl.load workbook('8xamp18.xlBx') >>> sh8et... wb.get sheёt ьу namе('Лиотl') »> sheet[ 'Al']    <Се1l Лист1.А1> »> sheet['Al'] .value datetime.datetime(2015, 4, 5, 13, 34, 2) »> с - sheet['Bl'] »> c.value 'Яблоки' »> 'Строха ' + str(c.row) + " Coп68Ц , + c.column + ': I +  c.value 'Строка 1, Столбец В : Яблоки' »> 'Ячейка ' + c.coordinat8 +, '+ c.value 'Ячейка В1 : Яблоки' »> sheet['Cl'].value 73 Объект Cell имеет атрибyr value, который содержит значение, хранящ(.... еся в ячейке. Объекты Cell также имеют атрибуты row, eolumn и eoordinate, которые предоставляют информацию о расположении данной ячейки в таблице. В данном примере обращение к значению атрибyrа value объекта Cell для ячейки В 1 дает строку Яблоки' . Атрибyr row дает целочисленное зна- чение 1, атрибут eolurnn  значение 'Б', а атрибyr eoordinate  значение 'Б1' . Модуль Opel1PyXL автоматически интерпретирует даты в столбце А и возвращает их в виде значений l'Ипа datetirne, а не в виде строк. Более под- робно о типе данных datetime расска3Ывается в шаве 16. Проrраммное задание столбца с помощью буквенноrо обозначения п началу может вызывать затруднения, особенно при переходе через стол- бец Z, после KOToporo обозначения становятся двухбуквенными: АЛ, АВ, АС и Т.д. В качестве aJIl>rернативноrо варианта можно обращаться к ячейке, используя метод eell () объекта Sheet с передачей ему цело численных зна- чений ею именованных apryмeHToB row и eolurnn. Первому столбцу или пер- вой строке соответствует целое число 1, а не О. Продолжите выполнение примера в интерактивной оболочке, введя следующие команды. »> sheet.oell(rov=l, cOlumn=2) <Сеl1 Лист1.В1> 
342 rлава 12 »> sheet. cell (row=l, соlшan=2). value 'Яблоки' »> for i in rangв(l, 8,2): print (i, sheet. cell (row-i I СОlшan=2). value) 1 Яблоки 3 rруши 5 Яблоки 7 Земляника Как можно видеть, вызывая метод cell () объекта Sheet с передачей ему apryмeHTOB row=l и column=2. мы получаем объект Cell для ячейки Вl, что до этоrо было сделано с 110МОЩI>Ю метода shee t ( 'в 1 ' ] . Затем мы ИСПОЛЬ:Jуем вызовы метода cell () (' I1ередачей ему именованных apryMeHToB 8 цикле for для вывода значений последовательности ячеек, Предположим, вы хотите (мещаться вниз по столбцу В и выводить зна- чения, содержащиеся в ячейках с нечетными номерами строк. Передав :шачение 2 параметру "step" ФУНКЦИИ range ( ) , можно получить ячейки из каждой второй строки (в данном случае  из каждой нечетной). Именован- ному apryмeHТY row метода cell () передается переменная i цикла for, тоrда как именоваlllЮМУ apryMCHТY соlШlП1 ВССlда передается значснис 2. :JaMcTbTe, что передается именно целое число 2, а не строка 'В'. Раамер листа мОЖНО определить с помощью методов get  highest row () и get  highest  column () объекта Worksheet. Введите в интерактивной обо-- лачке следующие КомаНДЫ. >>> illlport op8npyxl >>> wb = openpyx1.1oad workbook ( '8X8IIIp18 . xlsx I ) >>> sh8et . wb. q8t she;t ьу паше ( , ЛиС'l'l ' ) »> sh8et.q8thiqh;strow() 7 »> вh8еt.q8thiqhеstаоlшan() 3 Обратите внимание на то, что метод gethighestcoluron () возвращает целое число, а не букву, которая отображается в Excel. '",воnненне прео6р"зо,"Ннj .eqy 6ук.енн",." " ""фро,,,,.н 060ЗНII,енн..н non6"о. Чтобы преобразовать буквенное обозначение столбца в цифровое, сле- дует вызвать функцию openpyxl. cell.columnindexfrornstring (). Чтобы преобразовать цифровое обозначсние столбца в буквенное, следует BЫ звать функцию openpyxl.cell.getcolurnnletter (). Введите в интерактив- ной оболочке следующие команды. 
Работа с электронными таблицами Ехсеl 343 »> iщроrt openpyxl »> from openpyxl.cell import getcolumnletter, columnindexfromstring »> getcolumnletter(l) 'А' »> getcolumnletter(2) 'В' »> get column letter(27) 'М'   »> get column letter(900) 'АНР'   »> _Ь = openpyxl.load workbook('example.xlsx') >>> sheet = wb.get sheet ьу nuе('ЛиС'l'l') »> get column letter(sh;etget highest column(» 'С'     >>> columnindexfromstring( 'А') 1 >>> calumnindexfromstring( 'М') 27 Импортировав указанные две функции из модуля openpyxl . cell, мы мо- жем вызвать функцию get  со! uлm.  let ter () С передачей ей целочисленноrо ЗНачения, например 27, чтобы выяснить, какое буквенное обозначение со- ответствует столбцу 27. Функция СОlШnniпdехstriпg () решает обратную задачу: вы передаете ей буквенное имя столбца, и она возвращает вам ero номер. Чтобы испольэовать эти функции, заrружать рабочую книry вовсе необязатсльно. Однако нри желании можете :Jаrрузить рабочую книry, получить объект Worksheet и вызвать один из cro методов, например get  highest  соluлm. (), для получения цеЛочислеllНОl'О результата. Далее это цe лое чи(ло можно передать функции get  соluлm.  letter () . ПолучеНllе "рок н тм6,,0. ра60ЧНХ ЛII"О' Используя срезы объектов Worksheet, можно получать вес объекты СеН, принадлежащие определенной строке, столбцу или прямоуrольной области электронной таблицы. После этоrо можно ОРl'анизовать цикл по всем ячей кам среза. Введите в интерактивной оболочке следующие команды. >>> import openрух1 >>> _Ь = openpyxl.load workbook ( 'example . xlsx ' ) >>> sheet = wb.get sheet Ьу name (' ЛиС'l'l') >>> tuple (sheet[ 'АУ': 'СЗ'))  ((<Cell Листl.А1>, <Cel1 Листl.Вl>, <Cell Листl.С1», «Cell Лист1.А2>, <Cell Лист1.В2>, <Ce1l Листl.С2», «Cell Листl.АЗ>, <Cel1 Листl.ВЗ>, <Cell Лист1.СЗ») О >>> for rowOfCellObjects in sheet[ 'А1': 'СЗ'): . for cellObj in rowOfCellObjects: print(cellObj .coordinate, oellObj .value) print ( , КОНЕЦ СТРОКИ ') 
344 r лава 12 А1 20150405 13:34:02 В1 Яблоки С1 73  КОНЕЦ СТРОКИ  А2 20150405 03:41:23 В2 Вишни С2 85  КОНЕЦ СТРОКИ АЗ 20150406 12:46:51 вз rруши С3 14  КОНЕЦ СТРОКИ Здесь мы указываем, что нас интересуют Я(lейки прямоуrольной области таблицы, левый верхний и правый нижний уrлы которой определяются ячейками Аl и С3, и получаем объект Generator, который содержит объ- екты Сеll, принадлежащие указанной области. Чтобы было леrче оценить, что именно представляет собой данный объект Generator, можно восполь- зоваТЬСЯ функцией tuple () и отобразить ячейки в виде кортежа. Данный кортеж сам состоит из трех кортежей: по одному для каждой строки интересующей нас области в порядке следования сверху вниз. Каж- дый из трех внутренних кортежей содержит объекты Cell, при надлежащие одной строке, в порядке следования слева направо. Таким образом, в целом срез листа содержит все ячейки прямоуrольной области таблицы, yrлы K торой определяются ячейками Аl и С3. для вывода значений всех ячеек данной области ИПlOльзуются два цикла for. Внешний цикл lIере6ирает все (троки в срезе О, тоrда как вложенный цикл перебирает все ячейки текущей строки О. lVш доступа к ячейкам конкретной строки или столбца также можно вос- пользоваться атрибутами rows и colurnns объекта Worksheet. Введите в инт рактивной оболочке следующие команды. »> import openpyxl »> "ь . openpyx1.1oad "orkbook ( I exuple. xlsx' ) >>> sheet - wb.getactivesheet() »> sheet.соluшns[l] «Ce1l Лист1.Вl>, <Cell Лист1.В2>, <Ce1l Лист1.ВЗ>, <Cell Листl.В4>, <Се11 Лист1.Б5>, <Cell Листl.В6>, <Cell Листl.В7» »> for oellObj in sheet.ooluмns[l]: print(oellObj.value) Яблоки Вишни rруши Апельсины Яблоки Бананы Земляника 
Работа с электронными таблицами Excel 345 Используя атрибyr rows объекта Worksheet, мы получаем кортеж корте- жей. Каждый из внyrреНIIИХ кортежей представляет собой строку электрон- ной таблицы и содержит ее ячейки в ВИде объектов Cell. Атрибyr colurnns также дает кортеж кортежей, причем каждый из внутренних кортежей c держит объекты Cell, принадлежащие определенному столбцу. В случае та- блицы exaтple.xlsx, имеющей 7 строк и 3 столбца, атрибyr rows дает кортеж, состоящий из 7 кортежей (каждый из которых содержит по 3 ооьекта СеН), а атрибyr colurnns  кортеж, состоящий из 3 кортежей (каждый из которых содержит по 7 объектов Cell). Чтобы получить доступ к определенному кортежу, можно сослаться на Hero по ero индексу в охватывающем кортеже. Например, для получения кортежа, нредставляющеrо столбец В, следует использовать элемент sheet. co1urnns (1]. Чтобы получить кортеж, содержащий объекты Cell столбца А, следует использовать элемент sheet. col umns (О] . Как только в вашем рас- поряжении появляется кортеж, представляющий строку или столбец та- блицы, можете орrанизовать ЦИКЛ по содержащимся в нем объектам Cell и вывести их значения. ,,,60,.е к.",,,, л."", " .,енк. в качестве KpaTKoro резюме ниже описан процесс чтения содержим 1"0 ячейки электронной таблицы с упоминанием всех вовлеченных в Hero функций, методов и типов данных. 1. Импортируйте модуль openpyxl. 2. Вызовите функцию openpyxl.load  workbook ( ) . 3. Получите объект Workbook. 4. Вызовите метод get  acti ve sheet () или get  sheet  Ьу  пате () объекта Workbook. 5. Получите объект Worksheet. 6. Используйте индексирование или метод се 11 () объекта Sheet, пере- дав ему именованные apryмeHTЫ row И col urnn. 7. Получите объект СеН. 8. Прочитайте значение атрибута Cell объекта value. Проект: чтение данных Iпектронной та6пицы Предположим, у вас имеется электронная таблица, содержащая дан- ные переписи населения США в 2010 rоду. И вам предстоит утомительный проt:мотр тысяч строк для опредеJIения общей численности населения и количества переписных районов по oKpyraM. (Псрепиеной район  это просто rсоrрафическая территория. определенная для целей перениси 
346 r лава 12 населения.) Каждая строка таблицы представляет один перенисной район. Наш файл электронной таблицы будет называться censuspoPdata.xlsx, и ero можно заrpузить на сайте http://nostarch.com/automatestuff/. Содержимое файла ВbJl'ЛЯДИТ примерно так, как показано на рис. 12.2. А 1 ПереписноА раАон .... ....,.. . . , '9841 06075010500 . 9842 06075010600 . 9843 06075010700 . 9844 06075010800 . 9845 06075010900 r 9846 06075011000 r 9847 06075011100 в с  _ ! ey СА 58" Fr8"cisco СА Sз" Fr8"cisco СА 58" Fr8"cisco СА 58" Fr8"cisco СА 58" Fr8"cisco СА 58" Fr8"cisco СА 58" Fr8"cisco о Е РОР2010 . ." .,........ .... ....M_. . ._. ._. .. .. 2685 3894 5592 4578 4320 4827 51б4 :<1  ,. _! рrиQнdnыаАA ЧМCnetlНосТЬ нмеле ". .' ;''; ;;'0";  '-- ' , . --, -, ..-.-.. '.- Рис. J 2.2. Электронная таблица censuspopdata.x/sx Конечно, проrрамма Ехсеl способна рассчитать сумму нескольких BbIД ленных ячеек, IЮ при этом вам все еще необходимо самостоятельно выби рать ячейки для каждоrо из более чем 3000 с лишним oKpyrOB. Даже если на расчет численности населения oKpyra вручную у вас уйдет лишь несколько секунд, то вычисление всей таблицы потребует МI-юrих часов работы. В этом [lроеК'I'е вы напишете сценарий, который будет читать файл элек тронной таблицы с данными l1ереписи населения и рассчитывать статисти ку для всех oKpyroB за несколько секунд. Вот что должна делать данная проrрамма: . читать данные из электронной таблицы Excel; . подсчитывать количество переписных районов в каждом oKpyre; . подсчитывать численность населения, проживающеrо в каждом oKpyre; . выводить результаты. Это означает, что ваш код должен выполнять следующие операции: . открывать документ Ехссl и читать содержимое ячеек электронной таблицы с помощью модуля openpyxl; . рассчитывать Данные, касающиеся количества переписных районов и численности населения, и сохранять их в структуре данных; . записывать структуру данных в текстовый файл с расширением .ру с помощью модуля pprint, 
Работа с электронными таблицами Ехсеl 347 Шо, ,. ЧтеНllе АОНН"'Х Jлектронноi то6ЛII"", в электронной таблице censuspopdata.xlsx существует только один рабо- чий лист с именем' Реrиональная численность населения' , в каждой строке KOToporo хранятся данные, относящиеся к одному переписному району. Столбцами таблицы являются номер переписноrо района (А), сокращен- ное на.звание штата (8), название oKpyra (С) и численность населения в переписном районе (D). Откройте новое окно в файловом редакторе, введите в иеl'О следующий код и сохраните ero в файле readCensusExcel.py. #1 руthопЗ # readCensusExcel.py  Формирует таблицу данных о численности # населения и количестве переписных районов в каждом oKpyre. о import openpyxl, pprint рriпt('Открытие рабочей книrи...') е wb  openpyxl.loadworkbook('censuspopdata.xlsx') е sheet  wЬ.gеtShееtЬупаmе('Реrиональная численность  населения' ) countyOata  {} # ТООО: Заполнить словарь countyData данными о численности # населения и переписных районах oKpyroB. рriпt('Чтение строк...') О for row in range(2, sheet.gethighestrow() + 1): # В каждой строке электронной таблицы содержатся данные для # одноrо переписноrо района. state = sheet['B' + str(row)] .value county = sheet['C' + str(row)] .value рор = sheet['O' + str(row)] .value # ТООО: Открыть новый текстовый файл и записать в Hero # содержимое словаря countyOata. Этот код импортирует модуль openpyxl, а также модуль pprint, который вы используете для вывода окончательных данных по KPYry О. Далее мы от- крываем файл censuspopdata.xlsx 8, получаем лист с данными переписи .. и итерируем по el'o строкам О. Обратите внимание на создание переменной countyData, которая будет содержать данные по численности населения и количеству переписных районов, рассчитанные для каждоrо oKpyra. Однако, прежде чем сохрани'rь в ней что-либо, необходимо точно определить, как должны быть структури- рованы хранящиеся в ней данНЫе. 
341 rлава 12 Ш",2. 3""0."'..' "рук",,,, А".."'Х В качестве структуры данных, сохраняемых впеременной countyDa ta, мы выбираем словарь с сокращенными названиями штатов в качестве ключей. Каждое такое обозначение будет отображаться на дрyrой словарь, ключа ми KOToporo являются строки с названиями oKpyrOB дaнHoro штата. В свою очередь, каждое название oKpyra будет отображаться на словарь, имеющий вcero два ключа  'tracts' и 'рор'. Этим ключам соответствуют количество переПИСНblХ районов и численность населения OKpyra. Словарь BepXHero уровня будет выrлядеть примерно так. {'АК': {'Aleutians East': {'рор': 3141, 'tracts': 1}, 'Aleutians West': {'рор': 5561, 'tracts': 2}, 'Anchorage': {'рор': 291826, 'tracts': 55}, 'Bethel': {'рор': 17013, 'tracts': 3}, 'Bristol Вау': {'рор': 997, 'tracts': 1}, пропущенНblЙ KOД Если бы впеременной countyData был сохранен предыдущий словарь, то мы получили бы следующие рсзультаты. >>> countyData [' АК'] [' Anchorage'] ['рор'] 291826 »> countyData['AK'] ['Anchorage'] ['tracts'] 55 Ключи словаря countyData будут иметь следующий общий вид. countyData[state abbrev] [oKpyr] ['tracts'] countyData[state abbrev) [oкpyr] ['рор') Теперь, коrда вам уже известно, как будут структурированы данные в переменной countyData, вы можете написать код, который заполняет эту структуру данными oKpyra. Добавьте в конце проrраммы код, выделенный ниже полужирным шрифтом. #! python 3 # readCensusExcel.py  Формирует таблицу данных о численности # населения и количестве переписных районов в каждом OKpye. пролущенный KOД for row in range(2, sheet.gethighestrow() + 1): # в каждой строке электронной таблицы содержатся данные для # OДHOO переписноо района. state = sheet['B' + str(row)] .va1ue 
Работа с эпектронными таблицами Ехсеl 349 county = sheet['C' + str(row)] .value рор = sheet['O' + str(row)] .value * rаран'l'ИR существования lCJUOЧа для данно:ro Ш'l'а'l'а. О aountyData.s8t:default(state, {}) * rаран'1'ИЯ сущеС'l'вования кmDча для данно:ro oкpya * дaннoo Ш'l'а'l'а. е countyData[state] .setdefault(county, {'tracts': О, 'рор': О}) f Каждая С'1'рока npeДС'l'ав.пяе'l' один переnисиой район, * n08'l'OМY Инир8М8Н'l'ирО8а'l'Ь на единицу. . countyData[state] [county] [' tracts'] +- 1 * УаепиЧИ'1'Ь чисп8НИОС'l'Ь насепения oкpyra на численноO'l'Ь * насепения пеpenисноо района. .. countyData[state] [county] ['рор'] += int(pop) # ТООО: Открыть новый текстовый файл и записать в Hero # содержимое словаря countyData. Последние две строки кода выполняют всю фактическую вычислитель- ную работу, инкрементируя значение ключа tracts .. и увеличивая значе- ние ключа рор .. для текущеrо oKpyra на каждой итерации цикла for. Необходимость остальной части HOBoro кода обусловлена тем, что вы не можете добавить словарь oKpYI'a в качестве значения для ключа COKpa щенноrо названия штата до тех пор, пока сам ключ не будет (:ущсствовать Q неременной countyData. (Иначе rоворя, инструкция countyOata [' АК'] ['Anchorage'] ['tracts'] +== 1 ВЫ30ветошибку, если ключ 'АК' ещенесуще- ствует.) Чтобы rарантировать существование ключа сокращенноrо назва- ния штата в структуре данных, следует вызвать метод setdefaul t () И ycтaIl вить значение ключа, если для данноro штата оно еще не существует О. Подобно тому как словарю countyData нужен дрyrой словарь в качестве значения каЖДоrо из ключей сокращенных названий штатов, каждый из этих словарей также будет нуждатьс-я в собственном словаре в кa'lccTBe зна чения ключа для каждоrо oKpYl'a О. В свою очередь, каждому из этuхслова рей также требуются ключи 't ract s' И 'рор', значения которых последова тельно увеличиваются, начиная с О. (Если вы чувствуете, (rro потеряли нить рассуждений, вернитесь к рассмотрению примера словаря в началс этоrо раздела. ) Поскольку, если ключ уже существует, метод setdefault () не будет вы- полнять никаких действий, вы можете свободно вызывать ero на каждой итерации ЦИкла for. Шо, 3. 30пll" реJул.,оrо. . фойл Коrда цикл for закончит свою работу, словарь countyData будет содер- жать всю информацию о численности населения и количестве lIереписных 
350 r лава 12 районов, структурированную с помощью ключей по oKpyraM и штатам. На данном этапе вы уже моrли бы написать проrраммный код ДЛЯ запи. си этой информации в текстовый файл или друryю электронную Таблицу Excel. Мы же оrраничимся использованием функции pprint.pformat () ДЛЯ записи словаря countyData в виде массивной строки в файл ceпsиs201 О. ру. Добавьте в конец проrраммы код, выделенный ниже полужирным шриф том (ЭТОТ код должен быть вне цикла, поэтому проследите за тем, чтобы он был введен без отступов). #! python 3 # readCensusExcel.py  Формирует таблицу данных о численности # населения и количестве переписных районов в каждом oKpyre. пропущенный KOД for row in range(2, sheet.gethighestrow() + 1): пропущенный KOД # Открытие HOBOO TeXCTOBOO файпа и Запись в Hero # содержимоо словаря countyData. рrint('Заnись результатов...') re8ultFile = open('oen8UB2010.py', '.') re8ultFile.write('allData = I + ррrint.рfоrшat(соuntуDаta» re8ultFile.cl08e() print (' rOTOBO. ') ФуНКЦИЯ pprint. pformat () создает строку, отформатированную в виде действительноrо кода на языке Python. Выводя ее в текстовый файл census2010.py, вы rенерируете проrрамму На языке Pytlюп из своей Руlhоп- проrраммы/ Это может показаться ненужным усложнением, но теперь вы можете импортировать файл ceпsus2010.py подобно любому друrому модулю Python. В интерактивной оболочке выполНите инструкцию перехода из те- кущеr() раБО'lеrо каталоrа в папку, в которой находится вновь созданный файл census20l0.py (на моем ноyrбуке это папка С:\РуthonЗ4), и импортируй- те этот файл как модуль. »> illlport 08 »> 08.chdir('C:\\Python34') »> iшport cen8u82010 >>> c8n8u82010.allData[ 'АК'] ['Anchorage'] {'рор': 291826, 'tracts': 55} »> anchoragePop - OВn8u82010.allData[ 'АК'] ['Anchorage'] ['рор') »> print ( 'В 201 О %'Оду ЧИCneJUlOC'1'Ь иасе.пeНИR окружоа Anchorage  составпя.па ' + 8tr(anchoragePop» В 2010 roдy численность населения oKpyra Anchorage составляла 291826 
Работа с электронными таблицами Ехсеl 351 Проrpaмма reаdСеnsu.sЕхсеLруотносIПС.я к катеroрии одноразовых: как толь- ко вы получите результаты ее работы, сохраненныс в файле сеп..щs2010.ру, вам lIикоrда не придстся запускать ее повторно. Всякий раз, коrда вам 110- надобятся данные по oKpYI'aM, вы сможете пр<кто импортировать модуль census2010. Расчет этих данных вручную занял бы у вас несколько часов, тоrда как проrрамма управилась с этим за несколько секунд. И<:lIOЛЬЗУЯ модуль OpenPyXL, вы избавитесь от проблем с извлечением данных, сохраненных в электронных таблицах Excel, и выполнением вычислений над ними. 10To- вый код этой nporpaMMbI доступен для заrpузки на сайте http://nostarch. com/automatestuff/. Иде" от"0,,,,е.6"0 tОЗДII"". """.0r"'''6IX "porpll" Excel используется мноrими предприятиями и орrанизациями для xpa нения различных типов ДаННЫХ, и нередко электронные таблицы разраста ются настолько, что работать с ними становится трудно. Любая nporpaMMa, работающая с данными в формате Excel, подобна только что paCCMOTpeH ной: она должна заrрузить файл электронной таблицы, подrотовить соот-- ветствующие переменные или структуры данных, а затем Орl'аНИЗОваl'Ь  работку строк таблицы в цикле. Такие nporpaMMbI MOryr использоваться для решения следующих задач: . сравнение данных, хранящихся в нескольких строках электронноЙ таблицы; . открытие нескольких файлов Ехсе} и сравнение данных, хранящихся в различных таблицах; . поиск пустых строк или недопустимых данных в ячсйках электрон- ной таблицы и вывод предупреждающих сообщений в случае их об- наружения; . чтение данных из электронной таблицы и их использование в каче- стве входных данных проrрамм на языке PytlЮIl. Запись документов Excel Модуль OpenPyXL также предоставляет возможность записывать ДaH ные, а это означает, что ваши nporpaMMbI MOryr создавать и изменять фай- лы электронных таблиц. С помощью PythOIl создание электронных таблиц, насчитыВающиХ тысячи строк данных, не составляет никакоrо труда. 
352 r лава 12 'ОJАIIИllе 11 tохptlиеllllе Aoкyмellro. Ехее' Чтобы создать новый пустой объект Workbook, следует вызвать функцию openpyxl. Workbook (). Введите в инrерактивной оболочке следующие КОМaJЩЫ. »> 11RpOrt openpyxl >>> "ь .. openpyxl. Workbook () >>> wb.g.t sheet naJDеВ () [ , Sheet 1)   >>> sheet .. wb.get active sheet() »> eheet.title   'Sheet' >>> sheet. ti tle .. · SPaIII Васоп Eggs Sheet' »> "Ь. get sheet naaеВ О ['Spam Bacon Eggs Sheet'] Рабочая книrа открывается с одним рабочим листом Sheet. Имя листа можно изменить, сохранив в ero атрибyrе ti t le новую строку. Любые изменения объекта Workbook или ero листов и ячеек не будyr со- хранены в файле электронной таблицы до тех пор, пока вы не вызовете метод save () для этоro объекта. Введиrе в интерактивной оболочке следую- щие команды (предполаrается, что файл exaтple. xlsx находится в текущем рабочем каталоrе). > > > import openpyxl >>> "ь = openpyxl.load. workbook ( I 8X8IIIple. xlsx I ) »> sheet .. wb.get aatIve sheet() »> sheet.tit1e = 'враш SPam Spam' »> "Ь. save (Iexamplecopy .xlsx') Здесь мы нереименовываем рабочий лист. Чтобы сохранить это измене- ние, передаем методу save () строку с новым именем файла. Передача име- ни файла, отличноrо от первоначальноrо, например I example  сору. xlsx I , при водит К сохранению копии электронной таблицы. Всякий раз, коrда вы вносите изменения в электронную таблицу, заrру- женную из файла. сохраняйте ее в файле, имя KOToporo отличается от пер- воначальноrо. Это будет rарантией Toro, <по в случае записи в новый файл некорректных данных или их повреждения из-за ошибок в проrрамме вы всеrда сможете продолжить работу, вернувшись к исходному файлу. 'ОJАIIИllе 11 УАII.еиие Рll60ЧИХ .11"0' Для добавления и удаления листов из рабочей книrи используются соот- веТСТВенно методы create  sheet () и remove  sheet () . Введите в интерактив- ной оболочке следующие команды. 
Работа с электронными таблицами Ехсеl 353 > > > import openpyxl »> _Ь .. openpyxl. Workbook О »> wb.getsheetnam.s () r I Sheet ' ] »> wb.createsheet() <Worksheet "Sheetl"> »> wb.getsheetnam.s О [ 'Sheet " 'Sheet l' ] >>> _b.createsheet(index=O, title=' Первый nИC'l") <Worksheet "Первый ЛИСТ"> »> _b.getsheetnam.s () [ 'Первый лист', 'Sheet', 'Sheet 1 ' ] > > > _Ь. create  sheet (index=2, ti tle= 'Средний nис'l") <Worksheet "Средний лист"> >>> wb.qetsheetnam.s () ['Первый лист', 'Sheet', 'Средний лист', 'Sheetl'] Метод crea te  sheet () возвращает новый объект Worksheet с именем JlистХ, который по умолчанию становится последним листом книrи. При желании можно задать с номощью именованных apryMeHTon index и title не только имя, но и индекс вновь создаваемоrо листа. Продолжите предыдущий пример, в8СДЯ следующие команды. >>> wb.getsheetnames() ['Первый лист', 'Sheet', 'Средний лист', 'Sheetl') »> _Ь. remove sheet (wb. qet sheet Ьу name ( 'Средний nИC'l"» >>> wb.remove:sheet(wb.qet:sheet:by:nue ('Sheetl'» >>> _Ь. get sheet names () [ 'Первый лист', 'Sheet' J Метод remove  sheet () нринимает в качестве apryмeHTa не строку с ИМС нем Листа, а объект Worksheet. Если вам известно лишь имя листа, который вы хотите удалить, вызовите метод get  sheet  by_ пате () и передайте воз вращеннос им значение методу remove  sheet ( ) . Не забывайте вызывать метод save ( ) , чтобы (:охранить изменения после Toro, как добавили или удалили лист из рабочей книrи. 3"..,. 311""e.II. . ."е.к" Занись значений в ячейки во MHoroM напоминает заllИ<Ъ значений в клкr чи словаря. Введите в интерактивной оболочке следующий код. > > > iDaport openpyxl >>> wb = openpyxl. Workbook () >>> sheet = _b.get sheet ьу name('Sheet') >>> sheet[ 'Ы'] = 'З.црав"ётв;Й, мир!' »> sheet [ 'А! ' ] . value 'Здравствуй, мир!' 
354 rлава 12 Если У вас имеются координаты ячейки в ВИДе строки, можете исполь- зовать эту строку в качестве ключа словаря для задания ячейки, значение которой нужно изменить. Проект: 06новпение _пектронной та6пицы В этом проекте мы напишем nporpaMМY, которая обновляет ячейки элек тронной таблицы, содержащей данные об объеме продаж. Проrрамма бу дет просматривать электронную таблицу, находить конкретные виды пр дукции И обновлять их цены. Электронная таблица produceSales.xlsx, которую мы будем использовать (рис. 12.3), доступна для 3а1'рУЗКИ на сайте http: 1/ nostarch.com/automatestuff/. . д 1 lНАИМЕНОВАНИЕ 2 КартофеJ1Ь 3 Бамия 4 Бобы 5 Ар6узы . б Чеснок :.: 7 Пастернак 8 Спаржа 9 Авокадо . 10 СеJ1ьдерей : 11 6амия ::i-c"'4". JI пt1cr !.......,...,........ .."'....._.. .... ... :. ..oтo.o ,  в с L) Е I ЦEHA (18 1 фvнт) ПРОДАНО (фунт) ВЫРУЧКА 0,86 21,6 18,58 2,26 38,6 87,24 2,69 32,8 88,23 0,66 27,3 18,02 ; . 1,19 4,9 5,83 2,27 1,1 2,5 2,49 37,9 94,37 3,23 9,2 29,72 3,07 28,9 88.72 2,26 40 ...!.4.. .... . . 4 I."""":_w..........  @[Qj,Ш 125% !:;. + "",- Рис. J 2.3. Электронная таблица даннь,х об объемах продаж Каждая строка представляет отдельную операцию продажи. Столбцами являются вид проданною продукта (А), стоимость одною фунта продукта (В), проданное количество в фунтах (С) и сумма выручки от продажи (D). Для crолбца ВЫРУЧКА задана формула Excel =ROUND (вз*сз, 2), в соответствии с которой стоимость одноrо фунта продукта умножается на количество проданноrо товара и результат окруrляется с точностью до ближайших c тых. Блаrодаря этому знаЧСIlИЯ ячеек в столбце итоrо будут автоматически обновляться при изменении значений ячеек в столбцах В и С. А теперь представьтс, что цены на чеснок, сельдерей и лимоны были введены неправильно, и вам предстоит утомительный просмотр тысяч строк этой электронной таблицы для исправления данных о цене во всех строках, относящихея к чесноку, сельдерею и лимонам. Воспользоваться операцией поиска и замены с использованием значенИЯ цены в данном слу чае нельзя, поскольку цена какоrо-либо друrоrо продукта может случайно 
Работа с электронными таблицами Excel 355 оказаться такой Же, в результате чеro она буд(.'Т ошибочно раснозпана как неправильная. На выполнение этой работы вручную у вас уйдет MHoro ча сов. Однако вы можете написать ПрOl'}>амму. которая справится с этим за несколько сскунд. Ваша nporpaMMa должна делать следующее: . просматривать все строки в цикле; . изменять IlOказатели цсны для чеснока. сельдерея и лимонов. :-:по означает, что ваш код должен вьпюлнять следующие операции: . открывать файл электронной таблицы; . провсрять для каждой строки, нс содержит ли столбец А значение Ли МОН, Сельдерей или Чеснок. . В случас положительноrо результата проверки изменять значснис цены 8 столбце В. . сохранять электронную таблицу в новом файле (в качестве подстра ховки, чтобы не потерять таблицу с прежними значениями). Ш", '. 'OJA"Hlle "ру"'уры, ,одер."",е. A"IIHwe Р. 06110иенн. Ниже приведены новые цены, которые должны быть внесены в элек тронную таблицу: llимон 1.27 С8J1ltД8рей 1. 19 Ч.снок 3.07 Соответственно, мы моrли бы написать СJIедующий код. if produceName == 'Лимон': ce110bj = 1.27 if produceName == 'Сельдерей': cellObj = 1.19 :f produceName == 'Чеснок': cellObj = З. 07 Однако подход, основанный на жсстко запроrраммироваПIIЫХ данных обновления цен на продукты, не отличается элеra.IПНОСТЬЮ. Если придется вновь обновлять цены, и при этом необязатсльно для тсх же нродуктов, то это нотребует nOBTopHoro изменения кода. Но каждый раз. КOI'Да вы изме- няете код. существует риск внессния в Hcro ошибок в нроцессе ввода. Более rибкий подход состоит в том. чтобы сохраниТl. информацию о скорректированных ценах в видс (ловаря и написать код, ИСII()JIЬЗУЮ 
356 rлаВQ 12 щий эту структуру данных. Откройте новое окно в файловом редакторе и введите следующий код. #! python3 # updateProduce.py  Корректирует цены в электронной таблице # данных об объемах продаж. import openpyxl wb = openpyxl.loadworkbook('produceSa1es.xlsx') sheet = wЬ.gеtShееtЬупаmе('ЛИСт') # Типы продукции и их обновленные цены. PRICE UPDATES {'Лимон': 3.07, 'Сельдерей': 1.19, 'Чеснок': 1.27} # тооо: Создать цикл по строкам и обновить цены. Сохраните этот файл, присвоив ему имя uPdateProduce.f;y. Если вновь Ьо- требуется обновить электронную таблицу, нужно будет обновить только словарь PRICE  UPDATES и никакой ДР)'I"ой код. Ш",2. Про.ер." .tex C1'JIO" . 06.0_е..е "е"орре",.",х "ен в следующей части проrpаммы орrанизуется цикл по в('ем строкам элек тронной таблицы. Добавьте в конце файла uрdatePmduсе.jJyКод. выделенный ниже полужирным шрифтом. #! python3 # updateProduce.py  Корректирует цены в электронной таблице # данных об объемах продаж. пропущенный KOД * Создание цихпа по строхам и обновneние цен. О for rowNwll in range (2, sheet. get  highest  ro_ () ): f npопуС'т4'1'Ъ ft nepsYIG ft строку co1wвn-l) .va1u8 рrоduoeNlШ8 .. sheet. cel1 (rоw=rowNша, if рroduoeNаше in PRICE UЮАТЕв: sheet. 0811 (row-rowNWa, СО1шan=2). va1ue ..  PRICEUPDAТES[produceName] е . .. _Ь. save ( I updatedProduceSales . xlsx' ) 
Работа с электронными таблицами Excel 357 В этом коде циlUl по строкам электронной таблицы начинается со стро- ки 2, поскольку строка 1  это заrоловок О. Ячейка из столбца 1 (т.е. столб- ца А) сохраняется в переменной produceName О. Если ключ produceName СУЩ ствует в словаре PRICE  UPDATESO, значит, это и есть строка, цена в которой подлежит исправлению. Правильное значение цены хранится в элементе словаря PRICE  UPDATES [produceName] . Обратите внимание на то, насколько аккуратнее стал выrлядеть код бла roдаря использованию словаря PRICE  UPDATES. Для обновления всех цен, нуждающихся в исправлении, потребовалась Bcero лишь одна инструкция Н, а не три ин(:трукции наподобие if produceName == 'Чеснок':. А посколь- ку вместо жеСТКОl'О проrраммирования названий и обновленных цен про- дуктов используется словарь PRICE  UPDATES, то при необходимости внести дополнительные изменения в электронную таблицу нужно будет изменить лишь словарь, но не код nporpaмMbI. По завершении просмотра всей таблицы и внесения изменений код со- храняет объект Workbook в файле updatedProduceSales.xlsx О. Тем самым мы избеrаем затирания cTaporo файла электронной таблицы, который может по надобиться нам, если из-за ошибок в nporpaмMe внесенные в таблицу ис правления оказались неверными. Впоследствии, коrда вы убедитесь в кор- ректности обновленноrо варианта электронной таблицы, прежний файл можно будет удалить. rотовый код этой проrраммы доступен для заrрузки на сайте http: / / nostarch.com/automatestuff/. ИАе" оrно'''rеЛ6НО tОЗАIIН". IIНllлоr",нwх пporp".. Поскольку МIЮI'ИМ офисным сотрудникам I1РИХОДИТСЯ постоянно рабо- тать с электронными таблицами Ехсеl, любая проrрамма, способная авто- матизировать процесс редактирования и записи Ехсеl-документов, может принести реальную пользу. Такая npOl'paMMa может быть использована для решения следующих задач: . чтение данных из одной электронной таблицы и их запись в друryю таблицу; . чтение данных с веб-сайтов, из текстовых файлов или буфера обмена и их запись в электронную таблицу; . автоматическое "приведение в порядок" данных, хранящихся в элек- тронной таблице; например, nporpaмMa может использовать реryляр-- ные выражения для чтения телефонных номеров в различных форма- тах и приведения их к единому стандартному формату. 
358 rЛQва 12 Настройка типов wрифтов, испопыуемых в ячейках та6пицы Стилевое оформление определенных ячеек, строк или столбцов можно использовать для выделения важных областей электронной таблицы. Ha пример, в нашей электронной таблице проrрамма может ВЫДелить полу жирным шрифтом строки, содержащие данные, касающиеся картофеля, чеснока и пастернака. Возможно, вы захотите выделить курсивом все CTp<r ки, в которых цена продукта превышает 5 долларов. Стилевое оформление большой электронной таблицы очень трудоемко, но ваша проrpамма может это сделать в <:читанные секунды. Для настройки шрифтов, используемых в ячейках, необходимо импор- тировать функции Font () и Style () из модуля openpyxl. styles. from openpyxl.styles import Font, Style Это позволит использовать в коде запись Font () вместо более длинной записи openpyxl. styles. Font (). (Более подробно об этой форме инструк ции import читайте в разделе "Импортирование модулей" rлавы2.) Ниже приведен пример создания новой рабочей книrи, в которой для шрифта, используемоrо в ячейке Al, устанавливаются курсивное начерта- ние и размер 24 пункта. Введите в интерактивной оболочке СJIедующие K манды. »> import openpyxl »> from openpyxl.styles import Font, Style >>> _Ь = openpyxl. Workbook () »> Bhe8t. wb.qet sheet Ьу nam8('Sh8et') О >>> italic24i'ont =Font(siz;=24, italic=True) е >>> styleObj = Style (font=italic24Font) . >>> sheet[ 'А') .style/styleObj >>> sheet [ 'А1 ') = 'З.цравс'1'ВУЙ мир! ' »> _Ь. save (' styled., xlSX ) Модуль OpenPyXL предоставляет коллекцию стилевых настроек ячейки с помощью объекта Style, который хранится в атрибyrе style объекта Cel1. Чтобы задать стиль ячейки, следует присвоить атрибyry style соответству- ющий объект Style. В Данном примере вызов функции Font (size=24, italic=True) возвра- щает объект Font, который сохраняется в переменной italic24Font О. Именованные арryмеlПЫ функции Font () , si ze и i talic позволяют сконфи- rypировать стилевые атрибyrы объекта Font. Затем этот объект Font пере- дастся вызову Style (font=italic24Font), возвращаемое значение KOToporo 
Работа с электронными таблицами Excel 359 сохраняется в переменной styleObj О. А коrда значение styleObj присваи вается атрибyry style ячейки О, вся информация о шрифте применяется к ячейке А1. Объекты Font Атрибyrы style объектов Font оказывают влияние на то, как текст ото&- ражается в ячсйках. Атрибyrы стиля шрифта устанавливаются пyrем пер дачи именованных apryMclIToB функции Font (). Их перечень приведен в табл. 12.2. Та6пица 12.2. ИмеНОlанные apryMeиты' опредемющие атрибуты style объектов Font ИМ8Нованный apryмem name Тип даииых String Onиса..... size Integer Boolean Boolean НаЗlани. шрифта, например' Calibr i ' ИJlи 'Times New Roman' Разм.р шрифта т rue Д/lя noпY"'PHOro начертания т ru е Д/l1 КУРСИlнаro начертаНИII bold italic Вы можете вызвать функцию Font () для создания объекта Font и coxpa нить этот объект в переменной. Затем эту переменную можно передать функции Style (), сохранить результирующий объект Style в переменной и присвоить эту переменную атрибуту style объекта СеН. Вот пример кода, создающеrо различные варианты шрифтовых стилей. »> import openpyxl »> from openpyxl. styles iJIIport Font, Style >>> _Ь .. openpyx1. Workbook () >>> sheet .. 1fb.g8tsheetbyname('Sheet') »> fontObjl .. Fоnt(name='Тiшes New Raman', bold=True) »> styleObjl .. Style(font=fontabjl) > > > Bheet [ I А1 ' ) . style = styleObj 1 > > > sheet [ , А1 '] .. 'Вold Тiшes New Raman' »> fontObj2 .. Font(size=24, italic=True) »> styleObj2 .. Style(font=fontObj2) »> shееt['ВЗ').stylе" styleObj2 »> вhееt['ВЗ') .. '24 pt Italic' »> wb.save('sty18S.xlsx') Здесь мы сохраняем объект Font в переменной fontObj 1 и и<:пользуем ее ДЛЯ создания объекта Style, который сохраняем в переменной styleObj 1, 
368 rлово 12 после чеrо присваивасм значение этой переменной атрибуту style объекта Сеl1 ячейки Аl. Затем этот процесс повторяется с использованием дрyrих объектов Font и Style для задания стиля второй ячейки. В результате вы- полнения этоrо кода для ячеек Аl и В3 электронной таблицы будyr установ- лены заданные нами стили шрифrа, как показано на рис. 12.4. А В 1 &оld 1Iш!!. New КоlDав 2 с D 3 4 5 24 pt Italic Рис. 12.4. Электронная таблица с модифицированными стилями шрифтов в случае ячейки Аl мы устанавливаем для названия шрифrа пате значе- ние 'Tirnes New Roman', а для начертания шрифта bold  значение trце, поэтому для вывода текста с названием шрифrа "11шеs New Roman" исполь- зуется полужирное начертание. Поскольку размер шрифrа не был нами ука- зан, для Hero используется знаtlение 11, установленное модулем openpyxl по умолчанию. В случае ячейки В3 наш тек(:т выводится с использованием  сивноrо начертания и с размером шрифта, равным 24. Поскольку название шрифта не было нами указано, используется шрифт Calibri, установленный модулем openpyxl по умолчанию. Формулы Формулы, начинающиеся со знака равенства, позволяют устанавливать для ячеек значения, рассчитанные на основании :шачений, хранящихся в дрyrих ячейках. В этом разделе вы будете использовать модуль openpyxl для проrраммноrо добавления формул в ячейки тем же способом, которым ячейкам присваиваются обычные значения, например: »> sheet('B9') = '=SUM(Bl:B8) 1 Эта инструкция сохранит =SUМ (81: В8) в качестве значения в ячейке В9. Тем самым для ячейки В9 задается формула, которая суммирует значения, хранящиеся в ячейках от Вl до 88. Результат показан на рис. 12.5. Формула задается подобно любому дрyrому текстовому значению в ячей- ке. Введите в интерактивной оболочке следующие комаНДЫ. 
Работа с электронными таблицами Ехсеl 361 . :oe ,:;.-6eli...'..... .Шр"фт '.' €1:.1(.I.ttИ6!1Н В9 f. =СУММ(Вl:В8) А в с D Е 1 82 2 11 3 8S 4 18 5 S7 б Sl 7 38 .;..... 42 . 9 i 8CErO З84 ( 10 . .1:1.  . _. . .... _. ... _ : L  _.... p J. .=..J.1!!.q. ..._Дf. ,,',,,,"..'. .: roтo.o ,. ._......._ _ ". ._ __:--. .___. "''""40 ".""'" '_ .._... ":'_ ., ._.... - __ . - . . о.. . Рис. 12.5.8 ячейке 89 содержится формула =BUМ (В1: В8), которая складывает содержимое ячеек от 8 I до 88. >>> illlpOrt openpyxl >>> _Ь = openpyxl. Workbook () »> sheet = wb.get activ8 sheet() >>> sheet[ 'М') . 200  »> sheet[ 'А2') . 300 >>> sheet['A3') · '=вUМ(Al:A2)' »> _Ь. еаУе (' wri teFormula. xlsx 1) Для ячеек Аl и А2 устанавливаются значения 200 и 300 соответственно. Значение в ячейке А3 определяется формулой, складывающей значения ячеек Аl и А2. Если открыть эту электронную таблицу в приложении Excel, то в качестве значения ячейки А3 отобразится 500 . Хранящуюся в ячейке формулу можно читать, как любое друrое значе- ние. Однако, если вы хотите увидеть резулъmат ра(:чета ПО формуле, а не саму формулу, то при вызове функции loadworkbook () ей следует передать име- нованный apryMeHT dataonly со значением True. Это означает, что объект Workbook может отображать либо формулы, либо результаты вычисления формул, но не то и друrое. (Однако ничто не мешает вам иметь несколько заrруженных объектов Workbook для одной и той же электронной таблицы.) Чтобы увидеть разницу между заrрузкой рабочей книrи с использованием и без использования именованноrо apryмeHTa dataonly, введите в интерак- тивной оболочке следующие команды. >>> import openpyxl »> wbFormulas · openpyxl.loadworkbook(lwriteFormula.xlsx') >>> sheet = wbFormulas. get acti V8  sheet () 
362 r лава 12 »> sheet [ 'АЗ ' ] . value '=SUM (Аl :А2) , >>> _bDataOnly · openpyxl.load  _orkbook ( '_ri teFormula. xlвх' , ааи only=True) »> sheet · wbDataOnly.getactivesheet() »> еh88t['АЗ'].vаluе 500 Здесь при вызове функции load  workbook () с передачей ей именованно- 1"0 apryMeHTa dataonly=True в ячейке А3 отображается значение 500, кото- рое является результатом вычи(ления формулы =SUM (Al : А2) , а не TeK(TOM самой формулы. Формулы Ехсеl ПРИВIIОСЯТ определенный уровень проrpаммируемости в ЭJIсктронные таБJIИЦЫ, но по мере усложнения задач работать с ними становится все труднее. Например, даже при отличном владении инстру ментом формул Ехсе} попытки понять смысл формулы ==IFERROR (ТЮМ (IF (LEN (VLOOKUP (F7, Sheet2!$A$1:$B$lOOOO, 2,FALSE))>0,SUBSTITUTE(VLOOKUP (F7, Sheet2!$A$1:$B$lOOOO, 2, FALSE)," ", ""),"")), "") моryrдовестидо rоловной боли. Читать код на языке PytlЮIl roраздо Леrtlе. Настройка строк и стоп6цов в Ехсеl изменить размер строки и столбца не составляет никакоrо труда. Для этоrо достаточно перетащить мышью rраницу заrоловка строки или столбца в желаемую позицию. Но если вам необходимо устанавливать раз-- меры строк и столбцов в зависимости от содержимоrо ячеек или задавать их размеры ДJIЯ большой совокупности файлов электронных таблиц. то бу дет rораздо быстрее написать проrрамму на Python. которая сделает это вместо вас. Кроме '1'01"0, строки и столбцы можно полностью скрывать из виду. Их также можно закреплять на месте, чтобы они всеrДа были видны на экране и появлялись на каждой странице при выводе электронной таблицы на печать (эту возможность очень удобно использовать в отношении заrоловков). ИII"роiКII '"'0''' "рок 11 .IIPll.W "оn6"о. Объекты Worksheet имеют атрибyrы row dimensions и colUI!U1 dimensions, которые управляют высотой строк и шириной столбцов. Введите в инте- рактивной оболочке следующие команды. >>> import openpyxl »> _Ь - openpyxl.Workbook() »> sheet = _b.getactiveBheet() 
Работа с электронными таблицами Ехсеl 363 »> sheet['Al') = 'ВЫсокая строка' »> sheet['B2') = 'Широкий сопбец' »> sheet.rowdimensions[1).hei9ht = 70 »> sheet.column dimensions['B'].width = 20 »> -Ь.sаve('dim8nsiоns.хlsх') Атрибyrы rowdirnensions и co1umndirnensions рабочеro листа представ ляют собой значения, подобные словарю. Атрибyr rowdirnensions содер- жит объекты RowDirnension, а атрибyr co1umndirnensions содержит объекты Co1umnDimension. Доступ к объектам в rowdirnensions осуществляется с и(- пользованием номера строки (в данном случае  1 и 2), а доступ к объектам в со1шnn  dirnensions  с использованием буквы столбца (в данном случае  А и В). Внешний вид электронной таблицы dirnensions.x1sx представлен на рис. 12.6. д В 1 Высокая строка 2 Широкий столбец  Рис. 12.6. Для строки 1 и столбца В установлены большиs значения высоты и ширины Получив доступ к объекту RowDirnension, вы можете задать высоту строки, тоща как получение доступа к объекту Co1urnnDimension позволяет YCTaHO вить ширину столбца. Для указания высоты строки разрешено использова ние целочисленных или вещественных значений в диапазоне от О до 409. Единицами измерения служат пУllкт'Ы (1 пункт равен 1/72 дюйма). Поумол чанию высота строк устанавливается равной 12.75. Для указания ширины столбца разрешено использовать целочисленныс или вещественные значе нин в диапазоне от О до 255. Это значение определяет допустимое к()личе (TBO символов с заданным по умолчанию размером шрифта (11 пунктов), отображаемых в ячейке. По умолчанию ширина столбцов составляет 8.4 3 символа. Столбцы с нулевой шириной и строки с нулевой ВЫсотой невиди- мы для пользователя. 
364 rЛаsа 12 ел".""е " от.е"а ,Л"."". .,еек Ячейки, занимающие прямоуrольную область, MOryт быть объединены в одну ячейку с помощью метода mergecells () рабочеrо листа. Введите в интерактивной оболочке следующие команды. >>> iIIIport openpyxl »> .Ь . openpyxl.Workbook() »> sheet · wb.qet active sheet() >>> sheet.merqe ceTls ('Al:DЗ 1) »> sheet[ 'Al' ).. '06'Ъ8ДИН8НЫ дзенадц8'l'Ь ячеек.' »> sheet.merqecells('CS:ES') > > > sheet [ , С5 1) .. 'О&ъединены три ячейки. ' »> .b.save('merqed.xlsx') ApryMeHToM метода mergecells () служит (:трока, используемая для ука- зания верхней левой и нижней правой ячеек прямоyrольной области. При- надлеЖащие ей ячейки должны быть объединены в одну: строке 'Аl: 03 ' соответствует слияние 12 ячеек. Чтобы у(:тановить значение для этой единственной ячейки, достаточно установить значение для верхней левоЙ ячейки исходной rруппы. Выполнив этот код, вы получите результат, представленный на рис. 12.7. А в с D Е 1 2 3 Объединены двенадцать ячеек 4 5 Объединены ТрИ ячейки 6 7 Рис. 12.7. ОбьединеНИSllчеsк 8 электронной таблице Чтобы отменить слияние ячеек, вызовите метод un.merge  сеllз () рабоче- 1"0 листа. Введите в интерактивной оболочке следующие команды. > > > import openpyxl »> _Ь - openpyxl.loadworkbook('merqed.xlsx') »> sheet = wb.qet activ8 sh8et() »> sheet.unmerqeCellB ('А1 :DЗ') »> sheet.unmerqecells('CS:E5') »> wb.save('merged.xlsx') 
Работа с элект р онными таблицами Ехсеl 365 Если вы сохраните Изменения и взrлянете на таблицу. то увидите, что слившиеся ячейки вновь разделились. 3Dкреnnеннео6ЛD"е. Если размер электронной таблицы настолько велик, что ее нельзя уви деть целиком, можно заблокировать несколько верхних строк или крайних слева столбцов в их позициях на экране. В этом случае, например, пользо-- ватель всеrда будет видеть заблокированные заrоловки столбцов или строк, даже если он прокручивает электронную таблицу на экране. Такие забло-- кированные в своих позициях ячейки называют зш/фепле1l1lыuu облас1llЯМ,U (freeze panes). В модуле OpenPyXL у каждоrо объекта Worksheet имеется атрибyr freezeyanes, значением KOToporo может служить объект Cell или строка с координатами ячеек. Обратите внимание на то, что все строки и столбцы, расположенные соответственно выше и левее, будyr заблокиро-- ваны, но строка и столбец, в которых расположена сама ячейка, останутся свободными. Чтобы отменить блокирование всех закрепленных областей, доста- точно установить значение атрибута f ree ze yane s равным None или 'А1'. в табл. 12.3 показано, какие строки и столбцы будут заблокированы при некоторых значениях атрибута freezepanes, выбранных в качестве при- мера. Табпица 12.3. Примеры эакрепленныx областей Настройка атрибута freeze "'рапе 8 sheet.freezepanes 'А2' sheet.freezepanes 'В1' sheet.freezepanes 'Сl' sheet.freezeyanes 'С2' sheet.freeze panes 'Аl' или sheet.freeze=panes None Эабпокироваии... C'IpOКИ И croп6цм Строка 1 Столбец А Столбцы А и В Строка 1 и стопбцы А и В Закрепленные области отсутствуют Убедившись в том, что в текущем рабочем каталоre находитс.я файл элек- тронной таблицы с данными о продажах, заrруженный вами ранее на сайте ::ttp: //nostarch.com/automatestuff/, введите в интерактивной оболочке следующие команды. >>> import openpyxl »> wb = op8npyxl.loadworkbook('produoeSale8.x18x') »> Bheet · wb.getactive8heetO »> sheet.freezeane8 - 'А2' »> wЬ.8&v.('frееzеЕхашрlе.х18Х') 
366 rnaBO 12 Если установить для атрибута freezepanes значение' А2', то строка 1 будет всеrда оставаться видимой, независимо от прокрутки электронной таблицы. Результат представлен на рис. 12.8. !J;:Иld). ,. ... .r.ная .. .. НlБОl . . ! А i 1 НАИМЕНОВАНИЕ ! i59i .Ббы :\ i 1592 rрейпфрyr t i 1593 Зеленый перец ;194 Арбузы : 1595 Сельдерей ,1596 ЗеМЛRника . 1 r:;q7 ..':\Рn""''''й пn'()IIII'K . " ...I.,.лllg...:) . rOToIO ;  . Мiсrosoft Excel  @J , "" Раз..tтr.а cтpaH ' Формулы Данные Цtti!MPONH\<e Вид Acro"at '<:> ([J в с D Е ЦEH J!.+T) O (Фу1.. .I. .. ..... 2,69 0,7 1,88 0,76 28,S 21,66 1,89 37 69,93 0,66 30,4 20,06 3,07 36,6 112,36 4,4 5,5 24,2 ...1."'7... . ;=..>=1,-:,=,,::.; 13 :ijj;:lli 125% ".:'- ,. т Рис. 12.8. При установке эначения атрибута freezeyaпes равным' А2' строка 1 всеrда будет оставаться видимой, неэависнмо от прокрутки электронной таблицы на экроне Диаrpаммы Модуль Opel1PyXL поддерживает создание rистоrрамм, rрафиков, а TaK же точечных и KpyrOBbIX )щаrрамм с использованием данных, хранящихся в электронной таблице. Чтобы создать диarpамму, необходимо выполнить следующие действия: 1) создатьобъект Reference на основе ячеек в пределах выделенной пря- моyrольной области; 2) создать объект Series, передав функции Series () объект Reference; 3) создать объект Chart; 4) при соеДИНИТЬ объект Series к объекту Chart; 5) дополнительно моЖно установить значения переменных drawing . top, drawing .left, drawing. width и drawing. height объекта Chart, опреде- ляющих положение и размеры диarраммы; 6) добавить объект Chart в объект Worksheet. Следует сдслать несколько Пояснений отн()(ительно объекта Reference. Объекты Reference создаются путем вызова функции openpyxl. charts. Reference () , принимающей три apll'MeHTa, которые описаны ниже. 
Работа с электронными таблицами Ехсеl 367 1. Объект Worksheet, содержащий данные вашей диаrраммы. 2. Кортеж, который состоит ИЗ двух целых чисел, представляющих верхнюю левую ячейку выделенной прямоyrольной области ячеек, в которых содержатся данные вашей диаrраммы: первое целое число задает строку, второе  столбец. Обратите вниманис на то, что пер вой строке соответствует 1, а не О. 3. Кортеж, который состоит из двух целых чисел, предстаВJIЯЮЩИХ ниж IIЮЮ правую ячейку выделенной прямоyrольной области ячеек, в КО- торых содержатся данные вашей диarраммы: первое цслое число за даеТ строку, второе  столбец. Некоторые иллюстративные при меры задаНИЯ координат с помощью apryмeHToB приведены на рис. 12.9. Дl ,. вз J,. cs f. А D D ... D 1-' 1 J. I ,,.: . , .5", в ' 7 3 9 о- !l .s,o & 1::::1 .. '" Joo. !J!T 1 '. .t:(.T  0'0 ;.L!T- r ОТ050 :J 14 <1 .. ..oп.!.1. . .Лlt(О.. r11'::Z rOT050 .::J' ! 9 1: '!I " · · " . Л"Ч.1. .:-JI!lЧ...;1.;.':.:: rOTOIO , Рис. 12.9. Слева направо: (1, 1), (1 О, 1); (3, 2), (6, 4); (5, 3), (5, 3) Создайте rистоrрамму и добавьте ее в электронную таблицу, введя JJ ин терактивной оболочке следующие команды. >>> from орепрух1 import Workbook »> from openpyxl.chart import ВarChart, Ref8rence >>> _Ь - Workbook () »> sheet - _b.aotive »> Bh88t[ 'А1'] - "Серия 1" »> for i in ranq8(1,11): »> sheet.app8nd( [i]) »> ohart - BarChart() ,» ohart. tit1e - "Первая серия данных" »> data - Referenae(sheet, min 001-1, min ro..l,  шах 001-1, шах row=ll)   »> chart.add c1ata(data, tit18s from data=True) о» sheet.adcCchart(ohart, "С2")  > > > _Ь. save (" s8DIp1eChart1. х1вх") 
368 r лава 1 2 Вид полученной электронной таблицы представлен на рис. 12.10. Д1 f,. Серия 1 А В С [) Е G '" .1 I Се р ия 1 I 2 1 Первая серия данных з 2 4 3 12 5 4 б 5 10 7 б 8 S 7 I 9 8 б 11 iI СеРИЯ 1 10 9 11 10 4 11 [2 I 13 . 14 О 15 3 4 5 б 7 8 9 10 16 Рис. 12. 10. Электронная таблица с добавленной диаrраммой Мы создали rистоrрамму с помощью метода openpyxl. chart. BarChart ( ) . Точно так можно создавать rрафики, а также точечные и KpyroBbIe диа l'paMMbI, вызывая методы openpyxl. chart. LineChart () , openpyxl. chart. ScatterChart () и openpyxl. chart . PieChart ( ) . к сожалению, в текущей версии модуля OpenPyXL (2.3.3) функция loadworkbook () не позволяет зarружать диarраммы, хранящиеся в файлах Ехсеl. Даже еСJlИ в файле Excel содержатся диarpаммы, зarруженный объект Workbook нс будет их включать. Если вы зarрузите объект Workbook и сразу жс сохраните cro в том же .хlsфайлс, то это приведст к удалению из Hero диаrpамм. РеЗlOме Зачастую наиболсе сложпой частью обработки информации является не сам процесс обработки, а получение данных в формате, который подхО" дит для вашей nporpaмMbI. Но как только ваша электронная таблица будет заrpужена в PytllOl1, вы сможсте извлскать данныс и манипулировать ими rораздо быстрее, чем если бы делали это вручную. Кроме Toro, электронные таблицы MOryr rенерироваться вашей про rраммой в качестве выходных данных. Поэтому, если вашим коллеrам пО" надобится, чтобы ваш текстовый файл (или РDFдокумснт), содержащий данныс о тысячах операций по продажам, бbIJI нрсобра30ван в файл элек тронной таблицы, вам не придстся кропотливо копировать и переносить их вручную в Ехсеl. 
Работа с электронными таблицами Excel 369 'Iеперь, коrда вы имеете в своем распоряжении модуль openpyx1 и обла- даете определенными навыками проrраммирования, обработка даже очень больших электронных таблиц покажется вам сущим пустяком. КОНТРОRьные вопросы Отвечая на приведенные ниже вопросы, представьте, что у вас Име ются следующие объекты, хранящиеся в указанных переменных: объект Workbook  переменная wb, объект Worksheet  переменная sheet, объект Cell  переменная cell, объект Comment  переменная comm и объект Image  переменная img. 1. Что возвращает ФУНКIщя openpyxl.1oadworkbook ()? 2. Что возвращает метод getsheetnames () объекта Workbook? 3. Как получить объект Worksheet для рабочеrо листа с именем' Sheetl ,? 4. Как получить объект Worksheet для активною листа рабочей книrи? 5. Как получить значение ячейки С5? 6. как установить значение "Hello" для ячейки С5? 7. как получить номера строки и столбца ячейки в виде целых чисел? 8. Что возвращают методы gethighestco1umn() и gethighestrow() рабочеrо листа и к какому типу данных ОТНОСЯ'rся эти возвращаемые значения? 9. Какую функцию следует вызвать, чтобы получить целочисленный ин- декс столбца 'М'? 10. Какую функцию следует вызвать, чтобы получить строковое имя столбца 14? 11. как получить кортеж всех объектов Се 11 для ячеек от Аl до 1" 1 ? 12. как сохранить рабочую книry в файле exaтple.xlsx? 13. как задать формулу в ячейке? 14. Что вы должны сделать в первую очередь, если хотите получить ре- зультат вычисления формулы, заданной в ячейке, а не саму формулу? 15. Как изменить значение высоты строки с 5 на 100? 16. как скрыть столбец С? 17. Назовите несколько средств, которые модуль OpenPyXL 2.1.4 не за- rpужает из файла электронной таблицы. 18. Что такое закрепленная облаС'rI.? 19. Какие пять функций и метОДов вы должны вызвать, чтобы создать rи- cTorpaммy? 
370 r пава 12 Учебные проекты Чтобы закрепить полученные знания на практике, напишите проrрам мы для предложенных ниже задач. 'еиер",ор ,,,ми" YJI"оаеи", Создайте проrpамму rnultiplicationTable .ру, которая принимает число N из командной строки и (оздает таблицу умножения размером NxN в элек- тронной таблице Ехсеl. Например, если запустить проrpaмму следующим образом: ру mUltiplicationTable.py 6 то она должна создать электронную таблицу, которая выrлядит так, как по- казано на рис. 12.11. А В С D Е F G 1 I I 1 Z 3 4 5 6 2 1 1 2 3 4 S 6 3 Z 2 4 6 8 10 12 4 3 3 б 9 12 lS 18 5 4 4 8 12 16 20 24 б 5 5 10 lS 20 2S 30 7 6 6 12 18 24 30 36  Рис. 12. 11. Таблица умножения, сrенерироsанная s электронной таблице Для ра;JМетки таблицы умножения должны использоваться значения в строке 1 и столбце А, отображаемые полужирным шрифтом. nporp".." АЛ' "ТIIBK" пytrwx "рок Создайте проrрамму bla:1!<Rowlr.serter. ру, которая принимает два целых числа и строку с именем файла в качестве apryмeHToB командной строки. Обозначим первое число буквой N, а второе  буквой М. Проrрамма долж- на вставлять в электронную таблицу М пустых строк, начиная со строки N. Например, если вызвать проrрамму следующим образом: python blankRowInserter.py 3 2 myProduce.xlsx то электронная таблица будет выrлядеть до и после работы проrраммы так, как показано на рис. 12.12. 
Работа с электронными таблицами Excel 371 Аl ' Картофель Дl j;. I<артофель О f Кукур.,..а Арб",. rреипфрУ' Вишни , А, В С " Е :.1  еЛb,Цl!реБа"'ИА y.ypy.. Ар6у... : ] 6аМИА Ба ми А Б06.. 'РеипфрV'lIмwни : 3 Боб.. Шпин,т Арбу... Иl\\бирь Ябло_и  Арбу... Orypl.,", Имбир. Ба_лажан rрепф Чеснок Абрикось ку_уру.. OryPLl" Виноrр. Пастернак БаМИА rреипфру капуст. 3е"аны 7 Спаржа Б06ы им6ир. Ба_л.жан Помидо 6 Аво_.до 6 ..1 Бак".жан О Ц.' Аб ико А . В С . 1 IкарТофе/llc е"ь.цере Б.мия 2 Ба,,"ИА Б'МиА Бо6., : 3 .4 . 5 Бобы Шпинат Арбу... Имбир. яблоки : б АpбyI.. OrypЦOI Им6ир. БаК/lаж.!lН rреипф . 7 Часнок А6рикось Кукуру.а OryPLl" Виноrра . Е Пастернак Б',,"ИА rреипфру к.пуста 3е/lены Рис. 12.12. Вид электронной таблицы ДО (слева) и после (справа) вставки двух пустых строк в строке 3 Прежде вcero ваша проrpaмма должна читать содержимое электронной та- блицы. Затем в процессе записи новой таблицы проrpамма использует цикл for для копирования первых N строк. для каждой И3 оставшихся строк про- rpaмMa будст прибавлять М к номеру строки в выходной электронной таблице. OTp".eHlle электронной т"6ЛII",, отноtнтел'но IIН"'ОН"ЛII Напишите проrрамму. меняющую строки и столбцы электронной таб- лицы местами. Например. она должна пере водить значение ячейки И3 столбца 3 строки в столбец 5 строки 3 (и обратно). Аналоrичным обраЗ0М должны быть инвертированы все ячейки электронной таблицы. Например, электронная таблица До и после такой инверсии будет выrлядеть так, как Qоказано на рис. 12.13. А1 J. нАиМЕНОВАНИЕ .  в '. i' IНдИМЕНО8АНиEJ, ВblРУЧКд : 2 картофель 334 3 Б'МИА 252 4 Б06.. 238 5 Ар6у.", 516 б Чеснек 98 7 П.стерна_ 16 в Спаржа 335 . 9 Авокадо в4 lО D б н Аl /. НАИМЕНОВАНИЕ . А В С 1 IНАИМЕНОВАНИEl картофель Б',,"ИА :: ВЫРУЧКА 334 .1 :4 . S о Боб.. 252 G Н Ар6у... Чеснек Пастарн.к Спаржа Аеокадо 238 S16 98 16 ЗЗS s4  8 . ч lC Рис. 12.1 З. ВИД электронной таблицы до (вверху) и после (внизу) инверсии 
372 rлова 12 При написании этой проrраммы вЫ можете использовать вложенные циклы ДЛЯ чтения данных электронной таблицы и их переноса в CTPYK туру данных типа списка списков. Для хранения содсржимоrо ячейки из столбца у строки х электронной таблицы вы можете использовать элемент sheetDa ta [х] (у] этой структуры данных. Затем, коrДа вы будете записывать данные в электронную таблицу, используйте для ячейки в столбце х строки у значение элемента sheetData [у] :х]. Прео6РОЗО'ОНllе ,eKtтO.",X фойло. . элек,ронную ,о6ЛII"у Напишите проrрамму, которая читала бы содержимое нескольких тек- стовых файлов (возможно, созданных вами) и вставляла ero в электронную таблицу по одной текстовой строке в одну строку таблицы. Строки перв ro TeKcToBoro файла будyr заполнять ячейки столбца А, BToporo  ячейки столбца В и Т.д. Используйте метод readline s () объекта File для возврата списка строк, по одной строке На одну текстовую строку файла. В случае первоrо файла выведите первую текстовую строку в столбец 1 строки 1. Вторую текстовую строку следует записать в столбец 1 строки 2 и Т.д. СОl\ержимое следующеrо файла, прочитанное с помощью метода readl ines () , будет записываться в столбец 2, дальнейшеrо файла  в столбец 3 и Т.д. Прео6РОЗО'ОНllе элек,ронной ,о6ЛII"", . тeKtтO.",e фо..", Напишите проrрамму, которая выполняла бы операции предыдущей проrраммы в обратном порядке. Проrрамма должна открывать электрон- ную таблицу и записывать содержимое ячеек столбца А в один текстовый файл, содержимое ячеек столбца В  в дрyroй текстовый файл и Т.д. 
PASOTA С ДОКУМЕНТАМИ В ФОРМАТАХ PDF И WORD Документы в форматах PDF и Word представляют собой бинарные файлы, и IlОЭТОМУ работать с ними rораздо слож нее, чем с простыми текстовыми файлами. Помимо текста в них содержится масса дополнительной информации, опи сывающей шрифты, используемые цвета и компоновку д<r кумента. Если вы хотите, чтобы ваши проrраммы читали и записывали PDF или Wоrd-документы, вам потребуется нечто большее, не- жели просто передача имен файлов функции open ( ) . к сча(тью, в PytllOn предусмотрены модули, упрощающие взаимодей ствие с документами в форматах PDF и Word. В этой rлаве будут рассмотре- ны дВа таких модуля: PyPDF2 и PythonDocx. PDF-АОКУМ8НТbI PDf'  это сокращенное название формата Portable Document Pormat (фор-- \шт переносимых документов), для KOToporo используются файлы с расши рением .Pdf Несмотря на то что формат PDF поддерживает множество воз \fожностей, в этой rлаве мы сосредоточимся на двух из них, с которыми вы будете чаще всею иметь дело: чтение TeKCToBoro содержимоrо PD}o'-файлов 11 создание новых PDf'-документов на основе существующих. Для работы с РDFдокументами вы будете и(пользовать модуль PyPDF2. Чтобы установить ero, выполните команду pip install PyPDF2 в командной строке. Указанное здесь имя модуля чувствительно к реrистру, поэтому .1ИШЬ буква у должна вводиться в нижнем реrистре; все остальные буквы в нем вводятся в верхнем реrистре. (Более подробно процедура установки модулей, разработанных третьими сторонами, описана в приложении А.) Признаком 1'01"0, что модуль установлен корректно, является отсутствие сообщений об ошибке при выполнении команды import PyPDF2 в интерак- тивной оболочке. 
374 r лава 13 Пробnемотичность формата PDF РDFфайлы являются замечательным средством для передачи документов в эпек тронном виде, которые пользователям будет nerKo читать и ВЫ8ОДИТЬ на печать, од- нако 8ыоnнитьb их синтаксический анаnиз с цепью преобразования 8 простой текст С помощью друrих проrрамм  задача нетривиальная. А раз так, модуль PyPDF2 может вносить ошибки при извлечении текста из РDF-фойпа или вообще оказаться не в состоянии открыть некоторые докуменТы в РDF-формате. К сожаnению, с этим почти ничеrа нельзя поделать. 8 подобных ситуациях модуль PyPDF2 просто не смо- жет работать с некоторыми из ваших РDF-фойлов. Тем не менее за С80Ю практику я еще ни разу не сталкивалО! с тем, чтобы мне не удалось открыть РDF-файл с по-- мощью модуля PyPDF2. Извлеченне reKtrII IIЗ 'DF-фо.nо. Модуль PyPDF2 не располаrает возможностями извлечения изображ ний, диаrрамм и друrих мультимедийных данных из pl)F-документов, но способен извлекать текст и возвращать ero в виде строки Python. Присту- пая к изучению работы модуля PyPDF2, мы применим еro к примеру PDF- документа, представленному на рис. 13.1. ЗaI1>узите этот РDFдокумент с сайта http://nostarch. corn/automatestuff/ и введите в интерактивной оболочке следующие команды. »> import PyPDF2 »> pdfFileObj = open('meetingminuteB.pdf', 'rb') >>> pdfReader = PyPDF2. PdfFileReader (pdfFileObj) О »> pdfReader. numPaqes 19 е >>> paqeObj = pdfReader. qetpaqe (О) . >>> paqeObj .extractText() 'OOFFFFIICCIIAALL BBOOAARRDD MMIINNUUTTEESS Meeting of March 7, 2015 \п The Board of E1ementary and Secondary Education shall provide 1eadership and create policies for education that expand opportunities for children, empower families and communities, and advance Louisiana in ап increasingly competitive globa1 market. BOARD of ELEMENTARY and SECONDARY EDUCATION I Прежде Bcero, импортируйте модуль PyPDF2. Затем откройте файл meeting:minutes.Pdfn режиме чтения двоичных данных и сохраните ero в переменной pdfFileObj. Получите объект PdfFileReader, представляющий этот РDF-документ, вызовите метод PyPDF2. PdfFileReader () и передайте ему объект pdfFileObj. Сохраните этот объект PdfFi leReader в переменной pdfReader. 
Работа с документами в ф орматах PDF и Word 375 8ОМО of ELEMENТARY and SECONDARY ЕDUCA'ПОN !Ii,' }l\lщdА 't1t''''t't1''',,, o.IRl' ,,'IJ"J"п, :[il."'lIIilll ,h.,,' 1""""'" li>a";k:fh,1' dN,l I ,'II',' '''11', 4" 'I!t c'J'j( "II'P UtiJl x,.,jпJ '!РР-" IM»i,,,.. /,.". (/i,/J,"rtl. "-"".","'1" ,.Шlllk'.' ",.,1 ..I,.т'и""..... оАшl tlc"'dtkt' .t:.'ttlL\i"'k4 ", ,Iп ".аt'.,.\tщ,/ c,'tII"'."'I\.....rl.'t,."'j".k..,. OFFICIAL BOARD MINUTES Meeting of March 7, 2014 Рис. 13.1. Страница РDF-документа, из КОТорОЙ мы будем извлекать текст Полное количество страниц в этом документе хранится в атрибyrс ....::nPages объекта PdfFileReader.. Документ из этоrо примера содержит 19 страниц, но мы извлечем из Hero только текст первой страницы. Чтобы извлечь текст страницы, необходимо получить объект Page, ко- rорый представляет одиночную РDF-страницу из объекта PdfFileReader. Объект Page можно полуtlИТЬ, вызвав метод getPage () . для объекта : i:FileReader с передачей ему номера интересующей вас страницы, в дaн НОМ случае  О. В модуле PyPDF2 используется индексация страниц с отсчетом от пуля: первая страница  это страница О, вторая  страница 1 и Т.д. Такой порядок Н\'Мерации соблюдается всеrда, даже в тех (:лучаях, Коrда нумерация CTpa ниц в документе иная. Например, предположим, что ваш документ PDF представляет собой трехстраничную выдержку из длинноrо отчета и ero (rраницы имеют номера 42, 43 и 44. Чтобы получить первую страницу это- .0 документа, следует делать вызов pdfReader .getPage (О), а не getpage (42) .LШ getPage (1). 
376 rлава 13 Получив объект Page, вызовите ero метод extractText () , который воз- вратит строку, содержащую текст страницы .. Извлечение текста не про- ходит идеально: РDF-текст Charles Е "Chas" Roeтer, Presidentвыпал из строки, возвращенной методом extractText (), а в некоторых местах нарушена ра..l- рядка. И все же даже это неидеальное текстовое содержимое может быть достаточно подходящим для вашей проrраммы. Дешифро.ан"е PDF-Аоку.енrо. Некоторые РDF-документы MOryт быть зашифрованы, что препятствует их прочтению теми, кто будет открывать документ, не предоставляя пароль. Введите в интерактивной оболочке следующие команды, пытаясь открыть заrруженный вами РDF-документ, который зашифрован паролем rosebud. »> im.port PyPDF2 »> pdfReader · PyPDF2. PdfFileReader (open ( , encrypted. pdf' , 'rb'» о »> pdfReader. i8Encrypted True »> pdfReader. q8tPaq8 (О) е Traceback (most recent call last): File "<PyShell#173>", line 1, in <module> pdfReader.getPage() пропущенный KOД File "C:\Python34\lib\sitepackageS\PyPDF2\pdf.py", Нпе 1173, in getObject raise utils.PdfReadError("file has not been decrypted") PyPDF2.utils.PdfReadError: file has not been decrypted . > > > pdfReader. decrypt ( I r08ebud' ) 1 >>> paqeObj · pdfReader. qetPaqe (О) у всех объеКТОIJ PdfFileReader есть атрибyr isEncrypted, который име- ет значение True, если РDF-документ зашифрован, и False  в противном случае О. Любая попытка вызвать функцию, пытающуюся прочесть файл, прежде чем он будет дешифрован с использованием правильноrо пароля, вызовет ошибку О. Чтобы ПрОЧИl'ать зашифрованный РDF-документ, вызовите функцию decrypt () , передав ей пароль в виде строки .. Вызвав функцию decrypt ( ) справильным паролем, вы увидите, что вызов метода getpage () больше не сопровождается выводом сообщения об ошибке. В случае предоставления HeBepHoro пароля функция decrypt () возвратит О, и метод getpage () не сможет быть выполнен. Обратите внимание на то, что метод decrypt () рас- шифровывает объект PdfFileReader, но не фактический рI)Fайл. После 1'01"0 как ваша проrрамма завершит работу, файл на вашем жестком диске 
Работа с документами в форматах PDF и Word 377 останется зашифрованным. При следующем запуске ваша проrрамма долж на будет снова вызвать функцию decrypt ( ) . СОЗДIIнне "'-доку.енто. в модуле PyPDF2 объекты PdfFileReader дополняются объектами ?dfFileWriter, которые MoryT создавать новые РDFайлы. Но модуль PyPDl<2 не поддерживает запись ПРОИЗВОЛЫlOrо текста в формат PDF, как это делает Python с простыми текстовыми файлами. Возможности PyPDF2 в отношении записи РDFдокументов оrраничены копированием страниц IIЗ дрyrих РDFдокументов, поворотом и наложением (:траниц, а также шиф- рованием файлов. Модуль PyPDF2 не обеспечивает непосредственное редактирование РDFдокументов. Вместо этоrо нужно создать новый РDFайл, а затем ско-- пировать содержимое из существующеrо документа. Выполняя примеры в этом ра:щеле, мы будем действовать в соответствии со следующей общей процедурой. 1) открыть один или несколько существующих РDFайлов (исходных РDFдокументов) в объектах PdfFileReader; 2) создать новый объект PdfFileWriter; 3) скопировать страницы из объектов PdfFileReader в объект PdfFile Writer; 4) использовать объект PdfFileWriter для записи ВЫХОДНОI'О PDF- документа. Создавая объект PdfFileWriter, вы лишь (оздаете значение, KOTO рое представляет РDF-документ в РytlЮIl. При этом фактический PI)F файл не создается. Для этоrо необходимо вызвать метод wri te () объекта :: :::FileWri ter. eтoд wri te () принимает в качестве apryмeHTa обычный объект Fi le, от- .:рытый в режиме записи двоичных данных. Такой объект можно получить, вызвав функцию Pythol1 open () с двумя арryментами: строкой, содержащей желаемое имя РDF-файла, и строкой I wb I , указывающей на то, что файл .:Ю.lжен быть открыт в режиме записи двоичных данных. Если не все из сказанноrо вам понятно, не оrорчайтесь: вы увидите, как эсе это работает, в приведенных далее примерах кода. 10..,010... npa..q 10ДУЛЬ PyPDF2 можно использовать для копирования страниц из одно- <О PDl<'-документа в друrой. Это позволит вам объединять несколько PDF ф..uUL'Iов, переynорядочивать страницы или вырезать неНУЖllые. 
378 rлава 13 Заrрузите файлы тееting:minutes.рdjи тeeting;тinutes2.pdjc сайта http: / / nostarch.com/automatestuff/ и поместите их в текущий рабочий каталоr. Введите в интерактивной оболочке следующие команды. >>> iaport Руюп »> pdflFile = open('meetingшinute8.pdf', 'ш') »> pdf2File - open('meetingminute82.pdf', 'rb') О >>> pdflReader .. Руюп. pdfFileReader (pdflFile) е >>> pdf2Reader = Руюп. PdfFileReadвr (pdf2File) е »> pdf1fri ter - PyPDF2. PdfFil8Wri ter () »> for pageNwa in range(pdflReader.nWDPage8): .. pageObj = pdflReader. getpage (pageNum) . pdf1fri ter. addPage (pageObj) >>> for pageNum in range(pdf2Reader.nWDPage8): .. pageObj - pdf2Reader. g8tPag8 (pageNwa) . pdf1fri ter . addPage (pageObj) ф >>> pdfOutputFile = open('colllbinedminute8.pdf', 'wb') >>> pdf1friter."rite(pdfOUtputFile) »> pdfOUtputFile.clo8e() »> pdflFile.clo8e() »> pdf2Fi18.clo88() Откройте оба J>DFай:ла в режиме чтения двоичных данных и сохра- ните результирующие объекты File в переменных pdf1File и pdf2File. По- лучите объект PdfFileReader для файла тeeting;тinutes.pdf, вызвав функцию PyPDF2. PdfFileReader () и передав ей переменную pdf1File О. Вызовите эту же функцию повторно с передачей ей переменной pdf2File, чтобы полу- чить объект PdfFileReader для файла тeeting;тinutes2.pdjO. Затем создайте новый объект PdfFi1eWri ter, представляющий пустой J>DF-документ О. Далее скопируйте все страницы из двух исходных J>DFайлов и добавь- те их в объект PdfFileWriter. Получите объект Page, вызвав метод getpage () для объекта PdfFileReader .. После этоrо передайте этот объект page мето- ду addPage () cBoero объекта PdfFileWri ter О. Эти действия выполняются сначала для объекта pdflReader, а затем для объекта pdf2Reader. Закончив с копированием страниц, запишите новый РDF-документ coтbinedтinutes.pdf, передав объект File методу write () объекта PdfFileWriter О. Прu.мечание Модулъ PyPDF2 'Не обесnечuвает вста81(,У стра'Ниц, nосереди'Не дО1(,у,М,enта, npeдcтaв .л.я.е.мО20 об'6еКто'м' PdfFileWri ter; .метод addPage () сnособen лuшъ nрuсоеди'НЯтъ стра'Ни'Цы в ио'Н'Це дО1(,у.ме'Нта. 
Работа с документами в форматах PDF и Word 379 Вы создали новый РDI'--файл. объединяющий страницы из файлов тееlingтinutе..рdjи тeetingтinutes2.pdjB один документ. Не забывайте о том, что объект File, передаваемый функции PyPDF2 . PdfFileReader ( ) , должен быть открыт в режиме чтсния двоичных данных прем передачи строки , rb' в качестве BToporo apryмellTa функции ореп ( ) . Аналоrичным образом объект File, передаваемый функции PyPDF2. PdfFileWri ter () , должен быть открыт в режиме записи двоичных данных с помощью строки 'wb'. Поворот apa.. Страницы РDFдокумепта можпо поворачивать на уrлы, кратпые 900, в направлении по часовой стрелке и против часовой стрелки с номощью Iетодов rotateClockwise () и rotateCounterClockwise () соответствснно. В качестве арryмептов эти методы принимают целые числа 90, 180 и 270. Убедившись в наличии файла тeeliпgтinutes.pdjB текущем рабочем каталоrе, ввсдите в интерактивпой оболочке следующие команды. > > > import PyPDF2 »> minutesFile = open ('meetingminute8.pdf', 'rb') »> pdfR&ader = PyPDF2. PdfFileReader (II1inute8File) . >>> page = pdfReader. qetPage (О) . »> paqe.rotateClockwise(90) {'jContents': [IndirectObject(961, О), IndirectObject(962, О), пропущенный KOД } »> pdfWriter = PyPDF2.PdfFileWriter() »> pdfWriter.addPage(page) . >>> resul tpdfFile = open ( , rotatedPage. pdf', I wb I ) »> pdfWriter.write(re8ultPdfFile) »> resultpdfFile.close() »> minutesFile.close() Здесь мы вызываем мстод getpage (О) для выбора первой с.траницы PDF .1Окумента О, а затем поворачиваем эту страницу на 900 по часовой стрел ;e с llOМОЩЬЮ вызова rotateClockwise (90) О. Мы занисываем повернуryю I трапицу в новый РП}<'документ и сохраняем cro в файле 1vlaledPage.pdjO. Рсзультирующий документ будет содержать одну исходную страницу, по- вернyrую на 900 по часовой стрелке (рис. 13.2). Значения, возвращаемые "Iстодами rotateClockwise () и rotateCounterClockwise () , содсржат массу дo :Ю.lпительной информации, которую можно иrнорировать. 
380 rnasa 13 ..,... tcIt8I1idiijAdaJie  RSder ьс .Iiiл РWОСТИРООlнме Просмcnp 0...0 (про',,"  ,,".".} I з 1:iЮ >: ..:; -  : BESE (" " ( " ; 'i l .."'':,:........,'ij)0;5.........;".-1t:.''t:..,.;;.::......:,...IA.........l о IH "" "" 8 .. t' i з: n H  Н): r;. .. ........ .': 1':> .. ." а. ,. .з :O: ..  . - 11 :: J. i   ti ;ИI!::n! з; H B  :: :D ;:t; !R о r. '. :;  ::т а ...  1". О ; , 1: (" ё j Z ,. :' ;,. N . : о( О .. ... z -!  ' ,  j  ..  m : ;- .1.. . 1, ;  i. .. ... Рис. J 3.2. Сохраненная в файле rotatedPage.pdf страница, повернутая на 900 по часовой стреЛК8 НаJlо.ен.е cтpaH. Модуль PyPDF2 также предоставляет возможность наложения содержи Moro одной страницы поверх содержимоrо друrой, что может быть исполь- зовано для добавления в документ лоroтипа, временной метки или водяноro знака. С помощью Python можно леrко добавить водяные знаки в несколько файлов и только на те страницы, которые будyr указаны в nporpaмMe. 3аrрузите файл waterтark.рdfнасайте http://nostarch.com/automatestuff/ и поместите этот РDFдокумент в текущий рабочий каталоr вместе с фай- лом тееting:minиlP-s.рdj. После этоrо введите в интерактивной оболочке сле- дующие команды. »> ill1pOrt Руюп »> DlinutesFile = open('Dleetingainute8.pdf', 'rb')  »> pdfReader = PyPDF2.PdfFileReader(Dlinute8File) е»> Dlinute8Fir8tPage = pdfReader.getpage(O) е >>> pdfWaterDIarkВeader = PyPDF2 . PdfFileReader (open ( '_а teraark. pdf', I rb' ) ) о »> lDinute8Fir8tpage.lDergePage (pdfWaterDIarkP.eader .getpage (О» .. >>> pdfWriter = PyPDF2.PdfFileWriter() 
Работа с документами в форматах PDF и Word 381 . >>> pdfWriter. addPaqe (minutesFirstPaq8) е >>> for paqeNum in ranqe(l, pdfReader.numPaq8S): paqeObj .. pdfReader. qetPaqe (paq8Num) pdfWri ter . addPaqe (paqeObj) »> resultPdfFile = open('wateraarkedCover.pdf', 'wb') »> pdfWriter.write(resultpdfFile) »> шinutesFilе.сlоsе() »> resultpdfFile.close() Здесь мы создаем объект PdfFileReader па основе файла meeting;тinutes. PdfO. Чтобы получить объект Page для первой страницы и сохранить ero в переменной minutesFirstPage, мы вызываем метод getPage (О) О. Затем соз /l,ается объект PdfFileReader для файла waterтark.pdfO и вызывается метод mergepage () для объекта, coxpaнeHHoro в переменной minutesFirstPage О. ApryMeHToM, который мы передаем методу mergePage ( ) , является объект Page для первой страницы файла waterтark.pdj. Теперь, Коrда для переменной minutesFirstPage был выполнен вызов ме- тода mergePage (), она представляет первую страницу файла с добавленным водяным знаком. В следующей строке мы создаем объект PdfFileWri ter О и добавляем в неro эту страницу Ф. Затем мы выполняем цикл по оставшимся страницам файла тееting;тinutes.рdfи добавляем их в объект PdfFileWri ter О. Наконец, мы открываем новый РDF-файл watermarkеdCover.рdfи записываем в Hero содержимое объекта PdfFileWriter. Результат представлен на рис. 13.3. В нашем новом РDFдокументе waterтarkedCover.pdf представлено все содержимое документа meeting;тinutes. Pdfc первой страницей, помеченной водяным знаком.  т-i'''' .;;.:{)fi8i ...... Pe.A"'ТIIpOItone ПроСМO'lр Ot."D CnjWI1C8 '; ':_ ::;;:"i' ееАп РWIт'II'ОIlНИf. npOCl.letp С,нс Cnj::,et:o  y ., - 'vrIt8'm8r8oIdCOYIr.pcIf.AdМcAcro".I:> (п фlt.... PUIlI:Wpel,нttt I'lPO(MO'J! Orкo C"p..r, .. "l с.,'> \.. . . '\O о :' , .... ( ,". .....,:. ,О oy. Ф ""'. 1)1 "T"lty А  'i((ONO&ltv IР\К"ПON Ф ..... . .". - ," 1U"1tY I! ::OJIIDARY IIКК_nOff ......,.....".... .,......... ...........-............,..  :::.:::;.::-.;- '::-::':";.;;" ':. ''":''. :;.....; ?".::::;?:'. -: ,.;:':', .:; orrlCIAL IОА"О MINUTES orrlCIAL IОААО MINUTE8 м..,..' ,,('..,!It,,-II I :1)1' , '. .  ". , ..- .., " 1. ' ....:- I I Рис. 13.3. Ориrинальный РDF-документ (слева), воДJIНОЙ знак (в центре) и объединенный РDF-до/(умент (справа) 
382 rnaBO 13 W.ФРОlа... PDF-AQкумеIlТOI Объект PdfFileWriter также позволяет шифровать РDFдокументы. Вве- дите в интерактивной оболочке следующие команды. >>> import PyPDF2 >>> pdfFile = open('DleetinglRinute8.pdf', 'ш') »> pdfReader - PyPDF2.PdfFileReader(pdfFile) >>> pdfWri ter - PyPDF2. PdiFileWri ter () >>> for pageNwa in range(pdfReader.nWDPage8): pdfWri ter . addPage (pdfReader. getpaqe (paqeNuDl) ) о »> pdfWri ter . encrypt ( , 8"ordfi8h ' ) »> re8ultpdf = open('encryptedlDinute8.pdf', 'wb') »> pdfWriter.write(resultPdf) »> resultpdf.close() Прежде чем вызывать метод wri te () для сохранения файла, вызовите метод encrypt () И передайте ему строку пароля О. РDI'.документы MOryr иметь nарол:ь nолъзовтпR..fI.Я (позволяющий просматривать документ) и nарол"Ь владелъца (позволяющий устанавливать разрешения для вывода документа на печать, снабжения ero комментариями и извлечения тек(та, а также пре- доставляющий ДРУl'ие права). Пароли пользователя и владельца задаются соответственно в качестве первоrо и BToporo apryмeнтoB метода encrypt (). Если передать методу encrypt () только одну строку, то она будет использ вана для обоих паролей. В этом примере мы скопировали страницы документа meeting;тinutes.pdj R объект PdfFileWriter. Далее мы зашифровали объект PdfFileWriter с п мощью пароля swordfish, открыли новый РDFайл encryрtеdтinutеs.рdjи за- писали в Hero содержимое объекта PdfFileWriter. Прежде чем ктлибо сможет просмотреть содержимое файла encryptedтinutes.pdf, он должен будет ввести этот пароль. После 1'01"0 как вы убедитесь в том, что копия зашифро вана корректно, ориrинальный незашифрованный файл тeeting;тinutes.pdj при желании можно будет удалить. Проект:о6ъединениевы6ранных страниц из мноrих PDF-Аокументов Предположим, вам предстоит выполнить yrомительную работу по слия нию нескольких РDF-документов в один РDF-файл. Каждый из документов начинает(:я с титульноrо листа на первой странице, но вы не хотите, чт бы первые страницы попадали в ОКОнчательный документ. Несмотря на То что существует MHoro бесплатных проrрамм, позволяющих объединять РDF-документы, все, на что СПОСQбны мноrие ИЗIIИХ,  это просто слияние 
Работа с документами в форматах PDF и Word 383 ИСХОДНЫХ файлов в еДИНЫЙ файл. Давайте напишем проrрамму, позволяю щую выбирать страницы, которые должны включаться в результирующий РDFдокумент. На верхнем уровне планирования IIporpaмMa должна выполнять следую щие действия: . находить все РDFайлы в текущем рабоtlем каталоrе; . сортировать файлы по именам, чтобы файлы добавлялись в OIlреде ленном порядке; . записывать каждую страницу исходных РDFайлов, за исключением их первых страниц, в выходной файл; В терминах реализации ваш код должен выполнять следующие операции: . вызывать функцию 03 .li3tdir () для нахождения всех файлов в Teкy щем рабочем каталоrе и удалять все файлы, кроме файлов в формате PDF; . вызывать метод Python sort () для списка с целью расположения имен файлов в алфавитном порядке; . создавать объект PdfFileWriter для выходноro РDFайла; . орrанизовывать цикл по всем РI)Fфайлам с созданием объекта PdfFileReader для каждоrо из них; . орrанизовывать цикл 110 всем страницам (за исключением первой) каждоrо РDFайла; . добавлять страницы в выходной РDF-файл; . записывать выходной РDFфайл в файл allmin-иtes.рdJ Откройте для этоrо проекта новое оКНО в файловом редакторе и coxpa ните ero в файле coтbinePdfs.py. Шаr '. По",,, .еех "'-файло. Прежде Bcero ваша nporpaMMa должна получить спИ(:ок всех файлов с расширением .PdfB текущем рабочем каталоrе и выполнить ИХ сортировку. Введите в файл следующий код. #! руthопЗ # соmbiпеРdfз.ру  Объединяет все РDFдокументы, находящиеся # в текущем рабочем каталоrе, в единый РDFдокумеНт. 8 import PyPDF2, 03 # Получение списка имен всех РDFфайлов. pdfFile3  [] for filename in 03.1i3tdir('. '): if filename.endswith(' .pdf'): . рdfFilез.аррепd{filепаmе) 
384 rЛQва 13 е pdfFile5.$ort(keY=5tr.lower) о pdfWriter = PyPOF2.PdfFileWriter() # ТООО: Орrанизовать цикл по всем РОFфайлам. # ТООО: Орrанизовать цикл по всем страницам (за исключением # первой) с их добавлением в результирующий документ. # тооо: Сохранить результирующий РОFдокумент в файле. Вслед за "мarичсской (:трокой" и комментарием, описывающим назпаче- ние IIpOrpaMMbI, в этом коде импортируются модули 05 И PyPDF2 О. Вызов 05 .li5tdir ( '. ') возвращает список всех файлов, находящихея в текущем рабочем каталоrе. Затем код просматривает ЭТОТ список в цикле и добавля- ет в новый список pdfFile5 лишь файлы с расширением .pdJO. После этою список pdfFile5 сортируется в алфавитном порядке, который задается име- нованным apryмeнтoM key=str .lower при вызове метода sort () О. Для хранения объединенных РDF-страниц создается объект PdfFi1e Writer О. Наконец, несколькими комментариями описывается остальная часть ПрOI'раммы. Шаr J. Откр""", "'-файло. Теперь проrpамма должна прочитать каждый из РDF-файлов, входящих в список pdfFiles. Добавьте в проrрамму код, выделенный ниже полужир- ным шрифтом. #! руthопЗ # combinePdf$.py  Объединяет все РDFдокументы, находящиеся # в текущем рабочем каталоrе, в единьм РОFдокумент. import PyPOF2, 0$ # Получение списка имен всех РОFфайлов. pdfFile$ = [] пролущенRЫЙ KOД * Ор:rаииаaцюr ЦИJCJ1а по всек PDFфайпак. for filenaae in pdfFiles: pdfFileObj - open(filename, 'rb') pdReader · РуРОп. PdfFileReader (pdfFileOb]) " ТODO: Орf'аниаова'l'Ь ЦИJCJ1 по всем C'l'PЗНИЦёUC (8& ИCJCJIJDЧением t первой) с их добaв.nеииек в резу,m,orиpyJ:8;ИЙ докумен'l'. # ТООО: Сохранить результирй РОFдокумеНТ в файле. 
Работа с документами в форматах PDF и Word 385 в цикле каждый РDFайл открывается в режиме чтения двоичных дaH ных врем вызова метода open () с передачей ему строки' rb' в качестве BT poro apryMeHTa. Метод open () возвращает объект File, который передается функции PyPDF2. PdfFileReader () , создающей объект PdfFileReader для дан- ною РDFайла. Ш", 3.1I06ll_elllle ""'''11'' для каждою из РDI'айлов необходимо отобрать в ЦИКЛе все страницы. за исключением первой. Добавьте в проrрамму код, выделенный ниже по- лужирным шрифтом. #! руthопЗ t combinePdfs.py  Объединяет все РDFдокументы, находящиеся # в текущем рабочем каталоrе, в единый РDFдокумеНТ. import PyPDF2, 0$ пролущеRНЬ1Й KOД # Орrанизация цикла по всем РDFфайлам. for filename in pdfFile$: пролущеRRЬ1Й KOД * Орf'анизaциJI цима по зсем С'1'раницам (за ИCJC.JDOЧением * первой) с их добuпением в резупь'l'ИpyI)ЩИЙ дохумен'l'. О for pageNum in range(l, pdfReader.oumPaqes): pageObj .. pdfR&ader. getPage (pageNum) pdf1fri ter . addPage (pageObj) # тооо: Сохранить результирующий POPДOKyмeHT в файле. Код в цикле копирует каждый объект page по отдельности в объект PdfFileWriter. Вспомните, что вы хотите опускать первую страницу. По- скольку в принятой В модуле PyPDI'2 системе отсчета первой странице соответствует индекс О, циКЛ должен выполняться в диапазоне индексов, определяемом значениями 1 и pdfReader. numpage$ (последнее значение не включается в диапазон) о. Ш",4. Coxp"HeHlle резул.,,,rо. После выполнения вложенных циклов for переменная pdfWri ter будет содержать объект PdfFileWriter, включающий страницы всех объединен- ных РDFдокументов. Последний шаr состоит в том, чтобы записать это со- держимое в файл на жестком диске. Добавьте в проrрамму код, выделенный ниже полужирным шрифтом. 
386 rлава 13 #! руthопЗ # combinePdfs.py  Объединяет все РDFдокументы, находящиеся # в текущем рабочем каталоrе, в едИНЫЙ РDFдокумент. import PyPDF2, os пропущенный KOД # Орrанизация цикла по всем РDFфайлам. for filename in pdfFiles: пропущенный KOД # Орrанизация цикла по всем страницам (за исключением # первой) с их добавлением в результирующий документ. for pageNum in range(l, pdfReader.nurnPages): пропущеННЫЙ KOД * Сохранение резупъирующеro РDFдохум.на в файпе. pdfOUtput = open (1 allminuteB .pdf', I wb' ) pdfWriter.write(pdfOUtput) pdfOutput.clo8e() Чтобы открыть ВЫХОДНОЙ файл allтinutes.pdfB режиме записи ДВОИЧНЫХ данных, фуНКЦИИ open () передается с.трока 'wb'. В результате последующей передачи результирующею объекта File методу wri te () создает<:я фактич ский рI)Fайл. Проrрамму заВершает вызов метода close (). 'де" отно,.те.6НО 'OJДII"". IIHII.or,,'H6IX про,."".. Возможность создавать РDF-документы на основе страниц, извлекаемых из друrих РDFдокументов, позволяет создавать проrраммы для решения следующих задач: . удаление указанных страниц из РI>F-документов; . реорrанизация страниц в РПF-документах; . создание РI>F-документа на основе только тех страниц, которые содер- жат заданный текст, определяемый вызовом метода extractText (). Документы Word с помощью модуля Pytlюп pythondocx можно создавать и изменять до- кументы Word с расширением имени файла . docx. Чтобы установить этот модуль, следует выполнить команду pip install pythondocx. (За более под. робным описанием процедуры установки модулей, разработанных третьи- ми сторонами, обратитесь к приложению А.) 
Работа с документами в форматах PDF и Word 387 Прu.меча"uе ИспОЛЪЗУJl ко,М,анду pip для устаНО81Си 'м'одуля Python-Docx, обязател'Ь1l0 вводите iпstal.I pythoпdocx, а не docx. Имя docx исnолъзуется при уста1l0l11Cе дру2020 'м'одуля, который в данной кнше не рассматривается. В то же время при u.м. тировапии 'м'одуля pythoп docx в npo2jJa.м.мy CJU!дyem испол'ЬЗоват'Ь ",оманду im  port docx, а не iтport. pythoпdocx. в случае отсyrствия нриложения Мicrоsоft Wor(l можете ВОСПОЛЬ30вать ся ею бесплатными альтернативами LibreOffice Writer и ОрепОШсе Writer для операционных систем Windows, OS Х и LiIlUX, которые позволяют ОТ- крывать .dосх-файлы. Они доступны для заrрузки на сайтах https: / /www. libreoffice.org и http://openoffice.org соответственно. Полная доку- ментация 110 модулю Рytlюп-Dосх доступна по адресу https: / /pythondocx. readthedocs.org/. Хотя и существует версияWоrd для OSX, 8 этой rлаве мы сосредоточимся на работе с версией Word для Willdows. В отличие от простых текстовых файлов, файлы с расширением .docx обладают развитой внyrренней структурой. В модуле PythOB-Dосх эта струк- тура представлена тремя различными типами данных. На самом верхнем уровне объект Document представляет весь документ. Объект Document содер- жит список объектов Paragraph, которые представляют абзацы документа. (Новый аб:Jац начинается всякий раз, КOI'Да Iюльзователь пажимает клави- щу <Enter> или <Returl1> при вводе текста в документ Word.) Каждый из абзацев содержит список, состоящий из одноrо или нескольких объектов Run, нрсдста8ЛЯЮЩИХ фрarменты текста с различными стилями формати- рования. Прсдставлснный на рис. 13.4 одиночный абзац состоит из четы- рех таких фрarмснтов. А plain paragraph with some bold and some italic I 1 L.........J 1 I L...........J Run Run Run Run (Простой абзац с полужирным и курсивным текстом) 11 I LJ I 11 Run Run Run Run fbJ Рис. 1 З.4. Объекты Ruп, определенные 8 объекте Paragraph Текст в документах Word  это нечто большее, чем нро<:то текстовая строка. Он включаеr информацию, описывающую шрифт, цвет и pa:JMep шрифта, а также друryю относящуюся к тексту информацию. Коллскция :них атрибyrов образует стuл'Ь документа Wопl. Объект Run представляет непрерывный фparмент текста, оформленный с использованием одноrо и Toro же стиля. Каждой смене стиля соответствует новый объект Run. 
388 rnOBQ 13 ЧтеНllе 1I0Ky.ell1O. Word Давайте 110экспериментируем с модулем pythondocx. 3arрузите файл deтo.docx на сайте http://nostarch.com/autornatestuff/ и сохраните доку- мент в рабочем каталоrе. После этоrо введите в интерактивной оболочке следующие команды. >>> iJRport docx О »> doc · docx. Docwaent ( 'delDo. docx ' ) е>>> len(doo.paragraphs) 7 . »> doc. paragraphs [О] . text 'Название документа' о»> doc.paragraphs [1]. text 'А plain paragraph with some bold and some italic' . >>> len (doc. paragraphs [1] . runs) 4 0»> doc.paragraphs[l] .runs [О] . text 'А plain paragraph with some ' е»> doc.paragraphs[l] .runs[l] .text 'bold' .. >>> doc. paragraphs [1 J . runs [2] . text , and some ' o »> doc. paragraphs [1] . runs [З 1 . text 'italic' Этими командами мы открываем .doе»файл в PytIЮIl, вызываем функцию docx. Document () и передаем ей имя файла deтo.dQCX О. Эта функция во:шра Щает объект Document, атрибyr paragraphs Koтoporo представляет собой спи- сок объектов Paragraph. Возвращаемое в результате вызова метода len () для списка doc. paragraphs О значение 7 укааывает на то, что в этом документе содержится семь объектов paragraph. Каждый из этих объектов Paragraph имеет атрибут text, содержащий строку текста данною абзаца (без инфор-- мацИИ о стиле). В данном случае первый атрибyr text содержит строку 'Ha звание документа I О, а второй  строку' А plain paragraph wi th sorne bold and some i talic I е. Кроме Toro, каждый объект Paragraph имеет атрибут runs, который представляет собой список объектов Run. Объекты Run таКЖе имеют атри бут text, который содержит лишь текст данноrо участка стиля форматиро- ван ия. Обратимся к атрибyrам text BToporo объекта Paragraph с текстом 'А plain paragraph with sorne bold and sorne italic'. РС:Jультат выаова метода len () для этоrо объекта rоворит о том, что этот объект включает четыре объекта Run О. Тексту' А plain paragraph wi th sorne 'соответствует первый объект Run О. Затем к тексту применяется полужирное начертание, и поэ тому тексту 'bold' соответствует новый объект Run О. Далее следует теКСТ с обычным форматированием ' and sorne " которому соответствует третий 
Работа с документами s форматах PDF и Word 389 объект Run О.Ч<,,'Твсртый объект Run содержит текст' НаНе', к которому применено курсивное начертание О. Имея в своем распоряжении модуль Pytlюп-Dосх, ваши проrраммы на Python CMOryr читать текст из файлов с расширением .llocx и использовать еro как любое друrое текстовое значение. na"ell.e IIOnlloro ret".", IIЗ фай па .doa Если в документе Word вас интересует только текст без информации о стиле форматирования, можеrе воспользоваться функцией getText (). Она принимает имя .doайла в качестве aplyмeHTa и возвращает строку, содер- жащую полный текст файла. Orкройте новое окно в файловом редакторе, введите в нею (ледующий код и сохраните проrpамму в файле readDocx.py. П! руthопЗ import doex def getText(filename): doc = doex.Doeument(filename) fuH Text = [] for para in doc.paragraphs; fullText.append(para.text) return '\п' .join(fullText) Функция getText () открывает документ WOI'd, лросматривает в ЦИКЛе все объекты Paragraph в списке paragraphs, присоединяя содержащийся в них текст к списку ful1Text. По завершении выполнения цикла все строки, содержащиеся в списке ftj,llText, 06ьединяются с использованием символа новой строки в качестве ра.1делителя. Проrрамму readDocx.py можно импортировать подобно любому ДРУI'ОМУ модулю. Теперь, если все, что вам нужно,  это извлечь текст из документа Word, введите следующие команды. > > > import readDocx »> print(readDocx.q8tT8xt('demo.docx'» азвание документа ; plain paragraph with some bold and some italic Эаоловок, уровень 1 3ыделенная цитата ервый элемент маркированноrо списка ервый элемент HyмepoBaHHoro списка Вы также можете изменить строку, прежде чем возвратить ее. Напри- fep, чтобы ВЫДеЛИТЬ каждый абзац отступом, замените вызов append () в проrрамме readDocx.py следующим ero вариантом: 
390 rлава 13 fullText.append(' I + para.text) Чтобы добавить удвоенный междустрочный интервал между аБЗaJ\ами, замените вызов join () следующим: return '\п\п' .join(fullText) Как видите, для написания функций, обеспечивающих чтение файла .docx и возвращение строки с ero содержимым, видоизмененным в соответ- ствии с вашими потребностями, достаточно Bcero нескольких строк кода. ',....0. ОфоРМllен., "6з"",, " 06,..,,,, Run В Word для Windows стили можно увидеть, нажав комбинацию клавиш <Ctrl+A1t:+8hift+8> для отображения окна Стили (рис. 13.5). в 08 Х для этою следует выбрать пункты меню ViewStyles (ВидС:>Стили). , .. СТИII" ;  ОЧ&1СТ.1Т'tt все I '66.......... без it'lTepнпe ЗarОJ1050К 1 3!lrс;..моеок , , ЗorDl108QO; 3 , , Зoronaeoк 4 : ' Нa.Nttиe !loIIэero""""" CпIIбoeIlbUl lIbuIenoнмe CIVI.rCIe .....eneм<e СтpontЙ ЦlIтат. 2 LllТaт CNбмo CCЫI1I<. ваРИ:"е'J1t'1>"!D.li n"'"IQ::4(',":"P . ОТIoJ"It"::<Ч<!rЬ CE:::t! :";"II.r,, , !!: ,:t,;i'1, . ...... . . 1рнtЩI.: t 'Ш 1 ".; '11' !! - : в'" В '; и ., в' !I а а а а В В а  ",:6.,..", :;. ;JО:l6fЦ:. 6 .)(  . : j i ..;  .;; ] ,'i ,  J " ,) "1 i . .i Здравствуй мир. . -4 .. .... .....". ..,...,"..,,;, ,",101'-:10 .'rOB: 2 ру(О'ий.:J.: :.J'J .: Рис. J 3.5. Вид окна Стили в Windоws Стили ИПlОльзуются В Word и ДРyI'их текстовых процессорах ДЛЯ прида- ния документу или ряду однотипных документов единоо6разноrо внешне- 1"0 вида, который к тому же можно леrко изменить. Например, вы хотите, чтобы для абзацев использовался шрифт 1illles New Roman размером 11 пунктов, а текст выравнивалея по левому краю без выключки справа. Мож- но создать стиль с этими параметрами и назначить ero всем абзацам тела 
Работа с документами в форматах PDF и Word 391 документа. Впоследствии, если вы захотите изменить внешний вид абзацев по всему документу, достаточно будет изменить лишь стиль, и все абзацы автоматически изменят свой внешний вид. В документах Word применяются три типа стилей: стили абзацев, Кото- рые MOryr применяты:я к объектам Paragraph, стили CUIttlJO.IL08, которые MOryr применяться к объектам Run, и С8Я3а'н:н:ые стшu, которые MOryr при меняться к обоим типам объектов. как объектам Paragraph, так и объектам Run можно назначать стили, присваивая их атрибутам style значение в виде строки. Этой строкой должно быть имя стиля. Если для стиля задано значение Мопе, то у объекта Paragraph или Run не будет связанноrо с ним стиля Ниже привсдены имена стилей Word, используемых 110 умолчанию. 'Normal' 'BodyText' 'BodyText2' 'ВоdуТехtЗ' 'Caption' 'Headingl' 'Heading2' 'НеаdiпgЗ' 'Heading4' 'Heading5' 'Неаdiпgб' 'Heading7' 'Heading8' 'Heading9' 'IntenseQuote' 'List' 'List2' 'ListЗ' 'ListBullet' 'ListBullet2' 'ListВul1еtЗ' 'ListContinue' 'ListContinue2' 'ListСопtiпuеЗ' 'ListNumber' 'ListNumber2' 'ListNumЬеrЗ' 'Listparagraph' 'MacroText' 'NoSpacing' 'Quote' 'Subtitle' 'TOCHeading' 'Title' Онределяя значения атрибута style, не оставляйте пробелы в имени сти ля. Например, если стиль называется "Subtle Elllphasis", то в качестве значе ния атрибута s t У le укажите строку' SubtleEmphasi s ' , а не 'Subtle Emphas i s ' , Включение пробелов приведет к тому, что имя стиля не будет распознано Word, и он не будет применен, Если связанный стиль при меняется к объекту Run, то к ero имени сле- дует присоединять строку' Char'. Например, устанавливая для объекта Paragraph связанный стиль Quote, используйте инструкцию paragraphObj . style  'Quote', однако в случае объекта Run следует использовать инструк- цию runObj . style  'QuoteChar' . В текущей версии модуля PytllOп-Dосх (0.7.4) единственные стили, ко- торые можно исполь;ювать,  это заданные по умолчанию стили Word и стили, используемые в открытом ,dосх-документе. Возможность создания новых <:тилей в настоящее время отсутствует, хотя в будущих версиях моду- ля Python-Docx СИ1'уация может измениться. СОЗДIIн"е документо. Word , He""HA"pTHWMH етв.мн Если вы хотите создавать документы Word, в которых используются сти- ли, не являющиеся стандартными, прежде Bcero откройте в WOl'd пустой документ и создайте стили самостоятельно, щелкнув на кнопке Создать стиль в нижней Части окна Стили (на рис. 13.6 представлен фраrмент окна Wor<l для Windows). 
392 rлава 13 в результате откроется ДИaJIоrовое окно Создание стиля, в котором мож- но настроить новый стиль. После этоrо верпитесь в интерактивную 06 лачку, откройте этот документ с помощью функции docx. Docurnent () и ис- пользуйте cro в качестве основы для CBoel'O документа Word. 1'еперь имя. которое вы присвоили этому стилю, можно будет использовать вместе с модулем PytllOll-Dосх. с......""'" i8iiJ ""'*'- .... """" , ..... .. QDdI8I... '"""""'" Зеrоf\08Ol( .. !I C1'lllll>__. t'Cnoм.l . "........ !I ...  ............... r:Со.DЯlrgf!1;;IIIOJI; !I C..PtO {Qнa8111i1 и.. 11 . . I !I .. . смбое 1IIb1.4 а 1 . I. . . е . 11; '" ,. : iIt  ,. &,t:lII!! а i .. .--..-- . . . .- .. -_... ____о ... -----_..- _.._'_. .. .--    --.- . ., C,1fbJ1Of!8ЫDefIe/'94t: . . ." ! ;:TpoI"lt) а . i '1 ц'"Ta 2 !I j !з..,.....Тву.нuр! Всtдrлet1t1aя L.IfJ,)Ta !I I I C.1t!6It'RCCbIlt а . I . ..''- iш,н,",шш .. ,-,'-', ... ... ---'- _.ш_ . ..-'--"--- .. .    . f1r.:I;:';""":1bt-t-!itТ;о ;: P,WUМ. (1..... 3rкQ1мt<'1" . '''j'. OOaNtt"'<'IIWIf: O. ..,,:: ..:f""!:> ::;:;';'t;>..l,. ,- ("1'I1r..' :....... :.)'; t 1;,.: 'J}. ..:"-::'11?"I').: " '.. , ..........crкa. JIQRIIIIXa--' ......... .............. \..,..... I .  .. . t-м.mIII,.......-е ..............ах.!mИ'....... :с :5.t..п._ (1t!'1b. Сто.16еоц; 1'; t.Uf(.'IQ (J!'Je 1 . . "" ....... . ...... . Рис. 13.6. Кнопка Создать стиль (слева) и дналоrовое окно Создание стиля (справа) А,р"6ут,,, 06.е",0 Ruп Отдельные фраrмснты теК<:та, представляемые объектами Run, MOryr подверrаться ДОlIОЛIlИТСЛЬНОМУ форматированию с помощью атрибyrов text. Для каждОI'О из этих атрибyrов может быть задаНо одно из трех :шаче ний: True (атрибут постоянно активизирован, независимо от нрименения к данному фраrменту тскста дрyrих пилсй) , False (атрибyr постоянно отклкr чен) и None (по умолчанию ПРИМСIIЯется стиль, установлснный ДЛЯ дaHHoro объекта Run). Атрибyrы text, которыс MOryr назначаться объектам Run, приведсны в табл.13.1. Та6пица 13.1. Атрибуты text объекта Run Атрибут Описание bold italic underline strike Полужирное начертание Курсивное начертание Подчеркнутый текст Зачеркнутый текст 
Работа с документами в форматах PDF и Word 393 (Ж(т:чаиue табл. 13.1 Атрибут double strike all  caps smallcaps shadow outline rtl imprint emboss Описание Текст, зачеркнутый двойной линией Отображенне текста прописными буквами Отображение текста малыми прописными буквамн (капитель), размер которых на два пункта больше размера строчных букв Текст, отбрасывающий тени Контурный текст Направление текста справа налево ВАавленный текст Рельефный текст Поэкспериментируйте с изменением <:тилей форматирования текста из файла deтo. docx, введя в интерактивной оболочке следующие команды. >>> doc = doсх. Docum.ent ( 'demo. docx ' ) »> doc.paragraphs[O] .text 'Название документа' »> doc.paragraphs[O] .style 'Title' »> doc.paragraphs[O].style = 'Normal' »> doc.paragraphe[l] .text 'Д plain paragraph with some bold and some italic' »> (doc.paragraphs[l] .runs[O].text, doc.paragraphs[l].runs[l] .text, doc. paragraphs[l] .runs[2].text, dос.раrаgrарhs[l].runs[З].teхt) 'А plain paragraph with some " 'bold', , and some " 'italic') »> doc.paragraphs[l].runs[O].style - 'QuoteChar' »> doc.paragraphs[l].runs[l].underline = True »> doс.раragrарhs[l].runs[З].underlinе = True »> doc. save (' r&styled.docx') в этом при мере показано, как просто исследовать содержимое абзацев документа с помощью атрибyrов text и style. Вы видите, как леrко можно разделить абзац на фраrменты текста и получать доступ к ним по отдельн сти. В данном случае мы извлекаем первый, второй и четвертый фраrмен- ты BToporo абзаца, применяем к ним разные стили и сохраняем результат в новом документе. К словам Docиmeпt Тitle в начале документа -restyled.docx вместо стиля litle применен стиль Normal, к объекту Run для текста А plain paragraPlt with soтe применен стиль QuoteCllar, а атрибyrам underline двух объектов Run дЛЯ (.10В ЬоЫ и italic нрисвоено значение True. Результат форматирования абза- цев и отдельных фрarментов текста документа restyled.docxc помощью сти- .1ей показан на рис. 13.7. 
эм rлава 13 ЭlrОnoeoк 4 и . н....... D nOAWO/lOllOК D . (lЫ.OeI1O\'01I! а : ht4........... а , C'1IIIoНOe IIЫ/Ineнo", а CTpQrl""t а ': UJ1T,Ti 2 ----... Вош""6Т. D Название документа А plain paragraph with some ШШl and some ita.lk (Простой абзац с пОЛVЖIlРНЫ,М и 'O'pcuвHЬLM mel<cmoM) Рис. 13.7. Документ resly/ed.docx Более подробную информацию относительно применеllИЯ стилей фор матирования в документах Word с помощью модуля PythопDосх можно най ти по адресу https: / /pythondocx.readthedocs.org/en/latest/user/styles.html. а,nн" Доку.енто, Word Введите в интерактивной оболочке СJIедующий код. »> import docx >>> doc = docx.DocWl\8nt() »> doc.addaragraph('Нello world!') <docx.text.Paragraph object at ОхОООООООООЗВS6F60> >>> doc. 8аУе ( , h8110wor1d. docx ' ) Создайте собственный. dос»файл, вызвав функцию docx . Documen t ( ) , ко- торая возвратит новый, пустой объект Document. Метод add yaragraph () это- 1"0 объекта добавляет новый абзац текста в документ и возвращает ссылку на добавленный объект Paragraph. Завершив добавление текста, сохраните документ в файле, передав строку с именем файла методу save () объекта Document. В данном примере в текущем рабочем каталоrе создается файл hello world.docx, результат открытия KOToporo в приложении Word показан на рис. 13.8. Добавление абзацев осуществляется пyrем повторных вызовов метода addparagraph () с текстом новых абзацев. Кроме Toro, можно добавить текст в конце существующеrо абзаца, вызвав для Hero метод add  run () С п редачей ему строки текста. Введите в интерактивной оболочке следующие команды. »> iIIIport docx >>> doc = docx.Docwaent() »> doc.addaragraph ('88110 _or1d!') <docx.text.Paragraph object at ОхОООООООООЗ66ADЗО> »> paraObjl · doc.add,J>8.ragraph ('Э'1'о В'l'орой абзац. ') »> paraObj2 · doс.аddаragrарh('Э'1'О eIЦ8 один абзац. ') 
Работа с документами в форматах PDF и Word 395 »> paraObjl. add  run (' Э'1'0'1' '1'8хот бw.п добав.пеи 80 8'l'ороЙ абзац.') <docx.text.Run object at ОхОООООООООЗА2С8БО> »> doc. еаУе ( 'Dlul tipleParagraphs. docx' ) l!!i hellowoJld.do<x - МЮGScft \/'Iord ,_.raA 6аа..,,, P5::1Mt1K'cr. Саиак.. Н3ИРQf 8"А Pit60ТЧ" :: .J ,;J .:1  A.J .l. ' '" . u . о в :\ J ;"'; , ..... 06..чн..й :"'"..:) (:.; 11 ДиI1R''''',.ИКl АН.,.. rns :.t" . C.mbri. lОС.' 11 .. ; ::; !'" . I .... '.;! .J. , . . :J:! '01 ,.] ::,i "1 : 4 -':..; .: :. ....,.,.... .._I:"'.....,". .. ''.'.''.:':.' . ..__'....;}. (тр....щ.,1...1 СТРО.'.б (TOIl6... 1 Ч""'ОIJl""'l::  ."...-;ii...,, .... Здравствуй. IIIР! .' _; о; 5.  . ..? Рис. 13.8. Документ Word, созданный с использованием вызова addyaragraph ('Здравствуй, мир!') Результат показан на рис. 13.9. Обратите внимание на то, что текст Этот текст бьщ добавлен во второй абзац. был добавлен в объект Paragraph (пере-- менная paraObj 1), представляющий второй из добавленных в документ a зацев. Функции add paragraph () и add  run () возвращают объекты Paragraph и Run соответственно, что избавляет вас от лишних забот по извлечению необходимых фраI'ментов текста в различных ситуациях.  he!laworld,dOC1f . Мicrоsоft WE>rd "" @) >.;; _" rпl6waA Bcta.Bk PI3Jten:, СУ С<ЫЛКИ Peцewo! Вмд РI3ра6тч Ди"IJI!К'fИIЦ. .АВ8УУ trans V V I .J.:J:J .:d ,..1.1 .ry. IJ ,j,. о в .  1:'"'1 . .1!. Обычный " C.mbri. iOc. - 11 ... (';;;:; .} I'i' !"']: ' . i, ;, ":i .' ::j А!  : Здравствуй, мир! Это второВ абзац. Этот текст бы., добавлен во второй абвац, Это еще один абзац. ...  ' .;"':"'::.;,.:.:....I"-,.....,'"'.".......:...... *, 61 . ; ... ',+ С:транмцв; 1 13- 1 (ТРОК" 9 Стоп6оц: 1 '. ИIJIО t/108: 16. :l!I: l!;iI  .;; '" 100'0 Рис. 13.9. Документ с несколькими добавленными объектами Paragraph и Run 
396 rлава 13 Имейте в виду, что начиная с версии PythonDocx 0.5.3 новые объекты Paragraph можно добавлять только в Конце дoкyмeнra, а новые объекты Run  только в конце абзаца. Метод save () можно вызывать повторно для сохранения дополнитель- ных изменений. Оба метода, add yaragraph () и add  run (), принимают необязательный второй apryмeHT, содержащий строку стиля объекта Paragraph или Run со- ответственно, например: »> dос.аddраrаgrарh('Здравствуй, мир!', 'Title') Эта строка кода добавляет абзац с текстом Здравствуй мир!, отформати- рованным с использованием стиля Title (Название). АО6D.леннеЗDrоло,ко, Вызов метода add !;eading () приводит К добавлению абзаца, отформат,И- pOBaнHoro в соответствии с одним из возможных стилей зaroловков. Введи- те в интерактивной оболочке следующие команды. »> doc · doax.Document() >>> doc. add h8adinq ( I H8ader О', О) <docx.text.paragraph object at ОхОООООООООЗ6СВЗС8> »> doc.add h8adinq('H8ader 1', 1) <docx.text.paragraph object at ОхОООООООООЗ6СВБЗО> »> doc.add headinq('Header 2', 2) <docx.text.paragraph object at ОхОООООООООЗБСВ828> »> doc.add h8adinq('Header 3' I 3) <docx.text.paragraph object at ОхОООООООООЗБСВ2Е8> »> doc. add h8adinq ( 'Header 4', 4) <docx.text.paragraph object at ОхОООООООООЗ6СВ3С8> »> doc.8ave('headinq8.docx') Арryментами метода addheading () являются строка текста и целое число, которое может иметь значение от О до 4. Значению О COOTBeTCTBY еТ стиль заrоловка Ti tle (Название), используемый в начале документа в качестве заrоловка BepxHero уровня. Целым числам от 1 до 4 COOTBeтCTBY ют различные уровни заrоловков, наивысшему из которых COOTBeт(TByeT значение 1, а наинизшему  4. Функция addheading () возвращает объект Paragraph, избавляя вас от необходимости извлечения абзаца из документа отдельной операцией. Вид результирующеrо документа hP.аdings.dосхпоказан на рис. 13.10. 
Работа с документами в форматах PDF и Word 397 HeaderO i i J I i I ! Headerl Не ,1d('r 2 1-I....Асr3 i";t....':4el.4 . .. ..J Рис. 13.10. Документ headings.docx с эаrоловками, соответствующими уровням от О до 4 1106"..."". ,,,зр""О' "ро" " иptI""" Чтобы добавить разрыв строки (а не начинать новый абзац), можно BЫ звать метод addbreak () ДЛЯ Toro объекта Run, после KOToporo вы хотите вставить разрыв строки. Если же требуется добавить разрыв страницы, то функции add  brea k () следует передать значение docx. text . WD  BREAК. PAGE в качестве единственноrо apryмeHтa, как это сделано в следующем примере. »> doc · docx.Document() »> doc.addragraph(IТhis is оп the first page!') <docx.text.Paragraph object at ОхОООООООООЗ785518> О >>> doc. paragraphs [О] . runs [О] . add break (docx. text. 1m ВREAК. РЛGE) >>> doa. add ragraph ( I Тhis is оп  the 88Cond page! I )  <docx.text.Paragraph object at ОхОООООООООЗ7855F8> >>> doc. еаУе ( , t_opage. docx ' ) в результате создается двухстраничный документ Word с текстом This is оп the first page! на первой странице и текстом This is оп the second page!  на второй. Несмотря на то что на первой странице за текстом This is оп the first page! остается еще MHoro свободноrо места, мы прину Дительно начинаем следующий абзац на новой странице, вставляя разрыв страницы после nepBoro объекта Run I1CpBOrO абзаца О. 1I06".л."". "з06р".'''''. Метод add yicture () объекта Document позволяет добавлять изображения в конце документа. Предположим, в вашем текущем рабочем каталоrе Ha ходитс.я файл zophie .png. Чтобы добавить в конце документа изображение zорhiе.рngшириной 1 дюйм и высотой 4 сантиметра (Word распознает как 
398 rлаВQ 13 метрические, так и неметрические едИНИЦЫ измерения), введите следую ЩИе команды. > > > doс. add yicture ( , zophie. png', _idth=doc:x. shared. Inches (1) , height=doc:x. shared. с1а (4) ) <docx.shape.lnlineShape object at ОхОООООООООЗ6С7DЗО> Первый apryмeHT  это строка, содержащая имя файла изображения. Необязательные именованные apryмeHTЫ width и height задают ширину и высоту изображения в документе. Если их опустить, то значения этих apry- ментов по умолчанию будут определяться размерами caMoro изображения. Вероятно, вы захотите указывать высоту и ширину изображения в при- ВЫЧIIЫХ вам единицах  дюймах или сантиметрах, которые можно конкре- тизировать, используя функции docx. shared. Inches () и docx. shared. Ст ( ) при указании значений именованных apryMeHToB width и height. РеЗlOме Текстовая информация  это не только простые текстовые файлы. В действительности вы, скорее Bcero, в большинстве случаев работаете с документами в форматах PDF и Word. Для чтения PDf'-документов можно использовать модуль PyPDF2. К сожалению, при чтении текста из PDF- документов ero преобразование в строку не всеrда выполняется идеально в силу сложности файловоrо формата PDF, а некоторые РDF-документы прочесть вообще невозможно. Считайте, что в подобных случаях вам просто не повезло, и остается лишь надеяться, что в будущих версиях мо- дуля PyPDF2 будет обеспечена дополнительная поддержка этих возмож- ностей. В этом смысле документы Word более надежны, и их можно читать с помощью модуля pythondocx. Для манипулирования текстом в /OКYMeH- тах Word используют объекты Paragraph и Run. Им можно назначап) стили форматирования, хотя возможности выбора СТИлеЙ оrpаничиваются лишь стандартным набором стилей, а также стилями, уже существующими 8 до- кументе. Разрешается добавлять новые абзацы, заroловки, pa."pЫBЫ (TPOK и страниц, а также изображения, но только в конце документа. Мноrие оrpаничения при работе с документами в форматах PDF и Word обусловлены тем, что эти форматы предназначены в первую очередь для TOro, чтобы документы было удобно читать людям, и они не ориентирова- ны на анализ текста проrраммными средствами. В следующей rлаве обсуж- даются два дрyrиx распространенных формата, используемых для хранения информации:JSОN и C'V. Они предназначены для компьютерной обработ- ки, и вы увидите, что работать с этими форматами в Pytlюп rораздо леrче. 
Работа с документами в форматах PDF и Word 399 Контропьные вопросы 1. Функции PdfFileReader () модуля PyPDF2 не передается строковое значение имени РD}о'айла. Что же в таком случае ей передается? 2. В каких режимах должны открываться объекты File для функций PdfFileReader () и PdfFileWriter ()? 3. Как получить объект Page для страницы 5 из объекта PdfFileReader? 4. В какой пере мен ной объекта PdfFileReader хранится количество стра- ниц докумснта PDF? 5. Если PDf'-докумснт объекта PdfFileReader зашифрован с использова- нием пароля swordftsh, то что вы должны сделать, преждс чем сможете получить из псrо объекты page? 6. Какие методы используюТ(я для поворота страницы? 7. Какой метод возвратит объект Dосшnепt для файла deтo.docx? 8. В чем 91Ъ различия между объектами Paragraph и Run? 9. Как получить список объектов Paragraph для объскта Document, кото- рый хранится в переменной doc? 10. Какой тип об1.ектов имеет переменные bold, underline, italic, strike и outline? 11. Что соответствует значсниям True, False и None, присваивасмым нерс- мснной ЬоЫ? 12. Как создать объект Document для HOBoro документа Word? 13. Как добавить абзац с текстом I Неll0 there!' в объект Document, храня- щийся в псрсмснной doc? 14. Какие цеЛОЧИСJIСННЫС значения представляют уровни Зaf'оловков, до- стynпыс В документах Word? Учебные проекты Чтобы закрспить нолученныс знания на практике, напишите проrрам- мы для нрсдложенных нижс задач. "'-пороной. Используя функцию os. walk () из rлавы 9, напишите сценарий, который выбирает вес РDFайлы в папкс (и вссх ее подпапках) и зашифровывает их с использованием нароля, прсдоставленноrо в командной строке. Со- храните каждый зашифрованный РD}о'айл, добавляя к исходному имени файла суффикс encrypted.pdf. Прсжде чем удалять исходный файл, попы- тайтссь ПРО<lИтать И дешифровать результирующий файл, <IП.IOы убедиться В том, что файл был корректно зашифрован. 
.8 r лава 13 Затем напишите проrрамму, которая находит все зашифрованные PDF файлы в папке (и всех ее подпапках) и создает дешифрованную копию каж доrо из них, используя предоставленный пароль. В случае ввода непраВИЛIr Horo пароля проrрамма должна выводить предупреждающее сообщение для пользователя и переходить к обработке следующеrо файла. Пеptон"лнзнро.онн",е прнrло_нн. . ВНАе Аоку.е"то. Word Предположим, у вас есть текстовый файл guests.txt со списком rостей. Каждое имя в этом файле записано в отдельной строке. Prof. Рlшn Miss Scarlet Col. Mustard Аl Sweigart RoboCop Напишите проrpамму, которая reнерирует документ Word, содержащий персонализированные приrлашения (рис. 13.11). Поскольку модуль PythonDocx может использовать лишь те стили, кото- рые уже существуют в документе Word, вам придется сначала добавить эти стили в пустой файл Word, а затем открыть этот файл ( помощью модуля Python-Docx. Результирующий документ Word должен содержать по одно- му приrлашению на одной странице, поэтому вызывайте метод add  break () для добавления разрывов страниц за последним абзацем кажДОl'О приrлаше-- ния. Блаrодаря этому, чтобы напечатать сразу все приrлашения, вам нужно будет открыть только один документ Word. rотовый образец файла guests.txt можно заrрузить на сайте http://nos tarch.com/automatestuff/. Взло. п"роле' Р" меТОАО. (р"о' '.л", Предположим, у вас имеется зашифрованный РDF-файл, пароль досту- па к которому вы забыли, но помните, что им является какое-то слово на анrлийском языке. Уrадывание забытоrо пароля  довольно утомительная задача. Вместо этоrо вы можете написать проrрамму, которая дешифрует РВF-файл, перебирая все возможные слова до тех нор, пока не будет най- дено слово, совпадающее с паролем. Такой подход к взлому паролей но(ит название ата",а .методом. zpубой силы. Заrpузите текстовый файл dictionary. txt на сайте http://nostarch. corn/automatestuff/. В этом файле словаря содер- жится свыше 44 тысяч анrлийских слов, по одному слову в каждой строке. 
Работа с документами в форматах PDF и Word 481 11!1 [n.....itltion.doo . Mlcros.oft Word - . G ......: ... r.'1f1Itt1A B.(1'Ia PIIIblfru стран.и С'I;rIЛКИ Ц(lН3ИРOlJажtl В"1,4" Рiвр.зtlОТЩIК Дшii1.еI....Т....3 Аве,'", Тf.апsliфоr (:; :-;jt J -' ...i:d..;J J. "1' 1,:1 2'!.' Q 18 . j<, :;:;;  ij 06......ыИ . C.mb.i. 10'>  11 '. . JI( к .. '?t  k 4.  t6  th aI"'fI4'" tI/ RoboCop 4& IIOIO_th '--\'р,'Н I t 4t. 7 (I' i. :ч'[ f ..i ." . : '" :4 . [.::::. '. ,., . . . . ..,.,...:......,.....,..(.:..-i.,...,....:. .k.. ...... . ..... - ....."':=--,.,::..w..+-;.,,.,;;-.. .. ..,.......:......__..,  Стpt"оЩО: I "'.1 .. ч",;'" <11М: 24 ."""...,....."ШJ\) :] (] o i' \1:  l00c '.." .. Рис. 13.11. Документ Word, полученный с помощью сценаРИII, rенерирующеrо персонали- зироsанные приrлашения Используя свои навыки в чтении файлов, при06ретенные в rлавс 8, соз- дайте список слов, прочитав содержимое этоrо файла. Затем Орf"'dНИЗУЙТС цикл, в котором из словаря поочередно берyrся слова, которые далее пе даются методу decrypt ( ) . Если метод возвращает О, значит, аШIOС слово НС является паролем, и проrрамма должна переходить к следующему слову. Если Же метод decrypt () возвращает значение 1, то ваша проrрамма олжна выйти из цикла и вывести взломанное значение пароля. Каждое слово из словаря следуt..'Т испытывать в верхнем и нижнем реrистрах. (Па моем но-- утбуке IIсребор всех 88 тысяч вариантов пароля в верхнем и нижнем рcrи- страх отнимает около двух минут. Вот почему нельзя использовать в качес- тве паролей простые слова.) 
PASOTA С CSV-ФАйпАМИ И ДАННЫМИ В ФОРМАТЕ JSON в rлаве 13 вы научились извлекать текст из документов Word и РО[ Документы этоrо типа хранятся в файлах, запи- санных с использованием двоичноrо формата, и для досту- па к содержащимся в них данным приходится привлекать специальные модули PythOIl . С друrой стороны, для записи CSV- и jSОNфайлов используется простой текстовый фор- мат. Их содержимое можно просматривать в любом текстовом редакторе, в том числе в файловом редакторе IDLE. Тем не менее для этих файлов также предусмотрены специальные модули C5V и j 50П, каждый из которых предо- ставляет функции, упрощающие работу с этими файловыми форматами. CSV (СО1П1ПаSeраrated Values  значения, разделенные запятыми)  тек- стовый формат, ориентированный на работу с данными несложных элек- тронных таблиц, хранящихся в обычных текстовых файлах. Модуль PythOll C5V упрощает синтаксический анализ (парсинr) CSVайлов. jSON (JavaScript Object Notatiol1; произносится как джей-сou, причем не важно, на каком именно слоrе вы сделаете ударение, поскольку Bcerдa най- дутся люди, которые скажут, что ваш вариант произношения неправиль- ный)  это формат, используемый в языке проrpаммированияjаvaScriрt для хранения информации в обычных текстовых файлах. для применения ФОР-- MaTaJSON не нужно знатьjаvaScriрt, и знакомство с этим форматом будет для вас полезным, так как он используется во мноrих ве6-приложениях. Модупь CSV Каждая строка СSV-файла представляет одну строку электронной табли цы, значения отдельных столбцов которой разделены запятыми. Напри- мер, электронная таблица, хранящаяся в файле exaтple. xlsx на сайте h t tp : / I n05tarch.com/automatestuff/, будет иметь в формате CSV следующий вид. 
404 rлово 14 05.04.2015 05.04.2015 06.04.2015 08.04.2015 10.04.2015 10.04.2015 10.04.2015 1З:З4,ЯБЛQки,7З З:41,Вишни,85 12:46,rруши,14 8: 59, Апельсины, 52 2:07,ЯБЛQки,152 18:10,Бананы,2З 2: 40, Земляника, 98 Эта таблица будет использоваться в данной rлаве для выполнения при- меров в интерактивной оболочке. Вы можете либо зarрузить roтОВЫЙ: файл ехатРle.csvнасай:те http://nostarch.com/automatestuff/, либосамостоятель- но ввести текст в каком-либо текстовом редакторе и сохранить ero в файле exaтple. CSV. С CSV-файлами леrко работать, однако они не обеспечивают некоторые возможности, доступные при работе с электронными таблицами Excel: . отcyrствуют типы значений  все значения являются строками; . OTcyrcTBYeт возможность настройки размера и цвета шрифта; . отсутствует возможность и(:пользования нескольких рабочих ли- стов; . отсутствует возможность задания ширины и высоты ячеек таблицы; . OTcyrcTByeT возможность объединения ячеек; . OTcyrcTBYeт возможность внедрения изображений и диаrрамм. Достоинством СSV-файлов является их простота. СSV-файлы широко поддерживаются мноrими типами проrрамм, их можно просматривать в текстовых редакторах (ВlUIючая файловый редактор IDLE) и они представ- ляют непосредственно табличные данные. Формат CSV является в точнос- ти тем, о чем rоворит ero название: это обычный текстовый файл, в кото- ром значения разделены запятыми. Поскольку СSV.файлы  это Bcero лишь текстовые файлы, у вас может возникнyrь соблазн читать их содержимое в виде строки, а затем обрабаты- вать полученную строку, используя методики, которые вы изучили в rлаве 8. Например, поскольку столбцы данных разделены в СSV-файле запятыми, вы можете пытаться извлекать значения, содержащиеся в каждой строке, с помощью метода sp1it (). Однако не всякая запятая в СSV-файле представ- ляет rраницу между двумя столбцами. Кроме Toro, СSV-файлы MOryт иметь собственные экранированные символы, lIозволяющие включать сами за- пятые в значения. Такие экранированные символы методом sp1it () не об- рабатываются. С учетом этих потенциальных ловушек лучше B('.ero всеrда читать и записывать СSV-фaйJlы с помощью модуля CSV. 
Работа с СSV-файлами и данными в ф о р мате JSON 405 06.,.,,,, .Reader Чтобы прочитать данные из СSV-файла, необходимо создать объект Reader. Объект Reader обеспечивает возможность итерирования по стро- кам СSV-файла. Убедившись в том, что в текущем рабочем каталоrе при- сутствует файл exaтple.csv, введите в интерактивной оболочке следующие команды. о >>> illlport esv . >>> exupleFile · open ( '8X8lllple. e.v' ) . >>> exaapleaeader . esv.reader(exampleFile) . >>> exupleData · list (uaapleReader) . >>> uupleData [['05.04.2015 13:34', 'Яблоки', '73'], ['05.04.2015 3:41', 'Вишни', '85'], ['06.04.201512:46', 'rруши', '14'], ['08.04.20158:59', 'Апельсины', '52'], ['10.04.2015 2:07', 'Яблоки', '152'], ['10.04.2015 18:10', 'Бананы', '23'], ['10.04.2015 2:40', I Земляника', '98']] Модуль csv посrаВJlЯется вместе с PytllOll. поэтому мы можем просто им- портировать ею . без предварительной установки. Прежде чем читать СSV-файл с помощью модуля csv, откройте ero с 110- мощью функции ореn () .. как вы это делаете при открытии любоrо тексто- Bpro файла. Но вместо Toro чтобы вызывать метод read () или readlines () для объекта File, возвращаемою функцией ореn (), передайте этот объект функции csv. reader () .. Эта функция возвращает объект Reader, который вы будете использовать в дальнейшем. Обратите внимание на то, что функ- ции csv. reader () псрсдается объект, а не просто строка с именем файла. Самый прямой способ получсния доступа к значсниям в объскте Reader  нсредача этоrо 06'ьскта функции list () .. для IIрсобразования н обычный список PytllOl1. В рсзультате мы получаем СIIИСОК списков, который можно сохранить в переменной exampleData. Введя имя exampleData в интерактив- ной оболочке, можно увидеть этот список .. Теперь, коrда у вас есть СSV-файл в ВИДе списка списков, можете обра- щаться к значениям, относящимся к отдельным строкам и столбцам табли- цы, с помощью выражения exampleData [row] [col], rДе row  индекс одноrо из списков в объекте exampleData, а col  индекс нужноrо элемента из этоrо СI1И(ка. Введите в интерактивной оболочке следующие команды. »> exampleData[O] [О] 'J5.04.2015 13:34' »> exampleData[O] [1] 'Яблоки' »> exampleData[O] [2] 
406 rлово 14 '73' »> exampleData[l] [1] 'Вишни' »> ехamрleDаta[б] [1] 'Земляника' Выражение example Оа ta [О] [О] дает нам первую строку в первом списке, выражение exampleDa ta [О] [2]  третью строку в этом же списке и Т.д. ""Н.' ДОННЫХ .3 06.е"'08 Reader 8 ..."ле ror В случае крупных СSV-файлов целесообразно использовать объект Reader в цикле for. Тем самым удается избежать заrрузки сразу Bcero файла в память компьютера. Например, введите в интерактивной оболочке сле- дующие команды. >>> im.port csv »> exam.pleFile = ореn('ехащрlе.сsv') »> exam.pleReader = csv.reader(exam.pleFile) »> for row in exampleReader: print('CТPOKa *, + str(ехащрleReadеr.linе nuш) + ' , +  str(r01f»  Строка #1 ['05.04.2015 13:34', 'Яблоm', '73') Строка #2 ['05.04.20153:41', 'Вишни', '85'] Строка #3 ['06.04.201512:46', 'rруши', 'Н'] Строка #4 ('08.04.2015 8:59', 'Апельсины', '52'] Строка #5 ['10.04.20152:07', 'Яблоm', '152'] Строка 116 ('10.04.2015 18:10', 'Бананы', '23'] Строка 117 ['10.04.2015 2:40', 'Земляника', '98') Импортировав модуль еву и создав объект Reader из СSV-файла, можно орrанизовать цикл по строкам в объекте Reader. Каждая строка  это список значений, каждое из которых представляет отдельную ячейку таблицы. С помощью функции print () мы выводим номер строки и ее содержи' мое. Для получения номера строки используется переменная line  num объ- екта Reader, в которой хранится номер текущей строки. Цикл по объекту Reader может выполнятЬ(я только один раз. Для по- BTopHoro чтения CSV-файла необходимо заново создать объект Reader, вы. звав функцию еБУ. reader ( ) . 06.,,,,,, Jlri ter Объект Wri ter позволяет записывать данные в СSVфайл. Для создания объекта Wr i ter используется функция esv. wri ter ( ) . Введите в интсрактив ной оболочке следующие команды. 
Работа с СSVфайлами и данными в формате JSON 407 »> im.port езv О»> outputFile · open('output.esv' I 'w', newline=' ') . >>> outputWriter . езv. writer (outputFile) »> оutрutWritеr.writerоw(['зрam.', 'egg8', 'baeon', 'haш']) 21 »> оutputWriter.writerоw(['Здра8С8УЙ, мир! " 'egg8', 'bacan',  ' Ьаш. I ]) 32 »> outputWriter.writerow([l, 2, 3.141592, 4]) 16 »> outputFil..clo8e() Прежде Bcero необходимо вызвать функцию open () и передать ей apry мент' w' для открытия файла в режиме записи О. В результате этою созда ется объект, который далее передается функции csv. wri ter () . для созда ния объекта Writer. Если вы работаете в Windows, то в качестве значения именованноrо aJr ryMeHTa newline функции open () следует передавать пустую строку. В силу технических причин, обсуждение которых выходит за рамки данной книrи, если этоrо не сделать, в выводе появятся лишние пустые строки (рис. 14.1). Аl f" 42 . .. ..._.... - ..- . . . -  .".- .....  А В С D Е F G 1 42 1 2 3 4 5 6 7 2 3 2 4 6 8 10 12 14 4 5 3 6 9 12 15 18 21 6 7 4 8 12 16 20 24 28 8 9 5 10 15 20 25 30 35 10 PI1C. 14. 1. Если вы забудете задать пустую строку в качестве значеНI1Я I1MeHOBaHHoro ар- ryMeHTa п ew 1 iл е фУНКЦИI1 орел (), то при выводе содержимоrо СSV-файла появятся ЛI1WНl1е СТРОКI1 Метод writerow () объекта writer принимает apryMeHT в виде списка. Каждое значение ЭТOI'о списка помещается в отдеЛl.ную ячейку BЫXOДHO rо СSV-файла. ВО:Jвращаемым значением метода writerow() является число символов, записанных в файл для данной строки таблицы (включая симв лы новой строки). 
408 rЛQва 14 Вот как ВЫI'ЛЯДИТ содержимое файла оиtриt.с.щ полученноrо в результате выполнения предыдущеrо кода. spam,eggs,bacon,ham "Здравствуй, мир!",еggs,Ьасоn,hаm 1,2,3.141592,4 Обратите внимание на то, как объект Wri ter автоматически экраниру- ет кавычками запятую в значении' Здравствуй, мир!' в СSV-файле. Модуль csv избавляет вас от самостоятельной обработки подобных специальных случаев. Им,но.tI.."" tlр'1М'Н'''' dвl:l.mi ter . lineteJ:mina tor Предположим, вы захотели использовать в качестве разделителя яче- ек не запятую, а символ табуляции и при этом удвоить междустрочный интервал. Это можно обеспечить, введя в интерактивной оболочке сле- дующий код. »> im.port eBV > > > esvFile · open ( , exam.ple. tsv', ',,', newline=") о »> esvWriter · esv."riter(e8vFil., delilliter-'\t', lineterainator-' \n\n') »> еsVWritеr."riterо,,(['ябпоки', 'aneпьсикы', 'rp8ЙnФРYТW']) 24 > > > esvWri ter . "ri tero" ( [ 'яйца', 'бекон', 'окорок']) 17 »> esVWri ter ."ritero" ([ 'spaвa', I spaвa', 'араш', 'spaвa', 'враа', 'spaвa']) 32 »> csvFile.elose() В результате этоrо разделитель данных и разделитель строк в вашем файле изменятся. РазделитfJlЬ даииых (или просто  разделитель)  это символ, используемый для разделения значений в строке. По умолчанию в СSVфайлах в качестве разделителя используется запятая. Разделитель стрО'К  это символ, добавляемый в конце строки таблицы. По умолчанию в качестве разделителя строк используется символ новой строки. Вместо этих символов можно и(:пользовать друrие, передав соответствующие зна- чения функции csv. wri ter () в качестве именованных apryMeHToB delimi ter и lineterminator. В результате передачи apryMeHToB delimeter=' \ t' и lineterminator=' \ n \n' О разделителем становится символ табуляции, а разделителем строк  удвоенный символ новой строки. После этоrо троекратный вызов функции wr i te row () выводит три строки таблицы. 
Работа с СSVфайлами и данными в формате JSON 409 в результате выполнения этой проrpаммы мы получаем файл example.tsv, имеющий следующее содержимое. яблоки апельсины rрейпфруты яйца бекон ветчина spam spam spam spam spam spam в данном случае в СВJIзи с тем, что ячейки таблицы со значениями столб- цов разделены теперь символами табуляции, мы используем для файла pac ширение .lsv. Проект:удапениезаrоповковизCSV-файпа Предположим, вам предстоит выполнить рутинную работу по удалению lIервых строк из нескольких сотен СSV-файлов. Возможно, вы собираетесь передавать эти файлы какому-то автоматизированному процессу, которому требуются только данные без зarоловков столбцов. Вы М,оz.ли бы открывать каждый файл в приложении Excel, удалять первую строку таблицы и заново сохранять файл, но это отняло бы У вас MHoro часов времени. IЬраздо луч ше написать проrрамму, которая выполнит эту работу вместо вас. Проrрамма должна будет открывать каждый из файлов с расширением .csv, находящихся в текущем рабочем каталоrе, читать содержимое csv файла и перезаписывать содержимое без первой строки в файл с тем же именем. В результате старое содержимое СSV-файла будет заменено новым, в котором заrоловки столбцов таблицы отсутствуют. Предупреждение Как обыч1/,О, вся:кий раз, 'Коеда вы пишете проера.м.му, U3.Ме'НЯющую фаUJ//Ы, 1/,е за- бывайте со.здаватъ их резервные 'Копии хотя бы для moео, чтобы застраховать себя иа тот с.лучай, ес.лu чтто пойдет ш! так, 'Как ожидается. Будет ОЧe1lЬ обuд1/,О, если в подоб1/,ОЙ cumya1J,uu у вас не акажется под рукой исходных файлов. В целом проrрамма должна делать следующее: . выполнить поиск всех СSV-файлов в текущем рабочем каталоrе: . прочитать полное содержимое каждоrо файла; . записать содержимое, опуская первую строку, в новый СSV-файл. На уровне кода проrрамма должна выполнять следующие операции: . выполнить цикл по списку файлов. возвращенному вызовом функции os .listdir ( ) , пропуская файлы с расширениями, отличными от .CSV; . создать объект Reader и прочитать содержимое файла, используя атри бут line  num для определения тою, какую строку следует пропустить; . создать объект Writer и записать прочитанllые данные в новый файл. 
410 rЛQВQ 14 Чтобы создать проект, откройте новое окно файловоrо редактора и со- храните ero в файле removeCsvHeader.py. ш", ,. Ц.и по 8"8 CSV.фcrii."1I Первое, что должна сделать ваша проrрамма,  это орrанизовать цикл по всем СSV-файлам, находящимся в текущем рабочем каталоrе. Введите в файл removeC$vHeader .ру следующий код. #! руthопЗ # removeC$vHeader.py  Удаляет заrоловКИ из всех СSVфайлов # в текущем рабочем каталоrе. import C5V, 05 o5.makedir5('headerRemoved', exi5tok=Trиe) # Цикл по всем файлам в текущем рабочем каталоrе. for c5vFilename in O$.li$tdir('.'): if not C5vFilename.end5with('.C5V'): . continиe # пропустить файлы с расширением, # отличным от .C5V рriпt('Удаление заrоловка из файла' + csvFilename + '...') # тооо: прочитать СSVфайл (с про пуском первой строки) . # тооо: записать СSVфайл. Вызов 0$ .makedir5 () создает папку headerRcтoved, в которую будyr запи саны все СSVфайлы, не содержащие зarоловков. Цикл for по элементам списка 05 .li5tdir ( , . ' ) частично решает эту задачу, однако он итерирует по всем файлам, находящимся в рабочем каталOl'е, поэтому в начале цикла He обходимо добавить код, обеспечивающий пропуск файлов, имена которых не заканчиваются расширением .CSV. Если в цикле встречается такой файл, то инструкция continиe О обеспечивает ero про пуск и переход к следующе- му имени файла. Далее (:ледует вызов функции print (), которая выводит на экран имя текущеrо обрабатываемоro CSV-файла, что позволяет контролировать ход пьшолнения проrpаммы. Заканчивается проrpамма комментариями TODO, напоминающими о том, что должна делать оставшаяся часть проrpаммы. Шо,2. "те.., СSV.Ф"ii." В действительности проrpамма не удаляет первую строку каждоrо CSv файла. Вместо этоro она создает новую копию файла, но уже без первой 
Робота с СSVфайлами и данными в формате JSON 411 строки. Поскольку имя файлакопии совпадает с именем исходноrо файла, он перезаписывает ориrинал. В проrрамме необходимо прсдусмотреть проверку, позволяющую определить, является ли текущая строка в Цикле первой. Добавьте в файл reтoveCsvHeader. ру выделенный пиже код. #! python3 # removeCsvHeader.py  Удаляет заrоловки из всех СSVфайлов # в текущем рабочем каталоrе. nропущенный KOД * Проаъ СSVфайп (с npопуском пер80Й строки) . csvRo1fS - [] csvFileObj = open(csvFilename) readerObj = csv.reader(csvFileObj) for r01f in readerObj: if readerObj.line num == 1: continue * npODYститъ первую строку csvRows.append(rc1f) csvFileObj.close() # TODO: записать СSVфайл. Для отслеживания номера строки СSV-файла, которая читается в даll ный момент, можно использовать атрибут liпеnumобъекта Reader. Друroй цикл for итерирует по строкам, возвращенным объектом Reader, и все стро-- ки, кроме первой, присосдиняются к списку esvRows. В процессе выполнсния итераций цикла for по строкам код llроверяет, является ли значснис атрибута readerObj .linenum равным 1. Если это так, то инструкция continиe осуществляет переход к следующей строкс, не при соединяя текущую строку к СIlИ(:КУ esvRows. Для всех последующих строк условие будет иметь ложное значение, и они будут присоединяться к YKa занному списку. Шо,3. 30n." av-.o..o без nер.ом "ро". Теперь, коrда в списке esvRows содержатся все строки, кроме псрвой, ero нужно записать в СSV-файл, который будет находиться в папке hеаderRerruюed. Добавьте в файл remиueCsvHeader. ру выделенный ниже код. #! руthоnЗ # removeCsvHeader.py  Удаляет заrоловки из всех СSVфайлов # в текущем рабочем каталоrе. пропущенный KOД # цикл по всем файлам в текущем рабочем каталоrе. . for esvFilename in os.listdir(' .'): if not сsvFilепamе.епdswith(' .esv'): 
412 rпOBa 14 eontinиe # пропуек файлов е расширением, # отличным от .esv пропущенный KOД #1 Запись СSVфайпа. csvFi1eObj - open(os.path.join('headerRelDov8d',  csvFilename), 'w', newline=r") csVWriter = csv.writer(csvFileObj) for row in csvRows: csVWriter.writerow(row) csvFileObj.clcse() Объект Wri ter записывает список csvRows в СSV-файл в папке header& moved, ИСIlОЛЬЗУЯ переменную csvFilename (которую мы также использовали в объекте Reader). В результате этою исходный файл будет перезаписан. Создав объект Writer, мы орrанизуем цикл ПО всеМ подчиненным спи- скам, хранящимся в csvRows, И записываем каждый из них в файл. После выполнения блока кода Внешний цикл for О перейдет к следую- щему имени файла из списка os .listdir ( , . ' ).110 выполнении этою цикла проrрамма завершается. Чтобы протестировать nporpaмMY, заrрузите файл removeCsvHeader . zip с сайта http://nostarch . com/aиtomatestиff/ и рас накуйте ero содержимое в папку. Запустите nporpaMМY reтoveCsvHeader.py в этой папке. Вы должны получиТl. следующий вывод. Удаление заrоловка из файла NAICSdata1048.esv... Удаление заrоловка из файла NAICSdata1218.esv... пропущенный BЫВOД Удаление заrоловка из файла NАIСSdаtа98З4.еsv... Удаление заrООВКа из файа NАIСSdаtа998б.еsv... Данная проrрамма должна выводить имя файла всякий раз, Korдa из CSV- файла исключается первая строка. Иде. оrноt.reл.но (оздон.. оноло,.""ных .,orpo_ Вы можете писать аналоrичные проrраммы не только для с..V-файлов, но и для файлов Excel, поскольку в обоих случаях вы имеете дело с файлами электронных таблиц. Поэтому для вас не составит большоrо труда написать nporpaMMbI }(ЛЯ решения следующих задач: . сравнение данных, при надлежащих различным строкам в CSV-фaйJIе или различным СSV-файлам; . копирование специфических данных из СSVфайла в файл Ехсе] и об- ратно; 
Работа с СSVфайлами и данными в формате JSON 413 . контроль допустимости данных или ошибок форматирования в CSV файлах и вывод предупреждений для пользователя об обнаруженных ошибках; . чтение данных из СSV-файла с целью их использования в качестве входных данных для проrpамм на Pytlюп. JSON и интерфейсы ПРИКllадноro проrраммирования ]SON OavaScript Object Nоtаtiоп)  популярный способ форматирования данных в виде одной строки, которую удобно читать людям. Формат ]SON тесно связан с языком]аvaScriрt. ИСllOльзуется]аvaSсriрt-проrраммами для записи структур данных и обеспечивает форму представления данных, Kcr торая напоминает вывод, получаемый с помощью функции pprint () языка Рytlюп. Для работы с данными в формате ]SON знать ]avaScript не нужно. Вот пример представления данных в формате ]SON. {"пате": "Zophie", "isCat": trиe, "miceCaиght": О, "napsTaken": 37.5, "felineIQ": nиll} Знакомство с ]SON никоrда вам не помешает, поскольку именно TOT формат предлаrается мноrими веб-сайтами для обмена данными с проrpам- мами. Набор средств, предоставляемых веб-сайтом для использования во внешних проrраммных продуктах, называется прu'Кладны-м проера.м.м:н:ы.м. интерфейсо-м (Application Programming Interface  API). Получение доступа к API  это то же самое, что получение доступа к любой веб-странице пcr средством URL-aдpeca. Различие состоит в том, что aHHыe, возвращаемые API, форматируются (например, с ИСПОЛЬЗ0ванием]SОN) для чтения ком- пьютерами; человеку читать такие данные трудно. Доступ к данным в формате ]SON обеПlечивают мноrие веб-сайты. l<acebook, Twitter, Yahoo, Google, Tumblr, Wikipedia, F1ickr, Data.gov. Reddit, IMDb, Rotten Tomatoes, Linkedln и ДРYI'ие популярные сайты предлarают интерфейсы прикладноro проrpаммирования, которые вы сможете исполь- зовать в своих проrраммах. На некоторых из этих сайтов необходимо пред- варительно зареrистрироваться, что в подавляющем большинстве случаев осуществляется бесплатно. Вам нужно найти документацию с описанием TOI'O, 110 каким URLaдpecaM ваша проrрамма должна направлять запросы для получения требуемых данных, и выяснить, каков общий формат возвра- щаемых структур данных. Такая документация должна предоставляться тем сайтом, который предлаrает данный API; если на сайте имеется страница "Developers" ("Разработчикам"), то начните поиск документации оттуда. С помощью API можно писать проrраммы, способные, в частности,  шаТJ. следующие задачи. 
414 rЛQВQ 14 . Автоматический сбор "сырых" (необработанных) данных с ве6-сайтов (этот lIр(щесс называют веб-CICрапuнzо.м). (Обращение к АР! зачастую оказывается rораздо более уобным, чем заrрузка веб--страниц и син таксический анализ НТМIра:JМетки с IЮМОЩЬЮ нарсера Beautiful Soup. ) . Автоматическая заrрузка новых llOCTOB с одноЙ из ваших учетных за писей в социальных сетях и их публикация дЛЯ РУI'ОЙ учетной запи си. Например, вы мпжете ВЗЯТI. свои посты С сервиса микроблоrов Tumblr и опубликовать их в Facebook. . Создание "ЭНЦИКЛОlIеии кино" для своей ЛИЧlIОЙ коллекции кино- фильмов, ИСlIОЛь:JУЯ для сбора aHHЫX сайты IMDb, Rоttеп Тошаtоеs и Википедия и номещая их в один текстовый файл, храпящийся на вашем КОМlIьютере. С некоторыми примерами]SОN АР! вы сможете нознакомиты::я на сайте http://nostarch.com/automatestuff/. Модуяь j son Модуль PytllOIl j son выполняет всю работу по преобразованию данных из формата]SОN в значения PythOIl и обратно для функций j son .loads () и j 50n. dиmp5 ( ) . Формат ]SON не обеспечивает хранение всех типов значс-- ний Python. Он позволяет хранить лишь следующие типы данных: строки, целые и вещесrвенные числа, булевы значения, списки, словари и значс-- ние NoneType. Формат ]SON не может представлять специфические объ екты PythOIl, такие как объекты File, Reader и Writer, предназначенные для работы с СSV-файлами, объекты Regex или объекты WebElement модуля SeleniulJl. Ч"".е 110".",. JSON t по_ас'. ."'.Ц.. loa.ds () Чтобы транслировать строку, содержащую ]SОNдапные, в значение PyttlOn, передайте ее функции j son .load5 ( ) . Введите в интерактивной обо- лочке следующие команды. »> stringOfJsonData = '{ "naJl18": "Zophie", "isCat": true, "lIIiceCaught": О, "felineIQ": null} I >>> import звоn »> jsonDataAsPythonValue = json.loads(stringOfJsonData) »> jsonDataAsPythonValue {'isCat': Trиe, 'rniceCaиght': О, 'пате': 'Zophie', 'felineIQ': None} 
Работа с СSV-файлами и данными в формате JSON 415 Импортировав модуль j 50n, можно вызвать функцию loads () и передать ей строку jSON-данных. Обратите внимание на то, что в cTpOKaxjSON Bcer- да используются кавычки. Эта функция возвращает данные в виде словаря Python. Данные, хранящиеся в словарях PythOll, не упорядочены, поэто- му при выводе значения переменной j50nDataA5PythonValиe пары "ключ значение" MOryт появляться в произвольном порядке. 30.." JSON-дtlННЫХ , .ОМОЩ'IO ФУНllq.. dШlpS () Функция j 50П. dumps () транслирует значение Python в строку данных в форматеjSОN. Введите в интерактивной оболочке следующий код. »> pythonValue = {'isCat': True, 'miceCaught': О,  'паше': 'Zophie', 'felineIQ': None} »> import j аоn »> stringOfJsonData = json.dumps (pythonValu8) »> stringOfJscnData '{"i5Cat": true, "felineIQ": null, "miceCaught": О, "пате": "Zophie" } I Значением может быть один из следующих элементарных типов данных Python: словарь, список, целое или вещественное число, строка, булево зна- чение и None. Проект: ПОllучение текущеrо проrноза norOAbl Вообще rоворя, в получении сведений о ПOl'оде нет ничеrо СЛОЖноrо. Для этоro достаточно открыть браузер, щелкнyrь в адресной (:троке, ввести URL-aдpec поrодноrо сайта (или выполнить поиск таких сайтов и щелкнуть на одной из ссылок), выждать, пока зarрузится страница, пропустить рекла- му и Т.д. На самом деле при этом вам приходится выполнять множество лишних действий, от которых можно было бы избавиться, имея проrрамму, заrpужа- ющую проrноз поrоды на несколько дней и выводящую ero в виде простоrо текста. Для заrрузки данных из Интернета проrрамма будет использовать модуль reqиests, рассмотренный в rлаве 11. В целом проrрамма выполняет следующие действия: . читает название населенноro пункта, заданное в командной строке; . заrружает поroдные данные в формате jSON с сайта Open WeatherMap. org; . преобразует строку jSON-данных в структуру данных РytllOП; · выводит проrноз поrоды на сеrодНЯ и следующие два дня. 
416 rпOBO 14 Следовательно, код должен выполнять следующие операции: . объединять строки, хранящиеся в списке sys. argv, для получения на- звания запрошенноrо населенноrо пункта; . вызывать функцию requests. get () для зarpузки поrодных данных; . вызывать функцию j son .loads () для пре06разованияJSОN-данных в структуру данных Pythоп: . выводить проrноз поrоды. Откройте в файловом редакторе новое окно для этоro проекта и сохра- ните ero в файле quick Weather. ру. Шаr '. nОЛУ"ен.е ра,поло.ен., .3 apryмe"", п_аllДllоii аро". Входные данные для этой I1рOI'раммы поступают из командной строки. Введите в файл quickWeather.py следующее содержимое. #! руthоnЗ # qиickWeather.py  Выводит проrноз поrоды для заданноrо # населенноrо пункта import json, reqиests, sys # Определение названия населенноrо пункта из apryмeHToB # командной строки. if len(sys.argv) < 2: рrint('Использование: quickWeather.py location') sys.exit() location = I '.join(sys.argv[l:]) # TODO: заrрузить JSОNданные из API сайта OpenWeatherMap.org. # TODO: заrрузить JSОNданные в nеременную python. в Python apryMeHTbI командной строки хранятся в списке sys.argv. За "мarической строкой", которая начинается символами Jt ! , и инструкцией import следует код, выполняющий проверку TOro, что командная строка со- держит более одноrо apryMeHTa. (Вспомните о том, что в списке sys. argv вcerдa будет I1РИcyrствовать по крайней мере один элемент, s ys . argv (О] , со- держащий имя файла сценария PythOIl.) Если в списке имеется только один элемент, значит, пользователь не предоставил в командной строке назва- ние населенноrо пункта, и в этом случае проrpамма, прежде чем завершить работу, выводит сообщение, объясняющее способ ее вызова. ApryMeHTbI командной строки разделяются пробелами. Следователь- но, если пользователь предоставит название населенноrо пункта в виде Зап Francisco, СА, то в sys. argv будет содержаться следующий список: 
Работа с СSVфайлами и данными в формате JSON 417 ['quickWeather.py', 'San', 'Francisco,', 'СА'].Поэтомумыдолжныобъе ДИНИТЬ все хранящиеся в sys.argv строки, за исключенисм псрвой, вызвав метод join (). Объединенная строка сохраняется в переменной location. Шоr 2. 3t1rpY31l0 JSON-IIOHH",X Сайт Ореп WeatllerMap.org предоставляет ОIlсративную информацию о IЮI'ОДС в форматеjSОN. Вашей ПрOJ'раммс остается лишь заrрузить стра- ницу с URL-адресом flttр://арi.орепwеаthеrmар.оrg/dаtа/2.5/fоrесаst/ dаilу?q<rород>&спt=о3, !'де <Тород>  название I'орода, пol'одныc условия в котором вас интересуют. ДобаВl>те в файл quick Weather. ру следующий код, выделенный ниже полужирным шрифтом. #! pyLhon3 # quickWeather.py  Выводит ПрОI'ноз по:rоды для заданно:rо # населенноI'О пункта. прОllущенный KOД # За:rpузить JSONданные из API сай'1'а OpenWeatherМap.org. url .'http://api.openweathermap.org/data/2.5/forecвst/  dailу?q=%s&cnt-З' % (location) response · requests.get(url) response.raise for status() # ТООО: заI'рУЗИТЬ JSОNданные в переменную Python. Мы определили lIазванис населенноro пункта из apryMeHTOB комаllДНОЙ строки. Для создания URLaдpeca, к которому необходимо получить доступ, мы использусм символзаместитель % s и вставляем в эту ПОЗИЦИЮ В строке URL строку, сохранснную в псременной location. l'езультат сохраняет ся в IIеременной иrl, которая передается функции reqиests.get (). Вызов requests. get () возвращает объект Rеsропsе, корректность KOToporo I1рОВС ряется с помощью ВЫ;ЮDa rai se  f or  5 ta tus ( ) . в случае отcyrствия исключе- ний зarpуженпый текст будет находиться в I1сременной-члене response. text. Шоr 3. 30rpуз«0 JSON-lIо."",х . ."'.ОА .роr"озо .оroll" в пере мен ной-члене response. text хранится длинная строка, содсржа- щая данные в формате jSON. Для преобразования этой строки в значение Python вызывается функция j son .loads ( ) . Полученные jSОNданные будyr выrлядеть примерно так. ('city': {'coord': {'lat': Л.7'171, '1оп': 122.4?}, 'country': 'United States о[ America', 'id': '5391959', 'пате': 'San Francisco', 'popи1ation': О}, 
418 rЛQВQ 14 'cnt': 3, 'cod': '200', 'list': [{'clouds': О, 'deg': 233, 'dt': 1402344000, 'humidity': 58, 'pressure': 1012.23, 'speed': 1.96, 'temp': {'day': 302.29, 'eve': 296.46, 'тах': 302.29, 'min': 289.77, 'morn': 294.59, 'night': 289.77}, 'weather': [{'description': 'sky is clear', 'icon': 'Old', опущено Вы можете увидеть эти данные, передав переменную wea therDa ta (см. ниже) функции pprint .pprint (). Более подробное описание этих ,ю- лей вы найдете на сайте http://openweatherrnap.org/. Например, из онлай- новой документации можно узнать, что число 302.29, указанное за строкой I day' ,  это дневная температура, выраженная в rpaдycax Кельвина, а не в rpaдycax Цельсия или Фаренrейта. Интересующие вас Iюrодные данные указываются за строками 'main' и 'description'. Можно орrанизовать их аккуратный вывод, добавив в файл quickWeather.py выделенный ниже код. python3 # qиickWeather.py  Выводит проrноз поrоды для заданноrо # населенноrо пункта опущено # Заr.pyзка JSОNданНЫХ 8 переменную Python. weatherData = json.loads(response.text) . Вывод nporноза поrоды. . w = weatherData [ , list' ] print ( 'ПоroД8 сSI'OДКЯ 8 %а:' % (location» prin t (. [ О] [ 'wea ther ' ] [О] [ 'main ' ], , , , _[О] ['weather'] [О] ['description']) print () print ( 'Завтра: ' ) print(w[l] [ 'weather'] [О] ['lIIain' ], ,, w[l] ['weather'] [О] ['description']) print О print ( , Поспез&втра: ' ) print(w[2] ['weather'] [О] ['main'], '', _[2] ['weather'] [О] ['description']) 
Работа с СSVфай"ами и данными в формате JSON 419 Обратите внимание на то, как КОД сохраняет данные о поrоде weatherData [' list'] в переменной w, избавляя вас от лишнеrо ввода вруч ную О. Словари с поrодными данными на сеrодня, завтра и послезавтра извлекаются соответственно в элементы w [О] , w [1] и w [2] . в каждом из этих словарей имеется ключ 'wea ther', содержащий списковое значение. Вы заинтересованы в первом элементе списка с индексом О, представляющем собой вложенный словарь, который содержит несколько дополнительных lUIючей. Здесь мы выводим значения, которые сохранены в lUIючах 'main' и 'description', разделенных дефисами. Запустив эту проrpaмму с apryмeнтoM командной строки quickWeather. ру San Francisco, СА, вы получите примерно такой вывод. Поrода сеrодня в San Francisco, СА: Clear  sky is clear Завтра: Cloиds  few clouds Послезавтра: Clear  sky is clear (Основная причина, по которой мне нравится жить в Сан-Франциско,  это хорошая поroда!) Иде. 07,,«.reл.,,0 tоздо".. ОllОЛО,.""",,. nporpo_ Разработанный нами код для доступа к ПОI'ОДНЫМ данным может быть положен в основу мноrих типов проrрамм. Вы можете создать аналоrичные проrраммы, позволяющие решать следующие задачи. . Сбор пропюзов Поrоды для ряда rеоrрафических областей с целью выбора кемпинrа или пешсходноrо маршрута, наиболее блаrоприят- Horo с точки зрения ПОI'ОДНЫХ условий в определенный период вре- мени. . Внести в планировщик заданий ПрOl'рамму, выполняющуюся по рас- писанию, которая реl'УЛЯРНО проверяет текущие поrодные условия и заблаrовремешю предупреждает о заморозках, чтобы вы в случае необходимости успели занести комнатные растения с улицы в поме- щение. (Выполнение задач но расписанию обсуждается в rлаве 15, а отправке электронных сообщений посвящена rлава 16.) . Сбор ПРОI'НОЗОВ поrоды нескольких веб-сайтов для их одновременно- ro отображения или расчета и отображения усреднеНIIЫХ показатс- лей по совокупности проrнозов. 
420 rЛQва 14 РеЗlOме ('$У и jSON  распространенные текстовые форматы храпения данных. Их синтаксический анализ проrpаммами Не вызыва<.>'f никаких сложностей, и вместе с тем они леrко читаются людьми, чем и обусловлено их широкое использование для представления данных простых электронных таблиц или веб-приложений. Модули C5V и j 50n значительно упрощают процесс чтения и записи CSV иjSОN-файлов. Из нескольких предыдущих rлав вы узнали о том, как использовать Python для <:интаксическоrо анализа информации, сохраненной в файлах различных форматов. Одна из часто встречающихея задач  получение данных, подrотовленных в различных форматах, и их синтаксический aHa лиз для извлечения конкретной информации, которая представляет для вас интерес. Часто эти задачи настолько специфичны, что использование коммерческих llporpaмM не является оптимальным выходом. Написав соО- ственные сценарии, вы сможете заставить компьютер обрабатывать боль шие массивы данных, представленных в этих форматах. В rлаве 15 мы обсудим орraнизацию обмена данными пyrем отправки ccr общений электронной почты и текстовых сообщений. КОНТРОlIьные вопросы 1. Какие возможности электронных таблиц Excellle MOIyr быть реали- зованы в СSVтаблицах? 2. Какие apryMeHTbI необходимо передавать функциям C5V. reade r () и csv. writer () для создания объектов Reader и Wri ter? 3. С использованием каких режимов должны открываться объекты File для объектов Reader и writer? 4. Какой метод принимает список apryмeHToB и записывает ero в файл? 5. Каково назначение именованных apryмclrroB delimi ter и 1inetenninator? 6. Какая функция принимает <:троку jSON-данных, а возвращает струк- туру данных Python? 7. Какая функция принимает структуру lI,aHHbIx Python, а возвращает строку jSON-данных? Учебный проект Чтобы закрепить полученные знания на практике, напишите проrрамму для нредложенной ниже задачи. 
Работа с СSVфайлами и данными в формате JSON 421 nро'ра""о . nр,06ра'08"".. до"".,. ., фор.,,1О Ixte/ 8 фОР."7 CSV [жеIIlО3ВОЛЯет (охранить электронную таблицу в СSV-файле несколь- кими щелчками мышью, но если нужно преобразовать из формата Ехсе} в формат CSV сотни файлов, то придстся щелкать мышью на протяжении мноrих часов. Используя модуль openpyxl из rлавы 12, напишите ПрOl'рамму, которая читает все файлы Excel, расположенные в текущем каталоrе, и вы. водит их в виде CSV -файлов. В одном файле Ex(e} может еодержаться множество листов, IIОЭТОМУ не- обходимо создаваТl> 110 одному СSV-файлу на один лист. Файлы в формате CSV должны носить имена следующсrо вида: <uмяфайлаехсеl>  <зazОЛ08mс ЛUC11Ul>.с.щ rде <uмлфайлаехсеl>  имя файла в формате [хее} без расшире- ния (наllример, 'spamdata', а не 'spamdata.xlsx'), а <заеОЛО8mслuста>  строка из переменной ti tle объекта WorkstleeL. Данная IIрor'рамма включает ряд WlOженных циклов for. Каркас про- rpaMMbl может иметь примерно следующий вид. for excelFile in os.listdir(' .'): # Пропустить файлы, расширения имен которых отличны от .xlsx, # заrрузиь объект workbook. for sheetName in wb.get sheet names(): # ЦИКЛ по листам рабочей книrи. sheet = wb.getsheetbyname(sheetName) # Создание имени СSVфайла из имени Ехсеlфайла # и титула листа рабочей книrи. # Создание объекта csv.writer для этоrо СSVфайла. # Цикл по строкам электронной таблицы. for rowNum in range(l, sheet.gethighestrow() + 1): rowData = (] # присоединить каждую ячейку к списку # Цикл по всем ячейкам строки. for colNum in range(l, sheet. gethighestcolumn() + 1) : # Присоединение данных каждой ячейки к rowData. # Запись списка rowData в СSVфайл. csvFile.close() 3аrрузите ZIР-файл excelSpreadsheets.zip с веб-сайта http://nostarch .сот/ aиtomatestuff/ И разархивируйте электронные таблицы в тот же каталоr, в котором находится ваша проrрамма. Вы сможете использовать эти файлы для тестирования проrрамм. 
OSPASOTKA ЗНАЧЕНИЙ ДАТЫ И ВРЕМЕНИ, ппАНИРОВЩИК ЗАДАНИЙ И ЗАПУСК проrРАММ Выполнять проrраммы, сИДЯ перед компьютером,  это, конечно, хорошо, но лучше бы проrраммы выполнялись без непосредственноrо контроля над ними. И такая B03 можность действительно есть. Вы можете орraнизовать запуск проrрамм в определенное время определенноro дня или через реryлярные промежyrки времени с ПРИВJIзкой к часам cBoel'o компьютера. Например, ваша проrрамма может выполнять автоматический сбор данных на каком-либо сайте, отслеживая их обновле- ние, или откладывать выполнение задач, требующих ИlIтенсивноrо исполь- зования процессорноrо времени, на 4 часа утра, пока вы будете спать. Эта функция обеспечивается модулями Python time и datetime. Вы также можете писать проrраммы, которые будут запускать друrие проrраммы по расписанию, используя модули sиbprocess и threading. Часто самый быстрый способ проrраммирования заключается в использовании возможностей приложений, написанных друrими людьми. МОДУIlЬ time Системные часы вашеrо компьютера настраиваются на определенные дату, время и часовой пояс. Ваши проrраммы на PytllOn MOryт использовать системные часы для определения текущеrо времени с помощью встроенно- 1"0 модуля time. Чаще Bcero используются входящие в этот модуль функции time. time () и time. sleep (). 
424 r лава 15 Ф,,,,,q,,. time. time ( ) Общепринятая в проrраммировании система описания времени OCHO вана на понятии так называемой "эры Ullix", началом которой считается полночь 1 января 1970 rода по шкале Всемирноrо координированноrо Bpe мени (Coorclinatecl UJliversal1ime  UTC). Функция time. time () возвраща- ет количество секунд, истекших с этоrо момента времени, представленное вещественным числом. (Вспомните, что вещественными называют числа, содержащие десятичную точку.) Это количество секунд называется времен- 1IOй .мет'/(ой Unix. Например, введите в интерактивной оболочке следующие команды. >>> import time >>> time. time () 1425063955.068649 Этот код был выполнен мною 27 февраля 2015 rОДа в 11 :05 по тихоокеан- скому времени, или в 19:05 по времени UТC. Возвращаемое значение поКа- зывает, сколько секунд прошло с момента наступления эры Ullix до момен- та вызова функции time. time (). Прu.мечание До:та и время. отображаем:ые в npuмepax, '/(oтop'ble я предлazаю для выполиен.uя в Ull:тера'/(тив1l0Й оБОЛfJЧ'/(е, rоответствуют февралю 2015 еода, '/(оеда R писал эту маву. Если 11WЛЪ'/(О вы ne являemeсь путешественllU'/(ОМ 80 времени, ваша nроера.м. .ма 8'Ы8едет друzue 31lа'Чe1tUЯ даты и вре.меии. Временные метки Unix можно использовать для профилирования про- rраммы, Т.е. для измерения периодов времени, в течение которых выпол- няются определенные фраrменты кода. Вызвав функцию time. time () в на- чале блока кода, время выполнения Koтoporo вы хотите измерить, а затем в конце этоrо блока, и ВЫЧтя значение первой временной метки из значения второй, вы получите длительность промежyrка времени, прошедшеrо меж- ду двумя вызовами. Например, откройте новое окно в файловом редакторе и введите следующий коД. import time . def calcprod () : # Вычисление произведения первых 100000 чисел. product = 1 for i in range(l, 100000): product  product * i return product 
Обработка значений даты и времени, ппанировщик заданий и запуск проrрамм 425 . 5tartTime = time. time () prod = ca1cProd() . endTime = time.time() . print ( 'Длина результата: %5 цифр.' % (1еn (5tr (prod) ) ) ) . рrint('Расчет занял %5 секунд.' % (endTime  5tartTime») в строке. мы ОllредеJlяем функцию ca1cProd (), которая пере6ирает в ЦИlUlе все целые числа от 1 до 100000 и возвращает их произведение. В CTpcr ке . мы вызываем функцию time. time () и сохраняем ее в пере мен ной 5tartTime. Сразу за вызовом функции ca1cprod () мы вновь вызываем Функ цию time. time () и сохраняем возвращенное ею значение в переменной endTime .. Проrpaмма заканчивается выводом количества цифр в произве- дении, возвращенном функцией ca1cProd () О, и длительности промежyrка времени, в течение Koтoporo выполнялась функция ca1cProd () .. Сохраните проrрамму в файле calcProd.py и выполните ее. Вы получите примерно следующий вывод. Длина результата: 456569 цифр. Расчет занял 2.844162940979004 секунд. Прu.мечание Существует u друеая воэ.мОJ#Cностъ nРОфш/'uрованuя кода  с nомощъю функ""ии cProfile. ruп (), обеcnечuва1О'Ц!,ей 'н,о,мноео более информативный уровень дeтa лиза""ии, чем простая методика, основанная на uсnолъзованuи функ""ии tiтe. tiтe (). Более подробно о функ""ии cProfile. ruп () можно nрачuтатъ на сайте https://docs.pythoп.org/3/1ibrary/profile.htтl. Фун""., t.iJDe. sleep ( ) Если требуется приостановить работу проrpаммы на некоторое время, вызовите функцию time. 51eep ( ) , передав ей apryмeHT, определяющий дли- тельность паузы в секундах. Введите в интерактивной оболочке следующие команды. >>> im.port tim.e »> for i in ranqе(З) : . prin t ( I ТиJC ' ) 8 tim.e.sleep(l) . print('T81C') е tim.e.sleep(l) Тик Так Тик Так 
426 r лава 15 ТИК Так . >>> tiJIe.sleep(S) в цикле for выводится на экран слово ТИК О, ВЫЖИДается пауза длитель- ностью в одну секунду О, выводится слово так., выжидается пауза дли- тельностью в одну секунду О, и так до тех пор, пока пара слов Тик и Так не будет выведена на экран три раза. Функция time . s leep () блокируется, Т.е. не возвращает управление до тех пор. пока не истечет количество секунд, переданное ей в качестве apryMeH- та. Так, если вы BвelI,eTe инструкцию time. sleep (5) 8, с.ледующее приrлаш ние ко вводу (»» вы увидите лишь через пять секунд. Имейте в виду, что нажатие комбинации клавиш <Ctrl+c> не прервет BЫ полнение вызова time. sleep () в IDLE. Среда IDLE будет находиться в со- стоянии ожидания до тех пор, пока не истечет заданная вами пауза, и лишь тоrда возбудит исключение KeyboardInterrиpt. Чтобы обойти эту пробле- му, можно создать паузу длителыюстью 30 секунд не одним вызовом tiще. sleep (30), а тридцатью вызовами time. sleep (1), выполняемыми в цикле. »> for i in rапgе(ЗО): time.sleep(l) в этом случае нажатие комбинации клавиш <Ctrl+C> в любой момент на протяжении 30-секундноrо промежyrка приведет к немедленному возбужд нию исключения Keyboardlnterrиpt. Окруrllение чисеll в процессе работы со временем вы будете часто сталкиваться с вещес- твенными значениями, содержащими очень MHOro цифр после десятичной точки. Для упрощения работы с такими значениями их можно укоротить с помощью встроенной функции PythOI1 roиnd ( ) , которая окруrляет вещ ственные числа До задаННОЙ точности. Для этоrо достаточно передать ей число, подлежащее ОКРУI'Jlению, и необязательный второй apryMeHT, опре- деляющий количество цифр после де(ятичной точки, до которых вы хо- тите окрyrлить число. При опущенном втором apryMeHTe функция roиnd ( ) окруrляет первый apryMeHT до ближайшеl'О целоro чи(:ла. »> im.port tim.e »> now = tim.e.tim.e() »> nо. 1425064108.017826 »> round(now, 2) 1425064108.02 
Обработка значений даты и времени, планировщик заданий и запуск проrрамм 427 »> round(now, 4) 1425064108.0178 >>> round(now) 1425064108 После импортирования модуля time и сохранения возвращаемоrо значения функции t ime . time () в переменной now мы выполняем вызов round (now, 2) для окрyrления значения now до двух цифр после десятичной точки, вызов roиnd (now, 4)  для окрyrления до четырех цифр и вызов round (now)  для окруrления до ближайшеrо целоrо числа. Проект: суперсекундомер с остановом Предположим, вы хотите Наладить учет времени, непродуктивно ра(:- xoдyeMoro на выполнение трудоемких рyrинных задач, которые вы еще не успели автоматизировать. Физическоrо секундомера у вас нет, а найти ана- лоrичное по функциональным возможностям бесплатное приложение для смартфона или ноyrбука, работа которою не сопровождалась бы появлени- ем на экране назойливой рекламы или "кражей" истории посещений вашеrо браузера для маркетинrовых целей, на удивление трудно. (Права владельцев IIpol'paMM на совершение подобных действий оrовариваются в лицензион- ных СОIJ1ашениях, с которыми пользователи, как правило, соrлашаются, не читая. Но вы-то читаете эти соrлашения, не так ли?) Не лучше ли вместо этоrо самостоятельно написать на языке PythOIl проrрамму-хронометр? вor что должна делать ваша проrpaмма при высокоуровневом рассмотрении: . отслеживать величину промежутков времени между нажатиями кла- виши <EIlter>, причем каждое очередное нажатие этой клавиши озна- чает начало HOBoro периода измерений; . выводить номер измерения, суммарное время и длительность изме- рения. Orсюда следует, что ваш код должен выполнять следующие операции: . определять текущее время с помощью вызова time. time () и сохранять ero в виде временной метки в начале работы проrраммы, а также в на- чале каждоrо измерения; . поддерживать счетчик измерений и инкрементировать ero всякий раз, Korдa пользователь нажимает клавишу <Enter>; . рассчитывать истекшее время nyrем вычитания временных меток; . обрабатывать исключения КеуЬоаrdlпtеrrирt, чтобы пользователь имел возможность прекратить работу проrраммы, нажав комбинацию клавиш <Ctl'I+C>. Orкройте новое окно в файловом редакторе и сохраните ero в файле stopwatch. ру. 
428 rЛQВQ 15 Шо, ,. 'ОЭIIон.е "op"ato проrраММ6IIIЛ' оrtле...он.. .ремен. Проrрамме-хронометру потребуется использовать текущее время, поэ тому необходимо импортировать модуль time. Кроме Toro, ваша nporpaM ма должна выве(:ти краткие инструкции для пользователя еще до вызова функции iприt ( ) . чтобы таймер мOl' начать отсчет сразу же после нажатия пользоВателем клавиши <Enter>. После этоrо код начнет отслеживать дли тельность временнOI'О промежyrка. Введите следующий код в файловом редакторе, написав комментарий TODO в качестве замены оставшейся части кода. II! python3 11 stopwatch.py  Простая проrраммахронометр. import time # Отображение инструкции по использованию проrраммы. рriпt('Чтобы начать отсчет, нажмите клавишу ENTER. Впоследствии  для имитации щелчков кнопки секундомера нажимайте клавишу  <Enter>. Для выхода из проrраммы нажмите клавиши <Ctrl+C>. ') inpиt() # нажатие клавиши <Enter> начинает # отсчет рriпt('Отсчет начат. ') startTime = time.time() # получение времени начала первоrо # замера lastTime = startTime lapNиm = 1 11 TODO: Начать отслеживание замеров. Написав код для отображения инструкций, мы начинаем первый замер, фиксируем время и устанавливаем значение счетчика замеров равным 1. Шо,2. OrtlIe...on.e . .",.ОД АЛ_reлtнои. ,о.еров А теперь напишем код, который начинает каждый новый замер, рассчи тывает длительность предыдущеI'О замера и вычисляет суммарное время, истекшее с момента запуска хронометра. Далее мы отображаем длитель-- ность замера, а также суммарное время всех предыдущих замеров и увели чиваем значение счетчика для очередноrо замера. Добавьте в nporpaммy выделенный ниже код. II! python3 11 stopwatch.py  Простая проrраммахронометр. import time пропущенный KOД 
Обработка значений даты и времени, планировщик заданий и запуск nporpaMM 429 i Начапо С'l'cnехсивание еамеро_. . try: . _Мlе True: input () . lapTiae = round (tiJae. иае О  lastTilDe, 2) . totalTilDe - round(tim...tiae()  startTim.e, 2) . print( 'Замер i%s: %а (%а) I % (lаpNuш, totalTim.e, lapTiae), end-' 1) lapNum. += 1 lastTim.e = tiJae. иае () #1 nер8УС'l'8Но8И'1'Ь _ремя i nocneдн8I'O еамера . except КeyboardInterrupt: #1 Обрабо'1'а'1'Ь МCJCmOЧеиие <Ctrl+C>, Ч'1'Обы np8ДО'1'_ра'1'И'1'Ь i О'1'ображеиие 8I'O сообщений. print ( I \nrO'1'o_o. I ) в случае, если пользователь останавливает хронометр нажатием ком- бинации клавиш <Ctrl+C>, возбуждается исключение Keyboardlnterrиpt, И проrрамма завершается аварийно, если она выполняется не в блоке try. Чтобы избежать краха проrраммы, мы помещаем код в инструкцию try.. Мы обрабатываем исключение в инструкции except 8, ноэтому в случае на- жатия комбинации клавиш <Ctrl+C> и возбуждения исключения выполне- ние проrраммы передается инструкции except для BЫBoa сообщения ro то ВО и предотвращения вывода сообщения об ошибке Keyboardlnterrиpt. Пока этоrо не произойдет, проrрамма будет выполнять бесконечный цикл., вызывающий функцию input (), и ожидать нажатия пользователем клавиши <Ellter> для завершения замера. Далее мы рассчитываем длитель- ность замера путем вычитания времени ero начала, lastTime, из текущеrо времени, time. time () .. Суммарное истекшее время вычисляется путем вычитания общеrо начальноrо времени запуска хронометра, startTime, из текущею времени.. Поскольку все результаты временн'ых расчетов содержат чрезмер- но большое количество цифр после десятичной точки (например, 4.766272783279419), мы окруrляем их до двух цифр с помощью функции round () (. и .). в строке. выводятся номер замера, суммарное истекшее время и дли- тельность замера. Поскольку нажатие пользователем клавиши <Ellter> в ответ на вызов функции inpи t () инициирует вывод символа новой строки на экран, функции print () необходимо передать именованный apryMeHT end' , , устраняющий появление двойноrо междустрочноrо интервала в выводе. Выведя информацию о замере, мы выполняем подютовительные операции для следующею замера, прибавляя 1 к значению счетчика lарNшn и устанавливая для переменной lastTime значение текущеrо времени, кото- рое будет служить началом отсчета для следующею замера. 
430 rлово 15 Иде. оr.о(иreл..о (0311"... "lfало",...wх про'ра_ Отслеживание времени открывает широкие возможности для ваших проrрамм. И хотя для решения некоторых задач подобноrо рода можно найти rотовые приложения, написание собственных проrрамм обладает тем преимуществом, что за них не надо платить и они не будyr содержать ненужные вам средства или надоедать назойливой рекламой. В частности, вы моrли бы самостоятельно сделать следующее. . Создайте приложение, реализующее простой табель, которое при вводе имени сотрудника записывает соответствующее время прихода и ухода с работы, используя текущие показания часов. . Используя модуль reqиests (см. rлаву 11), добавьте в свою проrрам- му средство для отображения времени, истекшеrо с момента начала какоrо-либо процесса, например заrpузки файла. . Периодически проверяйте, как долrо выполняется проrрамма, и пр доставьте пользователю возможность принудителыюro завершения долrо выполняющихся задач. МОДУIlЬ da tetilDe Модуль time полезен для неl1ОСредственной работы с временными М("7ка- ми Ullix. Но если вы хотите отобразить дату в более удобном формате или выполнить над датами арифметические действия (например, определить, какая дата была 205 дней назад или какая дата наступит через 123 дня), ис- пользуйте модуль datetime. Модуль datetime имеет собственный тип данных datetime. Значения datetime представляют определенные моменты времени. Введите в инте- рактивной оболочке следующие команды. >>> iIIport datetiae . >>> datetilae.datetim.e.no1f() . datetime.datetime (2015, 2, 27, 11, 10, 49, 55, 53) . >>> dt = datetim.e.datetim.e(2015, 10, 21, 16, 29, О) . >>> dt.year, dt.m.onth, dt.day (2015, 10, 21) . >>> dt.hour, dt.ainute, dt.second (16, 29, О) Вызов функции datetime .datetime .поw () о возвращает объект datetime . для текущих даты и времени в соответствии с показаниями часов ваше- ro компьютера. Этот объект содержит информацию о rоде, месяце, ДНе, часе, минyrе, секунде и микросекунде текущеrо момента времени. Также можно получить объект datetime для любоrо задаllНоrо момента времени 
Обработка значений даты и времени, планировщик заданий и запуск проrрамм 431 с помощью функции datetime .datetime () 8, передав ей целые числа, пред ставляющие rод, месяц, день, час, минyry и секунду желаемOI'О момента Bp мени. Эти целые числа будyr храниться в атрибутах year, month, day О, hour, minute и second. объекта datetime. Временную метку Unix можно преобразовать в объект datetime с nOMcr щью функции datetime. datetime. fromtimestamp (). При этом дата и время объекта datetime будyr соответствовать местному часовому поясу. Введите в интерактивной оболочке следующие команды. »> datetime.datetime.fromtimestamp(1000000) datetime.datetime (1970, :., 12, 5, 46, 40) »> datetime.datetime.fromtimestamp(time.time(» datetime.datetime(2015, 2, 27, 11, 13, О, 604980) Вызов функции datetime.datetime. fromtimestamp () с передачей ей apry мента 1000000 возвращает объект datetime для момента времени, cooтвeт ствующеrо 1000000 секундам после наступления эры Unix. Функция time. time () при передаче ей временной метки Unix текущеrо момента времени возвращает объект datetiтe, соответствующий этому моменту. Поэтому вы- ражения datetime. datetime. now() и datetime .datetime. fromtimestamp (time. time ()) делают одно и то же: они оба возвращают объект datetime для Ha С'rоящеl'О момента времени. Пршсечание Эти npuмepы выnолuялUC'b на 1UJМnЪютере, настрoe1lЖJМ на тUXOО1СеанС'Кое вре.чя,. Если вы находитесь в друеом часовом nоясе, ваши рез,.II:ьто:ты будут выелядетъ иначе. Чтобы выя(:нить, какой из объектов datetime предшествует дрyroму, ис- пользуйте операторы сравнения. Более поздним объектам соответствуют "большие" значения. Введите в интерактивной оболочке следующие KO манды. . »> halloween2015  datetiae.datetime(2015, 10, 31, О, О, О) . >>> nеwyеаrs201б = datetime.datetiaе(201б, 1, 1, О, О, О) »> oct31 2015  datetime.datetiae(2015, 10, 31, О, О, О) . >>> halloween2015 == oct31 2015 Trиe . »> halloween2015 > nеwyears201б False . »> nеwyеаrs201б > halloween2015 Trиe »> nеwyеаrs201б != oct31 2015 Trиe 
432 rлова 15 Сначала здесь создается объект datetime для первоrо момента времени (полночь) 31 октября 2015 rода. Этот объект сохраняется в переменной halloween2 О 15 .. Затем создается объект da tetime для первоro момента Bpe мени 1 января 2016 rода; для сохранения этоrо объекта используется пере менная newyears2016.. После этоro создается еще один объект для полунcr чи 31 октября 1915 rода. Сравнение halloween2015 и oct312015 ПОlCa.зывает, что они равны .. Сравнение newyears2016 и halloween 2015 показывает, что значение пеwуеаrs201б больше (соответствует более позднему моменту Bpe мени), чем значение halloween2015. .. Т.п lIt1nnых tiJDedel ta Модуль datetime также предоставляет тип данпых timedelta, который нредстаWIЯет длительно<ти BpeMeHHъtX промежyrков, а не моменты време- ни. Введите в интерактивной оболочке следующие команды. о »> delta  datetime.tiaedelta(days-ll, hours-l0, lIdnutes-9, secondв=8) . >>> del ta. days, del ta. аесоndз, del и. aicroseconds (11, 36548, О) »> del ta. total  seconds О 986948.0 >>> str(de1ta) '11 days, 10:09:08' Для создания объектов timedel ta используется функция datetime. timedelta (). Функция datetime. timedelta () принимает именованные apry менты weeks, days, hours, minutes, seconds, milliseconds и microseconds. A ryмeHTЫ month и year не предусмотрены, поскольку "пюпth" (месяц) и "year" (rод) представляют персменные отрезки времени. Объект timedel ta име ет полную длительность, выражаемую в днях, секундах и микросекундах. Соответствующие числовые значения хранятся в атрибyrах days, seconds и microseconds соответственно. Метод totalseconds () возвращает дли- тельность, выраженную в секундах. При передаче методу str () объекта timedelta он возвращает аккуратно отформатированное, удобное для чте- ния людьми строковое представление объекта. В этом при мере мы передаем методу datetime. de1 ta () именованные a ryMeHTbI, определяющие длительность, равную 11 дням, 10 часам, 9 мину там и 8 секундам, и сохраняем возвращенный объект timedelta в перемен- ной delta .. В атрибyrе days объекта timedelta содержится значение 11, а в атрибyrе seconds  значение 36548 (длительность 10 часов 9 минyr и 8 секунд, пересчитанная в секунды) .. Вызов totalseconds () сообщает нам, что длительность в 11 дней 10 часоВ 9 минут и 8 секунд составляет 986948 
Обработка значений даты и времени, планировщик заданий и запуск nporpaMM 433 секунд. Наконец, метод str () после передачи ему объекта timedelta возвра- щает строку, которая в ясной форме отображает длительность данною прcr межyrка времени. Для выполнения арифметических действий над значениями datetime используются арифметические операторы. Например, чтобы определить дату, которая наступит через тысячу дней от текущей даты, введите в инте- рактивной оболочке следующий код. »> dt - datetime.datetime.now() »> dt datetime.datetime(2015, 2, 27, 18, 38, 50, 636181) »> thousandDays - datetime.timedelta(days-l000) »> dt + thousandDays datetime.datetime(2017, 11, 23, 18, 38, 50, 636181) Прежде Bcero мы создаем объект datetime для тскущеrо момента време- ни и сохраняем el'o в переменной dt. Затем мы создаем объект timedelta для длительнО(ти 1000 дней и сохраняем ero в переменной thoиsandDays. Далее значения переменных dt и thousandDays СlUlадываются для получе- ния объекта datetime для будущей даты, которая наступит через 1000 дней после текущей даты. PythOll выполняет все необходимые арифметические операции и определяет, что датой, которая наступит через 1000 дней после 27 февраля 2015 юда, является 23 ноября 2017 юда. Это очень удобно, пcr скольку при проведении самостоятельных расчетов вам пришлось бы учи тывать, сколько дней содержится в каждом месяце, не забывая при iTOM о високосных rодах, Т.е. помнить о множестве мелких деталей. Модуль datetime выполняет всю эту работу BMe(TO вас. Объекты timedel ta MOryт уча(твоваl'Ь в операциях с.ложения и вычита- ния с объектами datetime или друrими объектами timede1ta (: использова- нием операторов + и . Объект t imede 1 ta можно умножать или делить на целые или вещественные значсния с помощью операторов * и /. Ввсдитс В интерактивной оболочке следующий код. 49»> oct218t = datetime.datetiше(2015, 10, 21, 16, 29, О) 49»> aboutТhirtyYears - datetimе.tiaedelta(daуs-З6S * ЗО) >>> oct21st datetime.datetime(2015, 10, 21, 16, 29) »> oct21st  aboutТhirtyYears datetime.datetime(1985, 10, 28, 16, 29) »> oct21st  (2 * aboutТhirtyYears) datetime.datetime(1955, 11, 5, 16, 29) в этом кодс мы создаем объект datetime для 21 октября 2015 юда О и объект timedelta для длительности, равной примерно 30 лет (мы не 
434 r лава 15 учитывали високосные rоды) .. Вычитание aboutThirtyYears из oct21st дает нам объект datetime, которые соответствует дате, отстоящей на 30 лет назад от 21 октября 2015 roдa. В результате вычитания 2 * aboиtThirtyYears из oct21st мы получаем объект datetime, который соответствует дате, от- (тоящей от 21 октября 2015 roдa на 60 лет назад. Орrа..заq". ntlузы до "а'туnле".. ОnреАе.е..о. Д""" Метод time. sleep () позволяет приостанавливать работу nporpaMMbI на определенное количество секунд. Используя цикл while, вы можете приcr становить работу проrраммы до наступления определенной даты. Напри- мер, следующий код будет непрерывно выполнять цикл до наступления ХЭJшоуина в 2016 roдy. import datetime import time halloween2016 = dаtеtimе.dаtеtimе(201б, 10, 31, О, О, О) while datetime.datetime.now() < hаllоwееn201б: time.sleep(l) Вызов time. sleep (1) приостанавливает проrpамму, чтобы компьютер не тратил драrоценные такты процессора на непрерывную проверку времени. Вместо этоrо цикл while Bcero лишь проверяет выполнение условия каж- дую секунду и передает управление остальной части проrpаммы, как только наступит Хэллоуин 2016 rода (или любая наперед заданная дата). nрео6ра3080..е 06.е"708 da tetime 8 "ро.. Временные метки lJпiх и объекты ciatetime плохо приспособлеНbl для чтения людьми. Чтобы отобразить объект datetime в виде строки, ис- пользуйте метод strftime (). (Букваfв имени метода strftime ()  от слова forтat. ) В методе strftime () используются директивы, аналоrичные тем, кото- рые используются при выводе форматироваНIIЫХ строк PytllOn. Полный перечень директив метода strftime () приведен в табл. 15.1. Та611ица 15.1. ДиреКТИ8Ы функции strftime () Директива Описание %У %у rOA с указанием столетия: '2014' rOA 683 указония столетия: от '00' до '99' 119702069) Месяц в виде десятичноrо чиCJIО: ОТ '01' до ' 12' %т 
Обработка значений даты и времени, планировщик заданий и запуск nporpaMM 435 ОкUН 1 Ш'nue табл. J 5. J ДмpelmlВО %8 %Ь %d %j Описание ш '! 1 Подное назвоние меацо: 'November' Сокращенное названив меCJIца: 'Nov' День М8CJ1цо: от '01' до '31' День roдa: от '001' до '366' Ден!> Н8Д8JIИ: от '0' (воскресенье) до '6' (суббота) Подное назвоние ДНI н8Д8JIИ:.' Monday' Сокращенное назвоние дн_ недепи: ' Моп' Часы (24colOl ШКOIIO): от '00' до '23' Часы (12....acolOl WКOJla): от '01' до '12 Минyrы: от '00' до '59' Секунды: от '00' до '59' 'АМ' (до ПOJlУДИ_) ИIIИ 'РМ' (nOCJlB ПОДУДИ_) Литеродьный сим80д '%' 'l;w ,A ,a \;М %S %р ; Передайте ФУНКЦИИ strrftime () строку описания формата, содержащую директивы форматирования (вместе с любыми желаемыми символами об- ратной косой черты, двоеточиями и т.д.), и она возвратит информацию об объекте datetime в виде отформатированной строки. Введите в интерак- тивной оболочке следующий код. »> oct21st - datetiше.dat8timе(2015, 10, 21, 16, 29, О) »> осt21st.strftiше('%У/%m/%d %Н:%М:%В') '2015/10/21 16:29:00' »> oct21st.strftime('%I:%M %р') '04:29 РМ' >>> oct21st.strftime("%B of '%у") "October of '15" Здесь мы имеем объект datetime, соответствующий дате 21 октября 2015 rода и времени 16:29, который сохраняется в переменной oct21st. При передаче функции strftime () строки форматирования' %У /%m/%d *Н: %М: %S' эта функция возвращает числа 2015, 10 и 21, разделенные симвcr лами обратной косой черты, а также числа 16, 29 и 00, разделенные двоето- чиями. В случае передачи этой функции строки '% I : %М% р I она возвращает значенис '04: 29 РМ', а в случае передачи строки ",8 of '%у"  значенис "October of '15". Обратите внимание на то, что имя функции strftime () указывается без префикса datetime .datetime. 
436 r лава 15 прео6Р"ЭО8"".е арок 8 06.ек,,,, da tet.iJDe Если у вас имеется строка, содержащая информацию () даТе, например '2015/10/21 16:29:00' или 'October 21, 2015', которую нсобходимопре- образовать в объект datetime, то используйте функцию da tetime. datetime. strptime (). Функция strptime () выполняет операцию, противоположную той, которую выполняет метод strftime. Чтобы функция strptime () смоrла распознать и правильно выполнить синтаксический анализ строки описа- ния формата, в этой строке должны использоваться те же директивы фор- матирования, что и в случае метода strftime (). (Буква р в имени функции strptime ()  от слова parse.) Введите в интерактивной оболочке следующий код. . >>> datetim.e.datetim.e.strptim.e('October 21, 2015', '%8 %d, %У') datetime.datetime(2015, 10, 21, О, О) »> datetim.e.datetime.strptim.e('2015/10/21 16:29:00',  '%Y/%m./%d %H:%M:%S') datetime.datetime(2015, 10, 21, 16, 29) >>> datetillle.datetim.e.strptim.e("October с! '15", "%8 of '%у") datetime.datetime(2015, 10, 1, О, О) >>> datetim.e.datetim.e.strptim.e("Novem.ber of '63", "%80! '%у") dаtеtimе.dаtеtimе(20б3, 11, 1, О, О) Чтобы получить объект datetime из строки' October 21, 2015', передайте функции strptime () эту строку в качестве первоrо apryмeнтa, а строку опи- сания формата, соответствующую строкс 'October 21, 2015',  в качестве BToporo apryMeHTa О. Строка с информацией о дате должна в точности со- ответствовать строке описания формата, иначе Python возбудит исключс- ние ValиeError. Обзор функций Python дn_ работы с датами и временем для работы с датами и временем в PytllOn может потребоваться доволь- но большое количество различных ТИIIОВ данных и функций. Ниже кратко описаны три типа значений, ИСПОЛl):Jуемых для представления времени. . Временная метка Uni:x (используется модулем time)  это целос или вещественное число, представляющес количество сскунд, истекших с полуночи 1 января 1970 rода 110 шкале UTC. . Объект datetime (из модуля datetime) (:одержит целочисленные :шачс- ния, хранящиеся в атрибутах year, month, day, hoиr, minиte и second. . Объект timedelta (из модуля datetime) представляет длительность BpCMellHOf'O отрсзка, а не конкретный момент времени. 
Обработка значений даты и времени, планировщик заданий и запуск проrромм 437 Ниже приведено краткое описание функций для работы со временем, их параметров и возuращаемых значений. . Функция time. time () возвращает временную метку U nix в ВИДе вещес TвeHHoro значсния, которая соответствует текущему моменту. . Функция time. sleep (секунды) приостанавливает выполнение про- rраммы на время, определяемое количеством секунд, которое задает ся apryMeHToM секунды. . Функция dаtеtimе.dаtеtimе(rод, месяц, день, час, минута, секунда) возвращает объект datetime, который соответствует MOMeH ту времени, (:пецифицированному аРI'}'ментами. Если apryMeHTbI ча с, минута или секунда не предоставлены, они IIринимают заданное по умолчанию значение О. . Функция datetime .datetime .now () возвращает 06'ьект datetime, соот- ветствующий текущему моменту времени. . Функция datetime .datetime. fromtimestamp (временнаяметкаИпiх) возвращает объект datetime, который соответствует моменту време- ни, представленному apryMeHToM временная метка  Ил ix. . Функция datetime. timedel ta (недели, дни, часы, минуты, секунды, МИЛJ1исекунды, микросекунды) возвращает объект timedelta, представ ляющий временную длительность. Все именованные apl'}'MeHTbI этой функции необязательны и не включают apryмeHTЫ месяц И rод. . Метод total  seconds () объектов timede 1 ta возвращает количество се- кунд, прсдставлясмое объектом timedel ta. . Метод strftime (формат) возвращает строку времени, представленную объектом datetime, в пользовательском формате, основанном на CTpcr ке описания формата. Более подробно о деталях формата читайте в табл.15.1. . Функция datetime .datetime. strptime (строкавремени, формат) воз- вращает объект datetime, который соответствует моменту времени, опредсленному apryMcHToM строка  времени, синтаксически проана лизированным (' использованием CTpoKoBoro apryмeнтa формат. Более подробно о деталях формата читайте в табл. 15.1. Мноrопоточность Чтобы ввести понятие мноrОIlОТОЧНОСТИ, лучше обратиться к конкрет- IIОМУ примеру. Предположим, вы хотите назначить задержку или кон- кретное время начала выполнения некоторой проrраммы. Вы моrли бы добавить в начало I1pOrpaMMbI примерно следующий код. 
438 r лава 15 import time, datetime startTime = datetime.datetime(2029, 10, 31, О, О, О) while datetime.datetime.now() < startTime: tirne.sleep(l) print('Tenepb проrрамма запустится на Хэллоуин 2029 rода') пропущенRЬ1Й KOД Этот код назначает запуск проrраммы на полночь 31 октября 2029 rода и вызывает ФУНКЦИЮ time. sleep (1) в цикле до наступления этоrо момен- та. В l1роцессе ожидания завершения цикла вызовов функции time. sleep () ваша проrрамма ничеrо не сможет делать и будет "спать" до тех пор, пока не наступит Хэллоуин 2029 rода. События развиваются таким образом по той причине, что по умолчанию Python предоставляет проrраммам только один nотmc 8ыnол1te1tuя. Чтобы понять, что такое поток выполнения, вспомните () приведен- ной в rлаве 2 образной аналоrии, коrда процесс выполнения IIporpaMMbI уподоблялся перемещению вашеrо пальца по строкам кода  либо после- довательному, от одной строки кода к следующей, либо в соответствии с управляющими инструкциями, совершая скачки через строки кода. Выпол- нением однопоточных проrрамм может управлять только один "палец". Однако у мноrопоточных проrрамм таких управляющих "пальцев" может быть несколько. Каждый из них переходит от одной строки кода к друrой 8 соответствии с управляющими инструкциями, но при этом "пальцы" мо- ryT псремещаться по разным участкам проrpаммы, независимо выполняя разные строки кода в одно и то же время. (Все проrpaммы, которые мы pa(- сматривали до сих нор в этой КНиrс, были одноноточными.) Вместо топ> чтобы заставляТI. весь свой код находиться в состоянии ожидания до тех нор, пока не завершатся циклические вызовы функции time. sleep () , можно выделить для выполнения отложенной или заплани- рованной по расписанию задачи отдельный поток выполнения, используя модуль Pytholl threading. Этот отдельный поток будет находиться в состоя- нии ожидания все то время, пока вызывается функция time. sleep ( ) . Тем временем ваша проrpамма может выполнять дрyryю полезную работу в ис- ходном потоке. Прежде чем можно будет создать поток, необходимо предварительно создать объект Thread, вызвав функцию threading . Thread () . Введите в 110- вом окне файловоrо редактора следующий код и сохраните ero в файле threadDeтo. ру. import threading, time рriпt('Начало проrраммы.') 
Обработка значений даты и времени, планировщик заданий и запуск проrрамм 439 . def takeANap () : time.sleep(5) рrint('Проснись! ') .threadObj = threading.Thread(target=takeANap) . threadObj .start () print ( 'Конец nроrраммы. I ) в строке О мы определяем ФУНКЦИЮ, которая будет выполняться в новом потоке. Чтобы еоздать объект Thread, мы вызываем функцию threading. Thread () и передаем ей именованный apryмeHT target=takeANap .. Это озна чает, что функцией, которую мы хотим вызвать в новом потоке, является функция takeANap ( ) . Обратите внимание на то, что именованный apryMeHT записывается как target=takeANap, а не как target=takeANap (). Это обуслов- лено тем, что в качестве apryMeHTa мы хотим передать функцию takeANap как таковую, а не значение, возвращенное в результате ее вызова. Сохранив объект Thread, созданный функцией threading. Thread ( ) , в переменной threadObj, мы можем вызвать метод threadObj . start () . для создания HOBoro потока и запуска в нем целевой функции. Выполнив эту проrрамму, вы получите следующий результат. Начало проrраммы. Конец nporpaммы. Подъем! Этот результат может HeMHoro смущать. Если инструкция print ( 'Конец nporpaMMb!. ')  последняя в проrpамме, то почему ее текст не был выведен последним? Причиной Toro, что последним выводится текст Подъем!, явля- ется то обстоятельство, что после вызова threadObj. start () целевая функ- ция объекта threadObj выполняется в новом потоке. Возвращаясь к выше- упомянутой образной аналоrии, можете считать, что при запуске функции takeANap () появляется второй управляющий "палец". Основной поток пере- ходит к инструкции print ( I Конец nporpaMMbI. ' ) . Тем временем новый поток, выполняющий вызов time. sleep (5) , выжидает в течение 5 секунд. После этоrо он выходит из своей 5екундной спячки, выводит на экран строку 'Подъем! ' и осуществляет выход из функции takeANap ( ) . в соответствии с описанной хронолоrией последнее, что выводит проrрамма,  это строка 'Подъем! '. Обычно выполнение проrраммы завершается с выlIлнениемM послед- ней строки се кода (или в результате вызова функции sys. exi t () ). Но в IIporpaMMe threadDemo. ру существуют два потока. Один из них, первона' чальный поток, в котором начала выполняться проrрамма, завершается после выполнения инструкции print (' Конец проrраммы.'). Второй поток 
440 r лава 15 создается вызовом threadObj . start ( ) , начинает выполнение с запуском функции takeANap () и завершает выполнение, как только осуществляется возврат из этой функции. В Python проrрамма прекращает выполнение Torдa, коrда прекращают выполнение 8<.:е ее потоки. Коrда вы запустили проrрамму threadDeтo.py, то даже после Toro, как первоначальный поток завершился, второй поток все еще продолжал выполнение вызова time. 51eep (5) . перед""" "p".e,,1O. "еле.о. .",Цnn Если целевая функция, которую вы хотите выполнять в отдельном п01'<r ке, принимает apryмeнты. то можно передать их методу threading . Thread ( ) . Предположим, например, что вы хотите выполнить следующий вызов print () в собственном потоке. »> print('Cats', 'Dog8', 'Froqs', sep=' & ') Cat$ & Dog$ & Frog5 В этом вызове используются три обычных apl'YMeHTa, 'Cat5', 'Dog5' И 'Frog$', и один именованный, 5ер=' & '. Обычные apryMeHTbl Moryr быть переданы в виде списка именованному apryMeнтy arg5 функции threading. Thread ( ) , а именованный apryмeнт в виде словаря  именованному арryмеи- ту kwargs этой функции. Введите в интерактивной оболочке следующий код. >>> import threaciing >>> threadObj - threading.Тhread(target=print, args=[ 'Cats',  'Dogs', 'Frogs'], kwargs={'sep': ' & '}) »> threadObj.start() Cat5 & Dog$ & Frog5 Чтобы обеспечить передачу apryмeHToB 'Cat5', 'Dog5' и 'Frog$' функции print () в новом потоке, мы передаем именованный apryмeHT arg$= ['Cat5', 'Oog$ 1, 'Frog$'] функции threading. Thread ( ) . Аналоrичным обра:юм мы по- ступаем в отношении именованноrо apryMeHTa $ ер"" ' & ',передавая функ ции threading. Thread () именованный apryMeHT kwarg5= { , 5ер': '& '}. Метод threadObj . start () создает новый поток выполнения для вызова функции pr in t ( ) , и он же передает ей строки 'Са t 5 " 'Oog 5' И 'Frog 5' В ка- честве apryMeHToB и I & I  В качестве значения именованноrо apryмeHTa $ер. Ниже продемонстрирован неправильный способ создания Hoвoro пото- ка выполнения для вызова функции print (). 
Обработка значений даты и времени, планировщик заданий и запуск nporpaMM 441 threadObj = threading.Thread(target=print('Cats', 'Dogs',  'Frogs', sep=' & ')) в этом коде будет вызвана функция print (), и в качестве именоваННоrо apryMeHTa targe t функции threading. Thread () будет передана 'Не сама функ- ция print (), а возвращаемое ею значение (каковым всеrда является None). Для передачи apryMeHToB функции, выполняющейся в новом потоке, следу- ет использовать именованные apryмeHTЫ args и kwargs функции threading. Thread (). nр06л,,,,,, "ораллел.,,,,, Вы можете леrко создать несколько новых потоков, и все они будyr BЫ полнятыя одновременно. Однако выполнение нескольких потоков может порождать так называемые проблемы параллелuз.ма. Эти проблемы возника ют в тех случаях, коrда разные потоки одновременно пытаются читать и записывать одни и те же переменные, конкурируя между собой. Проблемы параллелизма трудно воспроизводить, поскольку они возникают, казалось бы, без какой-либо закономерности, что затрудняет отладку проrраммы. Мноrопоточное проrраммирование  это отдельная обширная тема, рас- смотрение которой выходит за рамки данной книf'И. Вам нужно лишь хоро- шо запомнить следующее: чтобы избежать проблем параллелизма, никоrда не позволяйте нескольким потокам читать и записывать одни и те же пере- менные. Создавая новый объект Thread, убеждайтесь в том, что ero целевая функция использует лишь локальные переменные данной функции. Тем са- мым вы избежите возникновения в своих проrpаммах проблем параллелиз- ма, трудно поддающихся отладке. Ilpuмeчание Руководство по ,МЖJzоnoтoч'НD.Му проzjJам..мuроваuuю для Ull'Чuuа'I()'ЩUХ дocmупио на сайте http://пostarch. coт/autoтa testuff/. Проект: мноrопоточный заrрузчик фаЙl10В с сайта XKCD в rлаве 11 вы написали проrрамму, зarружающую все комиксы с сайта ХКСО. Это была однопоточная проrрамма: она зarpужала комиксы по одно- му за раз. Значительная часть времени проrpамма расходовала на установле- ние ceтeвoro соединения, чтобы начать зarpузку изображений и их запись на жесткий диск. При наличии широкополосноro канала ПОДlUlючения к Интернету однопоточная проrрамма будет использовать не всю доступную полосу пропускания. 
442 rnaBa 15 Мноroпоточная проrрамма, в которой заrpузка комиксов, установление соединения и запись файлов изображений на жестКИЙ диск осуществля- ются разными потоками, будет использовать подключение к Интернету rораздо более эффективно, что приведет к ускорению зarpузки КOJшекции комиксов. Orкройте новое окно в файловом редакторе и сохраните ero в файле тиltidownloadXkcd.py. Мы модифицируем предыдущую проrpамму, сде- лав ее мноrопоточной. Полный модифицированный код проrpаммы можно скачать на сайте http://nostarch . com/aиtomatestиff/. Шо, ,. В.lIо.,..нен.е nро'р"""" nре. ."И'teН.' ее "01111 8 Фун"ц.1O В этой проrрамме для заrрузки файлов будет использоваться в основном тот же код, что и в rлаве 11, поэтому я опущу описание кода, взаимодейству. ющеrо с модулями Requests и BeautifulSoup. Основные изменения связаны с импортированием модуля threading и созданием функции downloadXkcd ( ) , принимающей номера начальноrо и конечноrо комиксов в качестве- apry- ментов. Например, вызов dоwпlоаdXkсd(140, 280) выполнит в цикле код для за- rрузки комиксов, хранящихея на страницах http://xkcd.com/140, http: // xkcd. соm/141, http://xkcd. соm/142 и Т.д. вплоть до комикса http://xkcd. соm!279. Каждый создаваемый поток выполнения будет вызывать функцию downloadXkcd () с передачей ей разных диапазонов номеров комиксов, под- лежащих заrрузке. Добавьте в проrpамму тиltidownloadXkcd.py следующий код. fI! руthопЗ fI multidownloadxkcd.py  Заrружает комиксы ХКСО с fI использованием нескольких потоков выполнения. import requests, 05, bs4, threading . os.makedirs('xkcd', existokTrue) fI сохранить комиксы в #папке .!xkcd . def downloadXkcd(startComic, endComic): . for иrlNиmber in range{BtartComic, endComic): 11 Заrрузка страницы. рriпt('Заrрузка страницы http://xkcd.com!%s...' %  (иrlNumber)) е res  reqиests.get('http://xkcd.com!%s' %  (иrlNumber)) res.raiseforstatus() . soup  bs4.BeautifulSoup(res.text) # Поиск URLадреса изображения комикса. 
Обработка значений даты и времени, планировщик заданий и запуск проrрамм 443 . comicElem  soup.select('#comic img') if comicElem  []: print('He удается найти изображение комикса. ') else: . comicUrl  comicElem[O].get('src') # Заrрузка изображения. рriпt('Заrрузка изображения %s...' % (comicUrl)) . res = requests.get(comicUrl) res.raiseforstatus() # Сохранение изображения в папке .jxkcd. imageFile = ореп(оs.раth.jоiп('хkсd', os.path.basename(comicUrl)), 'wb') for chunk in res.itercontent(100000): imageFile.write(chиnk) imageFile.close() # ТОDО: Создать и запустить объекты Thread. # ТОDО: Дождаться заверmения Бсех потоков. Импортировав необходимые модули, мы создаем каталоr для хранения комиксов. и определяем ФУНКЦИЮ downloadxkcd () .. в этой функции мы орrанизуем цикл для обработки комиксов в заданном диапазоне номеров . и заrружаем каждую страницу .. Далее мы используем модуль Beautiful Soup ДЛЯ IIросмотра НТМL-кода каждой страницы. и поиска изображе IfИЯ комикса 0. В случае отсутствия изображения на странице выводится сообщение. В противном случае мы получаем URLaдpec изображения. и заrружаем само изображение .. Наконец, мы сохраняем изображение в созданном каталоrе. Шо,2. С03110..' . зоnуt nO'OO. .",nол.'... Теперь, коrда у нас есть функция downloadXkcd () , мы создадим несколько потоков, каждый из которых вызывает функцию downloadXkcd () для заrруз ки комиксов с номерами, лежащими в различных диапазонах, с вclН:айта XKCD. Добавьте в файл тultidownloadXkcd.py следующий код после опреде ления функции downloadXkcd ( ) . #! руthопЗ # multidownloadXkcd.py  Заrружает комиксы ХКСD с # использованием нескольких потоков выполнения. пропу.щенный KOД * Сседаиие и еanуск обКО8 Thread. downloadThreads . [] * список 8сех обКО8 Thread for i in range(O, 1400, 100): * 14 иераций, С08Даащих * 14 ПООКО8 
444 rлова 15 downloadТhread - threading. Тhread (target=do1mloadXkcd,  args=(i, i + 99)) downloadТhreads.append(downloadТhread) downloadТhread. start () Прежде вcero мы создаем пустой список downloadThreads; этот список IJ(r может нам отслеживать множество объектов Thread, которые мы будем соз- давать. Затем мы запускаем цикл for. На каждом проходе цикла мы создаем объект Thread е помощью вызова threading. Thread ( ) , при соединяем el'o к списку и вызываем метод s tart () для запуска функции downloadXkcd () в 1I<r вом потоке. Так как переменнаяi цикла for принимает значения от О до 1400 с шаrом 100, в начале первой итерации она будет иметь значение О, в начале второй  значение 100, в начале третьей  знаlеНИе 200 и Т.д. Поскольку мы передаем функции threading. Thread () список apryMeHToB args= (i, i + 99), на первой итерации цикла два apryMeHTa, образующие этот список, будут иметь значения О и 99, на второй  100 и 199, на третьей  200 и 299 и Т.д. В процессе вызова метода start () объекта Thread и запуска HOBoro по- тока для выполнения кода функции downloadXkcd () основной поток будет продолжать выполнение, запуская очередной поток на следующей итера- ции цикла. Шо,3. О."lIон"е ,о.ершен", .tex 8010«0. В то время как друrие потоки, которые мы создали, заrружают комиксы, основной поток продолжает выполняться как обычно. Предположим, одна- ко, что у вас есть некоторый код, начало выполнения KOToporo до TOro, как завершится выполнение всех остальных потоков, для вас нежелателыю. Вызов метода join () будет блокироваться до завершения данноrо потока. Используя цикл for для итерирования по всем объектам Thread, входящим В список downloadThreads, основной поток может вызвать метод j oin () для всех остальных потоков. Добавьте в конце проrpаммы следующий код. #! руthоnЗ # multidownloadXkcd.py  Заrружает комиксы ХКСО с # использованием нескольких потоков выполнения. пропущенный KOД * Окидание завершеНИR всех ПООКО8 aыnопнеНИR. for downloadThread in downloadТhreads: downloadThread.join() print ( I I'OOBO. 1) Строка' rOTOBO. ' не будет выведена до тех пор, ((ока не будет выполнен возврат И:J всех вызовов метода j oin ( ) . Если окажется так, что к моменту 
Обработка значений даты и времени, планировщик заданий и запуск nporpaMM 445 вызова метода join () объект Thread уже завершил выполнение, то будет просто выполнен немедленный возврат из метода j oin ( ) . Если вы хотите дополнить проrрамму кодом, выполнение KOToporo должно начаться лишь после Toro, как завершатся все потоки, то вам следует просто заменить этим кодом инструкцию print ('roTOBO. 1). Запуск друrих проrрамм из Python Ваша проrрамма на PytllOll может запускать на компьютере друrие про rpaмMbI с помощью функции Popen () BcтpoeHHoro модуля sиbprocess. (Буква Рв имени функции Рореn () происходит от слова process.) Если открыто не- сколько экземпляров какоrо-либо приложения, то каждый из этих экзем пляров является отдельным процесс ом одной и той же проrраммы. Напри- мер, если вы откроете одновременно несколько окон в своем браузере, то все они будyr представлять собой разные процессы проrраммы браузера. Пример нескольких одновременно выполняющихся процсссов калькулято- ра приведен на рис. 15.1. : ': r "."blo:',/,.'1fWOP ......<:> .;.);,1 I ,  ц. .О I . I 1.................. : .t. '1 L" ':fll'"': i I I  k3n"y,.: ;j : /АС 'MR 1':.:.... : .:::-.,   . '"11 ...... СЕ l ' i 7 .... I i; i(;tлt;...'II'.,..,......I.. \(; I ....! I I 7  I 4! М( цр i l! .... СЕ i I 8 . 9  4 , 6 i l 2 3 ! 8 9 .. -'--l ! '''А .:.' 00 : [:' .;: i 15 М-. .,,-. I 'Р. MS М. .. . J::t 8 9 5 6 = 2 о jdJI[] ) . lIиA Про"" с..,.."" o МС .1Я: ".15 !.I- ".... .... . (Е : Н/, 7 Б 9 4 5 б 2 3 О = 1,  I o м.' ';' 1', , I I .:J Рис. 15. 1. Шесть одновременно ВЫПОЛНЯЮЩнхся процессоs ОДНоrо и Toro же npH ложення калькулятора 
446 r лава 15 Каждый процесс может иметь несколько потоков выполнения. В отли чие от потоков, проце(:с не может непосредственно читать и записывать переменные дрyrоrо процесса. Если выполнение мноrопоточной проrpам мы можно образно предстаалять себе так, как будто оно управляется He сколькими пальцами, СКОЛl.зящими по строкам исходноrо кода, то выпл нение нсскольких процессов одной и той же проrраммы можно уподобить одновременному выполнению отдельных экземпляров этой проrраммы, которые вы раздали своим друзьям. Каждый из вас может независимо BЫ полнять одну И ту же проrрамму. Если вы хотите запустить внешнюю проrpамму из сценария на Python, то передайте имя этой проrраммы функции sиbprocess. Popen (). (Чтобы увидеть имя приложения в Windows, щелкните на значке приложения в меню ПУСК правой кнопкой мыши и выберите в открывшемся контекстном меню пункт Свойства. В 05 Х, чтобы увидеть путь к исполняемому файлу, щелкните на значке приложения при нажатой клавише <Ctrl> и выберите пункт Show Package Contents (Показать содержимое пакета.) Функция Popen () немедленно выполнит возврат. Имейте в виду, что запущенная таким спосcr бом проrрамма выполняется в потоке, отличном от Toro потока, в котором выполняется ваша проrрамма на PytllOn. На компьютере, работающем под управлением Windows, введите в Иlrrе-- рактивной оболочке следующие команды. »> iщроrt subprooess »> Subprосеsв.Рореn('С:\\Windows\\SуsteшЗ2\\саlС.ехе') <sиbprocess.Popen object at ОхОООООООООЗО55А58> На компьютере, работающем под управлением UЬuпtu Linux, выполни те следующие команды. »> import subprooess »> subprocess.popen('/usr/bin/gnom.calculator') <sиbprocess.Popen object at Ох7f2Ьсf9ЗЬ20> На компьютере, работающем под упраалением 05 Х, это делается не- MHoro иначе (см. раздел "Orкрытие файлов проrраммами по умолчанию"). Возвращаемое значение предстааляет собой объект Рореп, имеющий два полезных метода: роН () и wai t ( ) . Образно rоворя, метод роН () словно спрашивает вашеrо друrа, завер шил ли он выполнение кода, который вы ему предоставили. Метод роН () возвращает значение None, если в момент ero вызова процесс все еще вы. полняется. Если же работа проrраммы к этому моменту завершена, то он возвращает Iелочис.ленный код выхода процесса. Код выхода используется в качестве индикатора Toro, завершился ли процесс без ошибок (код выхода 
Обработка значений даты и времени, планировщик заданий и запуск проrрамм 447 о) или же ero завершение было обусловлено ошибкой (ненулевой код BЫ хода, обычно  1, но Moryr быть и друrие значения, в зависимости от прcr rpaмMbI) . Метод wai t () словно выжидает, пока ваш друr не закончит работу со CBcr им кодом, прежде чем предоставить вам возможность работать со своим. Метод wai t () блокируется до завершения запущенноrо процесса. ЭТО MO жет быть полезным, если вы хотите, чтобы ваша проrрамма выждала, пока пользователь не закончит работать с друrой проrpаммой. Возвращаемым значением метода wai t () является целочисленный код выхода. Если вы используете Windows, то введите в интерактивной оболочке приведенный ниже код. Обратите внимание на то, что метод wai t () блоки руется до тех пор, пока вы не закончите работу с запущенной проrраммой калькулятора. о > > > calcproc -  subprocess. Рореn ( I с: \ \Windows\ \SysteJвЗ2\ \calc. ехе' ) . >>> calcProc.poll () == None True . >>> calcProc.wait() О »> calcProc.poll() О Сначала мы открываем процесс калькулятора О. Затем мы проверяем, возвращает ли метод роН () значение None О. СоблюдеНИt этоrо условия означает, что процесс все еще выполняется. После этоrо мы закрываем проrрамму калькулятора и вызываем метод wai t () для уже завершенноrо процесса.. Теперь оба метода, wait () и роН (), возвращают значение о, указывающее на то, что выполнение процссса завершилось без ошибок. nереll""О opryмeHr08 "ОМ"НII80n аро". ФУН"".. .Рореп ( ) Процессам, создаваемым с помощью функции Popen ( ) , можно пере- давать apryмeHTЫ командной строки. Для этою функции Popen () следует передать список в качестве единственноrо apryMeHTa. Первой строкой в этом списке должно быть имя исполняемоrо файла проrраммы, которую вы хотите запустить; все последующие строки  это apryмeнты командной строки, которые будyr переданы проrpамме при {,'С запуске. В конечном сче- те этот список будет значением sys. argv для запущенной проrpаммы. Приложения с rpафическим интерфейсом пользователя (GUI) в OCHOB ном используют apryмeнты командной строки менее интенсивно, чем при .10жения с текстовым пользовательским интерфейсом. Однако большин ство GUIприложений будyr принимать одиночный apryмeнт в виде имении файла, который должен быть открьп приложением сразу после ero запуска. 
441 rлава 15 Например, если вы используете Windows, то создайте обычный текстовый файл C:Vtello. txt и введите в интерактивной оболочке следующую команду. »> subprooess.Popen(['C:\\Windows\\notepad.exe',  'C:\\hello. txt']) <subprocess.Popen object at ОхОООООООООЗ2DСЕВ8> Эта команда не только запустит приложение Notepad, но и немедленно откроет в нем файл с: Vtello. txt. nIlОН.РО8Щ.1l ЭОIIОН.. Windows, с."е.о .н.ц.tllI.Эtlц.. launohd 811.moh-nлtlН.РО8Щ81l croп Если вы хорошо знакомы с компьютерами, то, возможно, вам известно, какие функции выполняют планировщик заданий в Windows, система ини циализации launchd в 05 Х и демон-планировщик задач cron в Liпuх. Эти надежные и хорошо документированные инструментальные средства обе.- спечивают возможность периодическоrо выполнения заданий в определен ное время. Более подробную информацию о них вы сможете получить, вос- пользовавшись ссылками на соответствующие руководства на сайте ht tp: // nostarch.com/automatestuff/. Встроенный планировщик заданий операционной системы избавляет от необходимости написания собственных проrрамм, способных запускать задачи по расписанию. Тем не менее, если нужно всеro лишь приостано- вить выполнение проrраммы на короткое время, используйте функцию time. sleep () . Кроме Toro, вместо использования планировщика заданий операционной системы ваш код может орrанизовать выполнение цикла до наступления определенных даты и времени, вызывая функцию time. sleep (1) на каждой итерации цикла. О"'р"".е 8е6-СО'708 с nо.ощ'. 'рЬоn Вместо Toro чтобы открывать приложение браузера пyrем вызова функ- ции sиbprocess. Popen () , можно открывать браузер с переходом на сайт 110 заданному адресу, используя функцию webbrowser . ореn ( ) . Для получения бо- лее подробной информации по этому вопросу обратитесь к разделу "Про- ект: проrpамма тaplt.pyc модулем webbrowser" rлавы 11. 30nУСllIIрр8Х сценор.е8 'рЬоn Можно запускать выполнение сценариев под управлением Pythоп в от- дельном "роцессе точно так же, как любое друrое приложение. Для это- ro нужно лишь передать функции Popen () в качестве apl'}'MeHToB имя 
Обработка значений даты и времени, планировщик заданий и запуск проrрамм 449 исполняемою файла Python (python.exe) и имя файла сценария с расшир нием .ру, который вы хотите выполнить. IIапример, следующая команда выполнит (ценарий мllо.ру, рассмотренный 8 rлаве 1. > > > subprocess. Popen ( [ 'с: \ \pythonЭ4 \ \python. ие', , hello. ру' ] ) <subprocess.Popen object at ОхОООООООООЗЗ1СF28> Передавайте функции Рореп () список, содержащий строку с путем до- ступа к исполняемому файлу Python и строку с именем файла сценария. Если запускаемый вами сценарий сам нуждается в арryментах командной строки, то добавьте их в список после имени файла сценария. Расположе- ние исполняемоrо файла интерпретатора Pyt}lOn зависит от платформы: Windows  С:'фуthonЗ4'фуthon.ехе, OS Х  /Lihrary//i'rameworks/Python.fraтewark/ Versiоns/З.З/Ыn/7}thonЗ, Linux  /usr/Ыn/РУthonЗ. В отличие от проrрамм на Python, импортированных в качестве моду- лей, проrрамма на Python, запущенная вашей проrраммой, выполняется в отдельном процессе, и обе IlporpaмMbI не CMOryт обмениваться между собой своими переменными. О'''р''''.' фойло. ll,or,._o.. по УМOll,о... Двойной щелчок на значке файла с расширением .txt па вашем компью- тере позволяет автоматически запустить приложение, ассоциированное с этим расширением. На вашем компьютере такие ассоциированные про- rpaMMbI уже должны быть заданы и для ряда друrих расширений имен фай- лов. PytllOIl также может открывать файлы подобным образом с помощью функции Рореn ( ) . в каждой операционной системе имеется проrрамма, выполняющая те же функции, что и двойной щелчок на значке документа для ero открытия. В WiIldows  это проrрамма start, в OS  проrрамма open, в UbUIltu Lillux  ПРOlрамма see. Введите в интерактивной оболочке следующий код, переда- вая функции Рореп () одну из строк' 5 tart 1, 'open' и 'see', в зависимости от Toro, какая операционная система установлена на вашем компьютере. »> fileObj - open('hello.txt', 'w') >>> filеОЬj.write('ЗдразС'1'8}IЙ, мир! ') 12 »> fileObj.close() »> iшроrt subprocess »> subprocess.Popen(['start', 'hello.txt'], shell-True) Сначала мы записываем строку Здравствуй, мир! в новый файл hello. txt. Затем вызываем функцию Popen ( ) , передавая ей crIИСОК, содержащий имя 
450 r лава 15 проrраммы (в данном случае  'start' для Windows) и имя файла. Кроме 1'01"0, мы передаем именованный apryMeHT shellTrue, который требуется лишь в случае Windows. Операционной системе известны все ассоциатив- ные соответствия проrpамм расширениям имен файлов, и она может са- мостоятельно определить, какую ПрOl'рамму следует выполнить, например Notepad.exe для обработки файла hello,txt. Фипософия Unix Проrраммы, xopowo приспесобленные к запуску друrих преrрамм, предлаrа- ЮТ rорезде белее wирокие возможности, чем престо ИХ код сам по себе. Филосо- фия Unix зиждется на рЯДе принципов преектирования преrраммнеrе обеспечения, установленных разработчиками операционной системы Unix (на основе которей созданы современные операционные системы Linux и 05 Х). 8 соответствии с зтой филесофией лучwе писат.. небольwие проrраммы узкоспециальноrо назначения, ко- торые Meryт взаимодействовать с друrими преrpаммами, чем крупные приложения С боrатыми ВОЗМОЖНОСТЯМИ. Меньwие проrраммы более помятны, а простота взаиме- действия с ними ПОЗВОЛЯет использовать нх в кечестве строительных кирпичиков ДЛЯ создания более мощных приложений. Такому же подходу следуют и ПРИЛОЖ$НИЯ дЛЯ смартфонов, Если приложению- справочнику необходимо отображать для пользователя направление движения к указанному кефе или ресторану, то разработчики не должны заново изебретат" велосипед, пытаясь написать собственный код для работы с картами местности, Та- кое прилежение-с:правочник просто должно запускать отдельное приложение-карту, передавая ему аДрес кафе, как зто делает Baw кед на Python, вызывая какую-либо функцию и передавая ей apryMeHTbl. Проrраммы на Python, которые вы пиwете, работея с зтой книrой, в основном укладываются в денную филосефию, особенно в одном вожном отнешении: вместо вызовов функции input () они используют apryMeHTbl командной строки. Если вся необходимая для работы проrраммы информация может быть заранее предостав- лена, то ее предпочтительно передавать в виде apryMeHToB командной строки, е не ждать, пока пользователь ее введет. Тем самым apryMeHTbI командной строки Meryт не ТОЛЬКО вводиться пользователем, но и предоставляться друrой проrраммой. Бла- rодаря такему подходу, обеспечивающему wирокие ВОЗМОЖНОСТИ взеимодействия, Bawa nporpaMMa может выступать в ропи MHorOKpaTHo исполыуемеrо компонента друrих nporpaMM. Единственным исключением являются пороли, передача которых в качестве apry- ментов командной строки нежелательна, поскельку в зтем случае перол.. попадает в историю коменд и сохраняется в ней. Поэтому в тех случеях, Korдa требуется вво- дить пороли, для этей цели лучwе вызывать функцию inpиt () . Более подробно о философии Unix можно пречитать в статы Википедии: httРS:/lrи.wikiреdiа.оrg/wiki/ФИЛОСОфИЯUПiх/. 
Обработка значений даты и времени, планировщик заданий и запуск nporpaMM 451 В случае 05 Х проrрамма ореп используется для открытия как ДOКYMeH тов, так и проrрамм. Введите в интерактивной оболочке следующий код, сели вы работаете на компьютере Мае. »> subprocess.Popen(['open', '/Applications/Calculator.app/']) <subprocess.Popen object at Oxl0202ff98> в результате выполнения этой команды должно открыться приложение Calculator. Проект: проста. nporpaMMa o6paTHoro отсчета времени Найти простую I1porpaMMY для обратноrо отсчета времени не менее трудно, чем приложение, выполняющее функции хронометра. Давайте на- пишем IIpOrpaMМY, которая ведет обратный отсчет времени и сообщает о ero завершении звуковым сиrналом. ВОТ что должна делать эта проrрамма при высокоуровневом рассмот- рении: . вести обратный отсчет, начиная от 60; . воспроизводить авуковой файл (alarт.wav), Коrда счетчик доетиrает Ilулсвоrо значения. Отсюда следует, что код проrpаммы должен выполнять следующие опе- рации: . создавать паузу длительностью 1 секунда перед выводом очередноrо значения счетчика, вызывая функцию time. sleep ( ) ; . вызывать функцию subprocess. Рореп () для открытия звуковоro файла е помощью проrpаммы по умолчанию. Откройте новое окно в файловом редакторе и сохраните ero в файле countdown.py. Шо, '. 06pt171f..ii ort"er Этой проrрамме понадобится модуль time для вызова функции time. sleep () и модуль sиbprocess для вызова функции sиbprocess. Рореп (). BBe дите следующий код 8 файл countdиwn.py. #! руthоnЗ # countdown.py  Простой сценарий обратноrо отсчета. import time, subprocess 
452 rлава 15 . timeLeft = 60 while timeLeft > о: . print(timeLeft, end=' ') . time.sleep(l) . timeLeft = timeLeft  1 # ТОDО: Воспроизвести звуковой файл по завершении # обратноrо отсчета. Импортировав модули time и sиbprocess, мы создаем переменную timeLeft, в которой будет храниться Количество секунд, оставшихся до окончания отсчета О. В данной проrрамме обратный отсчет начинается от значения 60, однако вы можете изменить ero в соответствии со своими потребностями или же сделать так, чтобы проrрамма получала el'o в виде apryмeHTa командной строки. На каждой итерации ЦИlUlа while отображается текущее значение обрат- Horo счетчика ., выдерживается пауза длительностью 1 секунда. и декре-- ментируется значение переменной timeLeft О. ЦиlUl продолжается до т.ех пор, пока значение переменной timeLeft больше о. Как только это условие перестает соблюдаться, обратный отсчет прекращается. Ultl, 2. 'оtnро.э.еllен.е Э8рО80rо фtllilltl Несмотря на то что существуют модули сторонних разработчиков, по- зволяющие воспроизводить звуковые файлы различных форматов, самый простой и быстрый способ заlUlючается в запуске приложения, уже исполь' зуемоrо пользователем для этих целей. Операционная система сама опреде- лит по расширению имени файла. wav, какое приложение следует запустить для воспроизведения файла. Разумеется, это может быть не только файл формата. wav, но и файлы друrих аналоrичных форматов, таких как . трз или .ogg. В качестве файла, который воспроизводится в конце работы проrpaммы обратноrо отсчета, вы можете ИСlIользовать любой звуковой файл, имею- щийся на вашем компьютере, или заrрузить файл alarт.wav, посетив сайт http://nostarch.com/aиtomatestиff/. Добавьте в проrpамму следующий код. #! python3 # coиntdown.py  Простой сценарий обратноrо отсчета. import time, sиbprocess пропущенный KOД 
Обработка значений даты и времени, планировщик заданий и запуск nporpaMM 453 # Восnpoиsаедеиие eaYKcaorc файла пс еавершении # сбраноrc осчеа. subprooe88.Popen(['start', 'alarm.wav'], shell=True) По завершении цикла whi1e пользователь Оllовещается об ОКОllчании pa боты HporpaMMbI воспроизведением файла alarт.wav (или дрyrОI'О выбран ною вами файла). В случае Willdows не забудьте включить строку' start' в СIIИСОК, lIередаваемый функции Рореn ( ) , и одновремснно псредать имено- ванный apryмeHT shell=True. В случае 05 Х нередайте строку 'о реп ' вместо строки' start' и удалите ареумент shell=True. Вместо Toro чтобы воспроизводить звуковой файл, можете IIOДI'ОТОВИТЬ текстовый файл с (.ообщением наподобие ПерерЫ8 за'Ко1t'Ч,u.лся! и ИСIIОЛЬЗО- вать функцию Popen () для cro открытия 110 завершении обраТllOl'О оп:чета. В результате :iTOrO откроется окно предупреждения с текстом сообщения. АнаJlOl'ИЧНЫМ образом можно использовать вызов функции webbrowser. ореn (), которая 110 завершении обраТllоrо отсчета будет открывать KOH кретный всб-сайт. В отличие от некоторых бесплатных IlporpaмM IIОДобно- ro рода, которые можно найти в Интернете, в своей IIporpaмMe обратноrо отсчета вы сможете использовать любой звуковой файл, который только пожелаете! Иде. 07НО'''''Л6''0 ",да".. а"ало,.""""х "ро'ра". Обратный отсчет  это IIростейший СllОСоб ОРI'анизовать паузу, по OKOII чанин которой ПРOI'рамма сможет продолжиТl) выполнение. Эта возмож насть может быть использована в ряде ДРУI'ИХ нриложений, подобных опи санным ниже. . ИСlIользуйте функцию time. sleep () для нредоставления пользовате лю возможности отменить какое--либо действие, lIаllример удаление файлов, нажав комбиацию клавиш <Crl+C>. Ваша nporpaMMa может выводить сообщение" Для отмены нажмите комбинацию клавиш <Ctrl+C>", а затем обрабатывать исключения Keyboardlnterrupt С по мощью инструкций try И except. . Для ОРl'aIlИзации обратноrо отсчета в течение длительных промежут- коп времсни можно использовать объскты timedel ta, чтобы отмерять количество дней, часов, минyr и сскунд, оставшихся до наступлсния опредеЛСllllOrо события (напримср, дня рождсния или юбилея) в бу- дущем. 
454 r лава 15 РеЗlOме Эllоха U Ilix (1 января 1970 rода, полночь, UTC)  это стандартная точ ка отсчета времени во мноrих языках проrpаммирования, включая PytllOll. В то время как функция time. time () возвращает временную метку (т.е. веще- ственное число, представляющее количество секунд, истекших с момента наступления эпохи Ullix), модуль datetime больше приспособлен дЛЯ BЫ полнения арифметических действий с датами, а также форматирования или анализа связанных с информацией о датах строк. Функция time. sleep () блокируется (т.е. не выполняет возврат) на опре- деленное количество секунд, что можно использовать для добавления пауз в проrрамму, Но если вы хотите запланировать запуск своих проrpамм на определенный момент времени, то инструкции, предоставленные на сайте http://nostarch.com/aиtomatestиff/, подскажут вам, как как обеспечить это с помощью rOToвoro планировщика заданий, предоставляемоrо вашей опе- рационной системой. Модуль threading используется для создания нескольких потоков выпл нения, что может приroдиться вам для заrрузки мноrочисленных файлов или одновременноro выполнения дрyrих задач. Однако при этом вы долж- ны убедиться в том, что потоки читают и записывают только локальные переменные, иначе вы можете столкнуться с проблемами параллелизма. Наконец, ваши проrраммы на Python MOryт запускать друrие прил<r жения с помощью функции sиbprocess. Рореn (). Функции Popen () MOryт передаваться apryMeHTbI командной строки, указывающие на документы, которые должны быть открыты в запускаемом приложении. Суть друrоrо возможноrо варианта состоит в том, чтобы использовать совместно с функ цией Popen () одну из проrрамм start, open и see и позволить операЦИОII ной системе автоматически определить, в каком приложении должен быТl> открыт документ, основываясь на заданных для компьютера файловых ассоциациях. Взаимодействуя с дрyrими приложениями, установленными на компьютере, ваши проrраммы на PytllOn CMOryт воспользоваться их воз можностями для автоматизации возникающих перед вами задач. КОНТРОlIьные вопросы 1. Что такое эпоха Unix? 2. Какая функция возвращает количество секунд, истекших с момента наступления эпохи U Ilix? 3. Как орrанизовать в проrpамме паузу длительностью ровно 5 секунд? 4. Что возвращает функция round ( ) ? 5. В чем состоит разница между объектами datetime и timedel ta? 
Обработка значений даты и времени, планировщик заданий и запуск nporpaMM 455 6. Предположим, у вас е<:ть ФУНКЦИЯ sрат ( ) . Как вызвать эту функцию и выполнить ее код в отдсльном потоке? 7. Что нужно сдслать для Toro, чтобы избежать проблем параллелизма при работе (: несколькими потоками? 8. Каким образом ваша ПРOlрамма на PythOIl может ВЫПОЛIIИТЬ проrрам- МУ calc.exe, нахОДЯЩУЮСЯ в папкс С:\Windоw.ф'ystemЗ2? Учебные проекты Чтобы закрепить полученныс знания па IIрактикс, напишитс проrрам- мыl\.1Iя прсдложенных ниже задач. nр.,.рашеннw. хронометр РаПIIИрlirе проскт IIporpaMMbIxpoHoMeтpa, рассмотренный в начале l'Ла вы, "украсив" вывод за счет ИСIЮЛl.зоваIlИЯ в этой IIporpaMMe строковых методов rjust () и ljust () (эти методы обсуждались в rлавс 6). Вмссто прсж- lIel'o вывода Замер #1: 3.56 (3.56) Замер #2: 8.63 (5.07) Замер #3: 17.68 (9.05) Замер # 4: 19. 11 ( 1 . 4 3) вы должны получить вывод слсдующсrо вида: Замер # 1 : 3.56 Замер # 2: 8.63 Замер # 3: 17.68 Замер # 4 : 19.11 3.56) 5.07) 9.05) 1.43) Обратитс вниманис на то, что для вызова ВЫlllеУIlОМЯНyrых строковых методов вам понадобятся строковые версии целочислснных и всщеСТВСIl- ных I1СРСМСННЫХ lapNum, lapTime и totalTime. На слсдующсм этапе доработки I1pOrpaMMbI используйте модуль pyperclip, рассмотренный в l'Лаве б, для копироваНИЯ TeKcToBoro вывода в буфер обмсна, блаl'одаря чему IIользователь сможет быстро вставить вывод в текстовый файл или в сообщсние электронной почты. 3аrруз". .е6..0МНIC'О. по ра,n.,ан.. НаIlишите ПрOl'рамму, которая I1роверяет несколько сайтов веб- комиксов и автоматически зarружа(т и:юбражения в случае обновления ко. У/икса со времени IlOслсднеl'О IIосещения сайта проrраммой. Планировщик 
456 rлово 15 вашей операционной системы (планировщик заданий  в Windows, систе- ма инициализации launchd  в 05 Х и демон-планировщик сron  в Linux) может выполнять вашу проrрамму ежедневно НО одпому разу. Проrpамма на PyttlOn может заrружать комикс, а затем копировать ero на ваш рабочий стол, rде ero можно будет леrко найти. Это избавит вас от необходимости самостоятельно посещать сайты для Toro, чтобы проверить, обновлялись ли комиксы. (Список сайтов с ве6-комиксами до<:тупеll по адресу http: 11 nostarch. com/aиtomatestuff 1.) 
ОТПРАВКА СООSЩЕНИЙ ЭЛЕКТРОННОЙ ПОЧТЫ И ТЕКСТОВЫХ СООSЩЕНИЙ Просмотр сообщений электронной почты и ответы на них отнимают orpoMHoe количество времени. Конечно, невоз можно написать проrрамму. которая обрабатывала бы элек тронную почту вместо вас. поскольку на каждое сообщснис приходится отвечать по-разному, И все же имеется множе- ство задач, связанных с обработкой электронной почты, которыс подцаются автоматизации, коль скоро вам известно, как напи(ать проrрамму. способную отправлять и получать электронные письма. Предположим, у вас есть электронная таблица с данными о клиентах, и вы хотите отправить каждому из них письмо, форма KOToporo зависит как от возраста клиента, так и от дрyrой конкретной информации. С поиском подходящей коммерческой проrраммы у вас MOryr возникнуть трудности, но, к счасrью, вы можете написать для этих целей собственную проrрамму, которая позволит сэкономить массу времени, избавив вас от MHoroKpaTHO- ro копирования и вставки формы письма. Кроме Toro, можно написать проrpаммы для отправки сообщений почты и SMS, доставляющих вам важные уведомления, даже если вы нахОДитесь вдали от компьютера. Если вы автоматизировали задачу, которая может вы- полняться несколько часов, то вряд ли захотите контролировать компыо- тер каждые пять минут, чтобы провсрить, не завершилась ли она. Вместо этOI'О проrрамма может отправить на ваш телефон текстовое сообщение сразу же после Toro, как будет выполнена, что даст вам возможность занять- ся друrими делами. SMTP Во MHorOM подобно тому, как HТfP используется в качестве протокола для пересылки веб-страпиц через Интернет, сетевой протокол SMTP (Simple \1ail TraIlsfer Protocol  простой протокол передачи почты) используется 
458 rлава 16 для передачи (:ооБЩtНИЙ электронной почты. SMTP определяет порядок форматирования и шифрования сообщений электронной почты, стандар- тизирует процсс(: их ретрансляции между почтовыми серверами, а также описывает друrие детали обработки сообщений компьютером, коrда вы щелкаете на кнопке Оmравить. Однако знать технические подробности вам вовсе не обязательно, поскольку для вас все сводится к работе с нескольки ми функциями модуля smtplib, "редоставляемоrо Python. Протокол SMTP отвечает лишь за отправку почты. для получения элек тронной почты используется дрyrой протокол  lМAP, описанный далее. Отправка Зllектронной почты Возможно, вы привыкли отправлять электронную почту с помощью таких приложений, как Outlook или Thullderbird, или посредством таких сайтов, как Gшаil или УаЬоо! МаН. К сожалению, Pytholl не предлаrает Ta кой же привлекательный rрафический интерфейс пользователя, как эти службы. Вместо этоrо для реализации всех основных действий протокола SMTP необходимо вызывать функции, как показано в следующем примере (интерактивная оболочка). Прuмeчан.uе Не nытаuтесъ вътол1tитъ этот npuмep в IDLE; 0и ие будет работатъ, nоско.лъку smtp. ехатрlе. сот, bob@example. сот МYSECRETPASSWORD и alice@exaтple. сот  это толъко заместители. Да1t1tъtu Код дает вQJl.f, лишъ общее nредставлепие о том, как происходит отправка электРО1t1tоu nачтъt с nомо'Щъю Python. »> import smtplib »> smtpObj .. smtplib.SМТP('smtp.example.com', 587) »> smtpObj.ehlo() (250, b'mx.example.com at your service, [216.172.148.131]\nSIZE 35882577\n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nCHUNKING') »> smtpObj.starttls() (220, Ь'2.0.0 Ready to start TLS') »> smtpObj.login('bob@example.com', 'МОЙ СЕКРЕТНЫЙ ПАРОЛЬ') (235, Ь'2.7.0 Accepted')   »> smtpObj.sendmai1('bob@example.com', 'alice@example.com',  'Subject: So long. \nDear Alice, во long and thanks for а11 the fish. Sincere1y, ВОЬ') { ) »> smtpObj .quit() (221, Ь'2.0.0 closing connection kol0sm23097611pbd.52  gsmtp') в следующем разделе мы тщаТСЛllНО проанализируем каждый шаr, под- ставляя вместо заместителей ваши реальные данные для установления 
Отправка сообщений электронной почты и текстовых сообщений 459 соединения и входа на SMТPepвep, отправки сообщения и разрыва соеди нения с сервером. У""nооенnе (оед.nеn.. t ."-tер.ерОМ Если вам коrдалибо приходилось настраивать ТhuпdеrЫпl, Out.look или любую друryю nporpaMМY для подключения к своей учетной записи элск тронной почты, то, вероятно, вы знакомы с процедурой конфиrурирова ния SMTPepвepa и порта. для каждою провайдера почтовых уcлyr эти Ha стройки будyr разными, однако, выполнив в Интернете поиск по ключевым словам <вашnровайдер> 'lШCтрой'Кu sтtp, вы сможете настроить соединение с сервером через нужный порт. Как правило, доменное имя SMTPepBepa будет совпадать с именем домена вашеrо почтовоrо ПрОDaЙДера, дополненным прсфиксом smtp. Ha пример, в случае Gmail имя SMTPepBepa  smtp. gmail. сот. В табл. 16.1 приведены имена ПрОDaЙДеров электронной почты и их SMTP-(ерверов. (Порт  это целочисленное значение, почти всеrда равное 587, которое ис пользуется протоколом безопасности транспортною уровня Tl.S.) Табпица 16.1. Провайдsры JЛSЩЮННОЙ ПОЧ110l и их SMТP-cepвepw Про_Ад., Gmoil Outlook.com/ Hotmail.com Yahoo Moil АТ&Т Comcast Verizon Доменное IIМII SМТP-cepвepa smtp.gmail.com smtpmail.oиtlook.com smtp.mail.yahoo.com smpt.mail.att.net(nopT465) smtp.comcast.net smtp. verizon.net InopT465) Как только вы определите имя домена и порт, которые будyr использcr ваться для установления соединения с вашим почтовым провайдером, соз- дайте объект SMTp, вызвав функцию smptlib. SMTP () с передачей ей двух apryMeHToB: строки, содержащей имя домена, и целоrо числа, идентифици рующеrо порт. Объект SMTP представляет соединение с почтовым SMTP- сервером и раcrюлаrает методами для отправки сообщений электронной почты. Например, следующий вызов создает объект SМТР дЛЯ подключе- ния к Gшаil. »> smtpObj = smtplib.SМТP('smtp.gmail.com', 587) »> type(smtpObj) <class 'smtplib.SMTP'> 
468 rлава 16 Результат выполнения команды type (smtpObj) демонстрирует, что в пе-- ременной smtpObj хранится объект SMTP. Этот объект потребуется вам для вызова методов, которые позволят выполнять процедуру входа и ОТllрав- лять сообщения. Неудачный вызов smptlib. SМТР () может означать, что ваш SМТР-сервер не поддерживает протокол TI.5 через порт 587. В таком случае вам потребуется создать объект SMTp, используя вызов smtpl ib. SМТР  SSL ( ) и порт 465. >>> smtpObj smtplib.SMTPSSL('smtp.gmail.com', 465) llpueчaнuе В случае отсутствия nоiж.ll:КfЧeuия 1€ И'Н:терuету Python сeeuерирует UСIUlЮЧeuue socket.gaierror: [Етто 11004] getаddnnfоfаileduлu аua.tlо2U'ЧUоеему. Для ваших проrрамм различие между протоколами Tl.S и SSL не явля. ется существенным. Чтобы подключиться к своему SMTP-серiJеру, вам др- статочно лишь знать, какой именно стандарт шифрования он использует. Во всех 1I0следующих примерах, выполняемых в интерактивной оболочке, переменная smtpObj содержит объект SMTp, возвращаемый вызовом функ- ции smtplib. SMTP () или smtplib. SМТР  SSL () . О,пра."а "ро". пр..""..' S""-tep.,py Создав объект SMTp, начните процедуру рукопожатия с почтовым сер- вером SDMTP, "поздоровавшись" с ним с помощью метода ehlo ( ) . Orправ- ка приветствия является в SMTP первым шаrом процедуры установления соединения с сервером. Знать все детали соответствующих протоколов вам вовсе необязательно. Вам лишь надо твердо запомнить, что первое, что вы должны сделать после 1'01"0, как создадите объект SMTp,  это вызвать метод ehlo ( ) , иначе все последующие вызовы методов будут при водить к ошибке. Вот пример вызова метода ehlo () и возвращаемоro им значения: »> smtpObj..hlo() (250, b'mx.google.com at your service, [21б.172.148.1Зl]\nSIZЕ 35882577\ n8BITMIME\nSTARTTLS\nENHANCEDSTATUSCODES\nCHUNKING') Если первым элементом возвращенноrо кортежа является целое число 250 (код успешноrо завершения операции в SMTP), то это означает, что процедура приветствия оказалась успешной. 
Отправка сообщений электронной почты и текстовых сообщений 461 Начало TIS-ШnФРО8анн. Если вы подключаетесь к серверу SMTP через порт 587 (т.е. используе-- те TlS-шифрование), то следующим должен бьrrь вызван метод starttls (). Этот шаr активизирует шифрование для вашеro соеДинения. Если вы под- ключаетесь к порту 465 (используя SSL), то шифрование уже настроено, и этот шаl' нужно опустить. Вот пример вызова метода starttls (): »> smtpObj.starttls() (220, Ь'2.0.0 Ready to start TLS') Метод starttls () переводит ваше SМТР-соединение в режим TL. Воз- вращаемое этим методом значение 220 означает, что сервер roтoB к работе. В",nолнен.е nроцеllУР'" 8Xa .а SIlT'-teр8ер Настроив зашифрованное соединение с SMTP-сервером, вы можете вы- полнить процедуру входа, указав свое имя пользователя (обычно это ваш адрес электронной почты) и пароль при вызове метода login () . »> smtpObj.login('my email аddrеSS@gшail.соm.. 'МУ  SECRET PASSWO!U)') (235, Ь'2.7.0 Accepted')  Испопllэование паропей припо_ений в Gmail В Gmail предусмотрено ДОпOJ1нитепьное средство обеспечения безопасности для учетных записей Google  так называемые пороли приложений. В случае получения сообщения об оwибкеАррliсаtiопsресifiс password reqиired при попытке выполнения ваwей проrраммой процедуры входа необходимо установить один из этих паролей для CBoero сценария на языке Python. Более подробные указания отно- сительно Toro, как установить пароль приложения для своей учетной записи Google, вы найдете по адресу http://nostarch. com/aиtomatestиff/. в качестве первоrо apryMeHTa методу передает<:я адрес электрон- ной почты, в качестве BToporo  пароль. Возвращаемое значение 235 ro- ворит о том, что процедура аутентификации была успешно пройдена. В случае ввода HeBepHoro пароля Python возбудит исключение smtplib. SМТРАиthепtiсаtiопЕrrоr. 
462 r лава 16 Предупреждение Будъте осторож'Ны, помещая пароли в исход'Ный 'Код. Если 'Кто.то nостороюtИй скопирует вашу nроерамму, то О'Н nолуцит доступ 'К вашей уцетной записи! В это.м отношении имеет смысл вызыватъметод iпput () и предоставлять nолъзователю возможность ввода пароля. Возможно, необходимость вводить пароль 'Каждый раз при запуске nроера.ммы будет доставляmь 'Не'Которое 'Неудобство, 'Но это позволит 'не допустить xpaue'IlWt вашеео nаршя в 'Комnьюmepе в 'НезашиФРованно.м файле, до Komopoeo лежо может добраться ха'Кер или вор, nоxuтивший ваш наутбу'К. О,пРtl8lltl по..'.. Выполнив ВХОД на SMTP-сервер cBoero поЧТоВоrо провайдера, можете вызвать метод sendmail () для фактической отправки сообщения электрон' ной почты. Вызов метода sendmail () выrлядит так. »> sшtрОЬj.sеndmаi1('mу ешаil аddrеss@gшail.сош',  'recipient@example.com', 'Subject: 80 10ng.\nDear Alioe, во 1on;  and thanks for а11 the fish. Sincere1y, ВоЬ') { } Метод sendmail () принимает три apryмeHTa: . ваш адрес электронной почты в виде строки (для поля "От" адреса); . адрес электронной почты получателя в виде строки или списка строк в случае нескольких получателей (для поля "Кому" адреса); . тело сообщения в виде строки. Строка с телом сообщения должна начинаться с текста' Тема: \n', пред' ставляющеrо строку с темой сообщения. Символ новой строки '\n' отделя- ет строку темы от oCHOBHOro текста сообщения. Возвращаемым значением метода sепdrnаil () является словарь. В нем будет содержаться по одной паре "ключзначение" для каждоro из получа- телей, кому 'не уда.п.оС'Ь доставить сообщение. Пустой словарь означает, что сообщение было уcneшнодоставлено всем получателям. 'аэр",. (оеll."е".. t SIl"-tер8ероII Отправив электронную почту, не забудьте вызвать метод qиi t ( ) . это при- ведет к отключению вашей проrраммы от SMТP-cepвepa. »> smtpObj.quit() (221, Ь'2.0.0 closing connection kоl0sm2ЗО97611рЬd.52  gsmtp') 
Отправка сообщений электронной почты и текстовых сообщений 463 Возвращаемое значение 221 означает завершение сеанса связи. Обзор всех шаrов процедуры, которая должна быть выполнена для уста- новления соединения и входа на сервер, отправки электронной почты и разрыва соединения, содержится в разделе "Отправка электронной почты". IMAP Подобно тому как SMTP  это протокол, управляющий отправкой элек- тронной почты, протокол lМAP (Internet Message Access Protocol  проmокол nрU1CJlад1tоео уровня для доступа 1€ элен:тро1t1tой nачте) определяет порядок об- мена данными с сервером почтовоrо провайдера для извлечения электрон- ных писем, отправленных в ваш адрес. Pyt)lOn поставляется вместе с моду- лем imaplib, но в действительности проще использовать модуль imapclient сторонних разработчиков. В данной rлаве вы познакомитесь с основами ис- пользования модуля lМAPClient; полная документация находится по адресу http://imapclient.readthedocs.org/. Модуль imapclient заrружает электронную корреспонденцию из храни- лища на lMAP-сервере в довольно сложном формате. Вероятнее вcero, вы захотите конвертировать письма из этоrо формата в простые строковы е значения. Всю трудоемкую работу по синтаксическому анализу этих элек- тронных сообщений вместо вас выполнит модуль pyzmail. Полную докумен- тацию по модулю PyzMail можно найти по адресу httр://www.magiksуs.пеt/ p:Y'zmail/. Установите модули imapclient и pyzmail из командной строки. Описание процедуры установки модулей, разработанных сторонними компаниями, приведено в приложении А. Извnечение и УДОllение сообщений Эllектронной почты с помощыо IМAP Поиск и извлечение сообщений электронной почты в Pytholl представ- .lяет собой мноrоэтапный процес(:, требующий использования сторонних !оюдулей imapclient и pyzmail. Чтобы вы моrли получить общее представ- .lение об этом процессе, ниже приведен пример, демонстрирующий про- хождение каждо('о ero этапа: входа на IМAP-cepвep, поиска сообщений, из- влечения сообщений и последующеrо ИЗWlечения из них текста. »> iшport imapclient »> imaPOb] . imapclient.IМAPClient('imap.gmail.coa', ssl-True) >>> imapObj.login('my email аddrеss@gmail.сош',  'и)й СЕКРЕТНЫЙ ПАРОЛЬ')  .y email address@gmail.com Jane Doe aиthenticated (Sиccess) ' ;» iшaрОЬ] .selectfolder ('!NВOX', readonly=True) 
464 r пава 16 »> UIDs  iшapObj.searсh(['SINCE 05Jul2014']) »> UIDs [40032, 40033, 40034, 40035, 40036, 40037, 40038, 40039, 40040, 40041] »> rawМessages = iшapObj.fеtch([40041], ['ВODY[]', 'FLAGS']) »> iщроrt руzшail »> IR8ssage .. pyzaail.PyzН8ssage.factory(rawМessages[40041]['ВODY[]']) »> message.getsubject() 'Hello!' »> message.get addresses('froa') [('Edward Snowden', 'esnowden@nsa.gov')] »> IR8ssage. get addresses ( , to' ) [(Jane Doe', 'jdoe@example.com')] »> message.get аddЖeввеа('сс') []  >>> message. get addresses ( 'ьс:с' ) []  »> message.text..J>&rt !- None True »> message.textt.get..J>&yload().deoode(message.text..J>&rt.charset) 'Follow the mопеу.\r\п\r\ПЕd\r\п' »> message.htID1..J>&rt != None True »> message.htID1..J>&rt.get..J>&yload().decode(message.htm1t.charset) '<div dir="ltr"><div>So long, and thanks for all the fish! <br><br></div>Al<br></div>\r\n' »> iaapObj.logout() Вам не нужно запоминать эти шаrи. После Toro как мы подробно разбе- рем каждый шаr этой процедуры, вы сможете вернyrься к этому обзорному примеру, чтобы освежить этот материал в памяти. 'оеА.нен.е t IIIA'-tep.epo8 Точно так же, как для соединения с SMTP-сервером и отправки сообще- ний электронной почты требуется объект SMTP, для установления соедине- ния с lMAP-сервсром и получения сообщений требуется объект IМАРСliепt. Первое, что вам ДЛЯ этоrо потребуется,  это доменное имя lMAP-сервера вашеrо почтовоrо провайдера. Это имя будет отличаться от домеВНоrо име- ни SMTP-сервера. В табл. 16.2 приведены имена некоторых популярных провайдеров электронной почты и их IMAP-серверов. Табпица 16.2. ПрО8аЙД8рЫ зпектронной почты и их 'МАР-серверы Проиiдeр Gmoil Outlook.com/Hotmail.com Доменное им-IМАР-сервера imap. gmail. сот imapmail.outlook.com 
Отправка сообщений электронной почты и текстовых сообщений 465 Пpoвaaiiдep Yahoo Mail АТ&Т Сотсш! УеПzо" OICон:чаuие табл. 16.2 Доменное 1IMII1MAP-сервера imap.mail.yahoo.com imap.mail.att.net imap.comcast.net iпсоmiпg.vеrizоп.пеt Определив доменное имя IМAP-cepвepa, вызовите функцию imapclient. lМAPClient () для создания объекта lМAPClient. для большинства почтовых провайдеров необходимо SSLшифрование, поэтому передайте функции именованный apryMeHT ssl=-Trиe. Введите в интерактивной оболочке сле- дующие команды (используя доменное имя своею провайдера). »> iщport iшapсliеnt >>> iDlapObj · imapc1ient.IМAPClient( 'iaap.gmail.CODI', ssl=True) Во всех примерах для интерактивной оболочки, приводимых в после- дующих разделах, переменная imapObj содержит объект IМAPClient, возвра- щаемый функци(й imapcl ien t . lМAPCl ient ( ) . в данном контексте 'К/t'Ш!1tm  это объект, который соединяется с (:ервсром. IХОII . у""ну" 'Оn.С' Ha/МAP-tep.,p, Создав объект lМАРСliепt, вызовите ею метод login ( ) . передав ему имя пользователя (обычно это ваш адрес электронной почты) и Ilароль в виде строк. »> iшaрОЬj.lоgin('mу email address@gmail.com.. I мои СЕКPiтный ПAPOJJЬ') 'myernailaddress@gmaiT.com JaneDoe authenticated (Success) I ПреUynрежденuе Запомиuте: 1lRЛЪ3Я записыватъ пармъ ueпосредствеиио в 'Код! Вместо этоео следу em nроектироватъ проерамму та'К, чтобы оиа npuиuма.ла nарм'Ь, возвращаемый фУU'К1J,UСЙ iпput () . Если lMAP-сервер отвсрrпет предложенную ему комбинацию u.мяпмъзо- ваrJU1JlЯ/nаролъ, то Pytlюп возбудит исключение imap1ib.error. В случае учет- ных записей Gmail вам, возможно, придется использовать пароль приложе- ния (более подробно об этом читайте в разделе "Использование паролей приложений в Gшаil). 
466 rЛQВQ 16 По.,,, ,006щен.. Фактическое извлечение сообщений, которое стаIJОВИТ<Я возможным после выполнения процедуры входа, выполняется в два этапа: сначала нуж- но выбрать папку, в которой будет выполняться поиск, а затем вызвать ме- тод search () объекта lМAPClient, передав ему строку ключей поиска lМAP. в..60Р nanK. Почти в каждой учетной записи по умолчанию имеется папка INBOX, но вы можете получить список папок, вызвав метод 1 ist  folders () объекта IMAPClient. Данный вызов возвращает список кортежей. Каждый кортеж содержит информацию об одной папке. Продолжите пример, введя в инте- рактивной оболочке следующие команды. »> import pprint »> pprint.pprint(imapObj.list folders()) [ ( ( I \ \HasNoChi1dren 1, ), '/' I 'Drafts') I ( (1 \ \HasNoChi1dren 1, ) I 1/' I 'Filler') I ( ( I \ \HasNoChi1dren' , ) I '/' I 'INBOX'), ( ( 1\ \HasNoChi1dren' , ) I '/' I 'sent') I пропущенньrй KOД ( ( 1\ \HasNoChildren 1, '\ \Flagged' ) I '/' I '[Gmail] /starred' ) I ( ( I \ \HasNoChildren 1, ,\ \Trash' ) I 1/' I '[Gmail] /Trash I ) ) Примерно так будет выrлядеть ваш вывод, если у вас есть учетная запись Gшail. (В Gmail папки называются ЯРЛ/Ы'КlLМU, но это не меняет cyrи дела  они работают точно так же, как папки.) Три значения, входящие в каждый кортеж, например ( ( 1\ \HasNoChildren I I ), '/', 'INBOX'), имеют следующий CMbIOI: . кортеж флаrов папки (подробное обсуждение этих флaroв выходит за рамки данной книrи, так что можете смело иrнорировать это поле); . разделитель, используемый в строке имени для разделения рОДИТСЛIr ских и подчиненных папок; . полное имя папки. Чтобы выбрать папку, в которой должен осуществляться поиск, передай- те ее имя в виде строки методу select  folder () объекта lМAPClient. »> imapObj.selectfolder(IINВOX', readonly=Тrue) Значение, возвращаемое методом select  folder (), можете иrнорирcr вать. Если выбранной папки не существует, то Python возбудит исключение imaplib. error. Именованный apryмeHT readonlyTrue позволяет избежать случайною из- менения или удаления любоrо сообщения в данной папке при последующих 
Отправка сообщений электронной почты и текстовых сообщений 467 вызовах методов. ..:ели вы не планируете удалять сообщения, то имеет смысл Bcerдa устанавливать значение арl)'Мента readonly равным Trиe. BWnO.HeHne no_cla Выбрав папку, можно приступить к поиску сообщений, используя метОД search ( ) объекта IМAPClient. ApryMeHToM, передаваемым методу search ( ) , является СIIИСОК строк, каждая из которых отформатирована поисковыми ключами IМAP (табл. 16.3). Та6пица 16.3. Поисковые lUIючи IMAP КпfOll поиска 'ALL' 'BEFORE дата', I ON да та ' , 'SINCE дата' 'SUBJECT ст.рока', !вооу ст.рока', 'ТЕХТ строка' 'FROM строка', 'ТО строка', 'се строка', 'все строка' 'SEEN' , 'UNSEEN' 'ANSWERED' , 'UNANSWERED' 'DELETED' , 'UNDELETED' 0nIIcaн... Поиск вс.х сообщений, хранящихся в данной папке. Запрашивая все сообщения в большой папке, вы можете столкнуться с оrpаничениями, которы. модуль imaplib налоrо.т на допустимый суммарный размер заrpужоемых сообщений (см. раздел "Предепьный размер сообщений") Эти три кпюча зодают поиск сообщений, помеченных соответственно бопее ранней датой, чем текущая, текущей датой и бonее поздней датой, чем текущая. Требуемый формат даты  07 Апр20 16. Кроме TOro, в то время как строке I S INCE О 7 Апр 2 О 16' соответствуют сообщения, датированные 7 апреля, и бonее поздние, страке 'BEFORE О 5  J u 1  2 О 1 5' соответствуют лишь сообщения, пр.дшествующие 7 опреля, Torдo как сообщения, помеченные сомой датой 7 апрenя, зтой строке не соответствуют Поиск сообщений, в поле темы, тела или в пюбом из зтих полей которых соответственно CoдepJККJCJI заданная строко. Строки, содержащие пpoбenы, CJleдyeт закпючan. в кавычки: 'ТЕХТ "поиск с учетом пробелов'" Поиск сообщений, содержащих заданную строку в поле адреса "От", поле адреС08 "Кому", попе адреС08 "Сс" ("Копия") или попе адр.С08 "Всс" ("Скрытоя копия") соответственно. При ноличии нескольких адресов в строке их следует разделять пробеломи или закпючать в кавычки: 'СС "firstcc@example.can secondcc@example.can'" Поиск сообщений, соответственно помеченных или не помеченных фпаroм \Seen (просмотрено). Этим флаrом помечаются сообщения, к которым осуществnялся доступ с помощью метода fetch () (описан далее) или на которых вы выпоnнияи щелчок во время "РОСМОтра почты с ПОМОЩЬЮ кпиентской почтовой праrpаммы или броузера. Чаще roворят о том, что сообщение "прачитано", а не "просмотрено", но оба варианта озночают одно и то же Поиск всех сообщений, соответственно помеченных или не помеченных флоrом \Answered. Сообщение помечоется зтим флаrом, если на Hero был дан ОТ8ет Поиск сообщений, соответственно помеченных или не помеченных флоrом \Deleted. Сообщення, удаленные с noмoщьюметода delete messages (), снабжаются флаroм \Deleted, но не удоляютс. безвозврarнo ДО тех лор, пока не будет вызван метод expиnge () (см. роздел "Удаление сообщений"). Имейте в виду, чro некоторые почтовые провайдеры автоматически выполняют 6еЗ8038ponюe удаление сообщений 
468 r лава 16 Qкон:чаиие табл. 16. 3 KIlJOII поиска , DRAFT' , 'UNDRAFТ ' 'FLAGGED' , 'UNFLAGGED' 'LARGER N', 'SМALLER N' 'NOT ключ поиска' 'OR К'ЛЮЧ поискаl ключ поиска 2 , Описанне ПОИСК сообщений, COOT8SТCТ8BHHO nOM8'leHHblX или не пом.чsнных флоroм \Draft (ЧSРН08ИК). Как пра8ИЛО, ч'рновики сообщений ХРОНIIТСII в отдельной папке Draft, а не 8 папк, INBOX Поиск сообщений, соответСТ8ВННО помеч,нных или не помеченных фl1аrом \Flagged. Обычно зтот флоr используеТСII ДПII пометки сообщений как 8ажных или срочных Поиск сообщений, размер которых СООТ8етственно больwе иl1и меньше N байт Поиск сообщений, которыв УКОЗ0ННЫЙ кпюч поиско не В038рОТнп бы Поиск сообщений, которыв соотвsтствуют l1ибо пер8аму, либо 8ТОРОМУ кпючу поиско Следует <.YI'метить, что разные IМAPepBepbI MOryт Шrразному обраба' тывать свои флаrи и ключи поиска. Возможно, для 1'01"0 чтобы выяснить, как ведет себя конкретный сервер, вам придется HeMHoro поэксперименти. ровать в интерактивной оболочке. Методу search () можно передать в списке apryмeHToB п<,сколько строк с поисковыми lUIючами IMAP. При этом метод возвратит те сообщения, кото- рые соответствуют всем ключам поиска. Если вам достаточно соответствия любому из КЛЮlей, то используйте поисковый lUIюч OR. За lUIючами NOT и OR должны следовать один или два полных поисковых lUIюча соответ- ственно. Ниже приведены некоторые примеры вызовов метода search () с крат. кими комментариями. . imapObj. search ( [ 'ALL') ) . Возвращает все сообщения, хранящиеся в выбранной папке. . imapObj. search ([ 'ON 05Jul2015'). Возвращает сообщения, от- правленные 5 июля 2015 roда. . imapObj. searc:h ([' SINCE 01Jan2015', '8EFORE 01Feb2015', 'UNSEEN' ) ) . Возвращает Все сообщения, отправленные в январе 2016 rода, которые остались неnpочитанными. (ЗамеТl)те, что указанный период включает 1 января и все последующие дни января вплот}. До, 'Но 'Не включая, 1 февраля.) . imapObj.search(['SINCE 01Jan2015', 'FROМ alice@exaaple.c:om']). Возвращает все сообщения <.YI' респондента alice@example. сот, отправ. ленные с начала 2016 roда. . imapObj.search(['SINCE 01Jan2015', 'NOT FROM alic:e@exampl.. оош' ) ). Возвращает все сообщения от всех респондентов, кроме alice@example.com, отправленные с начала 2016 rода. 
Отправка сообщений электронной почты и текстовых сообщений 469 . imapObj.eearch(['OR FROM alice@example.com FROM bob@example. cOJll' ] ). Возвращает все сообщения, отправленные коrдалибо респон- дентами аliсе@ехатрlе.соти bob@example.com. . imapObj . search ( [ 'FROМ alice@exam.ple . COJll', 'FROМ bob@example. COJII ' ] ) . Пример с подвохом! В результате этоrо поиска не будет возвращено ни одно сообщение, поскольку сообщения должны соответствовать всем поисковым словам. Но в поле "Or" сообщения может находиться только один адрес, так что не может быть сообщений, в которых в ка- честве отправителя фиrypировали бы одновременно alice@example. сот и bob@exarnple. сот. Метод search () возвращает не сами сообщения, а их уникальные иденти- фикаторы (UID) в виде целых чисел. Чтобы получить содержимое сообще- пий, вы можете передать эти уникальные идентификаторы методу fetch () . Продолжите выполнение примера в интерактивной оболочке, введя сле- дующие команды. »> UIDs · iaapObj.search(['SINCE OSJul201S']) >>> UIDs [40032, 40033, 40034, 40035, 40036, 40037, 40038, 40039, 40040, 40041] Здесь список идентификаторов сообщений (полученных начиная с 5 июля), возвращенный методом search ( ) , сохраняется впеременной UIDs. Список UIDs, возвращенный на вашем компьютере, будет отличаться от TOro, который представлен здесь; идентификаторы уникальны лишь для конкрет- ной учетной записи электронной поtЛ'ы. Если впоследствии вы будете исполь- зовать уникальные идентификаторы в дрyrих вызовах функций, то используй- те их значения, полученныс вами, а не те, которые отображены в примерах. ПреА8IIltНЫЙ размер сообщен.й Если в результате поиска обнаруживается слишком большое количество сообщений, удовлетворяющих заданным критериям, то Python может воз- будить исключение примерно со слсдующим текстом: imaplib.error: got more than 10000 bytes (imaplib.error: получено более чем 10000 байт). Если это произойдет, то вы должны разорвать, а затем восстановить соединение с lMAP-сервером и попытаться вновь выполнить поиск. Этот предел введен с целью не позволить проrраммам на Python потре- блять слишком MHoro памяти. К сожалению, заданный по умолчанию пре- дельный размер сообщений слишком мал. Вы можете изменить этот предел с 10000 байт на 10000000 байт, выполнив следующий код. >>> ilDport im.aplib »> im.aplib.INE . 10000000 
470 rпaBa 16 Это позволит вам избавиться от повторноrо получения таких сообще- ний. Возможно, вы захотите включить эти две строки кода во все проrрам мы для работы с IМAP, которые вы напишете. Использование метода gmailsearch ( ) объекта lМAPClient Если вы войдете на сервер imaр.gmail.соmдля доступа К учетной записи Gmoil, то объект lМAPClient предоставит дополнительную функцию поиска, имитирую- щую поле поиска в верхней части ве6-страницы (рис. 16.1). Рис. 16. 1. поле поиска 8 верхней части веб-строницы Эта;1 Вместо Toro чтобы ВЫПOllнять поиск С помощыо ПОисковых ключей IMAp' можете использовать более сложный поисковый ДВИЖОК Gmail, который великолепно справ- ляется с поиском соответствий для родственных слов и сортирует результаты поиска по наиболее значимым совпадениям. Кроме Toro, можно использовать расширен- ные операторы поиска Gmail (более подробную информацию об зтом вы найдете по адресу http://nostarch. com/automatestuff/). Если вы вошли в.учетную за- пись Gmail, то передайте поисковые слова методу gmail search (), а не методу search (), как в следующем примере. »> UIDs - imapObj.gaailsearoh(\meaning оЕ liE.') »> UIDs [42] Ах да: в моей почте нашлось письмо, содержащее слова meaп;ng о' life/ (смысл жизни), по которым я осуществлял поиск. 
Отправка сообщений электронной почты и текстовых сообщений 471 ИЗlllе"ен.е соо6ще".. JII8"rpoIIHO' по",,,, н (на6.ен.е .ро".,анн",х пнсе8 (.еqнал,ноj 8е,,,0. Получив список UIDs, можно вызвать метод fetch () объекта lМAPClient для получения фактическоrо содержимоrо ящика электронной почты. Список UIDs будет первым apf'}'MeHToM метода fetch (). Вторым apf'}'MeH- том является список [1 ВООУ [] , ] , в который метод fetch () должен заrрузить все содержимое тела сообщений, указанных в вашем списке UIDs. Продолжим выполнение нашеrо примера в интерактивной оболочке. »> rаwИ8ssаgеs . iшарОЬj.fеtоЬ(UIDs, ['ВODY[] ']) »> iщроrt pprint »> pprint.pprint(raWМessages) (40040: {'BODY[] 1: 'De1iveredTo: myemai1address@gmai1.com\r\n. 'Received: Ьу 10.76.71.167 with SMTP id ' пропущенный KOД '\r\n' ,. Part 6000970 7077З6290.140481948706б \r\n', 'SEQ': 54ЗО}}    Импортируйте модуль pprint и передайте значение, возвращенное ме- тодом fetch () и сохраненное в переменной rawMessages, функции pprint. pprint (), чтобы вывести ero на "красивую печать", и вы увидите, что это значение представляет собой вложенный словарь сообщений, в котором идентификаторы UIDs служат ключами. Каждое сообщение сохраняется в виде словаря с двумя ключами: I ВООУ [] I И I SEQ 1. Ключ I ВООУ [] I отображает- ся на фактическое тело электронноrо сообщения. Ключ I SEQ I иrрает роль порядковоro номера и выполняет функции. аналоrичные UП). Можете сме- ло ero иrнорировать. Нетрудно заметить, что содержимое ключа I ВООУ [] I выrляДИТ доволь- но непонятно. Это обусловлено тем, что оно хранится в формате RFC 822. предназначенном для чтения серверами IMAP и не приспособленном для чтения человеком. Однако знать этот формат вам не надо; далее мы исполь- зуем модуль pyzmail, позволяющий представить тело сообщения в удoб<r читаемом виде. Выбрав папку. в которой следовало выполнить поиск, вы вызывали ме- тод selectfolder () с именованным apryмeHToM readonly=True. Это делалось для Toro, чтобы предотвратить случайное удаление почты, но это также означает. что письма не будут помечены как прочитанные, если для их из- влечения используется метод fetch ( ) . Если необходимо, чтобы письма пcr мечались как прочитанные при их извлечении, то методу select folder () следует передать именованный apryмeHT readonly=False. Если же текущая папка уже была выбрана для работы в режиме "только чтение" , то ее можно 
472 rЛQВО 16 выбрать заново с помощью дрyrоrо вызова метода select folder (), на этот раз  с именованным apryмeHToM readonlyFalse: »> imapObj. selectfolder (' INВOX', reac1only=False) по.,,,,,,., адр'со. JЛ,Il,РО""о. ПО",''' ., 1I,,,,р,,,х ll '006""".;; "Сырые", Т.е. необработанные, сообщения, возвращаемые методом fetch ( ) , мало полезны тем, кому надо Bcero лишь прочитать свою почту. Модуль pyzmail выполняет синтаксический анализ "сырых" сообщений и возвращает их в виде объектов PyzMessage, обеспечивающих возможность простоrо доступа вашеrо кода на Python к теме, телу, полям "Кому" и "Or" , а также к друrим разделам сообщений электронной почты. Продолжите интерактивный пример, выполнив следующие команды (используя уникальные идентификаторы сообщений из своей учетной за писи электронной почты, а не те, которые здесь представлены). »> iшроrt руzшаil »> шеssаgе · руzшаil.РуzМessаgе. factory(raWМessages[40041] ['ВODY[]']) Прежде Bcero импортируйте модуль pyzmail. Затем создайте объект pyzMessage сообщения, вызвав функцию pyzmail. PyzMessage. factory () и передав ей раздел 'ВОDУ [] , необработанноro сообщения. Сохраните резуль- тат в переменной message. Теперь впеременной message содержится обь- ект PyzMessage, используя методы Koтoporo можно очень леrко получить строку темы сообщения, а также адреса отправителя и получателя. Метод getsubject () возвращает тему сообщения в виде простоro cтpoKoвoro зна чениЯ. Метод get addresses () возвращает список адресов для переданноro ему поля. Например, вызов данноro метода может выrлядеть примерно так. »> Ш8ssаgе.gеtSubjесt() 'Hello! ' »> message.get address8s('froв') [('Edward snоwdёn', 'esnowden@nsa.gov')] »> шеssаgе.gеt addresses('to') [(Jane Doe', .myemailaddress@gmail.com.)] »> шеssаgе.gеt аddжeавеа('ос') []  »> message.getaddresses('bcc') [] Обратите внимание на apryMeHTbl, передаваемые в этом примере методу get  addresses (): 'from', 'to', 'се' и 'Ьсс'. Значение, возвращаемое мето- дом get  addresses () , представляет собой еписок кортежей. Каждый кортеж 
Отправка сообщений электронной почты и текстовых сообщений 473 состоит из двух строк: первая из них  ЭТО имя, связанное с адресом элек тронной почты, вторая  сам адрес. Если в запрошенном поле адреса oтcyr-- ствуют, ТО вызов get  addresses () возвращает пустой список. В данном слу чае такими являются поля 'се' (копия) и 'Ьсе' (скрытая копия), и поэтому для них возвращаются пустые списки. пол,.ен.е ".а ".СWIfI .э 1I''''poro ll со06ще"". Сообщения электронной почты MOryr быть отправлены в формате прcr cToro текста, HTML или в комбинированном формате. В первом случае сообщения MOryт содержать только текст, Тоrда как в НТМL-сообщениях MOryт использоваться цвета, различные шрифты, изображения и дрyrие возможности, блarодаря которым электронные письма будyr выrлядеть как небольшие веб-страниЦbl. Если вся электронная почта  это простой текст, то значением атрибyrа htmlyart ero объекта PyzMessage будет None. Подоб- ным образом, если в сообщении используется только формат HTML, значе- нием атрибута textyart ero объекта PyzMessage будет None. Во всех друrих случаях значение textpart или htmlpart будет иметь метод getpayload (), который возвращает тело сообщения в виде значе- ния с типом данных bytes. (Рассмотрение типа данных bytes выходит за рам- ки данной книrи.) Однако это все еще не то строковое значение, которое мы можем использовать. Ух! Последний шаr заlUlючается в том, чтобы вы- звать метод decode () ДЛЯ значения типа byte.s, возвращенноrо методом get payload () . Метод decode () принимает один apryмeHT: кодировку символов сообщения, которая хранится в атрибyrе text yart. eharset или html yart. ::::harset. Окончательный результат, возвращаемый методом decode (), пред- ставляет собой строку с телом сообщения. Продолжите выполнение интерактивноrо примера, введя следующие команды. . >>> aessage. text yart ! = None True »> ш-ssаge.teхtrt.gеtуlоаd().  decode (aessage. text rt. charset) . '50 long, and thanks for all the fish!\r\n\r\nAl\r\n' 8>>> m,essage.h1:llll..;>art !- None True . >>> m,essage.h1:llllrt.getyload().  decode (ш-ssаge. ht.IDl rt. charset) '<div dirnltrn><div>50 long, and thanks for all the fish!<br><br></div>Al<br></div>\r\n' Электронная почта, с которой мы работаем, включает как простой Teкcr, так и НТМL-содержимое, поэтому в объекте PyzMessage, сохраненном в lIеремеНIIОЙ message, имеются атрибуты textpart и htmlpart, значения 
474 r лава 16 которых не равны None О .. В результате вызова метода getpayload () для атрибута text yart 06'ьекта message и последующеrо вызова метода decode () для знаlения типа hytes возвращается строка текстовой версии сообщения .. Использование вызовов методов get payload () и decode () с атрибутом htmlpart объектаmеssаgе возвращает НТМL-версию сообщения О. rдtlлеН8е toо6щеН8. Чтобы удалить сообщения, передайте список уникальных идентифика- торов сообщений Meтoдydeletemessages () объекта IМAPClient. В результа- те этоrо сообщения помечаются флаrом \Deleted (удалено). Вызов метода expunge () приведет к безвозвратному удалению всех сообщений в текущей выбранной папке, помеченных флаrом \Deleted. Рассмотрим следующий интерактивный пример. о >>> iшарОЬ). select fOlcier('!NВOX', readonly=False) . »> UIDs - imapObjsearch([ION 092015']) »> UIDs [40066] »> imapOb).cielete messag&s(UIDs) . {40066: (1\\Seen','\\Deleted')} »> imapOb).expunqe() ('Success', [(5452, 'EXISTS')]) В этом примере мы выбираем папку INBOX (Входящие), вызывая метод selectfolder () объекта IМAPClient с передачей ему строки' INBOX' в ка- честве первоrо apryMeHTa. Кроме Toro, методу select  folder () передается именованный apryMeHT readonly=False, чтобы сделать возможным удаление сообщений О. В папке INBOX выполняется поиск сообщений с указанной дa той получения, и идентификаторы возвращенных сообщений сохраняются в списке UIDs .. Вызов метода delete message ( ) , которому передается спи сок UIDs, возвращает словарь. В этом словаре каждая пара "ключзначение" представляет идентификатор сообщения и кортеж флаrов сообщения, который теперь должен включать флаr \Deleted .. Последующий вызов метода expunge () безвозвратно удаляет сообщения, помеченные флаrом \Deleted, и возвращает сообщение Success в случае успешноrо удаления со- общений. Имейте в виду, что некоторые провайдеры, например Gmail, ав- томатически безвозвратно удаляют сообщения, удаляемые с помощью мето- да delete  messages ( ) , не дожидаясь поступления соответствующей команды от клиента IMAP. 
Отправка сообщений электронной почты и текстовых сообщений 475 '"зр",. ,оед.нен.. , сер.ером 'МА' Чтобы разорвать соединение ( сервером IМAP, коrда ваша проrрамма завершит извлечение или удаление сообщений электронной почты, доста- TOtlНO вызвать метод logout () объекта lМAPClient: >>> imapObj .lcgout () Если ваша проrрамма будет выполняться несколько минут или дольше, то сервер IМAP может автоматически отсоединиться по истечении задан- ною l1ромежyrка времени (тайм-аута). В подобных случаях следующая по- пытка вызова какоrо-либо метода объекта lМАРСliепt приведет к возникно- вению исключения наподобие следующеrо: imaplib.abort: socket error: [WinError 10054] An existing connection was forcibly c10sed Ьу the remote host (Удаленный хост-компьютер разорвал установленное соединение). Если это произойдет, то для повторноrо установления соединения ваша проrрамма должна будет вновь вызвать функцию imapclient. lМAPClient ( ) . Ух, наконец-то! На пришлось проделать очень длинный путь, пока мы дошли До конца, но зато теперь вы знаете, как заставить свои проrраммы на PytllOn входить в учетные записи электронной почты и извлекать сооб- щения. Всякий раз, коrда вам нужно будет освежить в памяти детали Toro, как это делается, вы можете заrлянуть в раздел "Извлечение и удаление со- общений электронной почты с помощью 1 МАР" . Проект: раССЫlIка по Зllектронной почте напоминаний о необходимости УПllаты Чllенских взносов Предположим, вы на "добровольных" началах вызвались вести учет уплаты взносов членами Клуба обя.1lC1fU!.l1/ь'Ноео вало'Нmepства. ЭТо утомитель- ное занятие, требующее поддержки электронной таблицы, в которой от- мечается, кто из членов клуба уплатил ежемесячный членский взнос, а кто не уплатил, причем пос.ледним необходимо рассылать электронной почтой уведомления, напоминающие о необходимости уплаты взноса. Вместо Toro чтобы вручную просматривать список членов клуба и rотовить письма- напоминания тем, кто просрочил очередной платеж, копируя и вставляя в письма каждому из них один и тот же текст, лучше  и вы, наверное, сами уже об этом доrадались  написать сценарий, который выполнит эту работу вместо вас. Вот что должна делать такая проrрамма при высокоуровневом рассмо- трении: 
476 r лава 16 . читать данные из электронной таблицы Excel; . находить тех членов клуба, которые не уплатили взнос за последний месяц; . находить их адреса электронной почты и отправлять им персональ- ные напоминания. Это означает, что ваш код должен выполнять следующие действия: . oTKpbIBaTI) документ Ехсеl и читать (одержимое ero ячеек с помощью модуля openpyxl (о работе с файлами Ехсеl читайте в rлаве 12); . создавать словарь членов клуба, не уплативших членские взносы; . входить в учетную запись на SMTP-сервере путем вызова функций и методов smtplib. SМТР () , ehlo () , starttls () и login (); в отправлять электронной почтой персональные напоминания членам клуба, просрочившим уплату взносов, с помощью метода sendmail ( ) . Откройте новое окно в файловом редакторе и сохраните ero в файле sendDuesReтinders.py. UI", ,. О'''р''''., Ф"iлtl fxtel Предположим, что электронная таблица Ex(cl, которую вы используете для контроля уплаты членских взносов, выrлядит примерно так, как пока- зано на рис. 16.2, и хранится в файле duesRecords.xlsx. Этот файл можно за- rрузить на сайте http://nostarch . com/automatestuff/. 8 f).... dUбl-<еror(j"It!.к Eи1 т  L1 Х ФЙ'II r !1.:JcH;'-', A(1(IK41 РaJмсr.цt. форIt.'упы i Itтt.!{' , РrЦf>НJО1fl1l Вн,,\: 1 fAM ! 1. !, ! В.код;:: (")ll\01l1 1(OYn "l. . ....". 1( ...; (.  У,.ЛО8моt фQР"'tlТliроаI'НII.  ФаР"..!I'ТИРОI-!ТЬ I(,a i4б.nицу  Шр""фr ВtЩ,1l11ll'ilв.!lниt Ц1(.11a [.i СJi.1Л1 QЧН  . \.) ," ЯЧi!'Йt 1 Р.1ltтираIE!lНIе I  I , "' I ;':/$;:,", .',(rJ( '..ц . (НtЛl.I I i (,14 I А 8 с О" ,Е. F G Н 1 1 ЧJlен  АдРес IJlеиrромноА fIOIПbl Яив 2014 в.. 2014 М.р Z014 Anp 2014 Маll2О14 ИЮН 2014 2 Alice alice"ex.mple.COm УПЛiIЧено умацено ум.цено уплацено умачено 1 3 ВоЬ Dob"example.com упл,чено УПЛilЧено умацена уплацено 4 . Carol carol@example.com умацено УМilЧено УП1lацено уП1lilЧено уплацено упл'чено 5 Davld davld"example.com ум.ценО УМilЧено упмцено уnлацено уплацено УМilЧено 6 ЕУе eve@example.com уплацено УМilЧено УПЛilЧено 7 red fred@ex.mple.com уплацено уплацено уплацено уплацено уnлацено умацено 8 q [..':....::......... .. ...Щ:.. . :i=--..... Ш .. .+..I'J....J Рис. 16.2. Электронная таблица Д1Iя учета уплоты членских взносов 
Отправка сообщений электронной почты и текстовых сообщений 4Т1 В этой электронной таблице хранятся имена членов клуба и адреса их электронной почты. Каждому месяцу соответствует отдельный столбец, в котором делаются отметки об уплате взносов. Отметкой об уплате взноса служит текст уплачено. Проrрамма должна открывать файл dues&cords.xlsx и находить столбец, соответствующий последнему месяцу, вызывая метод gеthighеstСОlШnn (). (Для получения более подробной информации о доступе к ячейкам элек- тронных таблиц [хее} с помощью модуля openpyxl обратитесь к rлаве 12.) Введите в окне файловоrо редактора следующий Код. #! руthопЗ # sendDuesReminders.py  Рассылает сообщения на основании # сведений из электронной таблицы об уплате взносов. import openpyxl, smtplib, sys # Открытие электронной таблицы и получение последних данных об # уплате взносов. . wb = openpyxl.loadworkbook('duesRecords.xlsx') . sheet '" wb. get  sheet  Ьу  пате ( I Лист1 ' ) . lastCol '" sheet. get highest colurnn () . latestMonth = sheetcell(row=1, colurnn"'lastCol) .value # TODO: Проверить состояние уплаты взносов для каждоrо # члена клуба. # тооо: Войти в учетную запись электронной почты. # тооо: Отправить сообщения с напоминанием об уплате взносов. Импортировав модули openpyxl, smtplib и sys, мы открываем файл dues&cords.xlsx и сохраняем результирующий объект Workbook в перемен- ной wb О. Затем мы 1I0лучаем Лист 1 и (охраняем результирующий объект Worksheet в перемеНIIОЙ sheet .. Теперь, Коrда в нашем ра(поряжении имеется объект Worksheet, мы можем обращаться к строкам, столбцам и ячейкам электронной таблицы. Мы сохраняем номер последнеrо столбца в переменной lastCol О, а затем используем ячейку с. номером (:троки 1 и номером столбца lastCol для доступа к ячейке, в которой хранится но- мер последнеro месяца. Значение этой ячейки мы сохраняем впеременной latestMonth О. Шtl,2. По.,,, .сех члено. оу6t1, nе уnл",..UI.Х .,.« Определив номер последнеrо месяца (хранящийся в переменной :'astCol), можно орrаНИ30вать цикл по всем строкам, кроме первой (в кота- рой хранятся зarоловки столбцов), чтобы выяснить, против фамилий каких членов клуба стоит отметка уплачено для проверяемоrо месяца. Если кто- либо из членов клуба не уплатил взнос, можно извлечь ero имя и адрес элек- тронной почты из столбцов 1 и 2 соответственно. Эта информация зано сится в словарь unpaidМembers, с помощью Koтoporo будyr отслеживаться те, 
478 r лава 1 6 кто проnyстил оплату за ПOCJlедний месяц. Добавьте в файл sendDuP.sReтinder. ру следующий код. fI! руthопЗ fI sendDuesReminders.py  Рассылает сообщения на основании fI сведений из электронной таблицы об уплате взносов. пропущенный KOД fI Проверка состояния уплаты взносов для каждоrо члена клуба. unpaidМembers = {} . for r in range(2, sheet.gethighestrow() + 1): . payment = sheet.cell{row=r, column=lastCol) .value if payment != 'уплачено': . пате  sheet.cell(row=r, column=l) .value . email = sheet.cell(row=r, column=2} .value . иnpaidMembers[name] = email в этом коде создастся пустой словарь иnpaidМemЬers и выполняется циlUl по всем строкам, кроме первой О. Для каждой строки значение, нахО/l,я- щееся в последнем столбце, сохраняется в переменной payment .. Если зна- чением payment не является строка' уплачено' , то значение, наХО/l,ящееся в первом столбце, сохраняется в переменной пате ., а значение, находящ еся во втором столбце  в переменной ета il .., и обе переменные, пате и email, добавляются в словарь unpaidМembers .. Шtl, 3. Ornptlllltl neptOHtllI'H"X нtlпО..НtlН.. по JllellrpoHHoii по.," Получив список всех неплательщиков, можно отправить им напомина ния по электронной почте. Добавьте в проrрамму следующий код, исполь зовав в нем свой реальный адрес электронной почты и информацию о по чтовом провайдере. fI! руthопЗ fI sendDиesReminders.py  Рассылает сообщения на основании fI сведений из электронной таблицы об уплате взносов. пропущенНЬlЙ KOД fI Вход в учетную запись электронной почты. smtpObj = smtplib.SMTP('smtp.gmail.com', 587) smtpObj .ehlo () smtpObj.starttls() smtpObj.login(.myemailaddress@gmail.com.. sys.argv[l]) Создайте объект SMTp, вызвав функцию smtplib. SMTP () с передачей ей доменноrо имени и номера порта cBoero провайдера. Вызовите сначала 
Отправка сообщений электронной почты и текстовых сообщений 479 методы ehlo () и starttls (), а затем  метод login (), и передайте ему свой адрес электронной почты и значение переменной вув. argv [1] , в которой будет храниться строка с вашим паролем. Вы будете вводить пароль в ка- честве apryMeHTa командной строки каждый раз при запуске проrраммы, чтобы избежать ero хранения в исходном коде. После входа проrраммы в вашу учетную запись электронной почты она должна проанализировать содержимое словаря unpaidМembers и отправить по электронной почте персональные напоминания должникам. Добавьте в файл sendDuesReminders .ру следующий код. #! руthоnЗ # sendDuesReminders.py  Рассылает сообщения на основании # сведений из электронной таблицы об уплате взносов. пропущенный KOД # Отправка сообщений с напоминанием об уплате взносов. for nате, email in unpaidМembers.items(): О body = "Subject: %s dues unpaid.\nDear %s,\nRecords show  that уои have not paid dues for %э. Please make this раymепt  as soon аэ possible. Thank уои! '"  % (latestMonth, пате, latestMonth) . print('OTnpaBKa письма по адресу %э...' % email) . sendmailStatus =  smtpObj.sendmail('my email address@gmail.com', email, body) е' i f sendmailSta tus ! = {}: print('There was а problem sending email to %s: %э'  % (email, sendmailStatus)) smtpObj . qui t ( ) в этом коде выполняется цикл по именам и адресам электронной по- чты, хранящимся в словаре иnpaidMembers. Для каждоrо члена клуба, про- срочившеrо уплату взноса, создается персональное сообщение, в котором используется информация о 110следнем месяце и имени члена клуба, и это сообщение сохраняется в переменной body.. Мы также выводим инфор- мационное сообщение об отправке письма в адрес дaHHoro члена клуба.. Затем мы вызываем метод sendmail ( ) , передав ему адрес отправителя и пер' сонализированное сообщение .. Возвращаемое этим методом значение со' храняется в переменной sendmai1Statиs. Помните о том, что в случае получения от SMTP-сервера сообщения об ошибке при попытке отправки какоrо-либо сообщения метод sendmail () возвращает значение в виде непустоrо словаря. В последней части ЦИкла for . проверяется, является ли словарь непустым, и, если это так, выво' дятся адрес электронной почты получателя и содержимое возвращенноrо словаря. 
488 rлава 16 Коrда проrpамма завершит отправку всех сообщений, следует разорвать соединение с SМТР--сервером, вызвав метод qui t ( ) . Выполнив проrpамму, вы должны получить следующий вывод. Отправка письма по адресу alice@example.com... Отправка письма по адресу bob@example.com... Отправка письма по адресу eve@example.com... Полученные получателями сообщения электронной почты будyr выrля деть так, как показано на рис. 16.3. June 2014 dueB unpllld. '"..., . А' Swelglln <ЭS,'iе.gаrt@g:'"'all C." Dear Alice Records аho", that уоо have по! p;lid dues for June 2014 Please make this peymenl аа 8О0n аз ;юssiЫе Тhank youf Рис. 16.3. Аsтомотнческая отправка сообщений эпектронной почты с помощью проrpаммы seпdDиesRem;nders.py Отправка текстовых сообщений с помощыо Twilio Большинство людей предпочитают иметь под рукой телефон, а не ком- пьютер, поэтому текстовые сообщения MOryт оказаты:я более непосред ственным и надежным способом рассылки уведомлений, чем электронная почта. Кроме Toro, небольшой размер текстовых сообщений делает более вероятным их быстрое прочтение теми, кому они предназначены. В этом разделе вы узнаете о том, как подпи<.:атЬеЯ на бесплатную службу Twilio и использовать ее модуль Pytholl для отправки текстовых сообще ний. Twilio  это служба шлюзовоrо интерфейса SMS, Т.е. служба, с 1I0 мощью которой вы сможете отправлять текстовые сообщения из своих проrрамм. Несмотря на то что вы будете оrраничены в количестве еже месячных текстовых сообщений, которые вам будет разрешено отправ лять, а их текст будет предваряться словами Seпt froт а Twilio trial ассоun! (отправлено с использованием пробной учетной записи Twilio), пробный вариант этой службы, вероятно, вполне удовлетворит потребности ваших персональных проrрамм. Срок действия пробной версии неоrраничен, и вам не придется впоследствии в обязательном порядке обновлять ее до платной версии. Twilio  не единственная из проrрамм TaKoro рода. Если вы не захотите использовать Twilio, то можете выбрать одну из альтернативных служб, BЫ полнив в Интернсте поиск по lUIючевым словам free sms gateway, python sms api или даже twilio alternatives. 
Отправка сообщений электронной почты и текстовых сообщений 481 Прежде чем создать учетную запись на Twilio, установите модуль twilio. Более подробно об установке модулей, разработанных сторонними компа- ниями, можно прочитать в приложении А ПРUМtчaние Этот раздел Cne1J,uфu'Че'Н для США. Twilio nредлаеает услуzu по обмену SMS- сообщеиuя.мu u для друеих страи, одн.а"о их Сne1&uфu'Ка в даииой 'Кииее ие рассма- тривается. Тем не.меиее модуль twilio u еео фуи"1JUU будут работат;ь одина"ово u вие США. Более nодробиую uиформа1J,UЮ по эmoму вопросу можио иайти иа саuте http://twilio. сот/. COJДoH.e учеrноj ,ОП.,. Twilio Перейдите на сайт http://twilio.com/ и заполните реI'истрационную форму. После 1'01"0 как вы создадите новую учетную запись, вам нужно будет подтвердить номер мобильноrо телефона, на который вы хотите отправ- лять текстовые сообщения. (Верификация этоrо номера необходима для Toro, чтобы исключить возможность использования службы ДЛЯ рассылки спама на случайные номера.) Получив текст с верификационным кодом, ввсдите ero на сайте Twilio в качестве доказательства Toro, что именно вы являетесь владельцем вери- фицируемоrо номера. Теперь вы сможете отправлять текстовые сообщения на этот номер с помощью модуля twil io. Twilio предоставит вам I1робную учетную запись с телефонным номером для использования в качестве отправителя текстовых сообщений. Вам п<r требуются еще два информационных элемента: идентификатор безопас- ности SID вашей учетной записи и аутентификационный маркер. Соответ- ствующую информацию вы найдете на странице Dashboard, коrда войдете в свою учетную запись Twilio. Эти значения будут иrpать роль вашеrо имени пользователя и lIароля при входе в Twilio из Ilpol'paMM на PyttlOn. Ornptl.«o """о.ы. ,оо6щен.j Коrда вы установите модуль twi1io, заведете учетную запись Twilio, вери- фицируете свой телефонный номер, зареl'истрируете телефонный номер Twilio и получите SID и аутентификационный маркер для своей учетной за- писи, это будет означать, что вы полностью rотовы К отправке текстовых сообщений из своих сценариев на PytllOl1. По сравнению с полной процедурой реrистрации написание фактиче- cKoro кода на Python для использования службы Twilio не состаВляет тру- да. При условии, что ваш компьютер подключен к Интернету, введите в 
482 r лава 16 интерактивной оболочке приведенный ниже код, используя в нем для пере- менных accountSID, аиthТоkеп, myTwilioNumber и mуСе11 Phone реальные значе-- ния cBoero идентификатора безопасности, аутентификационноrо маркера, телефонноrо номера Twilio и собственноrо телефонноrо номера. о »> from twilio.rest import TwilioRestClient »> accountSID  'АСхххххххххххххххххххххххххххххххх' »> authToken  'хххххххххххххххххххххххххххххххх' . »> twilioC1i = TwilioRestC1ient(accountSID, authToken) »> myTwi1ioNumber = '+14955551234' »> myCellPhone = '+14955558888' . »> message = twilioCli.messages.create(body='Mr. Watson  Соте  here  I want to see уои. " from=myTwilioNumber, to=myCe11Phone) Спус.тя короткое время после ввода последней строки вы должны полу- чить текстовое сообщение следующеrо содержания: Sent from уоит Twilio Ыа! account  Mr. Watson  Соте here  1 want to see уои. В силу особенностей развертывания модуля twilio ero нужно импортиро- вать с помощью команды from twilio.rest import TwilioRestClient, а не просто команды import twilio О. Сохраните SID своей учетной записи в переменной accountSID, а свой аyrентификационный маркер  в перемен- ной aиthToken, после чеrо вызовите функцию TwilioRestClient () , передав accountSID и аиthТоkеп в качестве арl'}'Ментов. Вызов TwilioRestClient () воз- вращает объект TwilioRestClient .. Этот объект имеет атрибyr messages, в свою очередь имеющий метод create (), который вы можете использовать для отправки текстовых сообщений. Именно этот метод инструктирует серверы Twilio о том, что ваше текстовое сообщение нуждается в отправ- ке. Сохранив свой номер Twilio и номер coтoвoro телефона в переменных mуТwiliоNшnbеr и myCellPhone соответственно, вызовите метод create () и пе-- редайте ему именованные apryмeнты, задающие тело TeKcToвoro сообщения, номер отправителя (myТwilioNurnber) и номер получателя (myCellPhone) .. Объект Message, возвращенный методом create (), будет хранить инфор- мацию об отправленном текстовом сообщении. Продолжите выполнение примера, введя в интерактивной оболочке следующие команды. »> message.to '+14955558888' »> message.from '+14955551234 ' »> message.body 'Mr. Watson  Соте here  I want to see уои.' в атрибyrах to, from  и body должны храниться соответственно номер вашеrо cOToBoro телефона, телефонный номер Twilio и само сообщение. 
Отправка сообщений электронной почты и текстовых сообщений 483 Обратите внимание на то, что номер телефона отправителя хранится в атрибуте from (имя KOToporo содержит символ подчеркивания в конце), а не f rom. Это обусловлено тем, что f rom является ключевым словом в Python (например, оно уже встречалось вам в инструкциях импорта в форме from имя  модуля import *) и поэтому не может служить именем атрибута. Про должите выполнение примера, введя в интерактивной оболочке следую- Щие команды. »> message.statиs 'qиeued' »> message.date created datetime.datetime(2015, 7, 8, 1, 36, 18) »> message.datesent == None True Атрибут status предоставляет строку. Атрибуты date  created и date  sent предоставляют объект datetime, еели сообщение было создано и ()тправле но. Может показаться несколько странным, что атрибут status возвращает значение 'queued' (помещено в очередь), а атрибут datesent  значение None, коrда текстовое сообщение уже получено. Объя(няется ЭТО тем, что объект Message был сохранен в переменной message еще до фактической отправки TeKcTOBoro сообщения. Чтобы увидеть текущие значения атри бутов status и datesent, следует ааново измечь объект Message. Каждому сообщению Twilio присваивается уникальный строковый идентификатор (SID), который можно использовать для извлечения последнеrо состояния объекта Message. Продолжите выполнение npимера, введя в интерактивной оболочке следующие команды. »> message.sid 'SM09520de7639ba3af137c6fcb7c5f4b51, О »> updatedМessage = twilioCli.messages.get(message.sid) »> updatedMessage.status 'delivered' »> updatedMessage.datesent datetime.datetime(2015, 7, 8, 1, 36, 18) Команда message. sid отображает длинное значение SID этоrо сообще- ния. Передав это значение SID методу get () клиента Twilio О, вы можете получить новый объект Message, содержащий обновленную информацию о сообщении. Теперь атрибуты statиs и datesent этоrо HOBoro объекта Message содержат корректные значения. Атрибут status может принимать одно из следующих строковых значе- ний: 'queued' (помещено в очередь), 'sending' (отправляется), 'sent' (o правлено), 'delivered' (доставлено), 'undelivered' (не доставлено) или 
484 rЛQва 16 I failed' (не отправлено). Названия этих состояний rоворят сами за себя, но если вам нужны более подробные сведения, то ознакомьтесь с ресурсами на сайте http://nostarch . com/aиtomatestиff/. nол,,,,н,,, ""ао.". сообщ,н.. , помощ'lO "'1108 К сожалению, процедура получения текстовых сообщений с помощью Python HeMHoro сложнее процедуры отправки. Twilio требует, чтобы у вас был веб-сайт, выполняющий собственное веб-приложение. Рассмотрение этой темы выходит за рамки данной книrи, 110 вы сможете получить более подробную информацию по этому вопросу, обратившись к ресурсам, пред- ставлеНIIЫМ на сайте книrи (http://nostarch.com/automatestuff!). Проект: МОДУIlЬ "Черкни мне" Вероятнее Bcero, персоной, которой вы будете чащс Bcero отправлять текстовые сообщения из своих проrрамм, будете вы сами. Текстовые сооб- щения  замечательное средство отправлять себе напоминания, которыс можно прочитать, находясь вдали от компьютера. Если вы автоматизирова- ли рутинную задачу, выполняющуюся пару часов, то сможсте получить тек- стовое сообщение, извещающее вас о ее завершении. Друrой вариант  у вас есть проrрамма, реryлярно ВblПОЛНЯЮЩаяся по расписанию и при этом ИНOI'Да нуждающаяся в установлении контакта с вами. В качестве при мера можно привести проrрамму, проверяющую проrНОЗЫl10rоды и при fI<.обхcr димости сообщающую вам о том, что пора приrотовить зонтик по случаю ожидаемоrо дождя. Рассмотрим простой при мер проrраммы с функцией textmyself ( ) , кото- рая отправляет текстовое сообщение, псреданное ей в качестве CTpoKoBoro apryMeHTa. Откройте новое окно в файловом редакторе и ввсдите привс- денный ниже код, используя в нем для переменных accountSID, aиthToken, myTwilioNиrnber и myCe::"lPhone собственные данные. Сохраните код в файле textMyself. ру. #! руthопЗ # textMyse1f.py  Определяет функцию textmyse1f(), которая # отправляет текстовое сообщение, переданное ей в виде строки # Предустановленные значения: accountSID = 'АСхххххххххххххххххххххххххххххххх' authToken = 'хххххххххххххххххххххххххххххххх' myNurnber = '+15559998888' twilioNumber = '+15552225678' from twi1io.rest import Twi1ioRestC1ient О def textmyself (message) : . twilioC1i = TwilioRestClient(accountSID, aиthToken) 
Отправка сообщений электронной почты и текстовых сообщений 485 . twilioCli.messages.create(body=message, from=twilioNumber,  to=myNumbe r) Прежде всею в nporpaмMe сохраняются значения SID и аутентификаци- oHHoro маркера, а также телефонные номера отправителя и получателя. Затем определяется функция textmyself () , которая принимает apryмeHT О, создает объект TwilioRestClient . и вызывает метод create (), используя переданное ей сообщение О. Если вы захотите сделать функцию textmyself () доступной дрyrим CBcr им nporpaмMaM, то поместите файл textМyself.py в ту же папку. в которой Ha ходится исполняемый файл Python (С:\РуthопЗ4 в Windows, /иsr/local/ liЬ/руthопЗ.4 в OS Х и /иsr /Ып/руthопЗ в LiIlUX). После этою вы сможете использовать эту функцию в друrих nporpaмMax. Чтобы одна из ваших про- rpaMM моrла отправить вам текстовое сообщение, включите в нее следую- щий код: import textmyself tехrmуsеlf.tехtmуsеlf('Рутинная работа выполнена. ') 3ареrистрироваться на сайте Twilio и написать код, обрабатывающий отправку текстовых сообщений, нужно только один раз. После этоrо для отправки текстовоro сообщения из любой из ваших Проl"рамм вам потребу- ется добавить Bcero лишь пару строчек кода. РеЗlOме Мы общаемся ДРУI' с друrом через Интернет и посредством сотовых те- лефонных сетей, используя десятки всевозможных способов, но преимуще- ственно это электронная почта и текстовые сообщения. Ваша проrрамма может задействовать эти каналы связи, что открывает перед вами новые широкие возможности оповещения. Можно даже написать проrрамму, Kcr торая будет выполняться на разных компьютерах, обеспечивая непосред- ственный обмен данными между ними посредством электронной почты, коrда одна проrрамма отправляет сообщения электронной почты по npcr токолу SMTp, а друrая принимает их по протоколу IMAP. Модуль Python smtplib предоставляет функции, с помощью которых вы можете отправлять сообщения через SMTP-сервер CBOel"O 110чтовоrо про- вайдера. Подобным образом модули сторонних разработчиков imapclient и руzmаi111О31ЮЛЯЮТ получать доступ к серверам IМAP и получать отправ- .1енные вам (ообщения. Протокол IMAP несколько сложнее протокола SMTP. однако Ilредлаraет довольно широкие возможности, включая поиск конкретных сообщений электронной IIОЧТЫ. их заrрузку и синтаксический 
486 r лава 16 анализ для извлечения темы и тела сообщений в виде строковых значений. Обмен текстовыми сообщениями несколько отличается от электронной почты, IIОСКОЛЬКУ, в отличие от последней, для отправки СМС требуется не только подключение к Интернету. К счастью, службы наподобие Twilio предоставляют модули, позволяющие отправлять текстовые сообщения из проrрамм. Как только вы пройдете все этапы процесса начальной настрой- ки, вы сможете отправлять СМС с помощью Bcero лишь пары строк Кода. Имея в своем распоряжении указанные модули, вы сможете проrpамми- ровать конкретные условия, при которых ваши проrраммы должны отправ- лять уведомления или напоминания. В этом случае область действия ваших проrрамм распространится на большие расстояния за пределами компью- 'repa, на котором они выполняются! КОНТРОlIьные вопросы 1. Что такое протокол для отправки электронной почты? Что такое прcr токол для проверки и получения электронной почты? 2. Какие четыре функции/методы модуля smtplib необходимо вызвать для Toro, чтобы войти в учетную запись на SМТР-сервере? 3. Какие функции (методы) imapclient необходимо вызвать для Toro, чтобы войти в учетную запись на IМAP-cepвepe? 4. ApryмeHT кaкoro типа передается методу imapObj. search ()? 5. Какие меры вы предпримете в том случае, если ваш код получает ccr общение об ошибке got more than 10000 bytes (получено более чем 10000 байт)? 6. Модуль imaрсliепt отвечает за подключение к серверу lМAP и поиск сообщений электронной почты. Какой модуль отвечает за чтение ccr общений электронной почты, извлеченных модулем imapclient? 7. Какие три элемента информации нужно получить от Twilio, прежде чем отправлять текстовые сообщения? Учебные проекты Чтобы закрепить полученные знания на практике, напишите проrрам- мы для предложенных ниже задаЧ. '''t.peeJle..e р""n.",х 'tllI"" nуте. p"«"'II"n по Jllelll'pOHHOi попе Напишите проrрамму. которая принимает список адресов электрон- ной Почты и список рутинных задач, подлежащих выполнению, которые 
Отправка сообщений электронной почты и текстовых сообщений 487 должны рассылаться исполнителям, выбираемым случайным образом. Если вы хотите поставить перед собой более амбициозные цели, то орrанизуй- те учет ранее распределенных заданий, чтобы проrрамма не назначала ис. полнителю то же самое задание, которое он выполнял в npошлый раз. Как дополнительный вариант предусмотрите автоматическое выполнение прcr rpaMMbl по расписанию один раз в неделю. Вот вам подсказка: если передавать функции random. choice () список, то она будет каждый раз возвращать один элемент списка, выбираемый случай- ным образом. Часть вашею кода может иметь примерно следующий вид. chores  ['dishes', 'bathroom', 'уасиит', 'walk dog'] randomChore  random.choice(chores) chores.remove(randomChore) # это задание уже распределено, и # ero можно удалить из списка На"ом.нан., о ,он,.", В rлаве 11 было продемонстрировано, как орrанИЗ0вать сбор данных с поrодноrо сайта http://weather . gov / с помощью модуля reqиests. Напиши- те проrрамму, которая запускается непосредственно перед вашим утренним пробуждением и проверяет, не ожидается ли в этот день дождь. В случае TaKoro проrноза проrрамма должна отправить вам текстовое сообщение с напоминанием о том, что, покидая дом, необходимо Не забыть взять с ccr бой зонт. A.'OMa'."'t".' 0'''0' о, пОIl".'''. Напишите проrрамму, которая просматривает почтовый ящик вашей учетной записи электронной почты, находит все ссылки Unsubscribe (Отка- заться от подписки) в сообщениях и автоматически открывает их в браузе- ре. Проrрамма должна входить в вашу учетную запись на IMAP-сервере по. чтовоrо провайдера и заrружать все сообщения. Для обнаружения HTML- дескрипторов ссылок, содержащих слово Unsиbscribe, можно использовать модуль BeautifulSoup (см. rлаву 11). Получив список соответствующих URL-адресов, можете использовать функцию webbrows er. ореn () для открытия в браузере ссылок, позволяющих отказаться от подписки. Вам по-прежнему необходимо вручную проделать все остальные дей- ствия, связанные с отказом от подписки. В большинстве случаев это сво- дится к щелчку на ссылке. подтверждающему отказ. Тем не менее этот сценарий избавляет вас от необходимости просма- тривать всю электронную почту в поиске ссылок Unsubscribe. Кроме Toro, можно передать Этот сценарий своим друзьям, чтобы они моrли выполнять 
488 r лава 16 ero применительно к собственным учетным записям электронной почты. (При этом обязательно проследите, чтобы в коде не был жестко запроrрам- мирован ваш собственный пароль для доступа к электронной почте!) '.trtlHq.OHHoe ,nро.лен.е ,,08.'lOre,o_ nо'редtr.08 Jле",ронно;; по",,, Напишите проrрамму, которая проверяет вашу электронную почту каждые 15 минyr, осуществляя поиск поступивших от вас инструкций, и в случае их наличия автоматически их выполняет. Рассмотрим, например, файлообменную систему Вiйоrrеllt. Используя бесплатное проrраммное обеспечение BitTorrent, например qВittorrent, можно заrружать большие мультимедийные файлы на свой домашний компьютер. Если отправить проrрамме ссылку Bitlorrellt: (это действие  совершенно законное и вовсе не пиратекое ), то проrрамма в процессе очередной проверки электронной почты обнаружит это сообщение, извлечет ссылку, а затем запустит IUlИен- та qBittorrent для заl'РУЗКИ соответствующеrо файла. Таким образом, ваш компьютер сможет заrружать файлы даже в ваше отcyrствие, и к тому вре- мени, коrда вы вернетесь домой, файлы уже будyr заrpужены. В rлаве 15 речь шла о том, как запу<:кать проrраммы на компьютере с по- мощью функции sиbprocess. Рореn (i . Например, следующий код запустит проrрамму qВittоrrепt для указанною торрент-файла. qbProcess  sиЬрrосеss.Рореп(('С:\\Рrоgrаm Files (х86)\\ qBittorrent\\qbittorrent,exe'. shakespearecompleteworks.torrent')) Разумеется, вы захотите, чтобы проrрамма проверяла, поступило ли дан- ное сообщение именно от вас. Для этоrо, в частности, можно потребовать, чтобы в сообщении содержался пароль, поскольку хакерам не составляет труда подделать адрес в поле "От" сообщения электронной почты. Про- rрамма должна удалять все подходящие сообщения, которые она обнару. жит, чтобы не выполнять инструкции повторно каждый раз при провер- ке электронной почты. В качестве дополнительной возможности можете предусмотреть, чтобы проrрамма отправляла вам :лектронное письмо или текстовое сообщение, подтверждающие начало выполнения порученной работы. Поскольку во время выполнения проrраммы вы не будете сидеть за компьютером. будет неплохо, если вы орrанизуете ведение журнала (см. rлаву 10), записываемоrо в текстовый файл. чтобы вы моrли проверить, возникали ли ошибки в процессе заrрузки файлов. в проrрамме q Bittorrellt (равно как и в дрyrих приложениях BitTorrent) предусмотрена возможность автоматическоrо выхода по окончании 
Отправка сообщений электронной почты и текстовых сообщений 489 процесса зarрузки. В rлаве 15 речь шла о том, как проконтролировать завер- шение работы запущенной проrраммы с помощью метода wai t () объектов Popen. Вызов метода wait () будет блокироваться до тех пор, пока выполне- ние проrpаммы qBittorrent не будет прекращено, после чеro ваша проrpам ма сможет отправить электронное почтовое или текстовое сообщение, уве- домляющее вас о завершении процесса заrpузки. Существует множество др}тих возможностей, которые MOryr быть добав- лены в подобный проект. Если вы столкнетесь с какими-либо трудностями, то можете Зal'РУЗИТЬ при мерную реализацию такой проrраммы, посетив сайт http://nostarch . с от/ automatestuff/. 
РА&ОТА с ИЗО&РА>КЕНИЯМИ Если У вас есть цифровая камера или же вы прocrо размещае- те свои фотоrpафии на Facebook, то вам, вероятно, постоянно приходится иметь дело с файлами цифровых изображений. Возможно, вы даже умеете пользоваться простыми проrрам- мами для обработки rpафики, такими как Microsoft Paint. или Paintbrush, или даже более сложными, как, например, Adobc P]lOtoS]lOP, Но если вам нужно отредактировать большой массив изображе- ний, такая рyrинная обработка вручную может отнять у вас мною времени. Воспользуйтесь возможностями PyttЮIl. Pillow  это библиотека Pyt]lOn от сторонних разработчиков, предназначенная для взаимодействия с файлами изображений. В данной библиотеке предусмотрен ряд функций, упрощающих выполнение таких операций, как обрезка, масштабирование и изменение содержимоrо изображений. Располarая возможностями мани пулирования изображениями примерно с той же эффективностью, какую обеспечивают такие ПрOlраммы, как Мicrоsoft Paillt, Pyttюп позволяет леl'. ко автоматизировать редактирование сотен и даже тысяч изображений. ОСНОВЫ компыоернойй обработки изображений Чтобы манипулировать изображениями, нужно хотя бы на элементар- ном уровне понимать, как компьютеры обрабатывают rpафическую инфор- мацию и как выполнять соответствующие операции с помощью библиотеки Pillow. Но прежде чем мы продолжим наше рассмотрение, вам необходимо установить модуль pillow. Подробное описание процедуры установки моду' лей сторонних разработчиков приведено в приложении А. Ц.,т. . IGВA-,..",,,.. В компьютерных проrраммах для представления цвета часто ИСIЮЛЬЗУКYf RGВА-зuаченuя. Значение RGBA  это rpуппа чисел, задающих долю красной (red), зеленой (green), синей (Ыие) и алъфа-составляющей (прозрачности) 
492 r лава 17 в цвете. Каждое из этих значений представляет собой целое число в интер- вале от О (отcyrствие данною цвета) до 255 (максимум). Эти RGВА-значения присваиваются отдельным пикселям. ПU'КСe.ll/Ь  это наименьший элемент изображения, который может быть представлен на компьютерном экране (количество пикселей на экране исчисляется миллионами). RGВ-значения пикселей в точности определяют оттенок цвета для отображения пикселя. Использование альфа-составляющей приводит к созданию RGВА-значений. Если изображение выводится на экране поверх фоновоro изображения или обоев рабочеrо стола, то значение параметра "альфа" (или, как rоворят, альфаканала) определяет, насколько интенсивно должен "просматривать- ся" фон через пиксель изображения. В библиотеке PilIow значения RGBA представляются кортежем из че- тырех целочисленных значений. Например, красный цвет представля- ется кортежем (255, О, О, 255). В этом цвете <:одержится максимальное количество KpacHoro цвета, зеленый и синий цвета отсутствуют, а альфа- составляющая имеет максимальное значение, которому соответствует пол- ная непрозрачность. Зеленый цвет представляется кортежем (О, 255, "О, 255), а синий  кортежем (О, О, 255, 255). Белый цвст, являющийся сочt.'Та- нием всех цветов, представляется кортежем (255, 255, 255, 255), тоща как черный, соответствующий полному отсутствию цветов,  кортежем (О, О, О, 255). Если значение альфаканала в цвете равно О, то этот цвет  невидимый, и в действительности от конкретных значений параметров RGB ничеrо не зависит. В конце концов, невидимый красный  это все равно что невиди- мый черный. В библиотеке Pillow используются стандартные названия цветов, при. нятые в HTML. В табл. 17.1 приведены некоторые стандартные названия цветов и соответствующие им RGВА-значения. Табllица 17.1. Стандартные названия цветов и их RGВД-эначения Наuание Белый Зелвный Серый Чврный Эначение RGBA (255,255,255,255) (О, 128, О, 255) (128,128,128,255) (О, О, О, 255) Наlвание Красный Синий Жsптый Пурпурный Значение RGВA (255, О, О, 255) (О, 0,255,255) (255,255, О, 255) (128, 0,128,255) Библиотека Pil10w предлаrает функцию ImageColor. getcolor ( ) , которая избавляет от необходимости запоминать RGВАзначения цветов, планируе- мых к использованию. Эта функция принимает название цвета в качестве первоrо apryMcHTa и строку 'RGBA' в качестве BToporo apryMcHTa, а возвра- щает кортеж :шачений RGBA. 
Работа с изображениями 493 Цветовые схемы СМУК и RGB Из курса физики средней школы вам должно быть известно, что смешиванием красной, желтой и синей красок можно получить все остальные цвета. Например, если вы смешаете синюю и желтую краски, то получите зеленую. Эта схема называ- етсн субтрактнвной цветовой моделью и примеНRетСR при производстве красителей, чернил и пиrментов. По зтой причине цветные принтеры оБОРУДУЮТСR чернильными картриджами СМУК: смешивание чернил Суап (rолубой), Magenta (пурпурный), Yellow (желтый) и ЫасК (черный) пазволяет сформировать любой цвет. Однака в физике света используется CJДЦитивная цветовая мадель. Комбинируя лучи света различной частоть. (как в случае света, излучаемоrо компьютернь.м зкра- ном), можно сочетать красный, зеленый и синий световые лучи для формиравания любоrа Apyroro цвета. Вот пачему в компьютерных nparpaMMax цвета представлЯ- ются RGB-значеНИRМИ. Чтобы увидеть, как работает эта функция, введите в интерактивной обо- лочке следующий код. . »> from. PIL im.port Im.ageColor . > > > I_geColor. getcolor ( , red', 'RGВA') (255, О, О, 255) е »> I_gecolor. getcolor ( 'RED', 'aGВA') (255, О, О, 255) »> Im.ageColor.getoolor('Black', 'RGВA') (О, О, О, 255) »> Im.ageColor.getoolor('chooolate', 'RGВA') (210, 105, 30, 255) »> I_geColor.getoolor( 'CornflowerВlue', 'RGВA') (100, 149, 237, 255) Прежде Bcero, необходимо импортировать модуль ImageColor из моду- ля PIL О (обратите внимание  не Pillow; вскоре вы увидите, почему так). Строка с названием цвета, которую вы передаете функции ImageColor. getcolor (), нечувствительна к реrистру. поэтому при передаче как I red' 8, так и 'RED' . мы получаем один и тот же кортеж RGBA. Возможна передача и таких необычных названий цветов, как I chocolate' (шоколад- ный) и 'Cornf1ower Blue I (ва(ильковый). Библиотека PilIow поддержива- ет orpoMHoe количество названий цветов, от I аliсеЫие' до 'whi tesmoke 1. Полный список, включающий более 100 стандартных названий цветов, можно найти в ресурсах, предоставляемых на сайте http://nostarch.com/ automatestuff/. 
494 rnOBO 17 Кор',.. 1l00рд.нtI, . пp..oyrOR'..1l08 Для адресации пикселей изображений используют координаты х и " характеризующие расположение пикселя в изображении соответствен- НО в rоризонтальном и вертикальном направлениях. Началом оmсчета слу- жит пиксель, располаrающийся в верхнем левом уrлу изображения, коор- динаты KOToporo записываются в виде (О, о). Первый нуль представляет »координату, значения которой начинаются с нуля и увеличиваются в направлении слева направо. Второй нуль представляет )"координату. зна- чения которой начинаются с нуля и увеличиваются в направлении сверху вниз. Последнее утверждение стоит повторить: )"координаты увеличи ся в направлении вниз  противоположном тому, которое предполaraется в математике. На рис. 17.1 продемонстрировано, как работает эта система координат. Увеличение х >.. ф s: i  I {27.26} Рис. 17. 1. Координаты х и у для абласт", изображен",. размером 27х26 пикселей, предстовляющеro некое древнее устройство для хранен",я донных Мноrие из функций и методов Pi1low принимают в качестве apryMeHTa кортеж nрямоуzолъ'Н.uка. Это означает, что они ожидают кортеж из четырех целочисленных координат, который представляет прямоyrольную область изображения. Вот что означают эти целые числа в порядке их следования. . Левая: Х"координата левой стороны прямоyrольника. . Верхняя: у-координата верхней стороны прямоуrольника. . Правая: Х"координата внешнеrо пикселя, примыкающеrо к правой стороне прямоуrольника. Это число должно быть больше Toro, кото- рое опреlI,еляет положение левой стороны. . Нижняя: у-координата внешнеrо пикселя, примыкающеrо к нижней стороне I1рямоуrольника. Это число должно быть больше Toro, кото- рое определяет положение верхней стороны. 
Работа с изображениями 495 Обратите внимание на то, что прямоуrольник включает точки, COOТBeT ствующие координатам левая и верхняя, и в то же время, простираясь до координат правая и 1tUЖ1-lЯЯ, не ВlUlючает соответствуюбщие им точки. На- пример, кортеж (3, 1, 9, 6) представляет все пиксели, принадлежащие об- ласти черноrо прямоуrольника, показанноrо на рис. 17.2. о 1 234 5 6 789  EET.r:T1T'1 2    h-,'$,,,,,,,,,.,,, З[";' ,1 ' 4(" ,." 5;; , /.:&.: J."L,;: 6 ' 1, , " ,  f. " !\\" ,1,  '. .(j."""'41i<' '': <'Ji'...,q 7 j ' ..,. t t, ': ,,<:.,, ,<r,l,+ . .А 8  '      9 r'.'1 : ... ' 1 i ," " ......  .:1Ii "':"..; """'<... f. p . Рис. 17.2. Область, представляемая кортежем прямоуrольника (3, 1, 9, 6] МаНИПУlIирование изо6ро_ени.ми с помощыо 6и611иотеки Pillow Теперь, Korдa вам уже известно, как обрабатываются цвета и координаты в Pillow, мы используем эту библиотеку для манипулирования изображения- ми. На рис. 17.3 показано изображение, которое мы будем использовать в этой rлаве в примерах, выполняемых в интерактивной оболочке. И;юбра жение доступно для зarpузки насайте http://nostarch.com/automatestuff/. Поместив файл Zophie.pngB текущий рабочий каталоr, вы сможете заrру- жать изображение 3офи в PythOIl следующим образом. »> from PIL iщроrt Image »> саtIш = Image.open('zophie.tiff') Чтобы заrрузить изображение, вы импортируете модуль Image из би- блиотеки Pillow и вызываете функцию lmage. ореп ( ) , передавая ей имя фай- ла изображения. Затем вы можете сохранить изображение в переменной Catlm. В качестве имени модуля Pi110w используется PIL, чтобы обеспечить 
496 rлава 17 обратную совместимость со старым модулем, который называется "Python Imaging Library", и именно по этой причине вы должны выполнять команду from PI1 import Image, а не просто from pillow import Image. В силу особен ностей способа настройки модуля pil10w создателями Pillow необходимо ис пользовать команду импорта вида from PIL import Image, а не вида import PIL. .. 1 .!  ::::,),", Рис. J 7.3. Моя кошка Зофи. Камера добавила ей лишних J О фунтов (весь- ма заметное прибовление веса для кошки) Если файл изображения находится не в текущем каталоrе, сделайте pa бочим каталоrом папку, в которой содержится файл изображения, вызвав функцию OS. chdi r ( ) . 
Работа с изображениями 497 »> iJIIport 08 >>> 08. chclir ( 'С: \ \папка  с: файпом  иsoCSpuc8ИИR' ) Функция lmage. open () возвращает значение в виде объекта типа Image: именно так Pillow представляет изображения значениями PytlЮIl. Объект Image можно заrpузить из файла изображения (любоrо формата), передав функции lmage. ореп () строку с именем файла. Любые изменения, BHeceH ные в объект lmage, MOryт быть сохранены в файле изображения (также в любом формате) с помощью метода save (). Все операции поворота, изме- нения размеров, обрезки, рисования и дрyrие манипуляции изображением осуществляются посредством вызовов методов для :поro объекта lmage. Чтобы сократить размер примеров, приводимых в этой rлаве, предпcr ложим, что вы уже импортировали модуль Pillow Image и сохранили изcr бражение Зофи в переменной catlm. Прос.ледите за тем, чтобы файл zophie. pngнаходился в текущем рабочем каталоrе, rде ero сможет найти функция lmage . open ( ) . В противном случае нужно будет указать полный абсолютный пyrь к файлу в строковом apryмeнтe, передаваемом методу lmage . open ( ) . ,,,607,, t ",80,. II"ННЫХ 'т"" Объект Image имеет несколько полезных атрибyrов, предоставляющих основную информацию относительно файла изображения, из KOToporo он был заrружен: ширину и высоту изображения. имя файла и rрафический формат (например,]РЕG, GIF или PNG). Например. введите в интерактивной оболочке следующие команды. >>> from. PIL im.port Im.age »> c:atlm. = Im.age.open('zophie.png') »> catlm..size . (816, 1088) . »> width, height = catlm.. size . >>> width 816 .. »> height 1088 »> catlm..filenam.e 'zophie.png' »> catlm.format 'PNG' »> с:аtlm.fоrшat de8c:ription 'Portable network graphics' . >>> catlm..save('zophie.jpg') Создав объект lmage на основе файла Zophiе.рngи сохранив ero в перемен ной catlm, мы видим, что атрибyr size объекта содержит кортеж значений 
498 rЛQВQ 17 ширины и BЫ(OТЫ изображения, выраженных в пикселях О. Эти значения можно назначить переменным width и height ., что обеспечит возмож ность независимоrо доступа к значениям ширины. и высоты. изображе- ния. Атрибут filename содержит имя исходноrо файла. Атрибуты format и fonnat  description  это строки, содержащие описание формата изображе- ния в исходном файле (причем в атрибуте formatdescription содержится несколько более развернутое описание формата). Наконец, вызвав метод save () с передачей ему строки' zophie. jpg' в кa честве параметра, мы сохраняем изображение в новом файле zophie.jpg на жестком диске.. Модуль Pi110w замечает, что расширением имени файла является .jPg, и автоматически сохраняет изображение с использованием формата]РЕG. Теперь на вашем жестком диске имеются два изображения  zорhiе.рngи zophie.jpg. Несмотря на то что оба эти файла основаны на одном и том же изображении, они не идентичны, поскольку имеют различные форматы. Кроме тоro, модуль Pillow предоставляет функцию lmage. new ( ) , KOTcr рая возвращает объект Image аналоrично тому, как это делает функция lmage. open () , однако в данном случае изображение, представляемое объек том Image. new ( ) , будет пустым. ApryмeHTЫ функции Image. new () описаны Ниже. . Строка' RGВA ' , устанавливающая цветовую модель RGBA. (Также воз можна работа с дрyrими цветовыми моделями, которые в данной кни rе не рассматриваются.) . )азмер в виде кортежа из двух значений, npедставляющих ширину и высоту Hoвoro изображения. . Цвет фона, определяющий начальный вид изображения, который эа дается кортежем из четырех целочисленных значений, представляю- щих значение RGBA. В качестве этоro apryмeнтa можно использовать значение, возвращаемое функцией ImageColor. getcolor () . Друrим возможным вариантом является передача функции lmage. пеw () стро-- ки, содержащей стандартное название цвета. Введите в интерактивной оболочке следующие команды в качестве при мера. »> frош PIL iшроrt Iшаqе О >>> im. = Im.aqe.new('RGВA', (100, 200), 'purple') »> im..save('purplelm.age.pnq') . »> illl2 .. Im.aqa.new( 'RGВA', (20, 20)) »> im2.save('transparentlm.aqe.pnq') В этом IIримере мы создаем объект Image для изображения, ширина и высота KOToporo составляют соответственно 100 и 200 пикселей и которое 
Работа с изображениями 499 имеет пурпурный цвет фона О. Затем это изображение сохраняется в фай ле purpfelтage.png. Далее мы вновь вызываем функцию lmage. new () для созда ния дpyroro объекта Image, на этот раз с размерами (20, 20), но без указания цвета фона .. В тех случаях, коrда цвет не задается, по умолчанию исполь зуется невидимый черный цвет, (О, О, О, О), поэтому второе изображение имеет прозрачный фон; это прозрачное прямоуrольное изображение с p мерами 20х20 мы сохраняем в файле transparentlтage.png. 06рез"а .з06ра.ен.. Под обрезиоit изображения понимают выбор прямоуroлыюй области внутри изображения и удаление Bcero, что находится за пределами этоrо прямоуrольника. Метод crop () объекта Image принимает кортеж прямоу. rольника и возвращает объект Image, представляющий обрезанное изо. бражение. Процесс обрезки выполняется не на месте, Т.е. исходный объ- ект Image остается HeтpoHyrbIM, и метод crop () возвращает новый объект Image. Вспомните о том, что кортеж прямоyrольника, в данном случас  области обрезки, включает левый столбец и верхнюю строку пикселей и лишь достиrает правоrо столбца и нижней строки пикселей, но пе ВКJIЮ чает их. Введите в интерактивной оболочке следующие команды. >z> croppedlm - catlm.crop«33S, 345, 565, 560) »> croppedlm.save('cropped.png') Здесь мы создаем новый объек'r Image для обрезанноrо изображения, со- храняем ero в переменной croppedIm, а затем вызываем метод save (), что- бы сохранить обрезанное изображение в файле cropped.png. В рсзультате этоrо па основс исходноrо изображения создается новый файл cwpped.png (рис. 17.4). КО".РО8ан.е . 8"а8«а .з06,..е... 8I1PYl1le .з06ра.ен.. Метод сору () возвращает новый объект lmage с тем же изображением, что и объект Image, для Koтoporo он был вызван. Это может НрИI'одиться 8 тех случаях, коrда необходимо получить измененную версию изображения. но при этом сохранить нетронутым ориrинал. Введите в интерактивной оболочке следующие команды. »> catlm = Image.open('zophie.png') »> catCopylm = catlm.copy() 
500 r лава 17 ,......... 0.0 ." :,;..<-...;,;.. "., .... Рис. 17.4. Новое изображение будет лишь частью исходноrо В переменных catlm и catCopylm содержатся два независимых объекта Image, в каждом из которых хранится ОДНо и то же изображение. Теперь. коrда в переменной саtСоруlmхранится копия ИСХОДноrо объекта lmage, вы можете изменить ее по своему усмотрению и сохранить в ДРУI'ОМ файле, оставив файл zорhiе.рngнетроиутым. В качестве примера изменим изобра жсние, хранящееся в переменной catCopylm, с ПОМОЩI)Ю метода paste (). Будучи вызванным для объекта Image, метод paste () помещает поверх Hero друrое изображение. Продолжим выполнение примера в интерактив- ной оболочке, вставив поверх изображения, хранящеrося в переменной catCopylm, изображение с меньшими размерами, »> faoeIm . саtIm.сrор«ЗЗ5, 345, 565, 560)) »> faoeIm.size (230, 215) »> catCopyIm.paste(faoeIm, (О, О)) »> catCopyIm.paste(faoeIm, (400, 500)) »> catCcpyIm. save ('pasted.pnq') 
Работа с изображениями 501 Сначала мы передаем методу crop () кортеж прямоуrольника, задающий область изображения zophie. png, соответствующую rолове Зофи. В резуль- тате создается 06-ьект Image, представляющий обрезанное изображение с размерами 230х215, K<Yropoe сохраняется в переменной facelm. Теперь мы можем поместить это изображение поверх изображения catCopylm. Метод paste () принимает два apryMeHTa: объект lmage вставляемоrо изображения и кортеж, определяющий координаты х и у точки на основном изображе- нии, в которую должен быть помещен верхний левый yrол вставляемоrо изображения. В данном примере метод paste () вызывается для переменной саtСоруlmдважды: первый раз с передачей кортежа (О, О) и второй раз  с передачей кортежа (400, 500). в результате изображение facelm помещает- ся поверх изображения саtСоруlmдважды. В первом случае верхний левый yroJI изображения fасеlmпомещается в точку (О, О) изображения catCopyIm, а во втором  в точку (400, 500). Наконец, мы сохраняем измененное изcr бражение в файле pasted.png. Изображение, сохраненное в файле pasted.png, выrлядит так, как ноказано на рис.17.5. , 't Рис. 17.5. Мордочка Зофи была скопирована дваЖДЫ 
502 r лава 17 Прu.м.ечаllие Пусть 'Назва'Ния .методов сору() upaste () .модуля Pillow 'Не вводят вас в заблужде- 'Ние: их работа ИИl€Оu.м образом ие связа'На с буфером об.мта вашеео l€омnьютejю. Обратите внимание на То, что метод paste () изменяет объект Image на MeCTt', а не возвращает объект Image со вставленным изображением. Если вы хотите вызвать метод paste () и при этом сохранить нетронутой версию исходноrо изображения, то сначала создайте ero копию, а затем вызовите метод paste () ДЛЯ этой копии. Предположим, вы хотите покрыть изображениями rоловы 3офи ВСЮ об.- ласть основною изображения (рис. 17.6). Для создания этою эффекта нам хватит пары циклов. Продолжите выполнение примера в интерактивной оболочке, введя следующие команды. »> »> О >>> . >>> . catlmWidth, catlmНeight · catlm.size faceImWidth, facelmHeight · facelm.size catCopyТwo · catlm.ccpy() for 1eft in range(O, catlmWidth, facelmWidth): for top in range(O, ааtlmИеight, facelmНeight): print(left, top) catCopyТwo.paste(facelm, (left, top» о о о 215 О 430 О 645 О 860 О 1075 230 О 230 215 пропущенный KOД 690 860 690 1075 >>> catCopyТwo. save (' tiled.png') Здесь значения ширины и высоты изображения саtImсохраняются в пе- ременных catlmWidth и catlmНeight. В строке. мы создаем копию саtImи ccr храняем ее вперсменной catCopyТwo. Теперь, Korдa у нас есть копия, на кото- рую можно поместить дрyrие изображения, мы орraнизуем цикл для вставки изображения facelm поверх изображения catCopyТwo. Значение переменной left при запуске внешнеrо цикла for равно О и на каждой итерации ЭТОI'О цикла получает приращение, равное facelmWidth (230) .. Значение перемен- ной top при запуске BнyтpeHHero цикла равно О и на каждой итерации это- 1"0 цима получает приращение, равное faceImНeight (215) .. Получаемые в этих вложенных циклах значения переменных left и top обеспечивают 
Работа с изображениями 503 покрьrrие всею изображения catCopyТwo изображениями facelm (рис. 17.6). Чтобы можно было проследить за тем, как работают оба ЦИlUlа, мы выводим значения переменных left и top. По завершении вставки всех изображе- ний мы сохраняем измененное изображение catCopyТwo в файле tiled.png. Рис. 17.6. Использование вложенных циклов н метода pas te () Д/lЯ создания дубликатов изображения rono8bl кошки Вставка прозрачных пиксепей Обычно прозрачные пиксели вставляются в виде белых пиксепей, Если в изобра- жении, которое SbI хотите вставить, имеются прозрачные пиксепи, то передайте мето-- ду pas te () объект lmage в качестве TpeTberO apryMema, предотаращающеro встав- ку прямоyrопьника, закрашенноrо смошным цветом. Этот третий apryMeHT является "маской" объекта lmage. Маска  это объект lmage, в котором учитывается лишь значение алфканала, Torдa как красная, зеленая и синяя составляющие иrнориру- ются, Маска сообщает методу paste ( ), кокие пиксели следует копировать, а какие оставить прозрачными. Подробное рассмотрение масок выходит зо рамки данной книrи, но если вы хотите вставить изображение, содержащее прозрачные пиксели, то передайте объект lmage методу paste () вновь в качестве TpeTbero apryMeHTa. 
5М rЛQва 17 11змененне Р1l3_РО8 нз06,1I.'НН. Метод resize () вызывается для объекта lmage и возвращает новый объ- ект Image с заданнЫМИ значениями ширины и высоты. Он принимает ар- ryMeHT в виде кортежа из двух целочислеНIIЫХ значений, IIредставляющих новые значения ширины и высоты возвращаемоro объекта. Введите в инте- рактивной оболочке следующие команды. . >>> width, height - catIID.size . »> quartersizedIID - catIID.resize( (int(width I 2),  int (height I 2») »> quartersizedIID.save('quart8r8ized.png') . >>> svelteIID = catIID.resize«width, height + 300» >>> svel teIJa. ааУе ( I svel t8. png' ) Зде<ъ два значения, образующие кортеж catlm. size, присваиваются п ременным width и height О. Использование отдельных переменных width и height вместо элементов catlm. size [О] и catlm. size [1] повышает удобо- читаемость остальной части кода. В первом вызове метода resize () ему передаются значения int (width / 2) и int (height /2) в качестве новых значений ширины и высоты О, поэто- му объект Image, возвращенный методом resize () , будет иметь половинные значения ширины и высоты исходноrо изображения, Т.е. в целом будет в четыре раза меньше Hel'O. Метод resize () принимает в качестве apl"}'MellTa лишь кортеж целочисленных значений, и именно поэтому обе операции деления на 2 должны быть обернyrы в вызовы in t ( ) . В данном случае ширина и высота изменяются в одинаковой пропорции. Однако новые значения ширины и высоты, передаваемые методу resize (), не обязаны сохранять исходные пропорции изображения. Переменная svel telm содержит объект Image, ширина KOToporo совпадает с первона- чальной, но высота увеличена на 300 пикселей ., что делает Зофи более стройной. Заметьте, что метод resize () не изменяет объект Image на месте, а воз- вращает новый объект lmage. nОIОрОТ 11 З"IlIlIl.НО. ото6р".ен., .з06р".еннii Изображения можно поворачивать с помощью метода rotate () , кото- рый возвращает новый объект Image nOBepнyroro изображения, оставляя исходный объект Iшаgе неизменным. Apl)'MeHToM метода rotate () является целое или вещественное число, представляющее величину yrла поворота в rpaдycax против часовой стрелки. Введите в интерактивной оболочке сле- дующие команДЫ. 
Ра60та с изо6ражеНИRМИ 505 >>> catIID.rotate(90) .save('rotated90.png') »> catIID.rotate(lBO) .save('rotated1BO.png') »> catIID.rotate(270) .save('rotated270.png') Обратите внимание на то, как здесь используется возможность образо- вания цепочек вызовов методов, коrда метод save () вызывается непосред- ственно для объекта lmage, возвращенноrо методом rotate () . Вызовы мето- дов rota te () и save () создают новый объект lmage, который представляет изображение, повернyrое на 90. против часовой стрелки, и сохраняют это изображение в файле rotated90.png. Во второй и третьей строках кода дела- ется то же самое, но с поворотом исходноrо изображения на 180. и 270. со- ответственно. Результат представлен на рис. 17.7. I I ! I .., Рис. 17.7. Исходное изображение (слева) и изображsни", ПО8врнутые на 900, 1800 и 2700 Заметьте, что при повороте на 90. или 270. ширина и высота изображе- ния изменяются. При повороте на друrие yrлы подцерживаются исходные размеры изображения. В Windows для заполнения образуемых зазоров ис- пользуется черный фон. В 05 Х для этоrо используются прозрачные пик- сели. В методе rotate () предусмотрен необязательный именованный apryMeHT expand, установка значения KOToporo в Trиe приводит к увеличению разме- ров изображения таким образом, чтобы оно вписалось в оrраничивающий прямоуrольник HOBoro, повернутоrо изображения. Например, введите в интерактивной оболочке следующие команды. »> catIID. rctate (б) .sаve('rоtatedб.рng') >>> catIID. rotate (6, expand"True). еауе ( 'rоtаtedбеxpanded.рng' ) В первой строке кода изображение поворачивается на 6. и сохраняется в файле rotate6.png (рис. 17.8, слева). Во второй строке изображение повора- чивается на 6. при значении apryмeHTa expand, равном Trиe, и сохраняется в файле rotаtе6ехраnded.рng(рис. 17.8, справа). 
506 rлава 17 " .. \ . !I '" . ,.:.. ." ....... . Рис. 17.8. Изображение, повернутое но 6 о 8 обычном режиме (слева} и в режиме expaпdTrue (СПРО8а) у вас также есть возможность получить "зеркально отображенное" изоб- ражение с помощью метода transpose () . Методу transpose () должен пере- даваться apryMeHT Image. FLIP  LEFТ RIGHT или Image. FLIP  ТОР BOTTOM. Введи те в интерактивной оболочке t.ледующие команды. »> catIID.tranвpose(Image.FLIP иFТ RIGHT) .  еауе( 'horizontal flip.png')   »> catIID.tranвpose(Image.FLIP тор воттом) .  еаУе( 'verticalflip.png')   Как и метод rotate ( ) , метод traпspose () создает новый объект Image. В этом коде данному методу персдается aplJ'MeHT lmage. FLIP  LEFT  RIGHT, В результате чеrо изображение отражается 110 rоризонтали, а затем сохраня ется с lIОМОЩЬЮ метода save () В файле horizontalJliP.png. Для отображсния по вертикали мы передаем методу transpose () apryMellT Image. FLIP  ТОР  ВОТТОМ, а затем сохраняем ре:Jультирующее изображение в файле verticalJliP. png. Полученныс в результате этих преобразований изображения предстan лены на рис. 17.9. 
Работа с изображениями 587 Рис. 17. 9. Исходное изображsниs (слеsа), результат rоризонтальноrо отражения (в центре} и результат вертикальноrо отражения (справа} Измененне OfAeR6H6IX nH'elle' Для получения или установки цвета отдельнOI'О пикселя используются методы getpixel () и pиtpixel (). Оба метода принимают в качестве apryMeH- та кортеж, представляющий координаты х и у I1икселя. Кроме Toro, метод pU1:pixel () принимает дополнительный apryмeнт в виде кортежа, опредс-- ляющсrо цвет пикселя. Этим apryMeHToM может быть либо RGВА-кортеж из трех целых чиеел, либо RGВ-кортеж, включающий три целых числа. Введи- те в интерактивной оболочке следующие команды. . »> . »> (О, . »> . »> . »> . im. Image.nev('RG8A', (100, 100» im.getpixel( (О, О» О, О, О) for х in range(100) : for у in range(50): im.putpixel«x, у), (210, 210, 210» from PIL iшport ImageColor for х in range(100) : for у in range(50, 100): im.putpixel «х, у), ImageColor. getcolor ( 'darkgray', 'RG8A'» >>> im.getpixel«O, О» (210, 210, 210, 255) »> im.getpixel«O, 50» (169, 169, 169, 255) >>> iш. еаУе ('putpixel.png') В строке. мы создаем новое изображение в виде прозрачноrо квадра- та с размерами 100XIOO. Вызов метода getpixel () для одной из точек это- rо изображения возвращает кортеж (о, о, О, о), поскольку изображение 
501 rлава 17 прозрачно .. Для назначения цвета пикселям этоrо изображения мы мо- жем использовать R1l0женные циклы for, перебирая все пиксели в верхней половине изображения. и вызывая для каждоrо из них метод putpixel (). .. в данном случае методу pиtpixel () передается RG&-кортеж (210, 210, 21 О) , которому соответствует светло-серый цвет. Предположим, мы хотим закрасить нижнюю половину изображения темно-серым цветом, но не знаем, какие значения RGВкортежа ему c ветствуют. Метод pиtpixel () не принимает стандартные названия цветов наподобие' darkgray', поэтому мы получаем кортеж цветовых значений для цвета 'darkgray' с помощью функции lmageColor.getco1or (). ОрraнИ3}'ЙТе цикл по пикселям нижней половины изображения. с передачей методу pиtpixel () значения, возвращаемоrо функцией lmageColor. getcolor () ., и вы получите изображение, верхняя половина Koтoporo закрашена светло- серым цветом, а нижняя  темно-серым (рис. 17.10). Для проверки TOro, что цвет любоrо заданноrо пикселя соответствует ожидаемому, можно вызвать метод getpixel ( ) . Наконец сохраните изображение в файле putPixel.png. Разумеется, рисовать изображение по одному пикселю за один раз не очень удобно. Если вам надо рисовать фиrypы, используйте функции ImageDraw, о которых речь пойдет далее. ... .. . .... ......,_... . . Рис. 17.10. Изображение putPixe/.png Проект: добаВllение lIоrотипа Предположим, вам предстоит рутинная работа по изменению разме ров тысяч изображений и добавления в уrлу каждоrо из них неБОЛЬШОI'О лоrотипа в ВИде водяноrо знака. Выполнение такой задачи с помощью про стых rрафических проrрамм наподобие Paintbrusll или Paint длилось бы целую вечность. В более сложных rрафических приложениях, такиХ как Photoshop, существует возможность пакетной обработки, но такое про rpaMMHoe обеспечение стоит не одну сотню долларов. 
Работа с изображениями 5" На рис. 17.11 показан лоrотип, который мы будем добавлять в нижний правый yrол каждоrо изображения. Этот лоrотип представляет собой сти лизованный профиль кошки с белым контуром и прозрачной остальной частью изображения. Рис. J 7. J '. Лоrотип, КОТОрЫЙ будет добавляться 8 изображение Вот что должна делать данная ПРOl'рамма при высокоуровневом рассмо- трении: . заrружать изображение ЛОI'отипа; . обходить в цикле все файлы с расширениями .рпgи .jpgB рабочем кa талоrе; . проверять, не превышает ли ширина или высота изображения 300 пикселей; . в случае указанноrо превышения уменьшать ширину или высоту (в за- висимости от Toro, что больше) до 300 ПИКселей, уменьшая дрyrой размер в той же пропорции; . вставлять лоrотип в yrол изображения; . сохранять измененные изображения в друrой папке. Это означает, что код должен выполнять следующие операции: . открывать файл catlogo.pпgB качестве объекта Image; . проходить в цикле по всем строкам, возвращаемым функцией os.listdir('. '); . получать ширину и высоту изображения из атрибута size; . рассчитывать новые значения ширины и высоты изображения; . вызывать метод resize () для изменения размеров изображения; . вызывать метод paste () для вставки лоrотипа; . вызывать метод save () для сохранения изменений, используя имя ис ходноrо файла. 
510 r пава 1 7 Ша, '. Orp..r.e .з06"...... .0",I1I.a Для работы с этим проектом откройте новое окно в файловом редакто- ре, введите следующий код и сохраните ero в файле resizeAndAddLogo.frY. 11! руthопЗ 11 re5izeAndAddLogo.py  Изменяет размеры всех изображений 11 в текущем рабочем каталое таким образом, чтобы они 11 вписывались в квадрат с размерами ЗООхЗОО, и добавляет 11 изображение catlogo.png в eo нижний правый уол. import 05 from PIL import Image о SQUAREJITSIZE = 300 8 LOGO FILENAМE = 'catlogo.png' . logoIm = Image.open(LOGOFILENAМE) О logoWidth, logoHeight = 10goIm.size # TODO: Оранизовать цикл по всем файлам в текущем # рабочем каталое. # TODO: Проверить, нуждается ли изображение в # изменении размеров. 11 TODO: Рассчитать необходимые новые значения ширины и высоты. 11 TODO: Изменить размеры изображения. » TODO: Добавить лоrотип. 11 TODO: Сохранить изменения. Эадав в начале проrраммызначения констант SQUARE FIT  SIZE . и LOGO  FI LENAМE ., мы упростили внесение возможных изменений в проrрамму в будущем. Предположим, вы захотите использовать в качecrвe лоrотипа дpy rой рисунок или оrpаничить размеры лоrотипа не 300 пикселями, а дрyrой величиной. Расположив определения этих констант в самом начале про-- rраммы, вы должны будете внести изменения, если это потребуется, толь ко в одном месте проrраммы. (Возможен и друrой вариант, коrда значения этих констант предоставляются в виде apryмeHToB командной строки при вызове проrраммы.) Без использования этих констант вам пришлось бы просматривать весь код в поиске всех вхождений значений 300 и 'catlogo. png' и заменять их вручную для каждоrо HOBoro проекта. Короче rоворя, константы делают проrрамму более универсальной. Объект lmage лоrотипа возвращается вызовом функции lmage. open () .. Для улучшения удобочитаемости кода проrраммы значения, содержащиеся 
Работа с изображениями 511 в атрибуте 10goIm. 8ize, IJрисваиваютея отдельным переменным 10goWidth и 10goHeight .. По состоянию на данный момент каркас остальной части проrраммы представлен комментариями TODO. Ш", 2. Ц8. по "е" фа;;."" 8 OTp"""" 8Jо6ро.еи"." Теперь мы должны орraнизовать в текущем рабочем каталоrе 1I0следова тельный поиск всех .png- и .jрg-файлов. При этом следует учесть, что в до- бавлении изображения лоrотипа к самому лоrотипу нет никакой необходи мости, и поэтому проrрамма должна пропускать любое изображение с тем же именем файла, которое содержится в константе LOGOFILENAМE. Добавьте в I1porpaмMY следующий код. #! руthопЗ # resizeAndAddLogo.py  Изменяет размеры всех изображений # в текущем рабочем каталоrе таким образом, чтобы они # вписывались в квадрат с размерами ЗООхЗОО, и добавляет # изображение catlogo.png в ero НИЖНий правый уrол. import 05 from PIL import Image пропущенный KOД os.makedirs('withLogo', exist ok=True) # Цим по всем файлам в текущем рабочем каТ&пОIЧI. О for filename in o8.1i8tdir(' .'): 8 if not (filename.encl8with(' .png') or \ filename. end8wi th ( , . jpg' » or filenu.e .. LOGO FILENAМE: е continue * nPОПУС'l'ИТЬ фaйJw, не R8J!raoщи8СSll фамами * изображений, и файл caмoro J10rО'1'иnа о im = Image.open(filenu.e) width, height = im.8ize пропущенный KOД Прежде Bcero, мы создаем с помощью вызова 08 .makedirs () отдельную папку wi thLogo, предназначенную для хранения версий изображений с ло rотипами, чтобы не затирать исходные файлы. Указав именованный apry мент existok=True, можно избежать возбуждения исключений в методе 08 .makedirs () в том случае, если напка wi.thLogo уже существует. В процессе выполнения ЦИКЛа по всем файлам текущеrо рабочеrо KaTaJIOI'a с ИСIIOЛIr зованием вызова 08 .li8tdir (1. ') . длинная инструкция if . проверяет расширение имени каждОI'О файла. Если файл имеет расширение .рngили .jpg или же если это файл caMoro изображения лоrотипа, то цикл должен 
512 rлава 17 пропустить ero и использовать инcrрукцию continue . для перехо,ца к сле.- дующему файлу. Если же имя файла заканчивается расширением .png или .jрg(и это не файл лоrотипа), то можно открыть ero в виде объекта lmage . и задать ширину и высоту изображения. Ш",3. Из"енеН8е Р".'РО8 8з06р".еН8. Проrрамма должна изменять размеры изображений лишь в том случае, если ширина или высота превышает значение, определяемое константой SQUAREFITSIZE (в данном случае  300 пикселей), поэтому необходимый для этоrо код следует поместить в инструкцию if, проверяющую значения переменных width и height. Добавьте в проrpамму следующий код. #! python3 # re8izeAndAddLogo.py  Изменяет размеры всех изображений # в текущем рабочем каталоrе таким образом, чтобы они # вnисывались в квадрат с размерами ЗООхЗОО, и добавляет # изображение catlogo.png в ero нижний правый уrол. import 08 from PIL import Image пропущенный 1(OД # Проаерка необходимости и8N8Н8ИИR ра8мероа изобpa.eииR. if width > SgUARE FIT SIZE and height > SQUARE FIT SIZE: * Расч8'1' неосiходИМых HOIDIX 8иач8ИИЙ lIИpИИЫ  и выco'1'ы. if width > height: . height .. int «SQUARE FIT SIZE I width) * height) width .. SQUAREFITsizE  .18е: . width .. int( (SQUAREFITSIZE I height) * width) height .. SQUAREFITSIZE # Иsменеиие раекеров изобрa.eииsl. print ( I ИЗменение pasмepoB изoCSpusНИR %8... ' % (filenaIIEI» . im .. im.r.8ize«width, height» пропущенный KOД Если размеры изображения дейcrвительно надо изменить, то в этом слу- чае следует определить, какой именно из размеров превышает допустимый предел  ширина или высота. Если ширина изображения больше ero вы- соты, то последнюю следует уменьшить в той же пропорции, что и шири- ну .. Величина коэффициента пропорциональности находится делением значения SQUARE  FIT  SIZE на текущее значение ширины. Тоrда новое значе-- ние высоты будет равно ее текущему значению, умноженному на найденное 
Работа с изображениями 513 значение коэффициента пропорциональности. Поскольку результатом оп рации деления является вещественное число, а метод resize () требует за дания целочисленных размеров, не забудьте преобразовать полученный ре-- зультат в целое число с помощью функции int () . Наконец, новое значение ширины просто устанавливается равным SQUAREJITSIZE. Случай, коrда высота изображения больше ширины или равна ей (оба с.лучая обрабатываются с помощью инструкции else), обрабатывается с ис пользованием такой же расчетной схемы, за и«.:ключением Toro, что пере- менные height и width меняются ролями .. Установив впеременных width и height новые значения размеров, пер дайте их методу resize () и сохраните возвращенный объект Image в пе- ременной im .. Шоr 4. /lo60оен.е lIoror..a . ,охранен.е .эменен.й Независимо от TOro, изменялись ли размеры изображения, лоrотип дол жен помещаться в нижний правый уrол изображения. В какую именно по- зицию он должен вставляться, определяется размерами как изображения, так и caMoro лоrотипа. На рис. 17.12 ноказано, как рассчитать позицию вставки. Левая координата позиции для в(:тавки изображения должна быть равна разности между шириной изображения и шириной лоroтипа, тоrда как верхняя координата должна быть равна разности между высотой изо- бражения и высотой лоrотипа. Ширина изображения I Изображение :.i!;T.t.<:!--Ч .'1пr\) .;: с, t : т r .    : 11 .: !: J :\ '): .., ., :: 1 IJ., 10  a:I Рис. 17. 12. Координаты левоrо и sepXHero "роев лоrотипа при ero помещении в нижний про вый уrол зо6ражения определяются соответственно разностью значенИИ ширины изображения и ширины лоrотипа и высоты изобраЖ8НИЯ и высоты лоrотипа После Toro как ваш код вставит лоrОТИП в изображение, он должен co хранить измененный объект Irnage. Добавьте В проrpамму следующий код. #! руthопЗ # resizeAndAddLogo.py  Изменяет размеры всех изображений # в текущем рабочем каталоrе таким образом, чтобы они # влисывались в квадрат с размерами ЗООхЗОО, и добавляет 
514 rлава 17 # изображение catlogo.png в ero нижний правый уrол. import 08 from PIL import Image пропущенНЬlЙ к:oд # Проверка необходимости изменения размеров изображения. пропущенный к:oд # Добаanение nоroтиnа. Oprint( 'Добunение nоrО'1'ИПа в изображение %8...' % (filename» 8ilD.pa8t8(l0q011D, (width  logoWidth, height  log0Иеight),  10g011D) # Сохранение изменений. 8 ilD. 8ave (08 .path. join ('withLoqo', filenaae» Новый код выводит сообщение, которое извещает ПОЛI.зователя о д<r бавлении лоrотипа О, помещает изображение logoIm в позицию с рас- считанными координатами О и сохраняет изменения в файле в каталorе wi thLogo .. Если вы запустите проrpамму, коrда единственным изображени- ем в рабочем каталоre будет zophie. png, то получите следующий вывод. Изменение размеров изображения zophie.png... Добавление лоrотипа в изображение zophie.png... Изображение zophie. png будет преобразовано в изображение с размера- ми 225х300 пикселей, представленное на рис. 17.13. Не :щбывайте о том, что метод pa8te () не вставит прозрачные пиксели в результирующее из<r бражение, если вы не передадите ему дополнительно объект logoIm в каче- стве третьею apryмeHTa. Данная проrрамма способна" лоrотипизировать" сотни изображений и соответствующим образом изменить их размеры бук- вально за пару минyr. Иllе. OrHO'HreR6HO '03I1'Н". 'H'Ror."H6IX nporp,"" Возможность композиции и изменения размеров изображений в режиме пакетной обработки может быть полезной во мноrих приложениях. В част- ности, это позволяет делать следующее: . добавлять текст или URL-aдpeca веб-сайтов в изображения; . добавлять временные метки в изображения; . копировать или перемещать изображения в различные папки, исходя из размеров файлов; . добавлять водяные знаки, как правило, прозрачные, в изображения с целью предотвращения копирования последних. 
Работа с изображениями 515 r'";,: lШ I . r ... .... . ,.v.  ,. t Рис. 17.1 З. Изображение zophie.png с измененными размерами и добавленным лоrотнпом (слева). Если вы забудете о третьем apryMeHTe, то проэрочные пиксели лоrотипа скопиру- ются в виде сплошных белых пикселей (справа}, Рисование изображений в тех случаях, КOJ'да RОЗI.lИкает необходимость в рисовании отрезков, прямоyrольников, окружностей и друrих простых фиryр, используйте мо- l\УЛЬ ImageDraw библиотеки Pillow. Введите в ИНТtрактивной оболочке сле- дующий код. »> from PIL import Image, ImageDraw »> iш. Imaqe.new('RGВA', (200, 200), 'white') >>> draw · ImageDraw . Draw (i.JD) Прежде Bcero мы импортируем модули Irnage и ImageDraw. Затем мы соз- lщем новое изображение (в данном случае  квадрат белоrо цвета с разме- рами 200х200 IJикселей) и сохраняем объект Image в переменной im. Далее этот объект Image передается функции ImageDraw. Draw () для получения объекта ImageDraw. Этот объект имеет несколько методов, предназначен- ных для рисования ФИ1'Ур и текста. Сохраните объект ImageDraw в пере- менной draw, чтобы ero можно было леl'КО использовать в последующих при мерах.. 
516 r лава 17 '.'О'IIН.. фн"р Описанные ниже методы объекта ImageDraw предназначены для рисова- ния различноrо рода фиryp. Параметры fill (заливка) и oиtline (обводка) этих методов являются необязательными, и по умолчанию для них устанав- ливается белый цвет. ТОIIIИ Метод point (ху, fil1) прорисовывает отдельные пиксели. ApryмeHT ху представляет список точек, которые вы хотите нарисовать. Этим списком может быть список кортежей координат х и у, такой как [(х, у), (х, у), . . . ] , или же список ;)со и )""координат без кортежей, например [х 1, уl, х2, у2, ...]. Арryмеит f i 11 определяет цвет точек и может задаваться либо RGВА-кортежем, либо строкой с названием цвета, например' red' . Этот apryмeHT является необязательным. ОтР'.IИ Метод line (ху, fill, width) предназначен для рисования отрезков пря- мых линий или последовательностей таких отрезков. xy это либо список кортежей, такой как [ (х, у), (х, У), ...], либо Список целых чисел, напри- мер [х 1, у1, х2, у2, ...]. Каждая точка является одной из соединительных точек рисуемых отрезков. Необязательный apryмeHT fill определяет цвет линий и задается в виде RGВА-кортежа или названия цвета. Необязатель- ный aprYMeHT width определяет толщину линий и по умолчанию имеет зна- чение 1, если не определено иначе. ПрlмоуrОnЬННIИ Метод rectangle (ху, fill, out1iпe) предназначен для рисования пря- моуrольников. ApryMeHT ху  это кортеж прямоуrольника вида (1eft, top, right, bottoт). Значения lеН и tоропределяют;)Со и у-координаты BepXHero левоrо yrJJa ПрЯМОУI'ольника, тоrда как значения r i gh t и bot toт  координа- ты нижнеrо правurо уrла. НеобязатеЛЫIЫЙ apryMeHT fill определяет цвет заливки, анеобязательный apryMeHT out1iпe  цвет обводки прямоуrоль- ника. Эnnиnс.. Метод ellipse (ху, Пl1, out1iпe) предназначен для рисования эллип- сов. В случае совпадения ширины и высоты эллипса рисуется окружность. ApryмeHT xy это кортеж прямоуrольника (left, top, right, bottom), ко' торый представляет прямоуrольник, описанный BOKpyr эллипса. Необяза- тельный apryMeHT fi 11 определяет цвет заливки, а необязатеЛI.ный apry' мент outliпe  цвет обводки эллипса. 
Работа с изображениями 517 МиоrоуrОnЬИlllt1l Метод polygon (ху, fill, оut1iпе) предназначен для ри<:ования мноro уrольников с любым числом сторон. ApryMeHT ху  это список кортежей, такой как [ (х, у), (х, у), ...], или целых чисел, например [х1, у1, х2, у2, . . . ] , представляющий соединительные точки сторон мноrоyrольника. По- следняя пара координат будут автоматически соединяться с первой парой. Необязательный apryMeHT fill определяет цвет заливки, анеобязательный параметр ои t1 iпе  цвет обводки мноrоyrольника. Прнмер рнсованн, фиryр Введите в интерактивной оболочке следующий код. »> froa PIL import Iaage, IaageDraw »> 1ш. Iaage.new('RGВA', (200, 200), 'white') »> draw - ImageDraw.Draw(im) О >>> draw.line([(O, О), (199, О), (199,199), (0,199),  (О, О)], fill:z 'black') 8 »> draw.rectangle«20, ЗО, БО, БО), fill-'blue') е >>> draw.ellip8e( (120, ЗО, 160, БО), fill.'red') О »> draw.polygon«(57, 87), (79,62), (94,85),  (120, 90), (103, 11З», fill='brown') . »> for i in range(100, 200, 10): draw.line( [(i, О), (200, i  100)], fill=, 'green ) »> im.8ave('drawing.png') Создав объект lmage для белоrо квадрата с размерами 200х200 пикселей, передав cro методу lmageDraw. Draw () для получения объекта lmageDraw и со- хранив этот объект в переменной draw, мы можем вызывать для нее мето- ды, обеспечивающие рисование фиryp. В данном случае мы создаем тонкую черную обводку вдоль краев изображения О, rолубой прямоyrольник, ко- ординатами BepxHero левоrо и нижнеrо правOI'О yrлов KOToporo являют- ся соответственно (20, 30) и (60, 60) .. красный эллипс, определяемый описанным прямоyrольником с уrлами (120, 30) и (160, 60) ., коричневый пятиуrольник . и узор, нарисованный зелеными прямолинейными отрез" ками ( помощью цикла for .. Результирующее изображение, сохраненное в файле drawing.png, показано на рис. 17.14. Существуют и друrие методы объекта ImageDraw, предназначенные для рисования фиryp. Полная документация 110 ним доступна 110 адресу h t tp : / / pillow.readthedocs.org/en/latest/reference/lmageDraw.html. 
518 r лава 17  , I  11 _\1  Рис. 17. 14. Результирующее нзображение drawiпg.png '.".".8е Te"" Кроме описанных методов, объект ImageDraw имеет также метод text () , предназначенный для рисования текста. Этот метод принимает четыре ар- ryмeHTa: ху, text, fil1 и foпt. е ApryMeHT xy это кортеж из двух целых чисел, определяющий коор- динаты верхнеro левоrо yrла TeK<'Toвoro окна. . ApryмeHT tex t  это текстовая строка, которую вы хотите записать. . Необязательный apryмeHT fill определяет цвет текста. . Необязательный apryмeHT Еоп t  это объект ImageFont, испольауемый для задания шрифта и размера текста. Этот объект описан более под робно в с.ледующем разделе. Поскольку во мноrих случаях трудно заранее определить, каким будет размер TeKcToBoro блока при конкретном значении шрифта, в модуле ImageDraw предусмотрен метод textsize (). Ero первый apryMeHT  тексто- вая строка, размер которой вы хотите измерить, а второй  необязатель- ный объект lmageFont. Метод textsize () возвращает кортеж из двух целых чисел, представляющих ширину и высоту, которые будет иметь блок текста при cro прорисовке с заданным размером шрифта. Вы можете использо- вать эти значения для расчета точных размеров текста, который хотите по- местить в изображение. Первые три apryмeнтa метода text () не нуждаются в отдельных пояс нениях. Прежде чем использовать метод text () для рисования текста на 
Работа с изображениями 519 изображении, рассмотрим ero необязательный четвертый apryMeHT  объ ект lmageFont. И метод text () , и метод textsize () l1ринимают необязатеJlЬНЫЙ объект ImageFont в качестве cBoero последнеrо apryмeHTa. Чтобы создать один из этих объектов, необходимо предварительно выполнить следующий код: »> from PIL import ImageFont Теперь, коrда вы ИМI10РТИРОвали модуль ImageFont библиотеки Pillow, можно вызвать функцию lmageFont. trиetype (), которая принимает два ар- ryMeHTa. Первый apryMeHT  это строка с именем файла шрифта TrиeType, существующеrо на вашем жестком диске. Файлы шрифтов TrueType имеют расширение .ttfи обычно располаrаются в следующих папках: . WiIldows: C:\Windows\fOnts; . 05 Х: /Library/Fonts and /Systeт/Library/Fonts; . LiIlUX: /usr/share/fonts/trиetype. Вы не должны фактически вводить эти пути в каче(:тве части строки, содержащей имя файла шрифта TrueType, поскольку РytlЮIl известны эти каталоrИ, в которых он выполнит автоматический поиск шрифтов. Одна- ко, если РytlЮIl не удастся найти указанный шрифr, он выведет сообщение об ошибке. Вторым арI)'Ментом функции lmageFont. trиetype () является целое чис ло, определяющее размер шрифта в nунктах (а не, скажем, в пикселях). Имейте в виду, что по умолчанию Pi110w создает изображения в формате PNG с разрешением 72 пикселя на дюйм, а пункт  это 1/72 дюйма. Введите в интерактивной оболочке следующий код, заменив константу FONT FOLDER фактическим именем папки, которая используется вашей опе-- рационной системой. >>> from PIL import Image, ImageDra.., ImageFont >>> import 0& . >>> im .. Image.new('RGВA', (200, 200), '..hite') . >>> dra.. .. ImageDra... Dra.. (im) . >>> dra...text«20, 150), 'Hello', fill-'purple') »> font&Folder .. 'FQNТ FOLDER' # e.g. '/Library/Font&' О »> arialFont .. ImageFont.  truetype(c&.path.join(font&Folder, 'arial.ttf'), 32) . »> draw.text«100, 150), 'Привет', fill='gray', font-аriаlFоnt) »> im.&ave('t8Xt.png') Импортировав модули Image, ImageDraw, lmageFont и 0$, мы создаем сна- чала объект lmage для HOBoro изображения в виде квадрата бслоrо цвета с размерами 200х200 пикселей ., а затем объект ImageDraw на базе объекта 
520 rлава 17 Image .. Далее мы ИСПОЛЬ.1уем метод text () для прорисовки текста Hello nyp- пурноrо цвета с. началом в позиции с координатами (20, 150) .. В данном елучае мы не передаем методу text () неоБЯ3ательный четвертый apryмeнт, поэтому rарнитура и размер шрифrа выбираются по умолчанию. Чтобы задать rарнитуру и размер шрифrа, мы предварительно сохраня- ем имя папки (наподобие jLibтaryjFonts) в перемепной fontsFolder. Затем мы вызываем функцию ImageFont. truetype () , перtдав ей имя .tffфайла же-- лаеМоrо шрифта и целочисленный apryмeнт, определяющий размер шриф- та .. Объект Font, возвращенный вызовом ImageFont. trиetype (), сохраня- ется в переменной aria1Font, и эта переменная передается методу text () в ero последнем именованном apryмeнтe. Вызов метода text () . прорисовы вает текст Привет ceporo цвета с началом в позиции с координатами (100, 150) и с использованием шрифrаArial размером 32 пункта. Вид реЗУJJьтирующеrо изображения, coxpaHeHHoro в файле text.prtg, по- казан на рис. 17.15. иеllo Привет Рис. 17.15. Результирующее изображение text.pпg Резюме Изображения представляют собой коллекции пикселей, каждый из кото- рых имеет RGВА-значение, определяющее ero цвет и прозрачность, и адре- суется с помощью координат х и у. Двумя распространенными форматами изображений являютсяJРЕG и I'NG. Модуль pillow способен обрабатывать изображения как этих форматов, так и мноrих друrих. 
Работа с изображениями 521 После заrрузки изображения в объект lmage ero ширина и высота сохра- няются в виде кортежа из двух целочисленных значений в атрибyrе size. Объекты типа Image имеют также методы, позволяющие различным об- разом манипулировать изображениями: crop (), сору (), paste (), resize (), rotate () и transpose (). Для сохранения объекта lmage в файле изображения вызовите метод save ( ) . Если У вас возникает необходимость рисовать фиrypы на изображениях, используйте функции модуля lmageDraw для рисования точек, прямолиней- ных отрезков, прямоyrольников, эллипсов и мноrоyroльников. Кроме Toro, этот модуль предоставляет методы для рисования текста с использованием выбранных вами rарнитур и размеров шрифrов. Несмотря на то что rораздо более мощные (и дороrие) приложения, та- кие как PhotosllOP, предоставляют возможности автоматической пакетной обработки изображений, можно использовать сценарии Python для выпол- нения тех же операций, но бесплатно. В предыдущих rлавах. вы писали про- rраммы на Python для обработки простых текстовых файлов, электронных таблиц, документов в формате PDF и ДРУl'ИХ форматах. С модулем pillow вы расширяете сферу действия своих проrрамм еще и на обработку изо- бражений! Контропьныевопросы 1. Что такое RGВА-значение? 2. Как получить из модуля Pi1low RGВА-значение для CTaндapTHoro цв<..'Та 'CornflowerBlue'? 3. Что такое кортеж прямоyrольника? 4. С помощью какой функции можно получить объект lmage, скажем, для файла изображения zophie.рщj? 5. как определить ширину и высоту изображения, представляемоrо объ- ектом Image? 6. Какой метод вы вызовете, чтобы получить объект Image для изобра- жения размером 100хl00 пикселей, исключая ею нижнюю левую чет-- верть? 7. Как сохранить файл изображения после внесения изменений в пред- ставляющий ero объект lmage? 8. Какой модуль содержит код Pi1low для рисования фиryp? 9. Объекты lmage не имеют методов, позволяющих выполнять операции рисования. Какие объекты имеют эти методы? Как получить объекты этою рода? 
522 rлава 17 Учебные проекты Чтобы закрепить полученные знания на практике, напишите nporpaM мы .zvш предложенных ниже задач. '",ширен.е и 1I0ра60'." про,р"'" о,но,но,о nроеll'l' ,,0. ,.11.61 Рассмотренная в этой rлаве проrрамма тsizeAndAddLogo.py работает с фай- лами форматов PNG иjРЕG, но библиотека Pillow поддерживает HaMHoro больше форматов. Расширьте проrрамму тsizeАndАddLоgo.рутаким образом, чтобы она МОrла обрабатывать также изображения в форматах GIF и ВМР. Есть еще одна небольшая проблема. cyrb которой заключается в том, что проrрамма может работать с файлами PNG иjРЕG лишь в том случае, если их расширения указаны в нижнем реrистре. Например, она обработает файл %oPhie.png, но не файл zophie.PNG. Измените код таким образом, чтобы проrрамма была нечувствительна к реrистру расширения имен файлов. Наконец. предполаrается, что лоrотип, добавляемый в нижний правый уrол изображения, должен выrлядеть лишь как неболыпая метка. Но если размеры изображения и лоrотипа примерно одинаковы, то композицион ное изображение будет выrлядеть примерно так, как показано на рис. 17.16. Измените проrрамму resizeAndAddLogo.py таким образом, чтобы лоютип до- бавлялся лишь в том случае, если размер изображения по крайней мере в два раза llревышает размер лоrотипа. Ес.ли это условие не выполняется, то лоrОТИII не должен добавляты:я. Рис. J 7. 16. Коrда размер OCH08Horo изображения лишь HeHaMHoro больше размера лоrотипа, 8СЯ композициявыrлитуродnиsо 
Работа с изображениями 523 Оби"ру.,.., .".O , фorо,роф..". ." .«U'M A.'' У меня есть плохая привычка  перемещать файлы из своей цифровой камеры во временные папки на жестком диске, имена которых, конечно же, я впоследствии забываю. Было бы неплохо иметь проrрамму, которая просматривала бы весь жесткий диск и находила эти забытые "фотопапки" . Напишите проrpамму, которая просматривает все папки на вашем жест- ком диске и находит потенциальные папки, в которых MOryт находиться фотоrрафии. Разумеется, сначала вы должны определиться с тем, какие папки следует относить к этой катеrории. Например, это MOryт быть папки, более половины файлов в которых составляют фотоrpафии. Но как опреде-- лить, какие файлы являются фотоrрафиями? Прежде Bcero, файл с фотоrрафией должен иметь расширение .png или .jpg. Кроме Toro, фотоrрафии  это большие изображения; ширина и высота фотоизображения должны превышать 500 пикселей. Это безопасное пред- ПОJ10жение, поскольку ширина или высота большинства фотоrрафий, полу- чаемых с помощью цифровых камер, составляет несколько тысяч пикселей. Вот вам небольшая подсказка в виде rрубоrо каркаса, на основе Koтoporo может быть построена подобная проrpамма. #! руthопЗ # Импортировать модули и описать данную проrрамму в комментарии. for foldername, subfolders, filenames in os.walk('C:\\'): numPhotoFiles  О пиmNопРhоtоFilеs = О for filename in filenames: # Обнаруживать файлы, не имеющие расширения .png или .jpg. if тооо: numNonPhotoFiles += 1 continue # перейти к следующему файлу # Открыть файл изображения, используя модуль Pillow. # Обнаруживать файлы с изображениями, ширина или высота # которых превышает 500. if тооо: # Размеры файла изображения слишком велики, чтобы # ero можно было считать фотоrрафией. numPhotoFiles += 1 else: # Изображение слишком мало, чтобы ero можно было # считать фотоrрафией. nиmNonPhotoFiles += 1 # Если более половины фотоrрафий оказались фотоrрафиями, # вывести абсолютный Путь к папке. if тооо: print(TODO) 
524 rлава 17 После запуска проrрамма должна вывести на экран абсолютные пyrи до- етупа ко всем папкам, содержащим фотоrрафии. пep'OHaR6Htle nр.rRашен.. rлава 13 включала учебный проект по <:озданию пеРСОНaJIЬНЫХ приrлаше- ний на основе списка I'остей, хранящеrося в простом текстовом файле. Pac ширьте проект, добавляя в каждое приrлашение фотоrрафию rостя с пом<r щью модуля pil1ow. Для каждоrо из rостей, включенных в список, который хранится в файле guests.txt на сайте http://nostarch . com/automatestuff/, crенерируйте файл изображения, включающеrо элементы декоративноrо оформления и имя rостя. Для этой цели можете воспользоваться изобра- жением цветка, которое предоставляется вместе с друrими ресурсами на сайте http://nostarch. com/automatestиff/. Чтобы все приrласительные билеты были одноrо размера, добавьте пря- моyrольную rраницу черноrо цвета, окружающую изображение, которая бу- дет служить ориентиром при разрезании приrлашений после их вывода на печать. Библиотека Pillow позволяет создавать РNG-файлы с разрешением 72 пикселя на дюйм, так что для приrлашения с размерами 4х5 дюймов п<r требуется изображение с размерами 288х360 пикселей. 
УПРАвпЕНИЕкпАВИАТУРОЙ И МЫШЬЮ С ПОМОЩЬЮ СРЕДСТВ АВТОМАТИЗАЦИИ rРАФИЧЕскоrо ИНТЕРФЕЙСА попЬЗОВАТЕпя к этому времени вы успели изучить довольно MHoro моду- лей Python, которые MOryт приrодиться для автоматизации самых разных задач, таких как редактирование электрон- ных таблиц, заrрузка файлов или запуск проrрамм по рас- писанию. Но в некоторых ситуациях не удается 1I0дыекать модуль, соответствующий запросам приложения. В этом случае единственным выходом, позволяющим автоматизировать выполн ние приложения, может оказаться написание проrраммы, обеспечивающей неrlOсредственное управление клавиатурой и мышью. Такие проrраммы спо* собны управлять приложениями, отправляя им виртуальные нажатия кла- виш или щелчки мышью, как если бы вы сами взаимодействовали с приложе-- нием. для этой техники существует специальное название  авmомаmu.за1JUЯ zpафu-ческоzо nOЛЪЗ08а:mел:ьскоzо интерфейса (сокращенно  GUl-aвmомаmuза1JUЯ). В этом случае проrpаммы выполняются так, будто их работой управляет че-- ловек, сидящий за компьютером, с тем дополнительным преимуществом, что попадание на клавиатуру случайно пролитоrо кофе полностью исклю- чается. Средства автоматизации пользовательскоrо интерфейса можно считать своеобразным ПрOl'раммным роботом. Вы можете запроrpаммировать руки робота так, чтобы он нажимал IUlавиmи и перемещал мышь вместо вас. Эта техника о<:обенно полезна для решения тех задач, в которых необходимо выполнять множество щелчков на элементах управления или заполнять формы. Модуль pyautogui предлarает функции. позволяющие имитировать пере- мещения мыши, щелчки ее кнопками и вращение колесика. В :.пой rлаве 
526 rлава 18 рассматривается лишь некоторое подмножество средств автоматизации PyAutoGUI; полную документацию по этому вопросу вы найдете на сайте http://pyaиtogиi.readthedocs.org/. Установка моду". pyautogui Модуль pyaиtogui способен посьтать сиrналы виртуальных нажа1'ИЙ КЛа виш и щелчков мышью операционным системам Windows, 08 Х и Linux. В зависимости от типа используемой вами операционной системы, вам мо- жет понадобиться установка некоторых друrих модулей (называемых зави cu.м.оcmя.ми) перед установкой библиотеки PyAиtoGUI. . В Windows установка дополнительных модулей не нужна. . В 08 Х выполните последовательно команды рiрЗ install pyobjc frameworkQиartz, sиdo рiрЗ install pyobj ccore и sиdo рiрЗ install pyobj с. . В Linux выполните последовательно команды sudo рiрЗ install руthопЗхliЬ, sиdo aptget install scrot, sudo aptget install python3 tk и sudo apt get install руthоnЗdеv. (Scrot  это проrрамма для за хвата снимков экрана, которую исполыует библиотека PyAutoGUI.) Установив необходимые зависимости, выполните команду pip install pyaиtogui (или рiрЗ в случае 08 Х и Linux), чтобы установить библиотеку PyAutoGUI. Полная информация, ка(:ающаяся установки модулей сторонних раз работчиков, содержится в приложении А. Вы можете протестировать корректность установки PyAutoGUI, проверив, не сопровождается ли BЫ полнение команды import pyaиtogиi в интерактивной оболочке выводом сообщений об ошибках. Сохранение КОНТРОIl. над uавиатурой и MblWblO Прежде чем приступить к непосредственному использованию средств GUIавтоматизации, необходимо узнать о том, как избежать проблем, кото- рые при этом MOryт возникать. Python может перемещать указатель мыши и выполнять виртуальные нажатия клавиш с невероятной скоростью. В дей ствительности эта скорость может оказаты:я несоизмеримо большой для друrих проrрамм. Кроме Toro, если ЧТQJfО пойдет не так, но ваша ПрOI'рам ма будет по-прежнему псремещать мышь, то у вас MOryт возникнyrь :}aTpyд нения с точным определением причин неполадок и поиском способов их устранения, Подобно волшебной метле из мультипликационноrо фильма /l:иснея "Ученик чародея", которая не прекращала доливать воду в бак Мик ки Мауса и после тоуо, как она рекой полилась через края, ваша пршрамма 
Управление клавиатурой и мышью С помощью среДСТВ автоматизации... 527 может выйти изпод контроля, даже если она будет идеально выполнять ваши инструкции. Если указатель мыши начнет безостановочно беl'ать по всему экрану, не давая вам возможности щелкнyrь на кнопке закрытия окна IDI.E, то остановить работу проrpаммы будет чрезвычайно трудно. К счас тью, суще(твуют способы, позволяющие избеraть или преодолевать послед ствия проблем, связанных с GUIавтоматизацией. прерtl14е..е 'Ы.OII.е... "ех заll". ПУТ'" 'ЫХОД" .з У"етно. заn.,. Возможно, самый простой способ остановки вышедшей изпод контро- ля проrраммы GUIавтоматизации заключается в выходе из учетной записи пользователя, что приведет к прекращению выполнения всех запущенных в ней проrрамм. В системах Windows и Linux для эТоrо с.ледует нажать KOM бинацию из трех клавиш <Ctrl+Alt+Del>. В 08 Х это комбинация клаlШШ <И+8hift+Орtiоп+Q>. Выйдя из учетной записи, вы потеряете HecoxpaHeH ные результаты работы, но зато вам не придется перезщ'ружать всю систему. паУЗ61 . 6еЗОn"'Н6Ij резеР'Н6I. 'ЫХОД у вас есть возможность предусмотреть в своем сценарии паузы после каждоrо вызова функции автоматизации, которые вы можете использовать МЯ Toro, чтобы перехватить управление мышью и клавиатурой, если что-то пойдет не так. Для этоrо следует установить впеременной pyaиtogиi. PAUSE длительность паузы в секундах. Например, после выполнения инструкции pyautogui. PAUSE  1.5 каждый вызов любой из функций PyAutoGUI будет за- вершаться полуторасекундной паузой. Все остальные вызовы такой паузой сопровождаться не будут. Кроме Toro, для средств PyAutoGUI предусмотрена возможность без опасноrо резервноrо выхода из проrраммы. Перемещение указателя мыши в верхний левыЙ уrол экрана приведет к возбуждснию исключения pyautogui. FailSafeException. Ваша проrpамма может либо обработать это исключение с помощью инструкций try и except, либо позволить ИСКЛlOче-- нию аварийно завершить выполнение проrраммы. В любом случае макси мально быстрое перемещение указателя мыши в верхний левый yrол экрана завершит работу проrраммы. Это средство можно отключить инструкцией pyautogui. FAILSAFE  False. Введите в интерактивной оболочке следующие команды. >>> import pyautoqui »> pyautoqui.PAUSE = 1 >>> pyautoqui. FAILSAFE  True 
528 rпава 18 Здесь мы импортируем модуль pyautogui и с помощью инструкции pyautogui. PAUSE = 1 устанавливаем паузу длительностью 1 секунда после каждоrо ВЫ30ва функции. Мы устанавливаем для переменной pyaиtogui. FAILSAFE ;шачение Trиe, чтобы активизировать средство безопаП-lОl'О pe зервноrо выхода из проrpаммы. Управяение перемещени.ми указатея. мыши в этом разделе вы узнаете о том, как перемещать указатель мыши и от-- слеживать ero позицию на экране с помощью модуля PyAHtoGUI, 110 Clla1(a ла вам надо понять, как PyAHtoGUI работает с координатами. Функции PyAutoGUI /VIЯ работы с мышью используют координаты хи у. Система координат /{ля КОМllьютеРНОl'О экрана показана на рис. 18.1; она аналоrична системе координат, используемой при работе с изображения ми, которая обсуждалась в rлаве 17, 1 Ia чало координат, которому COOTBeт ствуют нулевые зпачения обеих координат, находится D верхнем левом yrлу экрана. Коордипата х увеличивается 8 направлении слепа направо, коор- дината у  в направлепии сверху вниз. Все координаты  положительные целые числа; координаты не MOryт быть отрицательными. Увеличение х  :... Q) :s: ::J: Q) :т :s: <:; Q) ID >- Рис. 18. 1. Система координат на компьютерном зкране разрешением 1920х 1080 Разрешение экрана указывает на cro ширину и высоту в пикселях. Если разрешение экрана установлено равным 1920х 1080, то координаты ero верхпеrо левоrо уrла  (О, О), а нижнею правоrо  (1919, 1079). Функция pyautogui. size () возвращает кортеж из двух целых чисел, пред ставляюlЦИХ ширину и высоту экрана в пикселях. Введите 8 интерактивной оболочке следующие команды. 
Управление клавиатурой и мышью с помощью средств автоматизации... 529 >>> iIDport pyautogui »> pyautogui.size() (1920, 1080) >>> width, height '"' pyautogui.size() Функцияруаиtоgиi.sizе() возвраща<..vrкортеж (1920, 1080) наКОМIIЬЮ тере с разрешением экрана 1920хl080; в зависимости от разрешения, уста- новлеНlIоrо для вашеrо экрана, вы можете IЮЛУ'IИТЬ друrие значения. Эти значения можно сохранить в переменных width и height для улучшения удо- бочитаемости проrpаммы. переllещеНllе y"aJare.. мыши Теперь, коrда вы хорошо понимаете, что собой представляют экранные координаты, мы можем заняться перемещением указателя мыши. Функция pyaиtogиi .moveTo () немедленно перемещает указатель в указанную пози цию на экране. В качестве первоrо и второro apryмeHToB этой функции за- даются координаты х и у соответственно. Необязательный именованный apryMeHT dиration в виде целOl'О или вещественнOl'О числа задает (в секун- дах) ДЛительнос.ть перемещения указателя в конечную точку. Если ero опу- стить, то он принимает значение О, соответствующее MrHoBeHHoмy пере- мещению. (Все необязательные именованные арryмеиты dиration функций PyAutoGUI ЯWlяются необязательными.) Введите в интерактивной оболоч- ке следующие команды. >>> iIDport pyautogui »> for i in range(10): руаutogui.шоveТо(100, 100, duration-0.25) руаutogui.шоveТо(200, 100, duration=0.25) pyautogui.moveTo(200, 200, duration-0.25) pyautogui.moveTo(100, 200, duration=0.25) В этом примере указатель мыши обходит все стороны квадрата в направ- лении по часовой CTpeJIKe де<:ять раз. Каждое перемещение 110 стороне квадрата осуществляется за четверть секунды, как это задано именован ным apryMeHToM dura tionO. 25. Если опустить третий параметр в любом из вызовов функции pyautogиi .moveTo (), ТО указатель мыши будет MrHoBeHHo телепортироваться из одной точки в друryю. Функция pyautogui .moveRel () перемещает указатель мыши относительно ero текущей позиции. В следующем при мере указатель также перемещает. ся по сторонам квадрата, за исключением Toro, что роль начальной точки квадрата иrрает та точка экрана, в которой указатель находится в момент запуска кода. 
538 rлава 18 >>> iIDport pyautoqui »> for i in range(10): pyautogui.aoveRel(100, О, duration-0.25) pyautogui.aoveRel(O, 100, duration-0.25) pyautogui.aoveRel(100, О, duration-0.25) pyautogui.aoveRel(O, 100, duration-0.25) ФунКЦИЯ pyaиtogиi .moveRel ( ) , как и предыдущая функция, принимает три арl)'Мента: величина перемещения в пикселях вправо по rоризонтали и вниз по вертикали, а также (необязательный apryMeHT) длительность пере-- мещения (в секундах). Отрицательное значение первоrо или BToporo apry мента означает перемещение указателя влево или вверх соответственно. nOR,.,eHHe n03.Ц.. уtlзareR. ".."'. Чтобы определить текущую позицию указателя мыши, вызовите Функ цию pyaиtogui. posi tion () , которая возвращает кортеж из двух координат х и у указателя мыши на момент вызова функции. Введите в интерактивной оболочке следующие команды, перемещая указатель мыши после каждоrо вызова. »> pyau togui . posi tion () (311, 622) »> pyautogui.position() (377, 481) »> pyautogui.position() (1536, 637) Разумеется, возвращаемые функцией значения будут зависеть от Toro, rде в данный момент находится указатель мыши. Проект "rAe сейчас находите. указатеllЬ мыши?" Возможность определять позицию указателя мыши является важной ча стью настройки ваших сценариев GUI-автоматизации. Однако, просто rля- дя на экран, определить точные координаты пикселя практически невоз- можно. Было бы удобно иметь проrpамму, которая постоянно отображала бы координаты указателя мыши, KOII\a вы перемещаете ero по экрану. Вот что должна делать такая проrpамма при высокоуровневом рассмо- трении: . отображать текущие :шачения координат х и у указателя мыши; . обновлять эти координаты при перемещении указателя мыши по экрану. 
Управление клавиатурой и мышью с помощью средств автоматизации... 531 Это означает, что ваш код должен выполнять следующие действия: . вызывать функцию posi t ion () для извлечения текущих значений ко- ординат; . ;Jатирать ранее выведенные значения координат путем вывода симво- лов забоя (\Ь) на экран; . обрабатывать исключение Keyboardlnterrиpt, чтобы пользователь Mor выходить из проrpаммы посредством нажатия клавиш <Ctrl+C>. Откройте новое окно в файловом редакторе и сохраните ero в файле mousеNошру. Ш" 1. И"nорr.РО.IIн.е "ОАУ.. Начните nporpaMМY вводом следующих инструкций. #1 руthопЗ # mouseNow.py  Отображает текущую позицию указателя мыши. import pyaиtogиi рriпt('для выхода нажмите клавиши <Ctrl+C>.') #тооо: Получить и вывести координаты указателя мыши. Этот код ИМlIортирует модуль pyaиtogui и выводит для пользователей напоминание о том, что для выхода из проrраммы следует нажат.> комбина- цию клавиш <Ctrl+C>. Ш,,2. КОА 'ЫХОА'"Э nРО'Р'''''Ы . ",.о.е.ный "... Вы можете обеспечить постоянный вывод текущих координат указателя мыши, возвращаемых функцией moиse. posi tion ( ) , орraНИ30вав бесконеч- ный цикл. Что касается кода для выхода из проrpаммы, вам Надо будет пе-- рехватывать исключение Keyboardlnterrиpt, которое reнерируется, коrда пользователь нажимает комбинацию клавиш <Ctrl+C>. Если вы не обра таете это исключение, то на ;')кран будет выведен пyrающий (.'Тек обратной трассировки ВЫ30вов и сообщение об ошибке для пользователя. Добавьте в свою llporpaммy следующий код, выделенный ниже полужирным шрифтом. #! руthопЗ # moиseNow.py  Отображает текущую позицию указателя мыши. import pyaиtogui рriпt('Для выхода нажмите клавиши <Ctrl+C>.') ау: "hile True: 41 1'000: По.пyчJWЬ и ВЫ88С'1'И lCоординаты ухаза'l'eJJЯ МblIIИ. . except KeyboardInterrupt: . print( I \nrO'l'oao.') 
532 rлава 18 Чтобы обработать исключение, поместите бесконечный цикл в тело ин- струкции try. Коrда пользователь нажмет комбинацию клавиш <Ctrl+C>, выполнение проrpаммы перейдет к инструкции except ., и в новой строке будет выведено сообщение Done.. Ша, 3. по.,."..е . ....011 OOPA.."" yaJtne.. ...... Код в теле цикла whi le должен получать текущие координаты указателя мыши, форматировать их для улучшения удобочитаемости и выводить на экран. Добавьте следующий код в тело цикла while. #! руthопЗ # mouseNow.py  Отображает текущую позицию указателя мыши. import pyaиtogиi рriпt('для выхода нажмите клавиши <Ctrl+C>.') пропущенный KOД # ПОJ1учи'1'Ь и 8WВeC'1'И lCoopдиIIа'1'Ы yкasa'P8J1R мыIIIи. х, у .. pyautoqui.po_ition() positionstr. 'Х: ' + str(x).rjust(4) + , У: ' + str(y) .rju_t(4) пропущенный KOД Используя трюк с rрупповым присваиванием, мы можем присвоить пс- ременным х и у значения двух целых чисел, возвращаемых в составе кор- тежа функцией pyaиtogиi. posi tion ( ) . Передав переменные х и у функции str (), можно получить целочисленные координаты в виде строк. Строко- вый метод rjust () выравнивает эти строки вправо, так что они будут зани- мать одинаковое пространство, независимо от TOro, из скольких цифр они состоят  из одной, двух, трех или четырех. Конкатенация выровненных вправо строк с подписями 'Х: 'и' У: 'дает аккуратно отформатирован- ную строку, которая будет сохраняться в переменной positi0nStr. Добавьте D конце проrpаммы следующий код. #! руthопЗ # mouseNow.py  Отображает текущую позицию указателя мыши, пропущенный KOД print(positionStr, end=' ') О print('\b' * len(positionStr), end..' " flush-True) Этот код осуществляет фактический вывод значения переменной posi ti0nStr на экран. Именованный apryмeHT end' , функции print () пре-- дотвращает добавление 3аданноrо 110 умолчанию символа новой строки в конец строки, выводимой на экран. Существует возможность затереть текст, который вы уже вывели на экран, но это относится только к послед- ней строке текста. Как только вы выведете символ новой строки, вы уже не сможете удалить ранее выведенный текст. 
Управление клавиатурой и мышью с помощью средств автоматизации... 533 Чтобы удалить текст, выведите управляющий символ забоя (\ь). Этот специальный символ удаляет символ, занимающий последнюю позицию в текущей строке на экране. В строке кода. используется репликация строк для Toro, чтобы получить такое количество следующих один за друrим сим- волов \Ь, которое совпадало бы с длиной строки, сохраненной в перемен- ной positi0nStr, что внешне проявляется как эффект затирания последней выведенной строки на экране. В силу теХJlиче(ких причин, рассмотрение которых выходит за рамки данной книrи, функции print (), выводящей символы \ь, следует всеrда пе- редавать именованный apl'}'MeHT flиsh=Trиe. В противном случае текст на экране может не обновляться так, как вам хотелось бы. Поскольку цикл while выполняется чрезвычайно быстро, пользователь фактически даже не заметит, что вы удаляете и заново выводите на экран числа целиком. Например, если -Х'"координата равна 563, а указатель мыши сместится на один пиксель вправо, то все будет выrлядеть так, будто только цифра 3 в числе 563 была заменена цифрой 4. Запустив проrрамму, вы увидите на экране только две выведенные стро- ки, которые будут выrлядеть примерно так. для выхода нажмите клавиши <Ctrl+C>. Х: 290 У: 424 Первая строка выводит пояснительную инструкцию относительно нажа- тия комбинации клавиш <Ctrl+C> для выхода из проrpаммы. Вторая строка с координатами указателя мыши будет изменяться по мере Toro, как вы бу- дете перемещать мышь. Используя эту проrрамму, вы сможете определять координаты указателя мыши, чтобы использовать их в своих сценариях GUI-автоматизации. УпраВllение взаимодействием с мышыо Теперь, КОI'да вы уже знаете, как перемещать указатель мыши и опреде- лять ero местоположение на экране, приступим к выполнению таких вирту альных операций, как щелчки, перетаскивание и прокрyrка. Ще.,,,. М"8I61О Чтобы отправиТl. компьютеру виртуальный щелчок мышью, вызовите метод pyautogui. click (). По умолчанию предполаrается, что этот щелчок выполняется левой кнопкой в месте текущеrо расположения указателя мыши. Если требуется выполнить щелчок в друrом месте, передайте коор- динаты х и у соответствующей точки в качестве необязатсльных первоrо и BTOpOl'O apryMelITOB. 
534 rлава 18 Если вы хотите указать кнопку, которой должен быть выполнен щелчок, то включите в вызов именованный apryMeHT button С одним из следующих значений: '1eft' (левая), 'midd1e' (средняя) или 'right' (правая). Напри- мер, вызовуруаutоgиi.СliСk(100, 150, button' 1eft 1) соответствует щелчок левой кнопкой в точке экрана с координатами (1 О О, 15 О) , тоrда как вызову pyautogui. click (200, 250, bиtton' right')  щелчок правой кнопкой в точ- ке экрана с координатами (200, 250). Введите в интерактивной оболочке следующий код. »> import pyautogui »> pyautogui.click(10, 5) Вы должны увидеть, как указатель мыши переместится в область экрана вблизи ero BepxHero левоrо уrла и выполнит однократный щелчок. Полный "щелчок" определяется как нажатие кнопки мыши и последующее ее отпус- кание без перемещения курсора. Также возможно выполнение щелчков с помощью вызова pyaиtogиi .moиseDown () , которому соотве1'«':ТВУет лишь на- жатие кнопки, и вызова pyautogui .moиseUp (), которому соответствует лишь отпускание кнопки. Эти функции принимают те ж apryMeHTbl, что и функ- ция click (), и в действительности функция click () просто используется в качестве оболочки для выполнения вызовов этих двух функций. Дополнительные возможности предоставляют функция pyaиtogиi. doиbleClick ( ) , выполняющая двойной щелчок левой кнопкой мыши, а так- же функции pyaиtogиi. rightClick () и pyaиtogиi .midd1eClick () , которые вы- IЮЛНЯЮТ щелчок соответственно правой и средней копками. пepero,,,..aHlle y"aJareJl. ""... Термин nеретаrжuва1tuе означает перемещение указателя мыши при од- новременном удерживании одной из ее кнопок. Например, можно пере-- мещать файлы между папками, перетаскивая их значки, или перемещать назначенные встречи в приложении календаря. Библиотека PyAutoGUI предоставляет функции pyautogui. dragTo () и pyaиtogui . dragRel ( ) , позволяющие перетаскивать указатель мыши в HO вое местоположение или местоположение, заданное относительно TeKY щеrо. Функции dragTo () и dragRel () принимают те же apl'}'MeHTbl, что и функции moveTo () и move Re 1 ( ) : х-координату / rоризонтальное смещение, у-координату/вертикальное смещение инеобязательный apryмcHT, опредс ляющий длительность перемещения. (В 05 Х передача этоrо apryMcHTa яв ляетея желательной, поскольку при слишком быстром перемсщснии указа теля мыши операция перстаскивания можст Dыllлнятьсяя пскорреКТIlО.) 
Управление клавиатурой и МЫШЬЮ с ломощью средств автоматизации... 535 Чтобы испытать, как работают эти функции, откройте какое--либо при ложение для работы с rрафикой, например Paint в Windows, Paintbrush в 08 Х или GNU Paint в Linux. (Если подходящее IIриложение не установле но на вашем компьютере, можете воспользоваться в онлайновом режиме тем, которое предлаrается на сайте http://sumopaint .сот/.) для выполне-- ния операций рИ«.ования в этих приложениях я буду использовать библио-- теку PyAutoGUI. При условии, что указатель мыши находится на холсте rрафическоrо приложения и в качестве текущеro инструмента выбран Pencil (Карандаш) или Brush (Кисть), введите в новом окне файловоrо редактора следующий код и сохраните еro в файле sрirаlDrашру. import pyaиtogui, time . time.s1eep(5) 8 pyautogui.click() # щелчок для перевода фокуса в # проrрамму рисования distar.ce = 200 whi1e distance > о: е pyautogиi.dragRel(distance, О, dиration=0.2) # сдвиr вправо О distance = distance  5 . pyaиtogиi.dragRe1(O, distance, duration=0.2) # сдвиr вниз Ф pyautogui.dragRe1(distance, О, duration=0.2) # сдвиr влево distance = distance  5 pyautogui.dragRel(O, distance, dиration=0.2) # сдвиr вверх Коrда вы запустите эту ПрOl'рамму, вам будет предоставлена пятисекунд ная пауза О, чтобы вы успели переместить указатель мыши в окно rрафи ческой проrраммы, в которой к этому времени должен быть выбран в каче стве инструмента карандаш или кисть. После этоro сценарий sрirаlDrашру перехватит управление мышью и выполнит щелчок для получения фокуса в проrрамме рисования .. Фокус получен окном, если в нем виден активный мерцающий курсор и предпринимаемые вами действия, в данном случае перетаскивание указателя мыши, воздействуют на Hero. Как только rрафи- ческая проrрамма получит фокус, сценарий sрirаlDrашру нарисует KBaдpa ную спираль наподобие той, которая представлена на рис. 18.2. Начальным значением переменной distance является 200, поэтому на первой итерации цикла while первый вызов dragRel () перемещает KYP сор на 200 пикселей вправо за 0,2 секунды.. Затем значение переменной distance уменьшается до 195 ., и второй вызов dragRel () перемещает кyp сор на 195 пикселей вниз.. Третий вызов dragRel () перемещает курсор на 195 по rоризонтали (195 I1икселей влево) ., значение переменной distance уменьшается до 190, а последний вызов dragRe1 () перемещает курсор на 19 О пикселей вверх. На каждой итерации мышь перемещается вправо, вниз, влево и вверх, а переменная distance принимает несколько меньшее 
536 rлава 18 значение, чем на предыдущей итерации. Выполняя этот код в цикле, вы перемещаете курсор таким образом, что 011 рисует квадратную (:пираль. ; .ж... 1 v 'j" i.. " 8i..I_ . Реint кi8 i.1'.o11!t-:;' 6ИА 1. 'JС:JIiЙI. . c.i ..,' {, .t'i ...... ... ,1 Ьуфер ИЗО6Р':«.fttие И"С1'р)""lН' tlС:-1 Фиf1'1')tll ТО..!IЩ""d Шiета .,6",",a ... }ОО . I 'j:; .;'"VU ..:,.::Ij .5\.",) + lБZ, 11п< 'Q 100'1,  :.1 +. Рис. 18.2. Результат работы примера nporpaMMbl, 8 которой используется функция pyautogui. dragRel () Вы моrла бы нарисовать эту спираль и вручную (вернее, с помощью мыши), но для точноrо рисования спирали вам пришлось бы работать очень медленно. Модуль PyAutoGUI способен выполнить эту работу за счи. танные секунды! ПРUJlfечание Вы .моелu 6'ы иарисоватъ это uзо6раже1tuе, ucnолъзуя фуюсц,ии .модуля pil10w (см. елаву 17). Одиахо средства GUI-авто.матизац,ии позволяют исnолъ:юватъ 60- лее СЛОЖ1tые иистру.меиты рисова1tuя, nредоставляе.мые ерафи'Ческuми nроерам..м,а. ми, тахие хах ерадиеиты, разлu'Ч1tые хисти или и1tструме1tт заЛИfJ1Cи. про."",,, Последней из функций библиотеки PyAutoGUI ДЛЯ работы с мышью мы рассмотрим функцию scroll ( ) , которая принимает целочисленный apry мент, определяющий количество единиц прокрyrки в направлении вверх 
Управление клавиатурой и мышью с помощью средств автоматизации... 537 или вниз. Величина единицы прокруrки изменяется в зависимости от опе-- рационной системы и приложения, поэтому в каждой конкретной ситуа- ции придется провести самостоятельные эксперименты, чтобы выяснить ее конкретную величину. Про крутка выполняется в текущей позиции кур- сора. Положительное значение apryMeHTa означает прокруrку вверх, отри. цатеЛЫlOе  прокрутку ВНиз. Выпотште следующую команду в интерактив- ной оболочке, предварительно расположив указатель МblШИ в окне IDLE: »> pyautogui.scroll(200) Вы увидите, как окно IDLE быстро прокрутится вверх, а затем вернется в исходную позицию вниз. Прокрутка вниз происходит по той причине, что IDLE делает это автоматически после выполнения инструкции. Введи- те теперь дрyrой код. >>> iaport pyperclip »> nUlllber& = l' »> for i in range(200) : nUlllbers . nUlllber& + &tr(i) + '\n' »> pyperclip.copy(nUlllbere) В этом коде прежде Bcero импортируется модуль pyperclip и определя- ется пустая строка nurnbers. Далее код циклически перебирает целые числа от о до 199 и добавляет каждое из них в строку numbers вместе с символом новой строки. После выполнения инcrрукции pyperclip.copy (nurnbers) в бу- фер обмена будут заrружены 200 строк чисел. Откройте новое окно в фай- ловом редакторе и вставьте в Hero содержимое буфера. В результате вы по- лучите окно с длинным текстом, удобным для наблюдения за прокруткой. Введите в интерактивной оболочке следующий код. »> import time, pyautogui »> time.&leep(S); pyautogui.&croll(100) Во второй строке вы вводите две команды, разделенные точкой с запя- той, которые воспринимаются Python так, как если бы они находились на разных строках. Единственное отличие состоит в том, что в этом случае Python не выводит приrлашение для ввода команды между двумя инструк- циями. В данном примере это важно, поскольку мы хотим, чтобы вызов функции руаи togui. scro11 () был автоматически выполнен по истечении паузы. (Заметьте, что, несмотря на всю полезность помещения двух команд в интерактивной оболочке в одной строке, в проrраммах rю-прежнему не- обходимо вводить инструкции в разных строках.) 
538 r лава 18 После нажатия клавиши <Enter> у вас будет пять секунд на то, чтобы щелкнyrь мышью в окне файловоrо редактора для перевода в Hero фокуса. Сразу же 110 истечении пятисекундной задержки вызов руаи togui. scroll () выполнит прокрутку окна файловоrо редактора в направлении вверх. Работа с экраном Ваши проrрамма GUI-автоматизации не должны вслепую выполнять щелчки и нажимать виртуальные клавиш. Библиотека PyAиtoGUI пред- лаrает средства, обеспечивающие получение снимков экрана и создание файлов и:юбражений на основе ero текущеrо содержимоrо. Эти функции также MOryT возвращать объекты Image Pillow, соответствующие текущему виду экрана. Если вы не читали эту книry rлава за rлавой, то вам следует обратиться к rлаве 17 и установить модуль pillow, прежде чем продолжить чтение этоrо раздела. Чтобы функции PyAиtoGUI, предназначенные для получения снимков экрана, можно было использовать на компьютерах с Linux, на них должна быть установлена проrрамма scrot. Для установки этой проrpаммы следу- ет выполнить в OKHt Теrшiпаl команду sudoaptget install scrot. Если вы используете компьютеры, работающие ПОД управлением операционной си- стемы Windows или 08 Х, то опустите этот шar и продолжите чтение дан- Horo раздела. по.у"ен", ,,,,,,,ко 'КРОНО Для получения снимка экрана в Python вызовите функцию pyautogui. screenshot (). Введите в интерактивной оболочке следующие команды. »> import pyautogui »> im = pyautogui.screenshot() Переменная im будет содержать объект Image снимка экрана. Теперь вы сможете вызывать методы для объекта Image, хранящеrося в переменной im, как для любоrо друroro объекта Image. Введите в интерактивной оболоч- ке следующие команды. »> im.getpixel«O, О)) (176, 176, 175) »> im.getpixel«50, 200)) (130, 135, 144) Передайте функции getpixel() кортеж координат, например (О, О) или (50, 200), и оНа вернет вам цвет пикселя изображения в точке с этими 
Управление клавиатурой и мышью с помощью средств автоматизации... 539 координатами. Во;шращаемое значение функции getpixel ()  это RGB- кортеж из трех целых чисел, представляющих соответственно количество красной, зеленой и синей составляющих в цв<.'Те пикселя. (Четвертая со- ставляющая  альфа-канал  oтcyrcTBYeт, поскольку снимки экрана полнос- тью непрозрачны.) Именно так ваша I1porpaмMa может "видеть", что нахо- дится в любой точке экрана в данный момент. А.tI.нэ '.H"tI Jptl.tI Предположим, что одним из действий, выполняемых вашей проrраммой СUI-автоматизации, ЯWIЯется щелчок на серой кнопке. Прежде чем вызваТI) метод click () , можно получить снимок экрана и проверить цвет пикселя, в котором проrpамма собирается выполнить щелчок. Если цвет пикселя не совпадает с серым цветом кнопки, значит, что-то пошло не так. Возможно, окно было неожиданно перемещено или же всплывающее диалоroвое окно перекрыло кнопку. В этом случае, вместо Toro чтобы продолжить выпол- нение, что имело бы непредсказуемые последствия из-за щелчка в непод- ходящем месте, проrрамма может определить, что выполнять щелчок не следует, и предпринять друrие соответствующие действия. Функция pixelMatchesColor () библиотеки PyAutoGUI возвращает зна- чение Trиe, если цвет пикселя с заданными экранными координатами х и у совпадает с заданным цветом. Ее первый и второй apryMeHTbI  это целые числа, I1редставляющие координаты х и у, а третий  это кортеж из трех целых чисел, представляющих RС&-цвет, которому должен соответствовать экранный пиксель. Введите в интерактивной оболочке следующие команды. »> ilDport pyautoqui »> ia · pyautogui.screenshot() . »> ilD. getpixel «50, 200) (130, 135, 144) . >>> pyautoqui.pixelМatchesColor(50, 200, (130, 135, 144)) True О »> pyautoqui.pixelМatchesColor(50, 200, (255, 135, 144) False После Toro как вы получите снимок экрана и используете функцию getpixel () для получения RGВ-кортежа. соответствующеrо цвету пик- селя с заданными координатами ., передайте эти координаты и RGB- кортеж функции pixelMatchesColor () ., которая возвратит значение True. Затем измените значение RGВ-кортежа и вновь вызовите функцию pixelMatchesColor () для тех же координат .. На этот раз функция долж- на возвратить значение False. Эту функцию полезно вызывать всякий раз перед тем, как ваша проrрамма GUI-автоматизации должна вызвать функ- цию click (). Обратите внимание на то, что совпадение цвета пикселя с 
541 r лава 18 заданным цветом в данном случае должно быть полным. Даже если отличие весьма незначительно, например пиксель имеет цвет (255, 255, 254) вме-- сто (255, 255, 255), функция pixelМatchesColor () все равно вернет значение False. Проект: расширение nporpaMMbl mouseNO'lf . ру Рассмотренную рансе проrpамму mouseNow. ру можно расширить так, что- бы она выдавала не только координаты х и у текущей позиции указателя мыши, но и RGВцвет находящеrося под ним пикселя. Модифицируйте про- rрамму moиseNow. ру, внеся в код, находящийся в теле цикла while, следую щие изменения. II! руthопЗ # moиseNow.py  ОТОбражает текущую позицию указателя мыши. пропу.щенный KOД positionStr  'Х: I + str(x).rjust(4) + , У: ' + str (у) .rjиst (4) pixelColor = pyautogui. scr"nshot () . getpixel «х, у» positionStr +- , RGВ: (' + str(pixelColor(O) .rjust(З) positionStr +- " 1+ str(pixelColor[l) .rjust(З) positionStr += " 1+ str(рiхеlСоlоr(2).rjust(З) + ')' print(positi0nStr, end=' ') пропу.щенный KOД Теперь, если вы запустите проrрамму moиseNow.py, вывод будет дополни- тельно включать информацию об RGВ-цвете пикселя, находящеrося под указателем мыши. Для выхода нажмите клавиши <Ctrl+C>. Х: 406 У: 17 RGB: (161, 50, 50) Использование этой информации совместно с функцией pixel МatchesColor () упростит добавление про верки цвета пикселей в ваши про- rpaMMbl GUIавтоматизации. Распознавание образов НО что делать, если вам неизвестно заранее, в каком месте экрана про. rpaмMa должна выполнить виртуальный щелчок? В этом случае можно BOC пользоваться распознаванием образов. Передайте модулю PyAиtoGUI изо- бражение, на котором следует выполнить щелчок, и позвольте проrрамме самостоятельно определить нужные координаты. Например, если ранее вы получили снимок экрана для захвата изобра- жения кнопки Отправить, которое сохранили в файле sиbmil.png, то функция 
Управление клавиатурой и мышью с помощью средств автоматизации... 541 1ocateOnScreen () возвратит координаты местонахождения этоrо изобра- жения на экране. Чтобы увидеть, как работает функция 1оса teOnScreen ( ) , попробуйте получить снимок небольшой области экрана, <.охраните это изображение, а затем введите в интерактивной оболочке следующие коман- ды, заменив имя файла' submi t. png' реальным именем файла rlOлученноrо вами изображения. »> import pyautogui > > > pyautogui .100& teOnScreen ( I .ubmi t. png I ) (643, 745, 70, 29) Кортеж из четырех целых чисел, возвращаемый функцией 1ocateOnScreen (), представляет ,»окоординату левоrо края, у-координату BepxHero края, а также ширину и высоту изображения в первой из пози- ций на экране, в которой оно обнаружено. Если вы попытаетесь выполнить ;пот код на своем компьютере с собственным снимком экрана, то ваши чис- ловые данные будут отличаться от тех, которые показаны здесь. Если функции 1ocateOnScreen () не удается обнаружить указанное изо- бражение на экране, то она возвращает значение None. Обратите внимание на то, что для Toro, чтобы изображение на экране MOrJIO быть распознано, оно должно в точности совпадать с предоставленным изображением. Если оно отличается хотя бы одним пикселем, то функция loca teOnScreen () ВОЗ- врэ,тит значение None. Если функция 1ocateA110nScreen () находит изображение в нескольких местах экрана, она возвращает объект Generator, который можно передать функции list () для возврата списка кортежей, включающих по четыре целых числа. Bcero будет по одному такому кортежу для каждоrо располо жения на экране, в котором было найдено заданное изображение. Продол жите выполнение нримера в интерактивной оболочке, введя следующие команды (с заменой строки 'sиbmi t . png' именем файла с подrотовленным вами изображением). »> list(pyautogui.locateAllOnSoreen('.ubmit.png'» [(643, 745, 70, 29), (1007, 801, 70, 29)] Каждый из кортежей, включающих четыре целых числа, представ- ляет одну область экрана. Если ваше изображение обнаружено лишь в одной области экрана, то в результате использования функций 1 i s t () и 1ocateAllOnScreen () вы получите список, содержащий один кортеж. Зная область экрана, в которой обнаружено искомое изображение, можно выполнить щелчок в центре этой области, передав соответствую- щий кортеж функции center ( ) , которая возвратит координаты х и у центра данной области. Введите в интерактивной оболочке следующие команды, 
542 rлава 18 заменяя apryMeHTbI именем еобствеНlIоrо файла изображения, а также c<r ответствующими кортежем и парой координат. »> руаutogui.lосаteOnSсreen('subшit.рng') (643, 745, 70, 29) »> руаutogui.оеnter«б43, 745, 70, 29» (678, 759) »> pyautogui.cliCk«678, 759» Передав полученные из функции center () координаты функции с 1 i с k ( ) , вы выполните виртуальный щелчок мышью по центру облаети экрана, совпадающей с изображением, которое вы передали функции lacateOnScreen(). Управпениекпавиатурой Библиотека PyAutoG 1Л также содержит функции, позволяющие посы- лать комш.ютеру виртуальные клавиатурные нажатия, что дает вам возмож- ность заполнять формы или вводить текст в приложениях. OTпpo." "PO'" н"6",,,,,0. НО'НР,,".'НО. ."'''tnype Функция pyaиtagui. typewrite () посылает компьютеру виртуальные кла- виатурные нажатия. Какие последствия это будет иметь, зависит от Toro, в каком окне и каком поле находится фокус ввода. Чтобы rарантировать на- хождение фокуса в нужном месте, можно предварительно выполнить вир-- туалыlйй щелчок мышью в соответствующем поле. В качестве простоrо примера используем Python для автоматическоrо ввода текста Hella world! в окне файловоrо редактора. Прежде Bcero, от-- кройте D файловом редакторе новое окно и расположите ero в верхнем левом уrлу экрана, чтобы в Hero можно было переместить фокус ввода, вы- полнив с помощью средств автоматизации PyA\.1toGUI виртуальный щелчок мышью в подходящем месте. Эатем введите в интерактивной оболочке сле- дующие команды: »> pyautogui.click(100, 100); pyautogui.typewrite('Нello world!') Обратите внимание на размещение двух команд, разделенных точкой с запятой, в одной строке, что предотвращает отображение приrлашения ко вводу между выполнением двух инструкций. Это позволяет избежать слу- чайноrо перемещения фокуса ввода в новое окно между вызовами функций click () и typewri te () , что внесло бы пyrаницу в выполнение примера. 
Управление клавиатурой и мышью С помощью средств автоматизации... 543 Сначала PytllOll выполняет ВИРТУd.llьный щелчок мышью в точке экрана с координатами (100, 100), что приводит К выполнению щелчка в окне фай- ловоrо редактора и перемещению в Hero фокуса ввода. Вызов typewri te ( ) отправляет текст Hello world! в ЭТО окно (рис. 18.3). Теперь вы располаrае- те кодом, который может набирать текст вместо васl jt ./thO 7' \(i 9.t- I I' ';:";:@':,и j::.if: :!I ...... :...._. ......,.. '.",'f'.:"''. ;--'(,: ....: "," FiIe Ed FOnnIt Ru" орьо", Wrndow Нelp Hello World!1 C'. s".Jiii ., L": 1 Col: 12 »> pyaиtogui >>> pyautogui .cliclc(lOO, 100) ;pyautogui. typewrite (':' .» ",,' , ,,: ' I .: ,Ln: 134 C"I:. Рис. 18.3. Использование средств PyAutoGU/ Дnll8ыполнеНИII щелчка в окне файловоrо ре- дактора и ввода в нем Т8кста Hello world! По умолчанию функция typewrite () немсдленно вводит всю етроку, Но можно добавить небольшую задержку между символами, передав функ- ции необязательный второй apryMeHT. Им является целое или веществен- ное число, выражающее длительность паузы в секундах. Например, вызов pyautogиi. typewri te ( 'НеНо world! " 0.25) будет выжидать одну четвертую долю секунды после ввода буквы Н, затем еще одну четвертую долю секунды после ввода буквы е и Т.д. Такой постепенный ввод текста может быть по- лезным в случае медленных приложений, которые не в состоянии обраба- тывать нажатия клавиш с той же скоростью, с какой их выполняет модуль PyAutoGUI. Для таких символов, как А ИЛИ!, PyAutoGUI автоматически имитирует одновременное нажатие клавиши <Shift>. 060зн".ен., ".tll.Ш Не все клавиши MOryr быть представлены одиночными символами. На- пример, это невозможно сделать для клавиш <Shift> и <t>, в библиотеке PyAutoGUI эти клавиши Ilредставляются короткими строковыми значения- ми, например' esc'  для клавиши <Esc> или I enter'  для клавиши <Enter>. 
544 rлава 18 Вместо одиночноrо CTpoKoBoro значения функции typewrite () можно передавать целый список строк, соответствующих таким клавишам. На- пример, следующий вызов соответствует нажатию клавиши <.А> и клавИIUи <В>, затем  двум нажатиям клавиши <....> и наконец  нажатиям клавиш <Х> и <У>: »> pyautogui.typewrite«('a', 'Ь', '18ft', '18ft', 'Х', 'Y'J) Поскольку нажатию клавиши <....> соответствует перемещение курсора клавиатуры на один символ влево, результатом выполнения этой команды будет вывод текста ХУаЬ. В табл. 18.1 приведены строковые обозначения клавиш PyAutoGUI, которые можно передавать функции typewrite () для имитации нажатий любых комбинаций клавиш. Вы сможете ознакомиться со всеми строковыми обозначениями клавиш, допустимыми в PyAиtoeUI, выведя список pyaиtogиi. KEYBOARDKEYS. Стро- ка 'shift' ссылается на левую клавишу <Shift> и эквивалентна клавише 'shiftleft', Тоже самое относится к строкам 'ctrl', 'аН' и 'wiп';всеони ссылаются на одноименные левые клавиши. Та6nица 18.1. Атрибуты РуКеуЬоаш СтроКо.... _IНа........ IUlClВИW Onисаllll8 'а', 'Ь', 'с', 'А', 'В', 'С', '1', '2', , 3 " , ! " '@', 't' и т.д. 'enter' (aTalCJКe 'return' ми' \n ') 'esc' 'shiftleft', 'shiftright' 'altleft', 'altright' 'ctrlleft', 'ctrlright' 'tab' (МИ '\ t ' ) 'backspace', 'delete' 'pageup', 'pagedown' 'home', 'end' 'ир', 'down', 'left', 'right' 'f1 " , f2 " , fЗ' и Т.Д. 'volumemute', 'volumedown', 'volumeup' 'paиse' Кnaвиwи ОДИНОЧНЫХ СИМВОП08 Кnавиwa <Enter> Кnaвиwa <Ек> Лева. и права. кпавиwи <Shift> ЛевOI и npa80l кnoвиwи <Alt> Лева. и права. КnOВИWИ <Ctrl> Кnaвиwа <ТаЬ> Кnавиwи <Backspace> И <Delete> Кnавиwи <Page Up> и <Page Down> Кnавиwи <Home> и <End> Кnавиwи <t>, <.!.>, <......> и ч> Кпавиwи от <F 1 > до <F 12> Кпавиwи ОТlU1lОчени., уменьwени. и увепичени. звука (на некоторых кпавиатурах ЗУИ клавиwи OTCYТCТByIOT, но оп.рационна. система все равно будет распознавать ЗУИ имитированиые нажати. кпавиw) Кnавиwa <Pause> 
Управление клавиатурой 11 мышью с помощью средств автоматизации... 545 Окон:чаuue табл. 18.1 Crpoк08l0l8 060........... IUlClllllIU , capslock " 'numlock', 'scrolllock' 'insert' 'printscreen' 'winleft', 'winright' 'comrnand' 'option' OnIlCCl..... КпОВИWИ <Caps Lock>, <Num Lock> И <Scroll Lock> Кпавиwa <Ins> ИlIИ <Insert> Кпавиwa <Prtsc> ИlIИ <Рпп. Screen> Лева. И права. клавиwи <Win> (8 Windows) Кпавиwа <Command IX) IB OS Х) Кпавиwa <Option> IB OS Х) Но.",.. . o",,,"..e о".... в тесной аналоrии с функциями mouseDown () и тоивеОр () функции pyautogui. keyDown () и pyautogui. keyUp () посылают компьютеру сиrналы виртуальных нажатий и отпу<:каний клавиш. В качестве арryмента эти функ- ции принимают строковое обозначение клавиши (см. табл. 18.1). Для боль- шеrо удобства библиотека PyAutoGUI предоставляет функцию pyautogui. press () , которая вызывает обе эти функции для имитации полноrо цикла нажатия клавиши. Выполните следующий код, который выведет знак доллара (получаемый в результате нажатия клавиши <4:> при одновременно нажатой клавише <Sl1ift:» : »> pyautogui.keyDoWn('8hift'); pyautogui.pre88('4'); pyautogui. keyUp ( , 8hift' ) Эта строка имитирует нажатие клавиши <Sl1ift>, нажатие (и отпускание) клавиши <4> с последующим отпусканием клавиши <Sl1ift>. Для ввода стро- ки в текстовом поле лучше подойдет функция typewrite () . Но в случае при- ложений, нуждающихся в ОДНОКJJавишпых командах, функция press () обе- спечивает более простой IIОДХОД. (ор.,.е R"'.". IЪрячие клавиши, или клавиши быстроro вызова,  это комбинации кла- виш, предназначенные для аК1'ивизации определенных функций приложе- ния. Так, распространенными rорячими клавишами являются комбинация клавиш <Ctrl+C> (Windows и LinHx) и <х+с> (05 Х). Пользователь нажима- ет и удерживает клавишу <Ctrl>, затем нажимает клавишу <С> и после этоrо отпускает клавиши <С> и <Ctrl>. Чтобы сделать это с помощью функций PyAHtoGUI keyDown () и keyUp ( ) , вам нужно ввести следующие команды. 
546 rлава 18 pyautogui.keyDown('ctrl') pyautogui.keyDown('c') pyautogиi.keyUp('c') pyautogui.keyUp('ctrl') Вводить все эти инструкции довольно yrомительно. Вместо этоrо лучше использовать функцию pyaиtogиi.hotkey(), которая принимает несколько строковых apryMeHToB, обозначающих клавиши, выполняет виртуальные нажатия клавиш в указанном порядке, а затем отпускает их в обратном по- рядке. Для комбинации клавиш <Ctrl+C> вызов будет выrлядеть так: pyautogui.hotkey('ctrl', 'с') Эта функция особенно полезна в случае длинных клавиатурных комби наций. В приложении Word комбинация клавиш <Ctrl+Alt+5hift+5> отобра жает панель Стили. Вместо Toro чтобы выполнять восемь разных вызовов функций (четыре вызова keyDown () и четыре вызова keyUp (», достаточно вызваТЬФУНКЦИЮhоtkеУ('сtrl', 'alt', 'shift', 'э'). Переместив новое окно файловою редактора ЮLЕ в верхний левый yrол экрана, введите в интерактивной оболочке следующие команды (в 05 Х вместо строки 'аl t' следует использовать строку 'с t r 1 ' ). »> import pyautogui, tiae »> def commentAfterDelay(): О pyautoqui.click(100, 100) 8 pyautogui.typewrite('In IDLE, Alt3 oOIlllll.nts out а lin.. ') time.sleep(2) е pyautogui.hotkey('alt' / '3') »> commentAft.rDelay () В этом коде определяется функция commentAfterDelay () , которая выпол- няет щелчок в окне файловоro редактора, чтобы переместить в Hero фокус ввода е, ВDОДИТ текст In /DLE, Atl-3 coттeпts oиt а liпe (в IDI.E нажатие клавиш <Alt+3> прсвращает строку в комментарий) 8, ВbIдерживает паузу в течение 2 секунд, а затем имитирует нажатие комбинации клавиш <Alt+3> (или <Ctrl+3> в 05 Х) 8, Это клавиатурное сокращение добавляет два символа решетки (#) в текущей строке, тем самым превращая ее в комментарий. (Данный прием будет вам очень полезен при написании собственноrо кода в IDLE.) Обзор функций PyAutoGUI Поскольку в этой rлаве обсуждалось MHoro различных функций, ниже приведен их краткий справочный обзор. 
Управление клавиатурой и МЫШЬЮ с ПОМОЩЬЮ средств автоматизации... 547 . lDоуеТо (х, у). Перемещает указатель мыши в точку экрана с заданны ми координатами х и у. . moveRel (xOffset, yOffset). Перемещает указатель мыши ОТНОПI тельно ero текущей IIОЗИЦИИ. . dragTo (х, у). Перемещает указатель мыши, удерживая нажатой ее левую кнопку. . dragRel (xOffset, yOffset). Перемещает указатель мыши отвоси тсльно ero текущей позиции, удерживая нажатой ее левую кпонку, . click (х, у, button). Имитирует щелчок (по умолчанию левой кноп кой мыши). . ri9htClick () . Имитирует щелчок правой кнопкой мыши. . lDiddleClick () . Имитирует щелчок ередней кнопкой мыши. . doubleClick () . Имитирует двойной щелчок левой кнопкой мыши. . lDouseDown (ж, у, button). Имитирует нажатие указанной кнопки мыши в точке экрана с координатами х и у. . 1II.ouseUp(x, у, button). Имитирует отпускание указанной кнонки мыши в точке экрана с координатами х и у. . scroll (uni ts) . Имитирует ПРОКРУЧИВaIIИе колесика мыши. Положи- тельному значению apryмeHTa соответствует прокрyrка вверх, отри- цателыюму  прокрyrка вниз. . typewri te (1II.essage) . Вводит символы в указанной строке сообщепия. . typewrite ([keyl, key2, kеуЗ]). Вводит указапные строковые обо- :шачения клавиш. . press (key) . Нажимает КJIавишу. заданную указанной строкой. . keyDown (key) . Имитирует нажатие указанной КJIавиши. . keyUp (key) . Имитирует отпускание указанной КJIавиши. . hotkey ( [keyl, key2, kеуЗ]). Имитирует нажатие КJIаВИШ, заданных их строковыми обозначениями, в указанном порядке с последующим их отпусканием в обратном норядке. . screenshot () . Возвращает снимок экрана в виде объекта Irnage. (Бо лее подробную информацию об объекте Irnage вы найдете в rлаве 17,) Проект: автоматическое заПОllнение формы Самая Надоедливая из всех рутинных задач  это заполнение форм. Счи тайте, что вам повезло: сейчас мы приступаем к рассмотрению проекта, который поможет вам с леrкостью решать задачи подобноrо рода. Пред- ноложим, что в электронной таблице хранится оrромный массив данных и вам предстоит кропотливая работа по переносу этих данных в форму дpy roro приложения, а свободноrо стажера, который сделан бы все это вместо вас, рядом не оказалось, Хотя в некоторых приложсниях и нрсдусмотрена возможность импорта данных, иноrда обстоятельства СКJIа;{ываются таким 
548 r лава 18 образом, что единственным приходящим на ум решением является MHoro. часовое щелканье мышью и ввод l1:aHHbIx с клавиатуры. Но поскольку вы уже прочитал и эту КНИl}' почти до конца, то, конечно же, вам хорошо из вестно, что существует альтернативный вариант. В этом проекте будет использоваться форма Google Docs, которую мож- но скачать на сайте http://nostarch. com/aиtornatestиff. Вид этой формы по- казан на рис. 18.4. \1:1 с'-'..' ',., :l!,">;.,-.- '1'0';;" ." ""' ,.." оС, :;"..,.., v"".t- ., rJ.:.м"J:'!'!'"::З:"';;:-х-;:" -  с <J;)-:s 9C\::9!e.cJ/n 'UI;4 Generic Form   Lt()J'lill1' li'[ !','I!}, I :t,.. .,. ,.,'" N;,)I!'I-e Grеэtt'!..1; F.ari; t1at.s Нн. "i.ОШС" о, ytJflr 't"ur.r'i pr)'t'c'e.:' A'{)t'1.<:':()P w,.. !j"O (J!(rf"" ';' ;,("flll' ,Т.,, ,.i... 'It !tH r '.tl'Q.. ... '" "."'! (.' s.;. ..... AqdffIO"' (;om",pt$  Рис. 18.4. Форма, используеМОII в этом проекте Вот что должна делать данная проrрамма при высокоуровневом рассмO'f'" рении: . щелкнyrь на первом текстовом поле формы; . перемещаться по форме, вводя информацию в каждое поле; . щелкнyrь на кнопке rOТOBO; . повторит.. весь процесс для слсдующеrо набора данных. 
Управление клавиатурой и мышью с помощью средств автоматизации... 549 Это означает, что ваш код должен выполнять следующие операции: · вызывать функцию pyautogиi. click () ДJlЯ выполнения щелчков на форме и кнопке raтOBo; . вызывать функцию pyautogui. typewrite () для ввода текста в COOTBeT ствующих IIОЛЯХ; . обрабатывать исключение Keyboardlnterrиpt, чтобы пользователь Mor выйти из проrраммы, нажав комбинацию клавиш <Ctrl+C>. Откройте новое окно в файловом редакторе и сохраните ero D файле jorтFilkr.py. Шаr '. (оетомен"е nRaHO де.".". Прежде чем приступить к написанию кода, определим точную последо- ваl'ельность нажатий клавиш и щелчков мышью для однократноrо прохода 110 форме, Рассмотренный ранее сценарий тouseNow.py поможет вам Оllредс-- лять конкретные значения координат указателя мыши. Единственное, что вам нужно знать,  это координаты псрвоrо TeKcToBoI'O поля. После Toro как будет выполнен щелчок на первом текстовом поле, вам будет достаточ но нажимаТl. клавишу <ТаЬ> для персхода к следующему полю. Это избавит вас от необходимости опрсделять координаты х и у для каждоrо поля, что- бы выполнить на нем щелчок. Ниже приведена rюшаrовая процедура для ввода данных в поля формы. 1. Щелкнуть на поле Name (Имя). (Используйте функцию тousеNошру, чтобы определить координаты этоrо поля после развертывания окна браузсра на весь экран. В случае 08 Х вам может потребоваться щел кнуть дважды: один раз  для перемещения фокуса в браузер, а вто- рой  для выполнения щелчка в поле Name.) 2. Ввести имя и нажать клавишу <ТаЬ>. 3. Указать lIредмет наибольшей уrрозы (Greatest Fear) и нажать клавишу <ТаЬ>. 4. Нажать клавишу <Ф> необходимое количество раз для выбора источ- ника маI'ической силы (Wizard Power 80urce): 1  волшебная палочка (Wапd), 2  амулет (Alllulet), 3  хрустальный шар (Crystal ВаЩ и 4  деньrи (Мопсу). Затем нажать клавишу <ТаЬ>. (Обратите внимание на то, что в случае 05 Х клавишу <Ф> необходимо нажимать на один раз больше для каждой опции. Возможно, в некоторых браузерах. потре- буется нажимать также клавишу <Enter>.) 5. Нажать клавишу <> необходимое количество раз ДЛЯ выбора вари- анта ответа на вопрос о том, был ли РобfЖОn лучшим фильмом 1980-х !'одов (раздел параметров Robocop was the greatest action movie ofthe 19808). 
550 rлава 18 Ее следует нажать один раз для выбора варианта 2, два раза  для вы- бора варианта 3, три раза  для выбора варианта 4 и четыре раза  для выбора варианта 5. Для выбора варианта 1 (который подсвечен по умолчанию) достаточно нажать клавишу пробела. После этоrо на- жать клавишу <ТаЬ>. б. Ввести дополнительный комментарий и нажать клавишу <ТаЬ>. 7. Нажать клавишу <Enter> для выполнения "щелчка" на кнопке Submit (Отправить ). 8. После отправки формы браузер перейдет на crpаницу, на которой вам нужно будет щелкнyrь на ссылке для возврата на страницу формы. Обратите внимание на то, что в случае IIоследующеrо повторноrо за- пуска проrраммы может потребоваться обновление координат точки для щелчка, поскольку позиция окна браузера за это время моrла измениться. Чтобы устранить эту проблему, всеrда убеждайтесь в том, что окно брау ра развернуто на весь экран, прежде чем определять координаты первоrо поля формы. Кроме тою, учтите, что различные браузеры ведут еебя по-- разному в различных операционных системах. Поэтому всеrда проверяй- те, как работают используемые комбинации клавиш на вашем компьютере, прежде чем запускать проrрамму, Шо,2. Ноетро;;"" "00РIl8НОТ Заrрузите форму примера (см. рис. 18.4) в браузер и разверните окно на весь экран. Откройте новое ОКIIО терминала или командной строки, чтобы выполнить сценарий тоusеNошру, а затем поместите указатель мыши над полем Name для определения el'o координат. 1 Iайденные координаты будут присвосны псрсмсшюй nameField. Кроме TOI'O, определите значения коор- динат и RС.Взначсния для rолубой кнопки Submit. Эти значения будут при- своены переменным submitButton и submitButtonColor соответственно, После этоrо заполните форму любыми фиктивными данными и щелкни- те на кнопке Submit. Вам нужно увидеть, что собой представляет следующая страница, чтобы можно было определить координаты ССbJЛКИ Submit another responseHa ней с помощью сценария тоusеNошру. Откройте новое окно в файловом редакторе и введите следующий код, заменив в нем значения, выделенные курсивом, значениями координат, найденными вами в собственных тестах. #! руthопЗ # formFiller.py  Автоматически запОлняет форму. import pyautogui, time 
Управление клавиатурой и мышью с помощью средств автоматизации... 551 # Задайте значения координат, соответствующие вашему компьютеру. nameFie1d ; (648, 319) submitButton = (651, 817) submitButtonCo1or (75, 141, 249) sиbmitAnotherLink = (760, 224) # TODO: Предоставить пользователю возможность прекратить выполнение сценария. # Тооо: Дождаться окончания заrрузки страницы формы. # TODO: Заполнить поле Name. # TODO: Заполнить поле Greatest Fear(s). # TODO: Заполнить поле Source of Wizard Powers. # TODO: Заполнить поле RoboCop. # тооо: Заполнить поле Additiona1 Comments. # TODO: Щелкнуть на кнопке Submit. # TODO: Дождаться окончания заrрузки страницы формы. # Тооо: Щелкнуть на ссылке Submit another response. Теперь вам нужны данные, которые вы хотите фактически вводить в эту форму. В реальном мире эти данные MOryт браться из электронных таблиц, простых текстовых файлов или веб-сайтов, и ДЛЯ их ЗaJ'рУЗКИ В проrрамму может потребоваться дополнительный код. Однако ДЛЯ данноrо проекта вам будет достаточно закодировать эти данные в переменной. Добавьте в проrрамму следующий код. #! руthопЗ # formFi11er.py  Автоматически заполняет форму. пропущенный к:oд formData  [{'пате': 'A1ice', 'fear': 'eavesdroppers', 'source': 'wand' , 'robocop': 4, 'comments': 'Te1l ВоЬ I said hi.'}, {'пате': 'ВоЬ', 'fear': 'bees', 'soиrce': 'amu1et', 'robocop': 4, 'comments': 'n/a'}, ('пате': 'Caro1', 'fear': 'pиppets', 'source': 'crysta1 bal1', 'robocop': 1, 'comments': 'Please take the pиppets oиt of the break room.'}, {'пате': 'A1ex Mиrphy', 'fear': 'EO209', 'source': 'топеу', 'robocop': 5, 'comments': 'Protect the innocent. Serve the pиblic trust. Uphold the law.'}, ] пропу.щенНblЙ к:oд 
552 rлава 18 Список formData содержит по одному словарю для четырех различных лиц. В каждом словаре ключами служат имена текстовых полей, а значения- ми  ответы на соответствующие вопросы. Последний элемент настрой- ки  установка для переменной PAUSE значения, обеспечивающеrо создание паузы длительностью полсекунды после каждоrо вызова функции. Добавь- те в проrpамму в<:лед за инструкцией присваивания значения переменной formData следующую иш:трукцию: pyautogui.PAUSE  0.5 Шаr 3. На,.о 880да данн"х Виртуальный ввод данных в текстовые поля будет осуществляться в ци. кле for, выполняющем итерации по словарям, которые содержатся в спис- ке formData, с передачей значений словаря соответствующим функциям библиотеки РуАшоС UI. Добавьте в проrрамму следующий код. #! руthопЗ # formFiller.py  Автоматически заполняет форму. пропу.щенный KOД for person in fоrшDаta: * ПреДОC'l'аапение Dопьsова'1'8J1lO аО8МО1СНОС'1'И npeкра'l'И'l'Ь # ВUI10JIНeние сцеНapиsI. print ( I >>> 5CEJCYНДНAR ПАУЗА, ЧТОSЫ ПOJlЬЗOВAТЕЛЬ УСПЕЛ  НАВТЬ Кa&fНAЦИI) ЮJAВИIП <CТRL+C> «< I ) . tilDe.sleep(5) i До-да'1'ЬСЯ окончания зarpУSJCИ C'1'paИИЦW формы. . "hile not pyautogui.pixelМatcheaColor(sublaitвutton[O],  sublDitвutton[l), subJDitвuttonColor): tiIIIe. sleep (0.5) пропу.щенный KOД в качестве дополнительной меры безопасности в сценарии преду- смотрена пятисекундная пауза., предоставляющая пользователю воз- можность нажать комбинацию клавиш <Ctrl+C> (или переместить ука- затель мыши в верхний левый уrол акрана для возбуждения исключения FailSafeException) с целью прскращения работы проrраммы, если что-то пойдет не так. Затем проrрамма дожидается Toro момента, коrда на экране отобразится кнопка Submit ., ,(то будет свидетелы:твовать о завершении за- rрузки формы. Вспомните о том, что информация о координатах х и у и 
Управление клавиатурой и мышью с помощью средств автоматизации... 553 цвете, необходимая МИ определения места выполнения щелчка, была со-- хрансна в переменных submitBиtton и sиbmitBиttonColor на шаrе 2. Эта ин- формация передается функции pixelMatchesColor () в виде apryMeHToB submi tBиt ton [О] , sиbmi tBи t ton [1] и sиbmi tBиt tonColor соответственно. Добавьте в проrрамму еледующий код, введя ero после кода, орrанизую- Щtrо задержку до появления на экране кнопки Submit. #! python3 # formFiller.py  Автоматически заполняет форму. пропущенный KOД о print( 'ВВОди'1'СR инфopмaциR о %е...' % (person[ 'name']» о pyautogui .click (nameField[O], nu.eField[l]) # Запonнение пonк Name. О pyautoqui. typewri te (person [ 'name'] + '\ t' ) # Запonнение пonк Greatest Fear(s). . pyautoqui.typewrite(person['fear'] + '\t')  пропущенный к:oд Чтобы информировать пользователя о ходе выполнения проrраммы, мы добавИJIИ вызов функции print () , отображающий статус проrраммы в окне ее терминала .. Поскольку к этому моменту времени проrрамме уже известно, что форма зarpужена, ничто не мешает вызвать функцию click () для выполнения вир-- туальноrо щелчка на поле Name . и функцию t ypewri te () для ввода строки из элемента person [ 'пате'] .. Символ '\ t ' , который добавляется в конце строки, передаваемой функции typewrite () , имитирует нажатие клавиши <ТаЬ>, что перемещает фокус lUIавиатуры в следующее поле  Greatest Fear(s). Второй вызов функции typewrite () вводит в это поле строку из элемента person [ , fear'] и выполняет переход к следующему полю формы с помощью виртуальной клавиши <ТаЬ>.. Ш", 4. 06р,,60,., ,n.,,,о. 86160Р" . nepe"RIO",reRe. Обработка раскрывающеrося списка, предлаrающеrо варианты источ. ника "маrической силы" (Wizard Powers), и кнопок-переключателей, пред. лаrающих варианты ответа на вопрос о фильме РобfЖОn, требует несколько больших усилий, чем обработка текстовых полей. Выбор этих опций с по. мощью виртуальных щелчков мышью требует определения координат х и у каждоrо из соответствующих элементов управления. Для этой цели проще использовать нажатия виртуальных клавиш стрелок. 
554 rлава 18 Добавьте в проrpамму следующий код. ,! руthопЗ # formFiller.py  Автоматически заполняет форму. пропу.щенный KOД # Запопнение попя Source of Wizard Powers. . if person [ , source '] == 'wand': . pyautogui.typewrite(['down', '\t']) elif person['source'J == 'amulet': pyautogui.typewrite(['down', 'down', '\t']) elif person [ , source '] == 'crystal ball': pyautoqui. typewri te ( [ 'down', 'down', 'down', ' \ t' ] ) elif person['source'] == 'money': pyautogui.typewrite(['down', 'down', 'down', 'down',  '\t']) # Запопиение попя RoboCop. . if person [ , robocop' J == 1: Ф pyautoqui.typewrite([" '\t'J) elif person['robocop') == 2: pyautoqui.typewrite(['right', '\t']) elif person [ , robocop'] == з: pyautogui.typewrite(['right', 'right', '\t']) elif person [ 'robocop'] == 4: pyautogui.typewrite(['right', 'right', 'right', ,\t']) elif person [ , robocop'] == 5: pyautogui.typewrite(['right', 'right', 'right', 'right',  '\t']) пропу.щенный KOД Как только раскрывающийся список получит фокуе ввода (вспомните, что написали код, имитирующий нажатие клавиши <ТаЬ> после заполне-- ния поля Greatest Fear(s», нажатие клавиши <.J.> осуществит переход к сле-- дующему элементу списка выбора. Количество нажатий клавиши <.J.>, ко- торые должна имитировать проrрамма, прежде чем перейти с помощью клавиши <ТаЬ> к следующему полю, определяется значением, хранящим ся в элементе person [ ,soиrce' ] . Если значением ключа' soиrce' в словаре данноrо пользователя является' wand' ., то мы имитируем однократное нажатие клавиши <.J.> (для выбора волшебной палочки) и нажатие клави ши <ТаЬ> .. Если этим значением является' amulet " то мы имитируем ДВа нажатия клавиши <.J.> (для выбора амулета) и нажатие клавиши <ТаЬ> и так далее для всех остальных возможных вариантов выбора. Для выбора переключателей, соответствующих различным вариан там ответа на вопрос о фильме Робокоп, можно использовать имитацию 
Управление клавиатурой и мышью с помощью средств автоматизации... 555 нажатий клавиши <4-> или, если вы хотите выбрать первый из вариантов ., оrраничиться нажатием клавиши пробела.. Ш",5. Отnро.." формы. O..A"Hlle Можно заполнить поле Additional Comments (Дополнительные коммента- рии) с помощью функции typewrite (), передав ей в качестве арI)'Мента ЭJI мент person [' comments']. Дополнительно можно ввести символ' \t', чтобы перемсстить фокус ввода на кнопку Submit. Как только кнопка Submit полу- чит фокус, вызов pyaиtogui.press ('enter') имитирует нажатие клавиши <Enter> и uтправит форму. После отправки формы ваша проrрамма будет в течение пяти секунд ожидать заrрузки следующей страницы. как только заrрузится друrая страница, она пuлучит ссылку Subтit another response, кuтuрая перенаправит браузер на новую, пустую страницу формы. Вы сохраняете координаты этой ссылки в виде кортежа в пере мен ной submitAnotherLink на шаrе 2, поэтому передайте эти координаты функции pyautogui. click () для имитации щелчка на этой ССbIЛке. Коrда новая форма будет rотова к работе, внешний цикл for сможет пе-- рсйти к следующей итерации и ввести в форму информацию, относящуюся к следующему лицу. Завершите nporpaMМY, добавив в нее следующий код. #!. руthопЗ # formFiller.py  Автоматически заполняет форму. пропущенНЬlЙ KOД * ЗапопН8ние попя Additional Comтents. pyautoqui . typewri te (per8on [ , ooaent8 '] + '\ t' ) * Вwnопн8ИИ8 чка на кнопке Submit. pyautoqui.pre88 ('enter') * Дожда'1'ЬСR окончаНИJII зarpузки страницы Формы. print('ВwnoпHeH щепчок на кнопке Submit. ') time . 81eep (5) * Щ-nчок на ссыпке Submit another response link. руаutogui.сliсk(8ubшitAnоthеrLink[О], 8ubшitAnоtherLink[1]) Как только основной цикл for завершит свою работу, проrрамма будет pacnOJIaraTb пuлной информацией по каждому лицу. В данном примере мы имеем дело Bcero лишь с четырьмя лицами. Но если речь идет о четырех тысячах человек, то написание подобной ПрOl'раммы сэкономит вам массу времени и избавит от необходимости потеть за клавиатурой! 
556 rЛQва 18 РеЗlOме Средства GUIавтоматизации, предлаrаемые модулем pyaиtogui, IЮЗВО- ляют вам взаимодействовать с приложениями, выполняющимися на вашем компьютере, посредством управления мышью и клавиатурой. Несмотря на то что этот подход достаточно rибок для Toro, чтобы выполнять все те действия, которые может выполнять человек, у Hero есть один недостаток, заключающийся в определенной слепоте проrрамм, в которых данный подход используется для имитации щелчков мышью и клавиатурноrо вво- да. При написании проrрамм GUIвтоматизации всеrда старайтесь обе- (печить возможность их быстроrо аварийноrо завершения, если им были предоставлены неподходящие инструкции. Аварийное завершение может раздражать, но все же это лучше, чем некорректное выполнение проrрам- мы, дающей непредсказуемые результаты. Используя средства PyAutoGUI, можно перемещать указатель мыши по экрану, а также имитировать щелчки мышью и нажатия обычных и rорячих клавиш. Кроме Toro, модуль pyautogui обеспечивает возможность проверки цвета пикселей на экране, что может быть использовано для анализа со. держимоrо экрана с целью контроля нормалыюrо процесса выполнения проrрамм GUI-автоматизации. Вы даже можете предоставлять средствам PyAиtoGUI снимки экрана и позволить им определять координаты области, в которой следует выполнить виртуальный щелчок мышью. Все эти возможности библиотеки PyAutoGUI можно комбинировать для автоматизации выполнения любых повторяющихся задач. В действитель- ности вид указателя мыши, автоматически перемещающеl'ОСЯ по экрану, или текста, появляющеrося в полях формы без вашеl'О вмешательства, дей- ствует завораживающе. Почему бы не потратить сэкономленное время на то, чтобы откинуться на спинку кресла и по наблюдать, как проrрамма вы- полняет всю работу за вас? Это ведь так приятно  осознавать, что твои уме- ния и смекалка позволили тебе избавиты:я от бремени рyrинной работы! КОНТРОllltные вопросы 1. Как запустить средства резервноrо выхода PyAutoGUI для нрекраще- ния работы проrраммы? 2. Какая функция возвращает текущее разрешение экрана? 3. Какая функция возвращает координаты текущей позиции указателя мыши? 4. В чем различие между функциями pyautogui .тоуеТо () и pyautogui. moveRel () ? 5. Какую функцию можно использовать для перетаскивания указателя мыши? 
Управление клавиатурой и мышью с помощью средств автоматизации... 557 6. Вызов какой функции введет текст «НеНо world!»? 7. как сымитировать нажатия специальных клавиш клавиатуры, таких как <>? 8. Как сохранить текущее содержимое экрана в файле изображения screeпshot. png? 9. С помощью какоro кода можно задать паузу длительностью две ceкyн ды после каждоro вызова функции библиотеки PyAиtoGUJ? Учебные проекты Чтобы закрепить полученные знания на практике, напишите проrрам мы для предложенных ниже задач. Ka .Р."ОР.'''' заН.,.,II Мноrие из проrрамм MrHoBeHHoro обмена сообщениями определяют, нет ли вас на месте, Т.е. перед компьютером, обнаруживая отсyrствие пе мещений мыши в течение определенноrо периода времени, скажем, деся ти минут. Возможно, вы отошли от компьютера, чтобы перекусить, но не хотите, чтобы дрyrие люди моrли подумать, основываясь на отображаемом (:татусе проrраммы'мессенджера, что вы бездельничаете. Напишите сце- нарий, который будет слеl'ка перемещать указатель мыши каждые десять минyr. Эти перемещеllИЯ должны быть достаточно небольшими, чтобы не мешать вашей работе, если вам потребуется использовать компьютер во время работы сценария. 50Т 11.1 о'.ра.". .rио.еННtlХ ,006щен.. Coogle Talk, Skypc, Уа}lOО Messenger, ЛIМ и друrис приложения для об.- мена мrновенными сообщениями нередко используют lIроприетарные протоколы, затрудняющие друrим разработчикам написание сценариев на Python, способных взаимодействовать с этими проrраммами. Но даже эти проприетарные протоколы не CMoryт помешать вам писа1Ъ проrpаммы GUI-автоматизации. В приложении Google Talk имеется строка поиска, позволяющая вам ввести имя пользователя из списка ваших друзей и открывающая окно со- общений при нажатии клавиши <Enter>. Фокус ввода автоматически пе мещается в новое окно. Друrие приложения-мессенджеры предлarают aнa лоrичные способы открытия новых окон сообщений. Напишите nporpaммy для автоматической рассылки уведомлений rруппе людей И.l списка друзей. Вашей проrрамме придется обрабатывать различные исключительные слу- чаи, такие, например, как отсyrствие адресатов в сети, появление окна чата 
558 r лава 18 в разных местах экрана или открытие диалоrовых окон для подтверждения действий, что может прерывать работу сценария. Ваша проrрамма должна будет получать снимки экрана и использовать их для управления взаим<r действием с rрафическим интерфейсом пользователя, а также предусма- тривать способы обнаружения ситуаций, в которых отправка виртуальных клавиатурных нажатий не может быть осуществлена. ПjJuечанue Возможно, вам стоит создат'Ь теcmoвые фиктивные учетные запиcu, 'Чтобы вы случайно ие забросали спамо..ч своих друзей, пока пишете эту профам.му. PyIlOIORtrlO ПО 'ОJАIIИ81О 8"01010 "ТО На сайте http://nostarch.com/aиtomatestиff/ вы найдете замечательное практическое руководство под названием "Создание бота, иrрающеrо в онлаЙНИI"рЫ, па языке Python". В этом руководстве объясняется, как сQЗ- дать проrрамму, основанную на средствах GUI-автомати3аЦИИ Python, кото- рая способна ИI"рать во флеш-иrру под названием Суши-бар. В этой иrре нуж- но щелкать на кнопках, соответствующих различным инrредиентам, для оформления заказов суши. Чем быстрее вы заполните заказы, не допустив ошибок, тем больше очков заработаете. Эта иrра идеально приспособлена для Toro, чтобы запроrраммировать ее средствами GUIавтоматизации и при этом... HeMHoro схитрить, добавив себе лишних очков! В этом руковод- стве затронуты мноrие вопросы, рассмотренные в данной rлаве, а кроме Toro, описаны базовые возможности распознавания образов средствами PyAutoGUI. 
УСТАНОВКА МОДУЛЕЙ СТОРОННИХ РАЗРАSОТЧИКОВ Помимо стандартной бибЛИОТtКИ модулей, входящей в поставку PytllOn, можно использовать модули сторонних разработчиков, расширяющие возможности исходноrо Ha бора. Основным средством для установки модулей J>ytl1011, разработанных ДРУI'ИМИ лицами, является утилита pip. Она обеспечивает безопасную заrрузку и установку модулей J>ythOll, доступных на сайте орrанизации Pyt}lOll Software FOUIldatioll по адресу https: / /pypi .python. org/. Сайт PyJ>I (от анrл. PytllOlll'a(kage Index  KaTor пакетов PythOIl) иrpает роль CВOCI"O рода "мaraзина бе(:lIлатных I1рИ ложений" для J>ythOIl. VТИlIита pip ИСlIолняемый файл утилиты pip для Windows называется pip, а дЛЯ OS Х и Нllих  рiрз. Путь к нему в Windows  С:\РуthопЗ4I,Scriрts'Фiр.ехе, в 05 Х  /I.iЬrаry/Frатewоrks/Руthоп.frатework/Versiопs/З.4/Ып/рiрз, в Linux  /usr/ bin/Pip3. в то время как в Willdows и 05 Х утилита pip устанавливается автомати чески вместе с Pyt}lOn 3.4, в Linux ее нужно устанавливать отдельно. Чт<r бы установить pip3 на компьютерах, работающих под управлением Ubuntu или Debiall I.iIlUX, откройте новое окно терминала и введите команду sиdo aptget install python3pip. В случае Fedora Linux для этою следует BBe сти в окне терминала команду sudo yиm install python3pip. Возможно, для установки этоrо IIporpaMMHoro обеспечения вам придется ввести пароль системноrо администратора. 
560 Приложение А Установка сторонних МОДУllей Утилита pip предназначена для запуска из командной строки: ей пере- дается команда install, за которой следует имя устанавливаемоrо модуля. Например, на компьютерах с Windows необходимо ввести команду pip install имя  модуля. На компьютерах с 05 Х или Linux yrилита рiрЗ должна запускаться с префиксом sudo, предоставляющим административные при- вилеrии для установки модуля. В этом случае команда принимает следую- щий вид: sudo рiрЗ install Имямодуля. Если модуль уже установлен, но вы хотите обновить ero до последней версии, доступной на сайте PyPI, выполните следующую команду: pip install u Имямодуля(илирiрЗ install u Имямодулявслучае05Хили IJnux). После Toro как модуль установлен, можно протестировать корректность ero установки, выполнив команду import имя  модуля В интерактивной обо- лочке. В случае отсyrствия сообщения об ошибке можно полаraть, что YCTa новка модуля прошла успешно. Чтобы установить все модули, о которых шла речь в книrе. выполните перечис.ленные ниже команды. (Не забывайте о том. что в случае 05 Х или Linux вместо pip следует использовать рiрЗ.) . pip install send2trash . pip install requests . pip install beaиtifиlsoup4 . pip install selenium . pip install орепрухl . pip install PyPDF2 . pip install pythondocx (именно pythondocx, а не docx) . pip install imapclient . pip install pyzmail . pip install twilio . pip install pillow . pip install pyobjccore (только на компьютерах с 05 Х) . pip install pyobjc (только на компьютерах с 05 Х) . pip install руthопЗхliЬ (только на компьютерах с Lin\lx) . pip install pyaиtogиi Пpwcечанue Полмователя.м 08 Х: дл.я ycmauo(J1(u .модул.я pyobj с .может nотребоват'Ься О1(,оло 20.мииут или даже бол'Ьше, поэтому ш! беCnО1(,оumeсъ, если оиа выnолu.яemся ие так быстро,ка'К вы рассчитШJaJlИ. Кроме тоео, первы.м с.ледует уcmаи08ит-ь .модуЛ'Ь pyobj ccore, что noзволит умен.'Ьшuт'Ь ofYи&ee вpp.мJl. уста'IWfJ'КИ. 
ЗАПУСК пРоrРАММ Выполнить проrрамму, открытую в окне файловоrо ре- дактора IDLE, не составляет труда  для этоrо достаточно нажать клавишу <F5> или выбрать пункты меню RunqRun Module (ВЫПОЛНИТЬВЫПОЛНИl'ь модуль). Это простейший способ запуска проrрамм в процессе их написания, но OT крывать IDLE для запуска rотовых проrpамм  слишком об ременительный метод. Для выполнения сценариев, написанных на языке I>ython, существуют более удобные <:IIOсобы. "Маrическа." строка Каждая ваша проrрамма на Pyt}lOll должна начинаться с "маrической" строки (аш'Л. "s}leballg liпе"), которая сообщает компьютеру о том, что вы- полнение данной проrраммы вы поручаете Pyt}lOn. "Маrическ(UI" строка начинается символами * ! , НО в остальном ее вид зависит от используемой вами операционной системы, а именно: . Wil1dows  #! руthопЗ; . OS Х  t! /иsr/bin/env руthоnЗ; . I.il1ux  #! /иsr/Ып/руthОПЗ. При запуске сценариев на Pyt}lOn в окне II)LE "маrическая" строка явля. ется излишней, однако она необходима IIрИ :Jапуске сценария из КОМaJЩНОЙ строки. Запуск nporpaMM на Python в Windows в Windows интерпретатору Python 3.4 соответствует путь С: \РуthопЗ4 \ python. ехе. Удобный альтернативный вариант предлаrает проrрамма ру. ехе, которая читает "мarическую" строку в начале .P.rФайла, содержащ ro исходный код, и запускает версию Python, подходящую для этоro сце- 
562 Приложение Б нария. Проrрамма ру.ехе rарантированно запускает нужную версию PytllOn, если на компьютере уетановлены сразу несколько версий. Чтобы обеспсчить удобный запуск своей проrраммы на Python, создайте пакетный файл (файл с расширением .Ьае), который будет запускать про. rpaMMY с помощью исполняемоrо файла ру.ехе. Для этоrо создайте простой текстовый файл, содержащий Bcero одну строку следующею вида: @ру.ехе С:\путь\к\вашем.у\сценарию\руthопSсriрt.ру %* Подставьте вместо указанноrо здесь пути абсолютный пyrь к своей про- rрамме и сохраните этот файл с расширением .bat (например, как файл pythoпScript.bat). Этот пакетный файл избавит вас от необходимости вводить полный абсолютный пyrь к файлу проrpаммы на Python при каждом ее за- пуске. Я рекомендую сохранять все свои .bat- и .руфайлы в одной папке, на- пример C:\МyPythoпScripts или С:\llОЛ'Ь3оватеJl,и\8аше имя'!УthonScriрts. Имя папки C:\МyPythoпScripts следует добавить в список каталоrов Windows, в которых расположены исполняемые файлы, чтобы пакетные файлы можно было запускать из диалоювоrо окна Выполнить. Для этоrо из- мсните содержимое переменной среды РАТН. Щелкните на кнопке Пуск и начните вводить текст ИЗ.м,еие'Нue пере.м.е'Н.'Н'ых среды meкye2o пОЛ'Ь30вателя. Эта опция должна автоматически отобразиться средством автозавершения 110 мере ввода вами ее названия. Щелчок на этой опции открывает диалоrовое окно Переменные среды (рис. Б.l). о.".. .'. ',' iйJ nеременН"и, с".,д.. [!ере.......... q:JeQOI nOl1D>OeIIT.... АЛЯ AIe. A э...ачение :1 . ТЕМР '!oUSERPROFILE%\AppO.\j.o\Te ТМР "oUSERPR.OF!LE'Io\API>O'\j.o\Temp ,Q>'Aeтb,,.: :.b:.. J '.___' Сиctм-le nePeНt!ННЫe , !1ереме.....  CLASSPАТН \. : ComSpec с:\:.'lndО"\SistemЭ2\1:md.ехе FP fiO..,НOST C." NO ..OF..P", -- еж i о...... Рис. Б. 1. Окно Перемвнные среды в Windоws 
Запуск nporpaMM 563 в разделе Переменные среды пальзователя для <В8шеuмя> выделите пере менную Path и щелкните на кнопке Изменить. Добавьте в конце TeKcToBol'O поля Значение точку с запятой и введите C:l,МyPythoпScripts, после чеl'О щел кните на кнопке ОК. Теперь для запуска любоrо сценария, файл KOToporo находится в папке C:l,МyPythoпScripts, достаточно будет нажать комбинацию клавиш <Win+R> и ввести имя сценария. Например, ввод имени pythoп,')'cript запустит пакетный файл pythonScript.bat, который, в свою очередь, избавит вас от необходимости вводить в диалоrовом окне Выполнить длинную KOMaH дуру.ехе C:\MyPythonScripts\pythonScript.py. Запуск nporpaMM на Python в OS Х и Linux в 05 Х последовательно выберите АррliсаtiопsUtilitiеsТеrmiпаl, что при ведет к открытию окна Теrmiпаl. Окно терминала обеспечивает возможность ввода команд исключительно в виде текста, а не с помощью мыши посред ством rрафическоrо интерфейса. Чтобы открыть окно терминала на KOM пьютерах с Ubuntu Ijnux, нажмите клавишу <Win> (или <5uper» для  крытия интерфейса Dash и введите Terminal. ОКIIО терминала открывается в базовой папке вашей учетной записи пользователя. Если моим именем пользователя является asweigart, то базо-- вой папкой будет jUsersjasweigart в 05 Х и /horм/asweigart в LillUX. Символ "ТИЛьда" () является сокращенным обозначением базовой папки, поэ тому для перехода к ней можно ввести команду cd . Команду cd также МОЖНО использовать для смены текущеl'О рабочеrо каталоrа. Как в 05 Х, так и в Linux для вывода имени текущеro рабочеrо каталоrа ИСПОJlЬ.lуеТОI команда pwd. Чтобы запустить проrpамму, написанную на языке PytlIOIl, сохраните ее .руфайле в своей базовой папке. Затем измените правадо<:тупа к этому фай лу, выполнив команду chmod +х pythonScript. ру. Рассмотрение нрав до<:тупа к файлам и каталоrам ВblXодит за рамки данной книrи, но вам обязательно нужно будет выполнить эту команду по отношению к файлу IIроrраммы, если вы хотите запускать ее из окна терминала. Сделав это, вы СМОЖете в любой момент запустить проrpамму, открыв окно терминала и введя K() манду . /pythonScript. ру. "Мarическая" строка в начале сценария сообщит операционной системе, rде искать исполняемый файл интерпретатора pyt }lOп. Запуск nporpaMM на Python с ОТКnlOченными утвер-дениtlми Вы сможете несколько увеличить скорость вьшолнения своих про rрамм на Python, отключив инструкции утверждений. Для этоrо, запуская 
Приложение Б 564 проrрамму из окна терминала, укажите переключатель o после python или руthопЗ и перед именем .ру- файла проrраммы. Это приведет к запуску опти- мизировашlOЙ версии вашей проrpаммы, в которой инструкции утвержде- ний будут иrнорироваться, 
ОТВЕТЬ. НА КОНТРОЛЬНЫЕ ВОПРОСЫ В этом приложении даны ответы на контрольные вопро сы, приведснные в конце каждой rлавы. Я настоятельно рекомендую вам пытаться самостоятельно находить пра вилыlеe ответы на них и обращаться к данному приложе нию лишь для сверки. Чтобы научиться проrраммировать, одноrо лишь знания синтаксиса языка и названий функций недостаточно. Как и при изучении иностранноrо языка, чем больше вы будете применять свои знания на практике, тем лучше будyr результаты. Существует множество веб-сайтов, на которых можно аналоrичным обра- зом проверить свои знания. Список их адресов приведен на саше http:/ / nostarch.com/aиtomatestиff/. rllaBa 1 1. Операторами являются +, , * и /. Значениями являются 'hell0', 8. 8 и 5. 2. Строка  'spam'; l1еременная  spam. Строки вcerдa заключаются в Ka вычки или апострофы. 3. Три типа данных, введенных в этой rлаве,  это целые числа, веще ственные числа и строки. 4. Выражение  это сочетание значений и операторов. Любое выраже- ние сводится к одиночному значению. 5. Любое выражение вычисляется, давая одиночное значение. В случае инструкций это не так. 6. Для персменной bacon установлено значение 20. Выражение bacon + 1 не изменяет значение bacon (ДЛЯ этоrо потребовалась бы инструкция присваивания: bacon  bacon + 1). 7. Вычисление обоих выражений дает одну и ту же строку 'spamspam spam'. 8. Имена переменных не Moryr начинаться с цифры. 
566 Приложение В 9. Целочисленную, вещественную и строковую версии передаваемоrо им значения возвращают соответственно функции int (), float () и str (). 10. В данном выражении ошибка возникает из-за Toro, что 99  целое чис- ло, Torдa как с помощью оператора + MOryт конкатенироваться только строки. Для получения правильноrо результата следует ИСIlользовать запись вида' я съел ' + str (99) + I лепешек. 1. rllaBa 2 1. Это значения Trиe и False, причем первые буквы, т и F,  прописные, остальные  строчные. and, or, and not. 2. Trиe and True  Trиe. Trиe and False  False. False and True  False. False and False  False. Trиe or Trиe  Trиe. True or False  Trиe. False or Trиe  Trиe. False or False  False. not Trиe  False. not False  True. 3. False False Trиe False False True 4. , ! =, <, >, <= и >=. 5. Оператор равенства == (:равнивает два значения и возвращает реЗУЛIr тат сравнения в виде булева значения, Torдa как оператор I1РИСВаива ния = сохраняет значения впеременной. 6. Условие  это используемое в управляющих инструкциях выражение, вычисление KOТOporo дает булево значение. 7. Тремя блоками являются: все тело инструкции i f, строка print ('bacon') и строка print ('ham'). print ( 'eggs' ) if spam > 5: 
Ответы на контрольные вопросы 567 print ('bacon') else: print ('ham') print('spam') 8. Код: if spam  1: print ( 'He1l0' ) е1Н spam  2: print ( 'Howdy' ) е1эе: print('Greetings!') 9. Для прекращения выполнения проrраммы, увязшей в бесконечном цикле, следует нажать комбинацию клавиш <Ctrl+C>. 10. Инcrрукция break осуществляет выход из цикла и продолжает выпл-- нение с инструкции, следующей за циклом. Инструкция continue осу- ществляет переход в начало цикла и продолжает ero выполнение со следующей итерации. 11. Все эти вызовы делают одно и то же. Вызов range (10) задает диапа- зон изменения переменной цикла от О ДО (но не включая) 10; вызов range (О, 10) в явном виде задает начальное значение переменной цикла равным о; вызов range (О, 10, 1) в явном виде задает инкре- мент псременной цикла равным 1 на каждой итерации. 12. Код: for i in range (1, 11): print(i) и i '"' 1 whi1e i < 10: print(i) i = i + 1 13. С 1I0МОIЦЬЮ вызова эрат. bacon ( ) . rllaBa 3 1. Функции уменьшают потребность в дублировании кода, что поло- жительно сказывается на размере проrрамм и их удобочитаемости и упрощает их обновление. 2. Код функции выполняется тоrда, коrда она вызывается, а не ТOI'Да, КOI'Да определяется. 
568 Приложение В 3. Для опрсделения (т.е. создания) функций предназначена инструкция def. 4. Функция включает инструкцию def и код, образующий ее тсло. Вызов функции  это передача унравлсния потоком вьшолнеllИЯ HporpaMMbI в тело функции и выполнсние ес кода для вычисления возвращаемоrо значсния. 5. Существует только ОДНа rлобальная область видимости, Torдa как каж /ый вызов функции создаст свою локальпую область видимости. 6. КOI'да нроисходит возврат И:J функции, ее локальная область видимо- сти уничтожается и вся информация о ее l1еремепных теряется. 7. Возвращаемое значение  это значение, вычисляемое в результате BЫ зова функции. как и любое др)'I'ое значение, возвращасмое функцией значсние может быть использовано в качсстве части выражения. 8. Если в функции отсутствует инструкция retиrn, ее возвращаемым зна чением является None. 9. l1.ля этоrо следует предварить имя I1еремепной ключевым словом globa1. 10. Тином данных значения None является NoneTypc. 11. Эта инструкция import импортирует модуль areallyoиrpetsnamederic. (Кстати, TaKoro модуля Pytholl не существует.) 12. Эту функцию можно вызвать ( но мощью вызова spam. bacon ( ) . 13. ПоместиТl. строку кода, в которой может возникнyrь ошибка, в тело инструкции try. 14. В ИНСТРУКI{ИЮ try помещастся КОД, в котором возможно возникнове-- lIие ошибки. В инструкцию except номещается код, который должен быть выполнен в СJIучае возникновения ошибки. rllaBa 4 1. Пустое списковое :шаченис, Т.С. списковое значение, не содержащее элсментов, :Jдесь наблю/ается полная аналOI'ИЯ с тем, как' , является пусrым строковым ЗIJaчением. 2. spam[2]  'hello'. (Обратите внимание нато, что третьим значением в списке является элемент с индексом 2, поскольку индексация начи- нается с О,) 3. 'd'. (Эаметьте, что рсзультатом вычисления выражения '3' * 2 явля стся строка' ЗЗ', которая персдается функции int () до выполнения онераlИИ /слсния на 11. Конечным результатом является целочислен- ное значение З. Выражсния MOryr использоваться везде, rде использу- ются значения.) 
Ответы на контрольные вопросы 569 4. 'd'. (Отрицательные значения индексов отсчитываются с конца.) 5. ['а', 'Ь'] 6. 1 7. [3.14, 'cat', 11, 'cat', Trиe, 99] 8. [3.14, 11, 'cat', Trиe] 9. Оператором конкатенации списков является +, оператором реплика- ции  *. (То же самое, что и для строк.) 10. В то время как функция append () добавляет значения только в конце списка, функция insert () может добавлять их в любом месте списка. 11. Для удаления значений из списка MOlyr ИСlIользоваться инструкция del и снисковый метод remove () . 12. Как спис.ки, так и строки MOryт передаваться функции len (), иметь индексы и срезы, использоваться в циклах for, конкатенироваться и реплицироваться, а также использоваться совместно с операторами in и not in. 13. Списки изменяемы; они допускают добавление, удаление и изменение хранящихся в них значений. Кортежи неизменяемы; они не допуска- ют В<юбще никаких изменений. Кроме Toro, кортежи записывают с использованием крyrлых скобок, ( and ), тоrда как списки  с исполь- зованием квадратных скобок, [ and ]. 14. (42,). (Завершающая запятая обязательна.) i5. С помощью функций tup1e () и list () соответственно. 16. Они содержат ссылки на списковые значения. 17. Функция сору. сору () создает поверхностную копию списка, тorдa как функция сору. deepcopy ()  полную копию. Это означает, что только функция сору .deepcopy () будет дублировать списки, содержащиеся в составе друrих списков. rllaBa 5 1. Две фиrypные скобки: {}. 2. {'foo': 42) 3. Элементы словаря не упорядочены, тоrда как элементы списка упо- рядочены. 4. Возникнет ошибка KeyError. 5. Между ними нет никакой разницы. Оператор in проверяет существо- вание значения по существованию ключа в словаре. б. Выражение 'са t' in spam проверяет, существует ли ключ · са t' в сло- варе, тоrда как выражение 'cat' in spam. valиes () IIроверяет, суще-- ствует ли значение · cat' для одноrо из ключей в словаре spam. 
570 Приложение В 7. 'spam.setdefaи1t('co1or', 'black') 8. pprint.pprint() rllaBa 6 1. Экранированные символы представляют те символы в строковых значениях, которые иначе было бы трудно или невозможно ввести в код. 2. \n  символ новой строки; \ t  символ табуляции. 3. Символ обратной косой черты представляется в строке экранирован ным символом \ \. 4. Апостроф в строке "How1 '5 is fine" вполне уместен, поскольку нача ло и конец строки обозначены кавычками. 5. Мноrострочные блоки допускают включение символов новой строки без использования экранированном символа \n. 6. Вычисление этих выражений даст следующие результаты: . 'е' . 'He11o' . 'Не11о' . '10 wor1d! 7. Вычисление этих выражений даст следующие результаты: . 'HELLO' е Trиe . 'he11o' 8. Вычисление этих выражений даст следующие результаты: . ['Remember, " 'remember, " 'the', 'fifth', 'of', 'November.'] . 'Therecanbeonlyone.' 9. Строковые методы rjust (), 1jиst () и center () соответственно. 10. Пробельные символы в начале и конце строки удаляются с помощью методов lstrip () и rstrip () соответственно. rllaBa 7 1. Функция re. compile () возвращает объект Regex. 2. "Сырые" (троки используют для TOI'O, чтобы символы обратной косой черты не Надо было экранировать. 3. Метод search () возвращает объект Мatch. 4. Метод groиp () возвращает строки, соответствующие шаблону pery- лярноrо выражения. 
Ответы на контрольные вопросы 571 5. rруппа о соответствует всему совпавшему тексту, rруппа 1  тексту, со- впавшему с выражением в первой паре круrлых скобок, rруппа 2  тексту, совпавшему с выражением во второй I1ape круrлых скобок. 6. Точки и крyrлые скобки можно экранировать символами обратной ко- сой черты: \., \ ( и \). 7. Если в реryлярном выражении отсyrствуют l"рУППЫ, возвращается спи сок строк; в противном случае возвращается список кортежей строк. 8. Символ I означает поиск соответствия одной из двух альтернативных rрупп. 9. Символ? может означать либо поиск нулевоrо или единичноrо коли чества вхождений предыдущей rруппы, либо Нtжадный поиск. 10. Символ + означает одно и более вхождений искомоrо выражения. Символ * означает нуль или несколько вхождений искомоro выраже-- ния. 11. Записи {З} соответcrвуют ровно три вхождения предыдущей rpуппы. Записи {З, 5} соответствуют от трех до пяти вхождений. 12. Сокращенные симВольные классы \d, \w и \s означают поиск одиноч ной цифры, словарноrо или про6ельноrо символа соответственно. 13. Сокращенные символьные lUlacCbl \О, \W и \S означают поиск одиноч Horo символа, не являющеrося цифрой, словарным или пробельным символом соответственно. 14. Передача константы re. 1 или re. IGNORECASE в качестве второro apry- мента функции re. соmрНе () сделает реrулярное выражение нечув- ствителыlмM к реrистру. 15. Обычно символ. совпадает с любым символом, за исключением сим- вола нОвой строки. Если функции re. compile () передать константу re. DOTALL в качестве BTOPOI"O apryмeHTa, то точке будет соответство- вать также символ новой строки. 16. Символы. * задают режим жадноrо поиска, а символы. *?  нежадllоrо. 17. Либо [O9az], либо [azO9] 18. 'х drwnmers, Х pipers, fi ve rings, Х hens' 19. ApryмeHT re. VERBOSE делает возможным добавление пробельных сим- волов и комментариев в строку, передаваемую функции re. compile ( ). 20. re. compile (r' Л\d {1, З) (, {З}) * $'), НО возможны и друrие варианты строк реryлярных выражений, используемых в качестве apryMeHTa. 21. re.compile(r' [AZ] [az]*\sNakamoto') 22. re.compile(r' (AliceIВobICarol)\s(eatslpetslthrows)\ s(appleslcatslbase balls)\. " re.IGNORECASE) 
m Приложение В rllaBa 8 1. Относительные пути определяются относительно текущеrо рабочеrо каталоrа. 2. Абсолютные пyrи начинаются с корневой папки, например / или с: \. 3. Функция os.getcwd() возвращает текущий рабочий каталоr. Функция os. chdir () изменяет текущий рабочий Каталоr. 4. Папка .  это текущая папка, папка. .  ее родительская папка. 5. с: \Ьасоп \eggs  это имя каталоrа, Torдa как spam. txt  базовое имя. 6. Строка' r' задает режим чтения, строка' w I  режим записи, а строка , а'  режим присоединения. 7. Содержимое существующеrо файла, OTKpblToro в режиме записи, уни- чтожается и полностью перезаписывается. 8. Метод read () возвращает все содержимое файла в виде одноrо СТРО- Koвoro значения. Метод readlines () возвращает список строк, в ката- ром каждая строка представляет строку содержимоrо файла. 9. Орrанизация хранилища, создаваемоrо с помощью модуля she1ve, напоминает орrанизацию словаря; в хранилищах, как и в словарях, используются ключи и значения, а также методы keys () и va1иes (), работающие аналоrично одноименным методам словарей. rllaBa 9 1. Функция shutil. сору () копирует одиночный файл, тоrда как функ- ция shиtil. copytree () копирует всю папку вместе со всем ее содер- жимым. 2. Функция shu ti1. move () используется для переименования файлов, а также для их перемещения. 3. Функции модуля send2trash перемещает файл или папку в корзину, Тоrда как соответствующая функция модуля shu til безвозвратно уда- JlЯет файлы и папки. 4. функция zipfile. ZipFile () эквивалентна функции open ( ); ее первым apryмeHToM является имя файла, а вторым  режим, в котором откры- вается ZIР-файл (чтение, запись, присоединение ). rllaBa 10 1. assert (spaт >= 10, 'Значение переменной врат меньше 10.') 2. assert (eggs .10wer () ! '" bacon .10wer (), 'Переменные eggs и Ьасоп coдep жат одинаковые строки! ') или assert (eggs. upper () ! = Ьасоп. иpper () , 'Переменные eggs и bacon содержат одинаковые строки! ' ) 
Ответы на контрольные вопросы 573 3. assert (False, 'Это утверждение всеI'да возбуждает исключение AssertionError.') 4. Чтобы можно было вызывать ФУНКЦИЮ logging . debиg ( ) , в начале " rраммы должны нахОДиться следующие две строки кода. import 10gging logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s  CQ %(leve1name)s  %(message)s') 5. Чтобы можно было записывать сообщения в файл журнала program 10g. t х t с помощью функции 10gg ing . debиg () , в начале проrраммы должпы находиться следующие две строки кода. import 10gging logging.basicConfig(fi1ename='programLog.txt', CQ 1eve1=10gging.DEBUG, format=' %(asctime)s  % (levelname)s  CQ % (message) s' ) 6. DEBUG, INFO, WARNING, ERROR и CRITICAL 7. 10gging.disable(10gging.CRITICA1) 8. Вывод сообщений протоколирования можно отключит.., не удаляя вызовы функций протоколирования. Вы можете селективно отклю- чать вывод сообщений протоколирования, соответствующих ошиб- кам более низких уровней критичности. Сообщения протоколирова ния можно создавать. Сообщения протоколирования обеспечивают создание временных меток. 9. По(ле щелчка на кнопке Step отладчик вызывает функцию и останав- ливается на первой строке ее кода. После щелчка на кнопке Over вы- полняется не только вызов функции, но и весь ее код n обычном ре- жиме. После щелчка на кнопке Out строки кода выполняются в обыч- ном режиме до тех пор, пока не будет осуществлен возврат из текущей функции. 10. После щелчка на КНОIlке Go запускается процесс обычноrо выполне- ния ПрОI'раммы до ее полноrо завершения или до достижения строки кода с точкой останова. 11. Установление точки останова для строки кода приводит к тому, что при достижении этой строки отладчик приостанавливает выплне-- ние проrраммы. 12. Чтобы установить точку останова в IDLE, следует щелкнyrь правой кнопкой мыши на строке кода и выбрать в контекстном меню пункт Set Breakpoint. 
574 Приложение В rllaBa 11 1. в модуле webbrowser имеется метод open ( ) , который запускает брау зер и направляет ero по определенному URLадресу. и на этом все. Модуль reqиests может заrружать файлы и страницы из Интернета. Модуль Beautiful 50ир осуществляет синтаксический анализ HTML. документов. Наконец, модуль selenium может запускать браузер и управлять ero выполнением. 2. Функция requests. get () возвращает объект Response, имеющий атри- бут text, который содержит заrруженное содержимое в виде строки. 3. Метод raiseforstatиs () возбуждает исключение в случае возникн БеН ия проблем в процессе заrpузки и ничеrо не делает в случае успеш ной заrpузки. 4. Атрибут statиscode объекта Response содержит код состояния НТТР. 5. Для этом следует открыть на компьютере новый файл в режиме за писи двоичных данных ( 'wb' ) и использовать цикл for для итерирО- вания по возвращаемому значению метода itercontent () объекта Response для записи порций содеРЖИМОI'О в файл. saveFile = open('filename.html', 'wb') for chunk in res.iter content(lOOOOO): saveFile.write(chunk) 6. Для открытия панели инструментов разработчика в браузере Chrome следует нажать ЮJaВИШУ <F12>. В браузере Firefox для этоrо следует нажать комбинацию клавиш <Ctrl+5hift+C> (Windows и Liпuх) или <X+Option+C> (05 Х). 7. Для этоrо следует щелкнуть правой кнопкой мыши на элементе на странице и выбрать в открывшемся контекстном меню пункт Просмо- треть КОД. 8. 'imain' 9. '. highlight ' 10. 'div div' 11. 'bиtton[valиe=»favorite»]' 12. spam.getText() 13. linkElem.attrs 14. Для импортирования модуля sеlепiшn следует использовать инструк- цию from sеlепiшn import webdriver. 
Ответы на контрольные вопросы 575 15. Методы findelement * возвращают первый совпавший элемент в виде объекта WebElement. Методы findelements* возвращают список всех совпавших элементов в виде объектов WebElement. 16. Методы click () и sendkeys () имитируют щелчки МЫllIЬЮ и нажатия КJшвиш соответственно. 17. Вызов метода sиbmit () для любоrо элемента формы инициирует ее отправку. 18. Щелчки на этих кнопках браузера имитируются методами forward ( ) , back () и refresh () объекта WebDriver. rnaBa 12 1. Функция openpyxl. load  wor kboo k () возвращает объект Wor kboo k. 2. Функция getsheetnames () возвращает объект Worksheet. 3. Для этоrо следует вызвать метод wb.getsheetbyname (1 Sheet1'). 4. Для этоrо следует вызвать метод wb.getactivesheet (). 5. sheet ['С5'] .value или sheet. cell (row5, column3). valиe. 6. sheet['C5']  'Hello' или sheet.cell(row=5, со1umn=3) .valиe = 'ИеНо'. 7. cell. row и cell. column. 8. Они возвращают целочисленный номер последнеrо столбца и послед- ней строки листа, содержащих значения. . openpyxl.cell.colиmnindexfromstring('M') 10. openpyxl.cell.getcolumnletter(14) 11. sheet [' А1' : 'F1'] 12. wb. вауе ( 'example. xlsx' ) 13. Формула задается для ячейки подобно любому значению. Для этоro следует установить в качестве значения атрибута valиe строку С тек- стом формулы. Не забывайте о том, что формула начинается со знака =. 14. Для этоrо следует передаТI значение Trиe в качестве именованноro ар,'}'мента da ta  only при вызове функции load  wor kboo k ( ) . 15. sheet.rowdimensions[5] .height  100 16. sheet.columndimensions['C') .hidden  Trиe 17. OpeIlPyXL 2.0.5 не заrружает закрепленные области, заюловки, изо- бражения и диarраммы. 18. Закрепленные области  это строки и столбцы, которые вcerдa видны на экране. Их полезно использовать в качестве заrоловков. 19. openpyxl.charts.Reference(), openpyxl.charts.Series(), openpyxl. charts. BarChart () , chartObj . append (seriesObj) и add  chart () . 
576 Приложение В rllaBa 13 1. Объект File, возвращенный функцией open () 2. В режиме чтения двоичных данных (' rb') для PdfFileReader () и в ре"- жиме записи двоичных данных (, wb') для PdfFileWri ter () . 3. Вызов getPage (4) возвратит объект page для страницы 5, поскольку первой страницей является страница О. 4. Целочисленное значение количества страниц в PDf'-документе хра- нится впеременной numPages объекта PdfFileReader. 5. Вызвать функцию decrypt (' swordfish'). 6. Методы rotateC1ockwise () и rotateCoиnterC1ockwise (). Величина yrла поворота в rpaдycax передается в виде целочисленноrо apryмeHTa. 7. docx.Document ('demo.docx') 8. Документ содержит множество абзацев, представляемых объектами Paragraph. Абзац начинается с новой строки и состоит из нескольких участков с различными стилями форматирования, представляемых об'ьсктами Rиn. Эти участки текста предcrавляют собой rpуппы сим- волов, прилеraющие одна к дрyrой в пределах абзаца. 9. Использовать выражение doc.paragraphs. 10. Эти переменные имеет объект Run (объект Paragraph их не имеет). 11. Установка для переменной bold значения Trиe приводит к тому, ЧТО к объекту Run всеrда будет применяться выделение полужирным шриф- том, а значения False  к тому, что оно никоrда не будет к нему при- меняться, какой бы ни была настройка ем стиля. При значении None к объекту Rиn выделение полужирным шрифrом применястся в с()()т-- ветствии с установленным для Hero стилем. 12. Для этоrо следует вызвать функцию docx.Document (). 13. doc. addyaragraph ( 'НеНо there!') 14. Целочисленные значения 0,1,2,3 и 4. rllaBa 14 1. в электронных таблицах MOryт храниться значения с тинами данных, отличными от CTpoKoBoro; ячейки MOryт иметь различные настрой- ки шрифтов, размера или цвета; ширина и высота ячеек MOryт изм IlЯТЬСЯ; смежные ячейки MOI'yr объединяться; в электронные таблицы можно внедрять изображения и диarpаммы. 2. Этим функциям передается объект File, возвращенный вызовом функции open ( ) . 
Ответы на контрольные вопросы 577 3. Объекты File необходимо открывать в режиме чтения двоичных дaH ных (, rb' ) для объектов Reader и в режиме записи двоичных данных (' wb') для объектов Wri ter. 4. Метод writerow ( ) . 5. ApryMeHT delimiter заменяет строку, используемую в качестве раз- делителя ячеек в строке таблицы. ApryMeHT lineterminator заменяет строку, используемую в качестве разделителя строк таблицы. 6. j son. loads ( ) 7. json.dumps () rllaBa 15 1. Начало отсчета времени, используемое мноrими проrраммами, пред- назначенными для работы со временем и датами. Им (:читается О ча сов 1 января 1970 rода, UTC. 2. time. time () 3. time.sleep(5) 4. Она возвращает целое число, ближайшее к переданному apryMeHТY; например roиnd (2.4) вернет значение 2. 5. Объект datetime представляет определенный момент времени. Объ- ект timedel ta представляет промежyJ'ОК времени. 6. Код: threadObj  threading.Thread(target=spam) threadObj.start() 7. Следует убедиться в том, что код, выплIIяющийсяя В одном потоке, не читает и не заПИСbJвает те же переменныс в коде, выплняющимся в друroм потоке. 8. sиЬрrосеss.Рореп('с:\\Wiпdоws\\SуstеmЗ2\\са1с.ехе') rllaBa 16 1. SMTP и IМAP соответственно. 2. smtplib. smp () , smtpObj . ehlo () , srnptObj . starttls () и smtpObj .login () . 3. imapclient. lМAPClient () и imapObj .login () . 4. Список строк, содержащих ключевые слова IМAP, такие как 'ВЕ FORE <da te> " 'FROM<s tr ing> , и 'SEEN'. 5. Следует присвоить lIеременной imapl ib .  МAXL INE большое целочис- ленное значение, такое как 10000000. 
578 Приложение В 6. Чтение заrpуженных сообщений электронной почты обеспечивает модуль pyzrnail. 7. Потребуется SП) учетной записи Twilio, аyrентификацИОIIНЫЙ маркер и телефонный номер пользователя 'lwilio. rllaBa 17 1. RGВАзначение  это кортеж из четырех целых чисел, каждое из ко- торых может иметь значение в пределах от О до 255. Эти четыре числа выражают количественные доли красной, зеленой и синей составля ЮЩИХ, а также альфаканала (прозрачности) в цвете. 2. Вызов IrnageColor. getcolor (' CornflowerВlиe', 'RGBA') вернет для это- rо цвета RGВА-значение (100, 149, 237, 255). 3. Кортеж прямоуrольника  это кортеж из четырех целых чисел: »координата левой стороны прямоуrольника, у-координата верхней стороны прямоyrольника, ширина и высота прямоуrольника соответ- ственно. 4. Irnage. open ( , zophie . png I ) 5. irnageObj. size  это кортеж из двух целых чисел: ширины и высоты изображения. 6. imageObj .crop ((О, 50, 50, 50)). Заметьте, что методу crop () переда ется кортеж прямоуrольника, а не четыре отдельных целочисленных apryMeHTa. 7. Для этоrо следует вызвать метод imageObj .save ('newfilename.png') объекта Irnage. 8. Код для рисования изображений содержится в модуле IrnageDraw. 9. Методы, предназначенные для рисования объектов, такие как point (), line () или rectangle () , имеют объекты IrnageDraw. Эти объек ты возвращаются функцией Image Draw. Draw () , которой передается объект Irnage. rllaBa 18 1. Для этоrо следует переместить указатель мыши в верхний левый yrол экрана, т.е. в позицию с координатами (О, О). 2. Разрешение экрана в виде кортежа из двух целых чисел, представляю- щих ширину и высоту экрана, возвращает функция pyaиtogиi. size (). 3. Текущие координаты указателя мыши в виде кортежа из двух цe лых чисел, представляющих x и у-координату, возвращает функция pyaиtogui.position(). 
Ответы на контрольные вопросы 579 4. Функция moveTo () перемещает указатель мыши в позицию с задан- ными абсолютными координатами на экране, тоrда как функция moveRel () перемещает указатель мыши относительно ero текущей по- зиции. 5. pyaиtogиi .dragTo () и pyaиtogиi .dragRel (). 6. pyaиtogиi.typewrite('Hello world!') 7. Для этоrо следует либо передать список строк с обозначениями кла- виш (таких, как 'left') функции pyautogui. typewri te (), либо пере-- дать строку с обозначением клавиши функции pyautogui. press (). 8. pyaиtogиi.screenshot('screenshot.png') 9. pyaиtogиi.PAUSE  2 
ПРЕДМЕТНЫЙ УКАЗАТЕJlЬ А API 413 в Вeat1tiful Soup 314 u Unicode 306 URL-aдpec 300 UTC 424 с СМУК 493 CSV 403 А Абсолютный путь 226 Аварийное завершение 42,474 Активный лист 338 ApryмeHT функции 53, 96 Атрибyr 309 & Бесконечный цикл 81,83 Бинарный оператор 65 Влок 68 Булево значение 62 Булев оператор 65 G (,oogle Maps 300 <;Ul-автоматизация 525 н HTML 308 I IDLE 34 IМAP 463 сервер 464,475 в Веб-скрапинr 299,414 Вещественное число 45 Виртуальная клавиатура 542 Возвращаемое значение 97 Временная метка Unix 424,430 Встроенная функция 89 Выражение 42 J jSON 403,413 р. РП}'-документ 373 дешифрование 376 извлечение текста 374 копирование страниц 377 наложение страниц 380 поворот страниц 379 создание 377 шифрование 382 РЕР8 50 Pillow 491,495 r fлобальная область видимости 10J lЪрячие клавиши 545 fрупповое присваивание 125, 149 fрупповой символ 208 R R<;BA 491 д Двоичный (бинарный) файл 232 Дерево каталоrов 259 Дескриптор НТМL 308 Диаrрамма 366 Документ Word 38б добавление заrОJJОВКОВ 396 добавление изображений 397 запись 394 извлечение текста 389 использование стилей 390 чтение 388 Домен вepXHero уровня 216 5 Selellium 328 SMTP 457 сервер 459 т TLS 461 Т.....шо 480 
582 )1( Жадный поиск 203. 209 Журнал 288 э Эначение 42 и Изображение 491 обрезка 499 поворот 504 размеры 504 рисование 515 Именованный apryMeHT 100 Имя файла 223 Индекс 116 отрицательный 118 Инициализация 48 Интерактивная оболочка 34, 41 Интерактивная среда разработки 34 Интерпретатор Pythol1 34 Исключение 108, 276 Исходный код 27 Итерация 79 к Кавычки 166 Канал 199 Капитель 393 Каталоr 223 Клавиатура 542 Ключ 145 Код выхода 446 Командная строка 301 Комментарии 53 Конкатенация 46 Координаты 494 Копирование файлов 254 Корневая папка 223 Кортеж 135 п Локальная область видимости 101 м Маrическая строка 561 Метод 126 П р едметный указатель appendO 127 cel1terO 1 77 endswithO 174 fil1dal10 204 getO 150 il1dexO 126 il1sertO 127 islowerO 171 isupperO 171 itешsО 148 joi110 175 keysO 148 UustO 176 lowerO 170 rешоvеО 128 rjustO 176 searchO 467 sendmail() 462 setdefaultO 150 sortO 129 splitO 17:. startswitl10 174 subO 211 ирре.'О 170 valuesO 148 wait() 447 МНОf01l0ТОЧНОСТЬ 437 МНOI'Острочный блок 167 Модуль CSV 404 dаtеtiше 430 IlIIageDraw 515 iшарсliепt 463 JSON 414 10ggiпg 283 орепрухl 338 os.patl. 227 pyautogui 526. 538, 546 PyPDF2 373 pyperclip 179, 214 pytholl--docx 386 pyzlIlail 463 l"e 195 Requests 303 sCII<12trash 258 sl.elve 237 
Предметный указатель shutil 254 threading 438 time 423 webbrowser 300 прШе 261 н Нсжадный поиск 203, 209 о Обратная трассировка стека вызовов 278 Объект Datetime 436 J:.ont 359 Reader 405 Regex 195 tillledelta 436 Writer 406 Окруrление чисел 426 Оператор 42 бинарный 65 булев 65 llрисваивания 47 комбинированный 125 сравнения 63 унарный 66 Отладка 29, 288 Относительный путь 226 n Параллелизм 441 Параметр функции 97 Пароль приложения 461 Передача управления б1 Переименование файлов 255, 264 Переменная 47 инициализация 48 правила именования 49 Перемещение файлов 255 Перетаскивание 534 Печать 152 Пиксель 492 ПЛанировщик 448 Последовательно<ть 123 Коллатца ] 14 583 Поток выполнения 438 управления 61, 68 Приоритет операций 43 П pOKpyrкa 536 Протоколирование 283 отключение 287 Пункт 363 Пустая строка 45 Пyrь к файлу 223 Р Рабочий каталоr 225 Разделитель 408 Расширение имени файла 223 Реryлярные выражения 191, 194 rpуппы 197 жадные 203 нежадные 203 необязательные rpуппы 200 Резервное копирование 258, 269 Репликация строк 46 Рисование текста 518 фиryр 51 6 с Сжатие файлов 261 Символьный класс 205 инвертированный 206 Синтаксический анализ 314 Словарь 145 Снимок экрана 538 Список 115 Срез 118 Ссылка 137 передача 139 Стандартная библиотека 90 Стек вызовов 278 Стиль 387 Строка 45 сырая 167,195 Строковый литерал 165 
584 т Таблица иcrинности 66 ler 308 Текстовый файл 232 Тип данных 45 бvлев 62 вщественный 64 неизменяемый 133 строковый 64 целочисленный 64 Том 224 TO<IKa останова 290, 294 у Удаление пробелов 178 файлов 257 Указатель мыши 528 перетаскивание 534 Унарный оператор 66 Управляющая инструкция 61 Уровень критичности сообщений 286 Условие 68 Утверждение 279 отключение 282 ф Файловый редактор 51 Факториал 283 Форма 547 Функция 95 соруО 140 deepcopyO 140 floatO 56 iпрtlt О 54 intO 56 lеп О 54 1istO 136 locateOnScree110 541 pformatO 152 Рореl1О 445,447 pprintO 152 printO 53 rоuпdО 426 scrоЩ) 536 sleepO 425 Предметный указатель strO 55, 56 timeO 424 tupleO 136 ц Целое число 45 Цикл 76 Циркумфлекс 206 Ч Число с плавающей точкой 45 щ Щелчок мышью 533 э 16 6 Экранирование символов Электронная почта 458 отправка 462 Электронная таблица 337 закрепленные области 365 настройка 362 создание 352 стили 358 формулы 360 чтение 339, 345 Элемент списка 116 Элемент HTML 308 Эра Unix 424