Поговорим на Python РАЗРАБОТК А ПО Д Л Я Н АЧИН АЮЩИХ ПАВЕЛ АННИ 2024 Павел Анни Поговорим на Python. Разработка ПО для начинающих Серия «Библиотека программиста» Перевела с английского О. Аксенова ББК 32.973.2-018.1 УДК 004.43 Анни Павел А68Поговорим на Python. Разработка ПО для начинающих. — СПб.: Питер, 2024. — 256 с.: ил. — (Серия «Библиотека программиста»). ISBN 978-5-4461-4105-0 Программирование — это приключение, полное трудностей, нового опыта, побед и поражений. В книге «Поговорим на Python» вы присоединитесь к Эрику и Саймону, которые учатся программировать на Python и создают свое первое приложение. Книга написана специально для будущих программистов и идеальна, чтобы начать осваивать навык, который останется на всю жизнь! Книга «Поговорим на Python» учит писать код, задавать вопросы, делать ошибки и пробовать разные решения — прямо как в жизни. Вы шаг за шагом будете создавать приложение для небольшой кофейни, начиная с онлайн-меню и заканчивая хранением заказов в базе данных. Уникальные практические задания приведут вас туда, куда не добираются другие книги для начинающих. Вы получите и отработаете на практике такие важные навыки, как планирование структуры приложения, поиск ошибок и управление ожиданиями пользователей. 16+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.) ISBN 978-1633438897 англ.Authorized translation of the English edition © 2024 Manning Publications. This translation is published and sold by permission of Manning Publications, the owner of all rights to publish and sell the same. ISBN 978-5-4461-4105-0 © Перевод на русский язык ООО «Прогресс книга», 2024 © Издание на русском языке, оформление ООО «Прогресс книга», 2024 © Серия «Библиотека программиста», 2024 Права на издание получены по соглашению с Manning Publications. Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав. Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные с использованием книги. В книге возможны упоминания организаций, деятельность которых запрещена на территории Российской Федерации, таких как Meta Platforms Inc., Facebook, Instagram и др. Издательство не несет ответственности за доступность материалов, ссылки на которые вы можете найти в этой книге. На момент подготовки книги к изданию все ссылки на интернет-ресурсы были действующими. Изготовлено в России. Изготовитель: ООО «Прогресс книга». Место нахождения и фактический адрес: 194044, Россия, г. Санкт-Петербург, Б. Сампсониевский пр., д. 29А, пом. 52. Тел.: +78127037373. Дата изготовления: 07.2024. Наименование: книжная продукция. Срок годности: не ограничен. Налоговая льгота — общероссийский классификатор продукции ОК 034-2014, 58.11.12 — Книги печатные профессиональные, технические и научные. Импортер в Беларусь: ООО «ПИТЕР М», 220020, РБ, г. Минск, ул. Тимирязева, д. 121/3, к. 214, тел./факс: 208 80 01. Подписано в печать 19.06.24. Формат 70×100/16. Бумага офсетная. Усл. п. л. 20,640. Тираж 1000. Заказ 0000. Оглавление Предисловие........................................................................................... 9 Благодарности...................................................................................... 11 О книге................................................................................................. 13 Для кого эта книга?......................................................................................................... 13 Структура книги.............................................................................................................. 13 О коде в книге................................................................................................................... 15 Форум liveBook................................................................................................................ 16 Другие онлайн-ресурсы................................................................................................. 16 Что дальше......................................................................................................................... 16 Об авторе.............................................................................................. 17 От издательства ................................................................................... 18 Глава 1. Кофе для друзей: начало........................................................... 19 Перво-наперво: установка............................................................................................ 22 Как говорить с компьютером....................................................................................... 25 Что такое программа?.................................................................................................... 35 Что мы узнали сегодня.................................................................................................. 36 Код из этой главы............................................................................................................ 36 Больше идей для проектов........................................................................................... 36 Глава 2. Списки: что в меню?.................................................................. 37 Что мы узнали сегодня.................................................................................................. 52 Код из этой главы............................................................................................................ 52 6 Оглавление Глава 3. Функции: не повторяйся!........................................................... 53 Что мы узнали сегодня.................................................................................................. 65 Код из этой главы............................................................................................................ 65 Глава 4. Ошибки пользователей: ошибаются все ..................................... 66 Что мы узнали сегодня.................................................................................................. 83 Код из этой главы............................................................................................................ 83 Глава 5. Работа с файлами: будни менеджера кофейни............................ 84 Что мы узнали сегодня.................................................................................................. 95 Код из этой главы............................................................................................................ 95 Глава 6. Главное меню: свободная касса!................................................. 96 Что мы узнали сегодня................................................................................................ 110 Код из этой главы.......................................................................................................... 110 Глава 7. Функции: получение и вывод заказа..........................................111 Что-нибудь выбрали?................................................................................................... 114 Покажите заказ!.............................................................................................................. 118 Что мы узнали сегодня................................................................................................ 120 Код из этой главы.......................................................................................................... 120 Глава 8. Работа с JSON: сохранение заказа............................................121 Что мы узнали сегодня................................................................................................ 134 Код из этой главы.......................................................................................................... 134 Глава 9. Завершение работы над меню: реальная программа....................135 Что мы узнали сегодня................................................................................................ 144 Код из этой главы.......................................................................................................... 144 Глава 10. Изучаем Flask: первое веб-приложение...................................145 Что мы узнали сегодня................................................................................................ 167 Код из этой главы.......................................................................................................... 167 Оглавление 7 Глава 11. Веб-форма для заказов: кофейня в интернете..........................168 Что мы узнали сегодня................................................................................................ 181 Код из этой главы.......................................................................................................... 181 Глава 12. База данных: создаем надежное хранилище.............................182 Что мы узнали сегодня................................................................................................ 200 Код из этой главы.......................................................................................................... 201 Глава 13. Стили: наводим красоту..........................................................202 Что мы узнали сегодня................................................................................................ 224 Код из этой главы.......................................................................................................... 225 Глава 14. Помощь искусственного интеллекта: улучшаем код..................226 Что мы узнали сегодня................................................................................................ 236 Глава 15. Что дальше: планы на будущее................................................237 Что мы узнали сегодня................................................................................................ 249 Код из этой главы.......................................................................................................... 249 Приложение A. Идеи для первого приложения.......................................250 Пиццерия.......................................................................................................................... 250 Кафе-мороженое............................................................................................................ 250 Фигурки LEGO.............................................................................................................. 251 Другие идеи для проектов.......................................................................................... 252 Приложение Б. Установка редактора Mu и окружения для Python...........253 Mu........................................................................................................................................ 253 Thonny............................................................................................................................... 255 Python................................................................................................................................ 256 Моему папе Предисловие В основе этой книги лежит реальная история. Мой сын Эрик любит ходить в Starbucks и пробовать напитки с новыми вкусами и топпингами. Однажды ему захотелось готовить кофе самому и угощать друзей. Он решил принимать заказы на планшете, и я предложил ему написать небольшое приложение. Он уже пробовал программировать, но тогда ему стало скучно. А вот на этот раз перед ним оказалась реальная задача, которую можно было решить с помощью программирования, и у него появился интерес. Так и родилась эта книга. Надеюсь, вы тоже найдете задачу, которая вас заинтересует, — а книга поможет ее решить. Несколько советов: Не спешите. Я понимаю, что вам не терпится открыть последнюю главу, скачать код финальной версии программы и запустить его. Не делайте этого. Продвигайтесь шаг за шагом, пишите код сами (не копируйте!), пробуйте запустить его и только потом идите дальше. Иногда вам нужно будет вернуться к началу и перечитать главу. Иногда вам просто нужен будет перерыв. Не волнуйтесь! Возьмите паузу и перечитайте главу — только не бросайте. Ошибайтесь. Если все идет идеально, значит, вы не учитесь. Единственный способ чему-то научиться — делать ошибки. Не бойтесь оши­баться. Экспериментируйте с кодом, меняйте его, получайте сообщения об ошибках и читайте их. Ищите информацию об ошибке в интернете, и вы увидите, что тысячи людей ошибались так же, как и вы. Разберитесь, как они исправили свою ошибку, и исправьте ее у себя. Идите дальше и не останавливайтесь. Задавайте вопросы. Друзьям, родителям, бабушкам и дедушкам. Задавайте вопросы в интернете. Рассказывайте о своей проблеме — иногда этого доста- 10 Предисловие точно, чтобы найти решение самому. Не стесняйтесь, в программировании не бывает «глупых вопросов». Спрашивайте и идите вперед. Двигайтесь дальше. Изменяйте приложение, которое вы создали с помощью этой книги. Добавьте в него что-то свое, пусть оно станет уникальным. Подумайте, какие еще приложения вы можете написать. Посмотрите вокруг: что можно автоматизировать? Можете ли вы написать приложение или сайт, похожие на известные вам? Расскажите о своих идеях друзьям — может быть, вы создадите что-то вместе. Программирование — это круто. Не бросайте его. Говорите. Поделитесь своим опытом на форуме liveBook. Расскажите о своих мыслях и идеях друзьям. Обсуждая проблемы, которые вам удалось решить, вы делаете свой «разговор на Python» интереснее. Благодарности Это моя первая книга, и я бы не смог написать ее без поддержки и руководства многих людей, чью помощь невозможно переоценить. Прежде всего, я хочу поблагодарить своих родителей. Мой отец был приверженцем STEM-образования1. Он наводнил наш дом научно-популярными изданиями и вдохновил меня на изучение программирования. Он предсказывал, что программирование станет второй грамотой, и оказался прав как никогда. Моя мать всю жизнь преподавала физику, и от нее я унаследовал призвание к педагогике. Я благодарю своих сыновей, Саймона и Эрика, за то, что они дали мне возможность научить их языку Python. Этот неоценимый опыт помог мне вырасти и как педагогу, и как разработчику. Спасибо моей жене Татьяне. Я искренне признателен ей за огромное терпение и постоянную поддержку. Она не упрекнула меня ни за один выходной, который я провел за компьютером, а не в ее прекрасном обществе. Все время, пока шла работа над книгой, она выслушивала мои мысли и идеи, давала неоценимые советы и делилась своей точкой зрения. Я благодарен редакторам издательства Manning: Трою Драйеру (Troy Dreier), Тони Арритоле (Toni Arritola) и Марии-Терезе Смит (Marie-Therese Smith), во многом благодаря которым эта книга появилась на свет. Трой увидел потенциал моих первых набросков и порекомендовал их издательству Manning, без которого работа над книгой никогда бы не началась. А мягкость и настойчивость Тони помогла завершить книгу вовремя: без нее работа над книгой никогда бы не закончилась. Мария-Тереза, выступавшая педагогом, инженером и специалистом 1 STEM — новый подход в образовании, при котором все отрасли естественно-научного и технического знания объединены, и ребенок получает эти знания не из учебника, а через решение творческих задач. — Примеч. пер. 12 Благодарности по данным, тщательно проверила код и технические подробности, чтобы читатели не застряли на первом же примере. Я хочу поблагодарить Николаса Х. Толлерви (Nicholas H. Tollervey), ведущего разработчика редактора Mu, и всех участников этого прекрасного проекта с открытым исходным кодом. Эта среда разработки на Python, удобная для новичков, обеспечила все необходимое для моей книги, от первых шагов в изучении языка до разработки полностекового веб-приложения. Спасибо всем рецензентам: Афифу Хайдеру, Элу Пежевски, Алану Гилу Форце, Алексису Перье, Амогу Рагхунатху, Эндрю Р. Фриду, Арсалану Хану, Арье АрунКумару, Жану-Батисту Банг Нтеме, Бену Макнамаре, Бернарду Фуэнтесу, Крису Карделлу, Кристиану Саттону, Кларе Макадамс, Клеменсу Баадеру, Дэвиду Кухте, Дхивье Сивасубраманиане, Элоне Виалатте, Файязу Эвану Хайдеру, Ганешу Сваминатану, Джорджу Томасу, Грегу Фриду, Ханне Чунг, Хасину Хайдеру, Джексону Макнамаре, Яне Драгосавлевич, Джанит Анжарии, Химене де Хесус Мата Кобиан, Жуану Динису Феррейре, Киту Киму, Кевину Чунгу, Мафинару Хану, Марку-Энтони Тейлору, Мэри-Энн Тигесен, Мигелю Эдуардо Хиль Биро, Натану-Стивену Тейлору, Нику Пипенбрейеру, Нинославу Черкезу, Ору Голану, Павлу Шимону, Филиппу Виалатту, Регине де Хесус Мата Кобиан, Роберту Кулаговски, Рупе Лахири, Сатадуру Рою, Шаурье Даре, Шиваншу Батре, Шиону Рою, Сраванти Редди, Шрише Редди, Шрише Тхиммаредди, Судипу Батре, Сумиту Бхаттачарии, Таруну Ганешу, Тавхиде Хуссейн, Вальтеру Александру Мата Лопесу, Уиллу Пежевски и Вольфи Баадеру (Afif Hayder, Al Pezewski, Alan Gil Forza, Alexis Perrier, Amogh Raghunath, Andrew R. Freed, Arsalan Khan, Arya ArunKumar, Jean-Baptiste Bang Nteme, Ben McNamara, Bernard Fuentes, Chris Kardell, Christian Sutton, Clara McAdams, Clemens Baader, David Kuhta, Dhivya Sivasubramanian, Elona Vialatte, Faiyaz Evan Hayder, Ganesh Swaminathan, George Thomas, Greg Freed, Hannah Cheung, Hasin Hayder, Jackson McNamara, Jana Dragosavljević, Janit Anjaria, Jimena de Jesús Mata Cobián, João Dinis Ferreira, Keith Kim, Kevin Cheung, Mafinar Khan, Marc-Anthony Taylor, Mary Anne Thygesen, Miguel Eduardo Gil Biraud, NathanSteven Taylor, Nik Piepenbreier, Ninoslav Čerkez, Or Golan, Pavel Šimon, Philippe Vialatte, Regina de Jesús Mata Cobián, Robert Kulagowski, Rupa Lahiri, Sataduru Roy, Shaurya Dara, Shivansh Batra, Shyon Roy, Sravanthi Reddy, Srisha Reddy, Srisha Thimmareddy, Sudeep Batra, Sumit Bhattacharyya, Tarun Ganesh, Tawhida Hussain, Walter Alexander Mata López, Will Pezewski, and Wolfie Baader) — ваши предложения помогли сделать книгу лучше. Наконец я выражаю сердечную благодарность всему сообществу Python. Объем знаний, которые я почерпнул из подкастов, видеороликов, выступлений на конференциях, книг и онлайн-форумов за все эти годы, неизмерим. Доброжелательность и поддержка сообщества, несомненно, одна из главных причин, по которым Python так ценят разработчики. Спасибо вам всем! О книге Я написал эту книгу для подростков, которым интересно изучать программирование на практике. Она построена в форме диалога двух братьев, которые вместе работают над приложением для кофейни. Следи за их разговором и пиши свое приложение вместе с ними. Для кого эта книга? Может быть, ты уже учишься программировать — онлайн или в школе. Ты знаешь основы, но еще не работал над реальным проектом. Может быть, ты еще не изучал программирование, но уверенно владеешь компьютером и тебе интересно разобраться, как работают программы. Может быть, ты использовал какие-то онлайн-приложения, а теперь хочешь создать свое. Может быть, твой старший брат или сестра уже пишут программы и приложения и ты тоже мечтаешь научиться программировать. Если хотя бы в одном из этих описаний ты узнал себя — отлично! Читая эту книгу, ты будешь учиться вместе с Саймоном и Эриком и напишешь свое приложение за пару недель. Если вы родитель (бабушка или дедушка) и вам нужно простое руководство, чтобы изучать программирование с ребенком (внуком), эта книга для вас! А может, вы сами хотите научиться программировать? Ведь моя книга не только для детей. Я приглашаю всех желающих изучать Python и писать приложения! Структура книги Каждая глава книги рассказывает, что Эрик и Саймон (а позже и Эмили) написали за день. Каждый день они собирались вместе примерно на час, обсуждали 14 О книге свое приложение и писали код. Ты можешь делать так же — читать по одной главе в день и писать код для нее. Если появятся вопросы, задавай их на форуме liveBook или ищи ответы в интернете. В книге 15 глав. Первая часть состоит из 9 глав, в которых Эрик и Саймон работают над первой текстовой версией приложения. Текстовой — значит, что оно работает как чат: спрашивает клиентов, что они хотят заказать, и ждет их ответов. В главе 1 Эрику приходит идея самого приложения. Он хочет готовить кофе для друзей, а для этого должен принимать у них заказы. Саймон помогает ему написать первую версию приложения. В главе 2 Эрик узнает о списках. С помощью списков можно организовать меню с разными вкусами и топпингами. В главе 3 Эрик замечает, что он по нескольку раз повторяет свой код. Чтобы не повторяться, Саймон предлагает использовать функции. Код приложения становится короче, и теперь его удобнее читать. В главе 4 Эрик и Саймон обрабатывают ошибки пользователя. Что, если клиент ошибется при вводе заказа? Приложение должно уметь обрабатывать ошибки. В главе 5 Саймон учит Эрика работать с файлами. Теперь менеджер кофейни может редактировать меню в текстовом файле. В главе 6 братья создают главное меню приложения. После этого приложение начинает работать постоянно и обслуживать клиентов одного за другим. В главе 7 Эрик и Саймон пишут новые функции, которые получают заказ и выводят его на экран. В главе 8 Эрик узнает о формате JSON. Саймон рассказывает, как хранить заказы в JSON-файле. В главе 9 братья заканчивают текстовую версию и обсуждают, как сделать из нее веб-приложение. Во второй части (главы 10–15) Эрик, Саймон и Эмили работают над веб-версией приложения. Они создают веб-версию меню, устанавливают соединение с базой данных и работают над стилями и цветами. В конце они тестируют приложение на смартфонах и планшетах. В главе 10 к братьям присоединяется Эмили. Саймон показывает Эрику и Эмили, как создать простое веб-приложение. Они узнают, как использовать веб-формы и меню. О книге 15 В главе 11 Эрик и Эмили работают над созданием веб-форм и меню для кофейни. В главе 12 Саймон рассказывает Эрику и Эмили, как сохранять заказы в базе данных. Они учатся использовать язык структурированных запросов (SQL). В главе 13 друзья начинают украшать свое веб-приложение. Теперь они знают, как поменять шрифт и цвет фона страницы, как добавить на страницу картинку. В главе 14 Саймон показывает Эрику и Эмили, как с помощью искусственного интеллекта можно улучшить код, написать комментарии и разобраться в сообщениях об ошибках. В главе 15 Саймон, Эрик и Эмили делают приложение доступным для смартфонов и планшетов, тестируют его, и оно работает! Они обсуждают, что делать дальше: как разместить приложение в облаке и добавить в него больше функций. Они решают продолжить работу над приложением и обмениваться идеями на сайте. Предлагаю читать эту книгу как рассказ, главу за главой. Не пропускай блоки с пометкой «ТВОЙ ХОД». Твоя задача — сделать то же, что Эрик, Саймон и Эмили, но по-своему. Подключи воображение, меняй код, создай свое уникальное приложение! Может быть, тебе захочется сделать перерыв. Или перечитать главу, чтобы лучше ее понять. Это нормально. Читай книгу в своем темпе — только не бросай! Если ты застрял на каком-то фрагменте кода, попробуй скопировать его из нашего репозитория (подробнее об этом ниже). Если что-то непонятно, спроси на форуме liveBook (о нем мы уже упоминали выше). О коде в книге Исходный код из этой книги можно найти на GitHub: https://github.com/pavelanni/ pythonicadventure-code. В конце каждой главы есть ссылка на код, относящийся к этой главе. Исполняемые фрагменты можно загрузить из электронной версии на liveBook по адресу https://livebook.manning.com/book/a-pythonic-adventure. Примеры исходного кода есть как в нумерованных листингах, так и в тексте. В обоих случаях код набран моноширинным шрифтом, в отличие от обычного текста. Во многих случаях оригинальный код переформатирован: добавлены разрывы строк и изменены отступы, чтобы он поместился на странице. Кроме того, комментарии в листингах часто опускаются, если код описан в тексте. Ко многим листингам имеются примечания, где я обращаю внимание на важные концепции. 16 О книге Форум liveBook Приобретая книгу «Поговорим на Python», вы получаете бесплатный доступ к закрытому веб-форуму издательства Manning (на английском языке), на котором можно оставлять комментарии о книге, задавать технические вопросы и получать помощь от автора и других пользователей. Чтобы получить доступ к форуму, откройте страницу https://livebook.manning.com/book/a-pythonic-adventure/ discussion. Информацию о форумах Manning и правилах поведения на них см. на https://livebook.manning.com/discussion. В рамках своих обязательств перед читателями издательство Manning предоставляет ресурс для содержательного общения читателей и авторов. Эти обязательства не подразумевают конкретную степень участия автора, которое остается добровольным (и неоплачиваемым). Задавайте автору хорошие вопросы, чтобы он не терял интереса к происходящему! Форум и архивы обсуждений доступны на веб-сайте издательства, пока книга продолжает издаваться. Другие онлайн-ресурсы У книги есть свой сайт: https://pythonicadventure.com. На нем я собрал идеи других похожих проектов (на случай, если тебе не понравится работать с кофейней), а также информацию о том, как исправлять ошибки, и предложения по улучшению приложения. Лучший информационный ресурс о Python — это официальный сайт https:// www.python.org. Там можно найти всю необходимую документацию, блоги, статьи и мастер-классы. Коллекция ссылок на другие ресурсы на https://wiki.python.org/ moin/BeginnersGuide также очень пригодится новичкам. Что дальше После работы с этой книгой ты сможешь решать на Python более серьезные задачи. Продолжить «разговор на Python» помогут следующие издания: Кен Юэнс-Кларк (Ken Youens-Clark). «Tiny Python Projects» (https://www. manning.com/books/tiny-python-projects) Реувен М. Лернер (Reuven M. Lerner). Python Workout1 (https://www.manning. com/books/python-workout) Дейн Хиллард (Dane Hillard). Practices of the Python Pro2 (https://www. manning.com/books/practices-of-the-python-pro) 1 2 Лернер Р. «Python-интенсив: 50 быстрых упражнений» Хиллард Д. «Секреты Python Pro». СПб., издательство «Питер». Об авторе ПАВЕЛ АННИ — главный инженер по работе с клиентами (Principal Customer Engineer) в SambaNova Systems. Прежде чем присоединиться к этому стартапу в области искусственного интеллекта, Павел работал в Sun Microsystems, Oracle и Red Hat. Основная цель его карьеры — обучать людей и популяризировать новые технологии. Он разработал и вел курсы по языкам программирования, операционным системам Unix и Linux, Kubernetes и другим темам. От издательства Вместе с героями книги ты будешь создавать свое приложение для кофейни. Для удобства чтения мы перевели на русский язык все тексты, которые вводятся пользователями приложения и выводятся на экран. Однако обращаем внимание, что весь исходный код к книге, который можно скачать на GitHub (https://github.com/pavelanni/pythonicadventure-code), разумеется, остался на английском языке. Соответственно скриншоты на рисунках тоже на английском. В большинстве случаев мы даем перевод текстов на скриншотах в выносках или под рисунками, кроме совсем элементарных случаев (предполагая, что читатель хотя бы немного владеет английским языком :-)). Свои замечания, предложения, вопросы отправляйте по адресу comp@piter.com (издательство «Питер», компьютерная редакция). Мы будем рады узнать ваше мнение! На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах. 1 Кофе для друзей: начало В ЭТОЙ ГЛАВЕ 33 Эрику приходит идея 33 Эрик и Саймон обсуждают будущее приложение 33 Эрик устанавливает редактор кода и пробует запустить первую программу на Python 33 Саймон объясняет Эрику, как использовать переменные 33 Эрик пишет первый диалог на Python Однажды солнечным летним днем Эрику пришла в голову мысль открыть кофейню и готовить кофе для друзей. Кто бы мог подумать, что для этого он создаст свое онлайн-приложение? «Сделаю как в Starbucks — с разными вкусами и топпингами, — подумал Эрик. — У меня есть все что нужно: кофе, три-четыре добавки для вкуса и шоколадные взбитые сливки для топпинга. Просто отлично!» — Где мой iPad? — спросил он старшего брата Саймона. — Там, где ты его оставил. Зачем он тебе? — Собирать заказы для моей кофейни! 20 Поговорим на Python Через пару минут Эрик появился с записанными на iPad заказами. Он приготовил четыре напитка для друзей и снова ушел. — Разве не отличная идея? — спросил Эрик у брата, вернувшись домой с четырьмя пустыми пластиковыми стаканчиками. — Да, неплохая, — ответил Саймон. — Но… — Что «но»? — спросил Эрик. Ему показалось, что брат, как обычно, хочет испортить ему настроение. — Ты принимаешь заказы на iPad, но используешь его как обычный бумажный блокнот. Можно ведь создать простое приложение для заказов. — Типа интернет-магазина? С меню и всем этим вот? — Эрик тут же представил себе страницу собственного интернет-магазина с огромным названием: «Кофейня Эрика». — Конечно. У тебя же был онлайн-курс по Python, разве нет? — Да, но я уже мало что помню. У нас были какие-то задания… Но мне кажется, сделать настоящий интернет-магазин будет сложно. — Ничего, — сказал Саймон. — Будем продвигаться постепенно. У нас были похожие темы в кружке робототехники. ПРИМЕЧАНИЕ Не волнуйcя, если у тебя нет вообще никакого опыта программирования. Эрик почти ничего не помнит со своих курсов, так что мы начнем с самого начала. Саймон заканчивает школу. Он выучил Python несколько лет назад и использовал его в школьном кружке информатики, а теперь и робототехники. — Так что, мы правда можем сделать настоящее приложение? — не поверил Эрик. — Да, конечно. Если ты не будешь сбегать с моих уроков, — улыбнулся Саймон, — то напишешь его за пару недель. Тогда твои клиенты смогут выбирать напитки с разными вкусами… — И топпингами! — добавил Эрик. — Да, и топпингами. Когда пользователь подтвердит заказ, ты сразу увидишь его на странице заказов. Ты точно будешь знать, что и кому приготовить. Примерно вот так, — Саймон взял лист бумаги и начал рисовать простую веб-страницу. — Вот страница заказов. Глава 1. Кофе для друзей: начало 21 Кофейня Эрика Введите свое имя Ваше имя Выберите напиток Выберите вкус Выберите топпинг Кофе Карамель Шоколад Кофе без кофеина Клубника Корица Горячий шоколад Зефир Заказать Карамель Ваниль Отмена — А это — список заказов. Кофейня Эрика Заказы Имя Напиток Вкус Топпинг Алекс Кофе Карамель Ваниль Дрю Кофе без кофеина Клубника Шоколад — Круто! И что, у нас правда получится? — все еще не верил брату Эрик. — Конечно! Я же сказал, только не сбегай. У тебя каникулы и полно времени, ты все успеешь. ПРИМЕЧАНИЕ Если тебя не привлекает кофейня, то в книге ты найдешь другие идеи для проектов. Некоторые мы обсудим позже, когда к Эрику присоединится его подруга Эмили, а остальные собраны в приложении А. 22 Поговорим на Python Перво-наперво: установка — Начнем с простого. Ты быстро вспомнишь Python. У тебя он есть на ноутбуке? — спросил Саймон. — Скорее всего, нет. — Вот отличный редактор Python, специально для новичков, как ты. Называется Mu. Попробуй найти его и установить. Уверен, ты справишься. Эрик нашел сайт, с которого можно загрузить Mu: https://codewith.mu/. Эрик открыл страницу Download (https://codewith.mu/en/download) и загрузил установочную программу. Он нажал кнопку Instructions (Инструкции) и выбрал инструкцию для своего компьютера, в которой были описаны все шаги установки. — Не волнуйся, это отличный редактор. И вовсе не игрушечный, — сказал Саймон. — Мы используем его в кружке робототехники для работы Глава 1. Кофе для друзей: начало 23 с микроконтроллерами. Как видишь, у него есть версии для Windows, macOS и Linux. У нас в кружке установлена Linux-версия. ПРИМЕЧАНИЕ Ссылки и инструкции см. в приложении В. Эрик не хотел слепо следовать указаниям брата и спросил: — А есть еще какие-нибудь редакторы Python? — Да, конечно, их много. Еще один хороший редактор для новичков — Thonny. Посмотри тут: https://thonny.org/. — Круто, мне нравится! — сказал Эрик. — Забавное название. — Конечно, есть и другие редакторы, которые совместимы с разными платформами: VS Code (https://code.visualstudio.com/); Sublime Text (http://www.sublimetext.com/). 24 Поговорим на Python — Все они отлично работают с Python. Python поддерживают даже такие старые редакторы, как Vim (https://www.vim.org/) и Emacs (https://www.gnu.org/software/ emacs/), но, чтобы их использовать, нужно быть ну очень хорошим программистом, — Саймон подмигнул брату и продолжил: — В Mu и Thonny Python уже есть. Но чтобы использовать Python и в других редакторах, ты должен сначала его установить. В некоторых операционных системах, таких как Linux и macOS, Python уже установлен по умолчанию. На Windows тебе придется установить его самостоятельно. Если захочешь, позже я покажу тебе как. Глава 1. Кофе для друзей: начало 25 ТВОЙ ХОД! Установи среду для работы с Python Теперь твоя очередь. Запусти ноутбук или стационарный компьютер и установи редактор Mu. Полные инструкции для каждой платформы (Windows, macOS и Linux) см. в приложении В. Если ты предпочитаешь другой редактор, можешь установить его вместо Mu. Не бойся экспериментировать! Как говорить с компьютером — Давай откроем Mu и начнем писать программу для кофейни, — сказал Саймон. Эрик запустил Mu и увидел первое окно. 1. Выбери Python 3 2. Нажми ОК — В главном меню выбери Python 3 и нажми OK, — предложил Саймон. Эрик так и сделал. 26 Поговорим на Python — Теперь, — продолжил Саймон, — Mu запомнит, что ты работаешь с Python 3. Ты, наверное, заметил, что там есть и другие режимы, их можно использовать для работы с микроконтроллерами, создания веб-приложений и многого другого. Но нам пока нужен только Python 3. Перед Эриком появилось окно редактора кода. # Напиши здесь свой код :-) — Что мне здесь написать? — спросил Эрик. — А что твоя программа должна сделать в первую очередь? — Сказать: «Добро пожаловать в кофейню Эрика!» — Отлично! Так и напиши. Помнишь функцию print() в Python? Эрик начал печатать. Первый шаг оказался простым. Глава 1. Кофе для друзей: начало 27 print ("Добро пожаловать в кофейню Эрика!") — Что теперь? — Теперь запусти. Нажми Run (Запустить). Нажми Run print ("Добро пожаловать в кофейню Эрика!") 28 Поговорим на Python Эрик нажал кнопку Run, и появилось другое окно, в котором спрашивалось, хочет ли он сохранить программу. Это тоже было легко. Эрик набрал имя файла — coffeeshop — и уже хотел нажать Enter, чтобы сохранить файл, но Саймон сказал: — Стой… Не забудь добавить к имени файла расширение .py. Так текстовый редактор поймет, что это программа на Python. Mu автоматически добавляет расширение, но другие редакторы этого не делают, так что убедись, что все твои файлы Python имеют расширение .py. Эрик добавил .py к имени файла и сохранил его. Сразу после этого в нижней части редактора появилось другое окно. Это было приветствие от кофейни — все как хотел Эрик! "Добро пожаловать в кофейню Эрика!" Добро пожаловать в кофейню Эрика! — Работает! — обрадовался Эрик. — Конечно, работает. Почему бы ему не работать? — спросил Саймон. — Ты вроде хотел принимать заказы? — Да, я хотел спрашивать у клиента, как его зовут и что он хочет заказать. — А потом? — Саймон, конечно, уже знал ответ, но хотел, чтобы Эрик догадался сам. — А потом на экране должно выводиться: «Привет! Ваш заказ», имя клиента, вкус, который он выбрал, и топпинг, как в настоящем счете. Глава 1. Кофе для друзей: начало 29 — Отлично, — сказал Саймон. — Только смотри: когда ты пишешь программу, ты не знаешь, что закажет твой друг, так? Поэтому в программе ты не можешь написать: «Вы заказали карамель». К тому же разные люди заказывают разное. Алекс может заказать карамель, а Эмили — клубнику. Как видишь, для разных заказов вкусы и имя клиента меняются. Ничего это слово не напоминает из программирования? — Переменная! — Эрик был страшно рад, что вспомнил это название из курса Python, который он прошел несколько месяцев назад. — Точно! — обрадовался Саймон. — Переменная похожа на коробку: ты кладешь в нее что-то, а потом открываешь и смотришь, что там лежит. А еще можно заменить содержимое коробки чем-то другим. Мы создадим коробку с названием answer (ответ), она будет хранить то, что ты услышал от клиента. Ты спросишь у клиента его имя, и он, например, ответит: «Алекс». Тогда ты положишь этот ответ в коробку answer и будешь хранить его там. Когда ты захочешь вывести ответ на экран, ты скажешь Python: «Напечатай то, что сейчас находится в коробке с названием answer». Следующим клиентом может быть Эмили, а значит, ты положишь в коробку имя «Эмили». И в следующий раз Python выведет на экран уже не «Алекс», а «Эмили», потому что в коробке answer будет храниться «Эмили». Давай теперь напишем код. — Прямо здесь? В этом же файле? — спросил Эрик. — Конечно, продолжай в этом же файле. Чтобы получить что-то от клиента, мы будем использовать функцию, которая называется input(). Когда ты вызываешь эту функцию, компьютер ждет, что пользователь введет какие-то данные. Пользователь набирает что-нибудь на клавиатуре и нажимает Enter, а функция возвращает нам эти данные. — Погоди, — остановил брата Эрик. — Что значит «возвращает»? И что это за функции? Нет, я, конечно, знаю, что это, но лучше объясни, что ты имеешь под этим в виду, — Эрик не хотел признаваться, что он не помнит о функциях почти ничего. — Функция — это фрагмент кода, который что-то делает. Почти каждый код что-то делает, но какой-то код мы используем чаще. Позже ты напишешь свои функции, но пока возьмем те, что уже написаны другими людьми. Какие-то действия выполняются особенно часто, например вывод на экран. Ты даже не заметил, что уже использовал функцию, когда написал print(). В программировании это называется вызвать функцию. — А, понятно, — сказал Эрик. — Вот это с круглыми скобками называется «функция». 30 Поговорим на Python — Да. Если ты поместишь что-то внутри скобок, то функция что-то с этим сделает. Например, выведет на экран в виде сообщения. То, что ты передаешь функции, называется аргументом. Аргументы могут быть строками, числами, а иногда аргументов может быть несколько. — Что такое строка? — спросил Эрик. — Строка — это слово или несколько слов. Хорошо, что ты спросил, мы часто будем использовать строки. — Мы говорим, что передаем аргументы функции, — продолжил Саймон. — Функция сделает что-то с этими аргументами и получит какой-то результат. Например, она может что-то вычислить или изменить строку, которую ты передал, например привести ее к ВЕРХНЕМУ РЕГИСТРУ или зашифровать. Потом функция возвращает результат основной программе. — А как я увижу результат? — спросил Эрик. — Функция выведет его на экран? — Нет, не выведет. Тут нам как раз понадобятся переменные. Мы скажем Python: «Вызови такую-то функцию с такими-то аргументами и положи то, что она вернет, в такую-то коробку, точнее переменную». Все это можно сделать с помощью обычного знака «равно»: =. Например, если ты хочешь вызвать функцию input() и положить ее результат в переменную answer, ты просто пишешь так: answer = input() Сначала ты сохраняешь ответ клиента, а потом можешь вывести его на экран. Для этого вызови функцию print() и передай ей свою переменную в качестве аргумента. — Отлично, — сказал Эрик. — Теперь я понял, как это написать. Эрик начал печатать в редакторе и за пару минут написал вот это: print("Добро пожаловать в кофейню Эрика!") answer = input() print(answer) — Запустить? — спросил Эрик. — Конечно, давай, жми Run Эрик нажал Run . . — Тут написано: «Добро пожаловать в кофейню Эрика!» и все. — А ты что хотел? — спросил Саймон. Глава 1. Кофе для друзей: начало 31 — Чтобы программа спросила, как меня зовут. — Но ты же не сказал Python, что он должен что-то спросить. Теперь он ждет, когда ты что-нибудь введешь. Напиши что-нибудь. Эрик набрал на клавиатуре «Эрик» и нажал Enter. Python вывел на экран Эрик. ("Добро пожаловать в кофейню Эрика!") Добро пожаловать в кофейню Эрика! Эрик Эрик >>> — Работает! — воскликнул Эрик. ТВОЙ ХОД! Напиши свой первый диалог Напиши такую же диалоговую программу, как у Эрика. Это короткая программа, так что я рекомендую не копировать ее из книги, а набрать самостоятельно. Придумай название кофейни и используй его в приветствии. Или придумай любой магазин, какой захочешь. Что ты будешь продавать? Мороженое? Пиццу? Цветы? Игрушки для животных? Попробуй запустить свою программу. Делает ли она то, что ты ожидал? Если нет, то скопируй код из книги или с сайта (https://github.com/pavelanni/ pythonicadven-ture-code/tree/main/ch01) и запусти снова. Программа должна заработать. 32 Поговорим на Python — Да, работает, — сказал Саймон, — но давай сделаем ее подружелюбнее. Помнишь, тебя смутило, что программа вывела только «Добро пожаловать!»? Нужно сказать пользователям, чего ты от них ждешь. А еще можно вывести на экран не просто Эрик, а, например: «Ваш заказ, Эрик». Можно передать эту строку функции input() как аргумент. Такие строки называются подсказками. Они поясняют пользователю, чего мы от него хотим. А еще в функцию print() можно добавить строку, которую ты хочешь напечатать перед переменной answer. Давай покажу. Саймон помог Эрику добавить в код новые строки, и вот что получилось: print("Добро пожаловать в кофейню Эрика!") answer = input("Введите свое имя: ") print("Ваш заказ, ", answer) Саймон заметил, что Эрик ищет кнопку Run , и пояснил: — Прежде чем снова нажимать Run , нужно завершить предыдущую сессию Python. Видишь эти треугольные скобки? Они означают, что Python запущен и ждет, что ты что-нибудь введешь. Потом объясню, как это работает, пока просто нажми Stop (Стоп) и можешь снова нажимать Run . Теперь программа запросила имя и ответила именно так, как запрограммировал Эрик: Добро пожаловать в кофейню Эрика! Введите свое имя: Эрик Ваш заказ, Эрик >>> ТВОЙ ХОД! Сделай программу дружелюбнее Добавь в программу подсказки для ввода и вывода. В подсказке для ввода спроси имя клиента, например: «Рады видеть вас! Как вас зовут?» — Ну вот, теперь лучше, правда? — спросил Саймон. — Все время думай о пользователях и задавай себе вопрос: «Понятно ли, чего я хочу от пользователя? Может ли он ошибиться?» Теперь, — продолжил Саймон, — нужно спросить клиента, что он хочет заказать. Ты говорил, что у тебя есть кофе и шоколад. А еще ты говорил что-то о вкусах и топпингах. — Да, — сказал Эрик. — Я хочу спросить клиента, какой топпинг и вкус он хочет. — Что ж, давай спросим. Можешь просто повторить тот же код, но не забудь поменять подсказки. И, наверное, лучше выводить весь заказ в конце, а не после каждого вопроса. Попробуй. Глава 1. Кофе для друзей: начало 33 Эрик написал такой код: print("Добро пожаловать в кофейню Эрика!") answer = input("Введите свое имя: ") answer = input("Введите основной напиток: ") answer = input("Введите вкус: ") answer = input("Введите топпинг: ") print("Ваш заказ: ", answer) Но на последней строке он задумался: — Ты сказал сохранять ответы в переменную answer, но как тогда я узнаю, где вкус, а где топпинг? — смутился Эрик. — Да, я сказал тебе сохранять ответы в переменную answer, — ответил Саймон. — И тут мы подошли к одной из самых сложных задач программирования: придумать имя переменной, — улыбнулся он. — Конечно, в переменную answer нельзя сохранить все ответы. Для каждого ответа мы создадим свою переменную и дадим ей понятное название. С именем клиента все просто: назовем переменную namе (имя). Основные напитки можно сохранить в переменную drink (напиток) или product (продукт), а вкусы и топпинги — в переменные flavor (вкус) и topping (топпинг). В конце выведи на экран каждую переменную в отдельной строке с помощью нескольких функций print(). Попробуй. Если что, я помогу. Эрик доработал код, и в итоге получилось так: Листинг 1.1. Первая версия приложения для кофейни print("Добро пожаловать в кофейню Эрика!") name = input("Введите свое имя: ") drink = input("Введите основной напиток: ") flavor = input("Введите вкус: ") topping = input("Введите топпинг: ") print("Ваш заказ, ", name) print("Основной напиток: ", drink) print("Вкус: ", flavor) print("Топпинг: ", topping) print("Спасибо за заказ!") Эрик нажал кнопку Run, и его программа начала диалог. Он ответил на все вопросы, и программа вывела на экран вот это: Добро пожаловать в кофейню Эрика! Введите свое имя: Эрик Введите основной напиток: кофе Введите вкус: карамель 34 Поговорим на Python Введите топпинг: шоколад Ваш заказ, Эрик Основной напиток: кофе Вкус: карамель Топпинг: шоколад Спасибо за заказ! >>> Саймон увидел последнюю строку и похвалил Эрика за инициативу: — Благодарить клиентов — всегда хорошо. — Да, я видел, так пишут в счетах из кофеен, — сказал Эрик. Он был доволен, что сделал что-то сам, без подсказки старшего брата. ТВОЙ ХОД! Добавь в диалог больше вариантов выбора. Используй переменные Отредактируй предыдущую программу и добавь в диалог новые строки. Не бойся менять подсказки и строки, которые ты выводишь на экран. Они должны подходить твоему проекту. Измени текст на экране. Посмотри на счета и чеки из мест, которые ты посещал (кофейни, рестораны, продуктовые и другие магазины). Попробуй написать что-то похожее. Чтобы текст выглядел интереснее, используй символы, например |, _, =, + и другие. Саймон решил, что на сегодня хватит. — Думаю, мы неплохо поработали, — сказал он. — Давай вспомним, что мы сделали. Во-первых, установили среду программирования. — Да, — сказал Эрик. — Мне нравится редактор Mu. Он подсвечивает разные части программы разными цветами. А еще, если я не поставил кавычки в конце строки, он подсвечивает строку красным. И у него есть темная тема! Я знаю, что настоящие разработчики всегда выбирают темную тему! Наверное, я тоже ее выберу. — Во-вторых, — сказал Саймон, — мы в первый раз использовали функцию. Какую? — Функцию print(), — сказал Эрик. — Я говорю функции, что вывести на экран, и она это выводит. — Правильно. Ты вызываешь функцию и передаешь ей аргумент. Глава 1. Кофе для друзей: начало 35 — В-третьих, — продолжил Саймон, — чтобы получить информацию от пользователя, ты использовал еще одну функцию. — Функцию input(), — сказал Эрик. — Я сохранял ответы в переменные. — Отлично! Делаешь успехи. — Саймон по-настоящему гордился братом. ТВОЙ ХОД! Объясни сам Попробуй объяснить своими словами: • • • • • Что такое функция? Приведи примеры. Что такое аргументы функции? Приведи примеры. Как вызвать функцию? Что такое переменная? Зачем она нужна? Какие имена нужно давать переменным? Почему? Что такое программа? — Добавим немного теории, — сказал Саймон. — Мы только что написали простую программу, но в ней те же составные части, что и в любой другой. Мы запросили у пользователя входные данные. Потом сделали что-то с этими данными. Обычно говорят обработали данные. В нашем случае мы просто сохранили данные, но можно было сделать с ними что-то еще. — Например? — спросил Эрик. — Например, ты вводишь «кофе» в нижнем регистре, но хочешь, чтобы все продукты и вкусы были записаны большими буквами. В Python для этого есть специальная функция. Так что мы можем обработать данные после того, как их получим. — Отличная идея, я хочу это добавить, — сказал Эрик. — Конечно, сделаем. И наконец, обработав данные, мы выводим их на экран. Другими словами, получаем вывод. Смотри. Ввод Пользователь Обработка Вывод 36 Поговорим на Python — Ввод не всегда делает пользователь, — продолжил Саймон. — Иногда взаимодействия с пользователем не происходит вообще и программа берет данные откуда-то еще. Например, последние результаты спортивных соревнований из интернета. Или данные с датчиков, как в робототехнике, или из документов и картинок. А вывод — это не только то, что выводится на экран. Моя команда робототехников получает данные с датчиков, мы обрабатываем их с помощью микроконтроллера и передаем сигналы механизмам робота, например «поверни налево, двигайся вперед» — это и есть наш вывод. Но структура одна и та же: ввод → обработка → вывод. Пока хватит теории. Завтра улучшим твою программу. — Улучшим? — удивился Эрик. — Но она уже хорошо работает, разве нет? — А если пользователь введет то, чего нет в твоей кофейне? — спросил Саймон. — Например, «кленовый сироп»? Что ты будешь делать? Ты должен сказать пользователям, что есть у тебя в кофейне и что они могут заказать. Так что завтра мы поработаем над меню. И посмотрим, что делать, если пользователь ошибется. Что мы узнали сегодня Переменная — это место («коробка»), где хранятся значения, например числа, буквы или строки. В один момент времени переменная может хранить только одно значение. Функция — это фрагмент кода, который делает то, что можно потом повторить. Аргументы функции — это информация, которая нужна функции для работы. Вызвать функцию — значит написать имя этой функции с круглыми скобками, а в скобках можно указать значения аргументов. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch01. Больше идей для проектов На сайте книги (https://pythonicadventure.com/projects/) ты найдешь больше идей для приложений. Пока не обращай внимания на Flask, SQLite и веб-приложения (которые там упоминаются). Мы поговорим о них позже. 2 Списки: что в меню? В ЭТОЙ ГЛАВЕ 33 Эрик создает меню кофейни 33 Эрик использует списки Python для напитков, вкусов и топпингов 33 Эрик выводит списки на экран с помощью цикла for 33 Саймон объясняет, как компьютеры хранят строки и числа 33 Эрик узнает кое-что важное об индексах списков На следующий день Эрик был готов продолжать работу над приложением для кофейни. Он вспомнил, как Саймон говорил, что клиенты могут попросить напитки, которых у него нет. Эрик пришел к брату и спросил: — Вчера ты сказал, что у меня могут попросить напиток, которого нет в моей кофейне. Что тогда делать? — Вспомни, когда ты в последний раз был в кофейне или ресторане? Как ты узнал, что можно заказать? — У них было меню с напитками, которые есть в кофейне. — Именно, — сказал Саймон. — Меню! Вот что мы будем делать сегодня. Как выглядело меню в той кофейне? 38 Поговорим на Python — Как список. Список напитков, например кофе, шоколад и кофе без кофеина. А еще там был список вкусов, которые можно добавить, например карамель, мята и что-то еще. И список топпингов. — Правильно: список! — Саймон был очень рад, что Эрик использовал именно это слово, и быстро набросал что-то похожее на меню: — Вот такой? Меню Напитки Кофе Горячий шоколад Кофе без кофеина Вкусы Карамель Ваниль ... Топпинги Шоколад Корица .... 1.00 1.50 1.20 0.50 0.45 0.30 0.30 — Списки — то, что нам нужно! Может, ты помнишь, что в Python есть списки. Это очень полезная штука. В списках могут быть числа, строки и даже другие списки. Например… — Саймон взял новый лист бумаги и записал несколько примеров. фрукты = ['яблоко', 'персик', 'банан'] числа = [42, 256, 1000] константы = [3.146, 2.718, 1.4142] — Давай создадим списки для твоего меню. Придумай название каждому списку, например flavors, и напиши все вкусы в квадратных скобках. Затем сделай то же самое для топпингов и основных напитков. Не забудь, что вкусы, топпинги и напитки — это строки, а значит, нужно взять их в кавычки. Создай в редакторе новый файл и назови его, например, menu.py. Эрик открыл редактор и начал печатать. Вот что у него получилось через несколько минут: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] Глава 2. Списки: что в меню? 39 — Хорошо, — сказал Саймон. — Теперь давай выведем это на экран в виде меню. — Просто print(drinks)? — предложил Эрик. — Можно и так, но это будет не очень красиво. Попробуй. Эрик добавил функцию print() в конце: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] print(drinks) — Нажми Run , — сказал Саймон. Эрик нажал Run, и внизу окна редактора появился вот такой вывод: ['горячий шоколад', 'кофе', 'кофе без кофеина'] >>> — Если твое меню — это список, то и выводить его нужно как список, — сказал Саймон. — И пользователь должен выбирать из этого списка. Например, можно попросить его напечатать одну букву. Но у нас есть 'кофе' и 'кофе без кофеина', так что нельзя просто взять первую букву 'к'. Давай лучше возьмем числа. Рядом с каждым пунктом меню мы напишем число. Чтобы сделать выбор, пользователь просто вводит число. Например, 1 — это шоколад, 2 — это кофе. Что-то в этом роде. Саймон взял лист бумаги и нарисовал простое меню. Кофейня Эрика Выберите напиток 1 горячий шоколад 2 кофе 3 кофе без кофеина Введите номер: Выберите вкус 1 карамель 2 ваниль 3 мята Введите номер: — Точно! Я видел такое в китайском ресторане — у каждого блюда был свой номер, — вспомнил Эрик. — Но как сделать это на Python? 40 Поговорим на Python — У тебя есть список из нескольких элементов, — пояснил Саймон. — Ты должен вывести каждый элемент, добавив перед ним число. Если нужно что-то повторить, в Python мы используем цикл. У нас будет цикл for. Мы скажем Python, что с каждым элементом списка он должен выполнить какое-то действие. Например, вывести на экран. Напиши простой цикл for. Давай первый цикл я сам напишу. Саймон взял клавиатуру Эрика и добавил в его код пару строк: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] for d in drinks: print(d) Саймон нажал Run, и братья увидели вот такой результат: горячий шоколад кофе кофе без кофеина >>> — Теперь ты, — сказал Саймон. — Напиши такой же код для двух других списков. Обрати внимание, что для вывода на экран элементов списка я создал еще одну переменную. Список называется drinks, поэтому для каждого напитка в списке я использовал просто d. Переменные всегда лучше называть со смыслом, например drinks — список напитков. Но если переменная нужна, только чтобы пройти по списку в одном цикле, то можно дать ей короткое имя из одной или двух букв. Это не обязательно, просто так меньше печатать. Что еще важно — отступы в Python. Видишь, функция print() сместилась вправо на четыре знака. Так мы говорим Python, что именно нужно повторять в цикле. Часть кода, которая сдвинута вправо, называется блоком. Все, что ты напишешь в этом блоке, повторится для каждого элемента списка. Сейчас в этом блоке только один вызов функции, но позже мы добавим туда кое-что еще. Кстати, ты заметил, что я не набирал четыре пробела на клавиатуре? Редактор сделал это автоматически. Эта функция есть у всех редакторов кода, которые поддерживают Python. Когда они видят двоеточие (:), они автоматически сдвигают следующую строку. Это называется «начать блок». Теперь напиши циклы сам. Эрик добавил еще два цикла, которые выводят на экран оставшиеся списки. Ему понравилось давать переменным короткие имена (меньше печатать!). И он обратил внимание, что редактор автоматически сдвинул строку после двоеточия. Очень полезно! Вот что он получил: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] Глава 2. Списки: что в меню? 41 for d in drinks: print(d) for f in flavors: print(f) for t in toppings: print(t) Эрик сохранил программу и запустил ее: горячий шоколад кофе кофе без кофеина карамель ваниль мята малина без добавок шоколад корица карамель >>> ТВОЙ ХОД! Создай свой список товаров и выведи его на экран Открой редактор кода и напиши программу, похожую на ту, которую только что написал Эрик. Программа должна содержать хотя бы три списка. Выведи на экран содержимое этих списков с помощью цикла. Возьми разделы меню Эрика или придумай свои. Вкусы мороженого, пончиков, мини-фигурки — все, что захочешь! — Хорошо, — сказал Саймон. — Но у нас все еще нет чисел. Надо это исправить. Помнишь, я говорил, что мы добавим в блок кое-что еще? Вот что я предлагаю. Мы создадим переменную, которая будет хранить номер элемента в списке. Каждый раз при переходе к следующему элементу мы будем прибавлять единицу к этой переменной. В нашем случае «шоколад» будет номер один, «кофе» — номер два и так далее. Давай покажу, — Саймон снова взял у Эрика клавиатуру: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] i = 1 for d in drinks: print(i, d) 42 Поговорим на Python i = i + 1 for f in flavors: print(f) for t in toppings: print(t) Саймон запустил программу, и братья увидели такой вывод: 1 горячий шоколад 2 кофе 3 кофе без кофеина карамель ваниль мята малина без добавок шоколад корица карамель >>> — Видишь, я добавил переменную i. Теперь для каждого элемента списка я вывожу не только его значение, но еще и номер. Чтобы перейти от номера 1 к номеру 2, от номера 2 к номеру 3 и так далее, я добавляю единицу. Давай теперь поменяем остальные списки, — сказал Саймон. Эрик внес изменения: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] i = 1 for d in drinks: print(i, d) i = i + 1 for f in flavors: print(i, f) i = i + 1 for t in toppings: print(i, t) i = i + 1 Он запустил программу и увидел на экране вот это: 1 горячий шоколад 2 кофе 3 кофе без кофеина Глава 2. Списки: что в меню? 43 4 карамель 5 ваниль 6 мята 7 малина 8 без добавок 9 шоколад 10 корица 11 карамель >>> — Это не то, что я хотел! — сказал Эрик. — Я думал, будет 1, 2, 3 для напитков, потом 1, 2, 3 для вкусов и 1, 2, 3 для топпингов. — Правильно! — согласился Саймон. — Как это сделать? — Взять разные переменные? — Да, можно и так. Но можно использовать ту же переменную i. Главное, перед каждым циклом присваивай ей значение 1. Это называется инициализировать переменную. Эрик добавил i = 1 перед каждым циклом и получил вот это: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] i = 1 for d in drinks: print(i, d) i = i + 1 i = 1 for f in flavors: print(i, f) i = i + 1 i = 1 for t in toppings: print(i, t) i = i + 1 ТВОЙ ХОД! Выведи на экран хотя бы три раздела меню с номерами Измени свою предыдущую программу и добавь номера к пунктам меню. Используй циклы. Для каждого списка не забывай сбрасывать значение переменной i (обычно ее называют счетчиком цикла). 44 Поговорим на Python Эрик нажал Run и получил вот такой результат: 1 горячий шоколад 2 кофе 3 кофе без кофеина 1 карамель 2 ваниль 3 мята 4 малина 5 без добавок 1 шоколад 2 корица 3 карамель >>> — Теперь давай наведем красоту, — сказал Саймон. — Добавь заголовки перед каждым списком, например «Наши напитки». Помни, что нам надо объяснять пользователю, что он видит и что он должен делать. Эрик добавил заголовки и дополнительную строку после каждого списка. Он был уверен, что теперь его меню выглядит как настоящее: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] print("Напитки кофейни Эрика") print("-------------------------") i = 1 for d in drinks: print(i, d) i = i + 1 print("Вкусы напитков от Эрика") print("--------------------------") i = 1 for f in flavors: print(i, f) i = i + 1 print("Топпинги для напитков от Эрика") print("---------------------------") i = 1 for t in toppings: print(i, t) i = i + 1 ТВОЙ ХОД! Добавь заголовки к разделам меню Чтобы текст на экране стал красивее, добавь заголовки к разделам меню. В заголовках укажи название магазина. Попробуй использовать не только дефис, но и другие символы. Глава 2. Списки: что в меню? 45 Как и ожидалось, меню стало красивее: Напитки кофейни Эрика ------------------------1 горячий шоколад 2 кофе 3 кофе без кофеина Вкусы напитков от Эрика -------------------------1 карамель 2 ваниль 3 мята 4 малина 5 без добавок Топпинги для напитков от Эрика --------------------------1 шоколад 2 корица 3 карамель >>> — Выглядит неплохо, — сказал Саймон. — Такой формат хорош тем, что теперь у тебя три раздела в меню и три списка в программе. Дальше попроси пользователя выбрать что-то в каждом разделе меню и сохрани этот выбор. Помнишь, как получить информацию от пользователя? Ты это делал вчера. — С помощью функции input()? — спросил Эрик. — Конечно! — Саймон был очень рад, что Эрик не забыл вчерашний урок. — Сможешь сам написать? — Попробую, — сказал Эрик и начал редактировать код. Сначала он вспомнил, что нужно использовать функцию input(). Потом он добавил подсказку в круглые скобки, а слева написал название переменной. Эрик вспомнил, что для каждого вопроса к пользователю нужно взять свою переменную. Вот что он написал: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] print("Напитки кофейни Эрика") print("-------------------------") i = 1 for d in drinks: print(i, d) i = i + 1 drink = input("Выберите напиток: ") print("Вкусы напитков от Эрика") 46 Поговорим на Python print("--------------------------") i = 1 for f in flavors: print(i, f) i = i + 1 flavor = input("Выберите вкус: ") print("Топпинги для напитков от Эрика") print("---------------------------") i = 1 for t in toppings: print(i, t) i = i + 1 topping = input("Выберите топпинг: ") ТВОЙ ХОД! Добавь в меню пользовательский ввод Добавь функции input() к каждому разделу меню. Сохрани ответы пользователя в переменные с соответствующими именами. — Что дальше? — спросил Эрик у Саймона. — Дальше пользователь введет число, по которому ты найдешь нужный элемент списка. В Python это число называется индексом списка. Напиши это число в квадратных скобках после имени списка, и получишь нужный элемент. Вот так, — Саймон написал пример: drinks[drink] — То есть если ты получишь число, то сможешь найти соответствующий элемент списка. И сможешь вывести на экран раздел «Ваш заказ», как вчера, но теперь уже со списком меню. Попробуй. Если что, я помогу. Это оказалось немного сложнее. Эрик посмотрел на вчерашнюю программу, скопировал из нее нужные строки и вставил их в нижнюю часть новой программы. Он заменил переменную drink и все похожие на нее переменные элементами списков, как и предложил Саймон. Вот его код: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] print("Напитки кофейни Эрика") print("-------------------------") i = 1 Глава 2. Списки: что в меню? 47 for d in drinks: print(i, d) i = i + 1 drink = input("Выберите напиток: ") print("Вкусы напитков от Эрика") print("--------------------------") i = 1 for f in flavors: print(i, f) i = i + 1 flavor = input("Выберите вкус: ") print("Топпинги для напитков от Эрика") print("---------------------------") i = 1 for t in toppings: print(i, t) i = i + 1 topping = input("Выберите топпинг: ") print("Ваш заказ: ") print("Основной напиток: ", drinks[drink]) print("Вкус: ", flavors[flavor]) print("Топпинг: ", toppings[topping]) print("Спасибо за заказ!") — Теперь давай запустим и посмотрим, что получилось, — сказал Саймон. Эрик нажал Run, программа вывела на экран меню напитков и попросила сделать выбор. Пока работает. Эрик быстро ввел номера для всех трех разделов меню и увидел вот это: Ваш заказ: Traceback (most recent call last): File "/home/erik/mu_code/menu.py", line 30, in <module> print("Основной напиток: ", drinks[drink]) TypeError: list indices must be integers or slices, not str1 >>> — Что это? — озадаченно спросил Эрик. — Поздравляю! — сказал Саймон. Эрик тут же начал злиться на брата: — Чему ты радуешься? Что моя программа сломалась? 1 Сообщение об ошибке: «Индексы списка должны быть целыми числами или срезами, не строками». Английское слово slices означает и «срезы», и «ломтики», поэтому Эрик не понимает, о чем идет речь; str — сокращение от string (строка). — Примеч. ред. 48 Поговорим на Python — Конечно, нет! — ответил Саймон. — Ты получил первое сообщение об ошибке, и это хорошо! Чему-то научиться можно, только если делать ошибки и исправлять их. Ты сделал ошибку — попробуем ее исправить. Обычно Python пишет причину ошибки, поэтому сначала прочитай последнее сообщение. Что там написано? — Какие-то ломтики… И что-то должно быть целым, а не «стр». Что это значит? — Конечно, чтобы научиться читать сообщения от Python, нужно потренироваться. Здесь сказано, что если ты используешь переменную в качестве индекса, то эта переменная должна быть целым числом, например 1, 2, 3. — Но я же вводил числа! — все еще не понимал Эрик. — Да, ты набирал числа на клавиатуре. Но для Python все, что ты набираешь на клавиатуре, является строкой. В Python строка с цифрой ‘1’ и целое число 1 — разные вещи. Это связано с тем, как устроена память компьютера. Компьютер хранит в памяти число 1, но если он отображает это число на экране, то он преобразовывает число в строку ‘1’. Также компьютеры преобразовывают числа, которые они получили с клавиатуры. Ты набираешь ‘123’, компьютер получает эту строку и преобразовывает ее в число 123. Смотри, — Саймон нарисовал компьютер, клавиатуру, дисплей и пользователя. выводит '1' отправляет строку '1' для вывода на экран набирает '1' Пользователь преобразует число 00000001 встроку '1' преобразует строку '1' отправляет строку '1' вчисло 00000001 — Нам нужно, чтобы Python преобразовывал строки, которые ты вводишь с клавиатуры, в целые числа. Для этого есть специальная функция, которая называется int(). Давай покажу, как ее использовать. Саймон изменил программу Эрика в одном месте и попросил его сделать то же самое в двух других местах. После этого программа стала выглядеть так: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] print("Напитки кофейни Эрика") print("-------------------------") i = 1 for d in drinks: print(i, d) Глава 2. Списки: что в меню? 49 i = i + 1 drink = input("Выберите напиток: ") print("Вкусы напитков от Эрика") print("--------------------------") i = 1 for f in flavors: print(i, f) i = i + 1 flavor = input("Выберите вкус: ") print("Топпинги для напитков от Эрика") print("---------------------------") i = 1 for t in toppings: print(i, t) i = i + 1 topping = input("Выберите топпинг: ") print("Ваш заказ: ") print("Основной напиток: ", drinks[int(drink)]) print("Вкус: ", flavors[int(flavor)]) print("Топпинг: ", toppings[int(topping)]) print("Спасибо за заказ!") Преобразует строку в число Эрик запустил программу, сделал выбор (кофе, карамель, шоколад) и получил вот такой результат: Ваш заказ: Основной напиток: кофе без кофеина Вкус: ваниль Топпинг: корица Спасибо за заказ! >>> — Что?! Все неправильно! — воскликнул Эрик. — Я не это выбрал! — Я не сказал тебе кое-что важное о списках Python, — сказал Саймон. — Индексы списков начинаются с нуля, а не с единицы. Так что если ты хочешь получить первый элемент списка, то в квадратных скобках нужно написать 0. Если хочешь второй, то — 1. — Но почему? — Эрик был в шоке от такой неожиданности. — Это длинная история, — ответил Саймон. — Дело в том, как компьютеры хранят списки в памяти. Индекс, который ты используешь, — это количество элементов, которое ты должен пропустить с начала списка, чтобы получить нужный элемент. Если ты хочешь получить первый элемент списка, тебе не нужно ничего пропускать. Ты просто берешь его из начала списка. То есть ты должен пропустить ноль элементов, так? Вот поэтому индекс первого элемента равен 0. Смотри, — Саймон нарисовал еще одну картинку. 50 Поговорим на Python начало списка 'k' 'a' 'b' 'c' 'd' пропустить 3 элемента пропустить 2 элемента пропустить 1 элемент пропустить 0 элементов k[0] k[1] k[2] k[3] — И что мне теперь делать? — спросил Эрик. Он, кажется, понимал, о чем говорит Саймон, но это новое неудобство все еще его раздражало. — Вижу, ты недоволен, — сказал Саймон. — Не волнуйся, ты быстро привык­ нешь. И начнешь считать с нуля, как все настоящие программисты, — улыбнулся Саймон. — Пока просто вычти единицу из каждого индекса в квадратных скобках. Только будь внимателен: вычитай эту единицу не до, а после того, как преобразуешь входные данные в целые числа. Вот так: drinks[int(drink) - 1]. Эрик исправил свой код, и вот что получилось: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] print("Напитки кофейни Эрика") print("-------------------------") i = 1 for d in drinks: print(i, d) i = i + 1 drink = input("Выберите напиток: ") print("Вкусы напитков от Эрика") print("--------------------------") i = 1 for f in flavors: print(i, f) i = i + 1 flavor = input("Выберите вкус: ") print("Топпинги для напитков от Эрика") print("---------------------------") i = 1 for t in toppings: print(i, t) i = i + 1 Глава 2. Списки: что в меню? 51 topping = input("Выберите топпинг: ") print("Ваш заказ: ") print("Основной напиток: ", drinks[int(drink) - 1]) print("Вкус: ", flavors[int(flavor) - 1]) print("Топпинг: ", toppings[int(topping) - 1]) print("Спасибо за заказ!") ТВОЙ ХОД! Добавь в программу вывод заказа на экран Измени свою программу так же, как Эрик: выведи заказ на экран. Не забудь преобразовать индексы из строк в числа. И не забудь вычесть из каждого индекса единицу (1) — ты же помнишь, что индексы начинаются с нуля? Эрик запустил программу, ввел 2, 1, 1 и наконец получил желаемый результат: Ваш заказ: Основной напиток: горячий шоколад Вкус: карамель Топпинг: шоколад Спасибо за заказ! >>> — Круто! Работает! — Эрик был абсолютно счастлив. — Мне нравится моя программа! Мы ее доделали? — Почти, — ответил Саймон. — Смотри, ты три раза написал один и тот же код. — И что? — Представь, что ты захотел что-то изменить. Например, ты решил переделать вывод разделов меню на экран. Тебе придется внести изменения в трех разных местах. А если в меню появятся новые разделы? Например, ты добавишь десерты. Значит, тебе придется скопировать тот же код еще раз. А если ты ошибешься? Программисты называют ошибки багами (позже я расскажу почему). Тебе придется исправлять этот баг в четырех местах, повторяя одно и то же. У программистов есть принцип «не повторяйся» (Don’t Repeat Yourself, DRY). — Но я не знаю, что делать, — смутился Эрик. — Если у меня в меню три списка, то я должен вывести их три раза. И три раза попросить пользователя выбрать. — Тут нам и пригодится функция, — пояснил Саймон. — Вспомни, когда мы в первый раз вызвали функцию print(), я сказал, что функции нужны для операций, которые мы хотим повторять. Раньше мы вызывали функции, которые уже написал кто-то другой. Теперь мы сами напишем функцию и вызовем ее. — Круто. Мне нравится, — сказал Эрик. 52 Поговорим на Python — Отлично, мы сделаем это завтра. На сегодня, наверное, хватит. Ты хорошо поработал, Эрик. — Саймон, конечно, радовался успехам брата. — Давай повторим, что мы сегодня узнали. Что было сначала? — Сначала мы создали списки, — сказал Эрик. — И поместили в них все напитки, вкусы и топпинги. — Хорошо. Что дальше? — Дальше мы вывели списки на экран с помощью циклов. И добавили номер каждому напитку и вкусу. — Именно, — подтвердил Саймон. — А что потом? — Я попытался вывести на экран напитки из списка, но Python выдал ошибку. Ты объяснил, как компьютер хранит числа в памяти. Мы преобразовали числа в строки, и я снова попробовал вывести заказ. И только потому, что ты не сказал мне, что индексы начинаются с нуля, — Эрик не забыл об этом! — все пункты моего заказа вывелись неправильно! — Прости, — улыбнулся Саймон. — Зато уверен, теперь ты это запомнишь! — В конце я исправил индексы, и теперь все хорошо работает! — закончил Эрик. — Отлично! — сказал Саймон. — Дай пять! Завтра продолжим и напишем первую функцию. Что мы узнали сегодня Список — это набор значений в Python. Список может содержать строки или числа и даже строки и числа одновременно. Индекс списка — это число, с помощью которого можно получить элемент списка. Индексы всегда начинаются с нуля и увеличиваются на единицу для каждого следующего элемента: 0, 1, 2, 3 и так далее. Числа и строки — это разные типы переменных в Python. Когда компьютер выводит что-то на экран или получает с клавиатуры, он использует строки. Если ты хочешь выполнить какую-то математическую операцию с полученными от пользователя цифрами, сначала надо преобразовать их из строк в числа. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch02. 3 Функции: не повторяйся! В ЭТОЙ ГЛАВЕ 33 Саймон объясняет, как (и зачем) избежать повторения кода 33 Эрик пишет первую функцию на Python 33 Эрик использует инструмент взаимодействия с Python 33 Эрик дорабатывает свою функцию, чтобы счет, который она выводит, выглядел как настоящий — На чем мы остановились вчера? — спросил Саймон на следующий день. — Ты хотел рассказать, почему баги называются багами, — напомнил Эрик. — Точно! — сказал Саймон. — Дело в том, что много лет назад компьютеры состояли из электромеханических штук с контактами как у выключателей, — такие штуки назывались реле. Однажды инженеры нашли ошибку в программе. При этом код оказался верным, но между контактов одного реле застрял какой-то жук. Вот почему программисты называют ошибки багами1. Баги обычно спрятаны в коде, и, чтобы программа заработала, их нужно убрать. Что еще было вчера? 1 По английски bug (баг) — жук, насекомое. — Примеч. пер. 54 Поговорим на Python — Ты сказал, что я не должен повторять свой код. А еще ты сказал, что сегодня мы напишем функцию. — Правильно! Сначала скажи, что ты уже знаешь о функциях? — Мы два раза использовали функции, — ответил Эрик. — Это были print() и input(). Ты сказал, что кто-то их уже написал, так что теперь мы можем ими пользоваться. А еще у функции могут быть аргументы. Мы просто перечисляем аргументы в круглых скобках, и функция с ними что-то делает, например выводит на экран. — Верно! Ты отличный ученик! — улыбнулся Саймон. — Самое главное, что функции каждый раз делают одно и то же, но с разными аргументами. Поэтому если ты видишь, что тебе нужно делать одно и то же много раз, то стоит подумать, можно ли записать эти действия в виде функции. Чтобы понять, как написать функцию, посмотри на повторяющийся код и задай себе вопрос — какие части повторяются, а какие отличаются. Где твоя вчерашняя программа? — Вот она, — Эрик открыл файл menu.py в редакторе: drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] print("Напитки кофейни Эрика") print("-------------------------") i = 1 for d in drinks: print(i, d) i = i + 1 drink = input("Выберите напиток: ") print("Вкусы напитков от Эрика") print("--------------------------") i = 1 for f in flavors: print(i, f) i = i + 1 flavor = input("Выберите вкус: ") print("Топпинги для напитков от Эрика") print("---------------------------") i = 1 for t in toppings: print(i, t) i = i + 1 topping = input("Выберите топпинг: ") print("Ваш заказ: ") print("Основной напиток: ", drinks[int(drink) - 1]) Глава 3. Функции: не повторяйся! 55 print("Вкус: ", flavors[int(flavor) - 1]) print("Топпинг: ", toppings[int(topping) - 1]) print("Спасибо за заказ!") — Посмотри, что здесь повторяется? — спросил Саймон и начал рисовать диаграмму кода Эрика. повторяется повторяется повторяется print ("Напитки кофейни Эрика") for d in drinks: print d аргумент — заголовок аргумент — выбор пользователя drink = input ("Выберите напиток") аргумент — подсказка — Цикл. Функция print() в начале и функция input() в конце. — А чем отличаются эти три случая? — Подсказкой для функции input(), — ответил Эрик. — И заголовок немного меняется. Саймон решил помочь брату: — И ты запускаешь циклы for для разных списков — drinks, flavors и toppings, так? Давай передадим эти различия в качестве аргументов. В нашем случае аргументами будут: список того, что выбрал пользователь, название меню и подсказка. Давай откроем новый файл в редакторе и назовем его menu_function.py. В нем мы будем писать наши функции. Эрик нажал кнопку New (Создать), а потом Save (Сохранить) в редакторе. Он набрал имя файла menu_function.py и приготовился писать код. — Функции в Python начинаются со слова def, дальше идет имя функции, — продолжил Саймон. — Давай назовем нашу функцию menu. Дальше открой круглую скобку и перечисли аргументы. Эрик написал def menu( и задумался, что делать дальше. Саймон ему помог: — Мы говорили об аргументах. Ты, наверное, не знаешь, как их назвать. Помнишь, какая самая трудная задача в программировании? Выбрать имя для переменных и аргументов. Давай назовем их choices (выбор), title (заголовок) и prompt (подсказка). Просто напиши эти названия после скобки, закрой скобку и поставь двоеточие. 56 Поговорим на Python Эрик напечатал следующее: def menu(choices, title, prompt): Он нажал Enter, курсор перешел на следующую строку и сдвинулся на четыре знака вправо. — Мне здесь писать? — спросил Эрик. — Конечно! — ответил Саймон. — Видишь, редактор помогает тебе писать функцию! Посмотри на свой вчерашний код и скопируй строки, которые хочешь поместить в функцию. Сначала мы выводили заголовок, так что давай сделаем то же самое и здесь, но вместо реальной строки мы подставим аргумент с названием title. Дальше можно добавить строку с пунктиром, как было во вчерашней программе. Эрик написал вот это: def menu(choices, title, prompt): print(title) print("--------------------------------") — Теперь пиши цикл, — продолжил Саймон. — Но помни, что список теперь называется не drinks или flavors, а choices. Переменную цикла можно назвать с, потому что это первая буква слова ‘choices’. Эрик скопировал цикл из предыдущей программы. Он добавил в функцию input() аргумент prompt, не дожидаясь подсказки брата: def menu(choices, title, prompt): print(title) print("--------------------------------") i = 1 for c in choices: print(i, c) i = i + 1 choice = input(prompt) Саймон увидел код Эрика и сказал: — Прекрасно! Теперь нужно вернуть выбор пользователя. — Просто вывести на экран переменную choice из основной программы? — спросил Эрик. — Нет! — Саймон был рад, что Эрик задал этот вопрос. — Это еще одна очень важная вещь, которую нужно знать о функциях, — переменные, которые находятся внутри функции, видны только этой функции. Смотри, я нарисую. Глава 3. Функции: не повторяйся! 57 Основная программа не видит, что находится внутри функции Функция «sum» вPython a=5 b=3 s=a+b return s print(a) print(b) result = sum() ошибка! Она видит только то, что возвращает функция ошибка! print(result) — То есть если ты находишься вне функции, то ты не видишь, что внутри этих переменных. И если ты хочешь, чтобы основная программа увидела эти переменные, ты должен вернуть их. Внутри функции может быть много переменных, но в качестве результата мы обычно возвращаем одну или две. В нашем случае можно вернуть номер, который ввел пользователь, — он хранится в переменной choice. Саймон на секунду остановился и задумался. — Но можно сделать лучше, — сказал он. — Посмотри на свой вчерашний код. Что еще в нем повторяется? Эрик посмотрел на код и сказал: — Функции int(), а еще мы три раза вычитали единицу. Это раздражало, — Эрик никак не мог привыкнуть к тому, что индексы начинаются с нуля. — Хорошо, давай добавим все это в функцию, — предложил Саймон. — Мы преобразуем ответы пользователя в целые числа, получим элемент списка и вернем сам элемент, а не его индекс. Так наша функция станет еще полезнее. Основная программа вызовет функцию и выведет на экран не просто какое-то число, а название напитка, который выбрал пользователь. Давай покажу, — Саймон добавил в функцию операторы преобразования и строку return: def menu(choices, title, prompt): print(title) print("--------------------------------") i = 1 for c in choices: 58 Поговорим на Python print(i, c) i = i + 1 choice = input(prompt) answer = choices[int(choice) - 1] return answer — Теперь давай все проверим, — сказал Саймон. — Сразу после функции начинается основная программа, из которой мы вызываем функцию и выводим на экран полученный результат. Но сначала нам понадобятся списки с напитками и всем остальным. Скопируй их из верхней части вчерашней программы. Эрик добавил три списка сразу после функции. В этот раз строки не сдвинулись вправо, а начались с первой позиции. drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] — Хорошо, — сказал Саймон. — Теперь можно вызвать функцию. Передай ей заголовок, список напитков и подсказку input. Затем запиши результат работы функции в какую-нибудь переменную, например choice, и выведи эту переменную на экран. Эрик сделал, как ему сказал брат, и получил вот что: def menu(choices, title, prompt): print(title) print("--------------------------------") i = 1 for c in choices: print(i, c) i = i + 1 choice = input(prompt) answer = choices[int(choice) - 1] return answer drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] choice = menu(drinks, "Напитки Эрика", "Выберите напиток: ") print(choice) ТВОЙ ХОД! Создай свою функцию Создай свою функцию по аналогии с функцией Эрика. Подставь свое меню, заголовки и подсказки. Попробуй запустить функцию (раньше Эрика!). Глава 3. Функции: не повторяйся! 59 — Запускать? — спросил Эрик. — Да, все готово. Давай! Эрик нажал Run, и программа, как и раньше, попросила выбрать напиток. Эрик ввел ответ и получил результат, которого и ожидал: Напитки от Эрика -------------------------------1 горячий шоколад 2 кофе 3 кофе без кофеина Выберите напиток: 2 кофе >>> — Работает! — сказал Эрик. — Добавлю другие пункты меню, — он начал писать код. Меньше чем через 10 минут Эрик был готов проверить всю программу. Теперь она выглядела так: def menu(choices, title, prompt): print(title) print("--------------------------------") i = 1 for c in choices: print(i, c) i = i + 1 choice = input(prompt) answer = choices[int(choice) - 1] return answer drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] drink = menu(drinks, "Напитки от Эрика", "Выберите напиток: ") flavor = menu(flavors, "Вкусы напитков от Эрика", "Выберите вкус: ") topping = menu(toppings, "Топпинги для напитков от Эрика", "Выберите топпинг: ") print("Ваш заказ: ") print("Основной напиток: ", drink) print("Вкус: ", flavor) print("Топпинг: ", topping) print("Спасибо за заказ!") ТВОЙ ХОД! Добавь пункты меню Добавь в свою программу другие пункты меню. Они будут использовать ту же самую функцию, но с другими аргументами — список того, что выбрал пользователь, заголовки и подсказки. Попробуй запустить ее и проверить со своим меню. 60 Поговорим на Python Программа сработала именно так, как ожидалось! Эрик запустил программу, снова ввел 2, 2, 1 и получил свой заказ: Ваш заказ: Основной напиток: кофе Вкус: ваниль Топпинг: шоколад Спасибо за заказ! >>> — Заметил? Твоя программа стала короче, — сказал Саймон. — Теперь, если ты захочешь ее изменить, тебе нужно будет менять код только в одном месте. — Зачем мне ее менять? Она и так хорошо работает, — сказал Эрик. — Поверь, всегда можно что-то улучшить! — ответил Саймон. — Давай немного изменим заголовок. Ты обратил внимание, что пунктирная линия длиннее заголовка? — Да? А ты прав, — сказал Эрик. — Это потому, что я изменил заголовок на «Напитки от Эрика». Я могу сделать пунктирную линию короче. Это легко. — Конечно, ты можешь сделать ее короче, но смотри — в разделе drinks заголовок короче, а в разделе toppings — длиннее. Пунктирная линия должна соответствовать длине заголовка. — Но как это сделать? Может, лучше совсем удалить эту линию? — смутился Эрик. — Нет, она мне нравится, с ней твой счет выглядит как настоящий. Пусть остается. Мы рассчитаем длину заголовка и выведем линию такой же длины. Сейчас ты познакомишься с новой функцией и операцией. Функция, которую мы будем использовать, называется len(). Ты пишешь аргумент внутри круглых скобок, и функция возвращает тебе длину строки. Давай покажу. Сейчас мы применим новую опцию редактора Mu, она называется REPL и расшифровывается как Read — Eval — Print — Loop (цикл «чтение — вычисление — вывод»). Она позволяет взаимодействовать с Python. Обычно я использую ее, если хочу быстро что-то проверить или что-то кому-то показать, как сейчас, — улыбнулся Саймон. — Нажми кнопку REPL , — сказал он. Эрик нажал кнопку, и внизу редактора Mu появилось новое окно. — Видишь, здесь можно взаимодействовать с Python, — сказал Саймон. — Ты можешь набрать любой код на Python, и среда выполнит его. Можно даже использовать это как калькулятор. Попробуй набрать print('hello world') или 2 + 2 сразу после In [1]:. В REPL In означает ввод, а номер, который идет дальше, — это номер команды. REPL выведет Out[1]: и ответ. Сейчас увидишь. Глава 3. Функции: не повторяйся! 61 Эрик все напечатал. Саймон оказался прав: In [1]: print('hello world') hello world In [2]: 2 + 2 Out[2]: 4 — Теперь давай рассчитаем длину строки. Набери len('abcd'). Эрик набрал то, что сказал брат, и получил следующее: In [3]: len('abcd') Out[3]: 4 — Теперь ты видишь, что длина строки abcd равна 4, — сказал Саймон. — Со строковыми переменными можно делать то же самое. Задай переменную s, присвой ей строку 'hello' и рассчитай длину строки. Уверен, ты уже знаешь, как это сделать. Эрик набрал код в окне REPL и получил вот такой результат: In [4]: s = 'hello' In [5]: len(s) Out[5]: 5 — Хорошо, — сказал Саймон. — Теперь ты знаешь, как вычислить длину любой строки. Но это еще не все, таким же способом можно получить длину списка. Создай список из чисел 1, 2, 3, назови его, например, n и вычисли его длину. Эрик набрал: In [6]: n = [1, 2, 3] In [7]: len(n) Out[7]: 3 — Мы воспользуемся этим позже, а сейчас я покажу тебе фокус, — продолжил Саймон. — Что выдаст Python, если я попрошу умножить 2 на 2? — 4? — Эрику показалось, что это не очень похоже на фокус. Вопрос уж слишком простой. 62 Поговорим на Python — Правильно. А что выдаст Python, если я возьму букву A и умножу ее на 2? — Не знаю. Может быть, 2A? — Давай, попробуй в REPL! — предложил Саймон. Эрик напечатал выражение и получил результат: In [8]: 2 * 'A' Out[8]: 'AA' — Интересно! — удивился Эрик. — А что, если вместо буквы A ты возьмешь дефис и умножишь его на 10? Эрик почти догадался, к чему клонит Саймон, и напечатал: In [9]: 10 * '-' Out[9]: '----------' — Теперь замени цифру 10 длиной строки 'hello'. Эрик, наконец, уловил идею Саймона: In [10]: len('hello') * '-' Out[10]: '-----' ТВОЙ ХОД! Воспользуйся REPL и поэкспериментируй с функцией len() Запусти REPL, нажав его иконку в редакторе. Повтори все эксперименты Эрика. Попробуй умножить число на строку из двух или трех символов. Угадай результат. — Я понял! — сказал Эрик. — Мы берем аргумент title, вычисляем его длину и выводим пунктирную линию такого же размера! — Можешь сам доработать функцию? — спросил Саймон. — Да, конечно, теперь я знаю, что делать! — Эрик уже начал печатать. Он изменил только третью строку, и его функция стала выглядеть так: def menu(choices, title, prompt): print(title) print(len(title) * '-') i = 1 for c in choices: print(i, c) i = i + 1 Повторяет дефис (-) несколько раз Глава 3. Функции: не повторяйся! 63 choice = input(prompt) answer = choices[int(choice) - 1] return answer ТВОЙ ХОД! Измени функцию, чтобы она выводила пунктирную линию нужной длины Сделай так, чтобы программа выводила нужное количество дефисов, как это сделал Эрик. Попробуй использовать другой символ: знак равенства (=), нижнее подчеркивание (_) или что-то еще. Эрик проверил основную программу, и теперь все пунктирные линии оказались той же самой длины, что и заголовки. Саймон отметил: — Теперь ты видишь, что от аргументов функции зависит не только результат, который она возвращает, но и то, что она выводит на экран. — Всегда полезно анализировать аргументы, которые получает функция, — продолжил Саймон. — Мы только что проверили длину заголовка. А что, если твоя функция получит пустую строку, длина которой равна нулю? Что она сделает? — Не знаю, — ответил Эрик. — Наверное, она выведет пустую строку, то есть ничего. — Правильно, — сказал Саймон. — Но может быть, даже для пустого заголовка можно вывести что-то со смыслом? Может быть, просто «Меню кофейни Эрика» и пунктирную линию. Для таких случаев в Python есть значения аргументов по умолчанию. В твоей функции я бы изменил первую строку вот так, — Саймон отредактировал файл Эрика: def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): — В этой строке мы говорим Python: «Если аргумент title есть, возьми его значение». Если же при вызове функции аргумент title не передали, то функция возьмет значение по умолчанию, то есть значение Меню кофейни Эрика. Лучше всегда устанавливать значения по умолчанию. Их можно изменить при вызове функции. — Давай проверим, как это работает, — предложил Саймон. — Удали заголовок и подсказку в первом вызове функции, там, где список drinks. Оставь только аргумент drinks. Эрик сделал то, что предложил Саймон, и получил вот такой вызов функции: drink = menu(drinks) 64 Поговорим на Python Эрик снова запустил программу и увидел первый раздел меню: Меню кофейни Эрика ---------1 горячий шоколад 2 кофе 3 кофе без кофеина Выберите напиток: Вот полная программа Эрика. Листинг 3.1. Полная версия программы вывода меню с функцией def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): print(title) print(len(title) * "-") i = 1 for c in choices: print(i, c) i = i + 1 choice = input(prompt) answer = choices[int(choice) - 1] return answer drinks = ["горячий шоколад", "кофе", "кофе без кофеина"] flavors = ["карамель", "ваниль", "мята", "малина", "без добавок"] toppings = ["шоколад", "корица", "карамель"] drink = menu(drinks) flavor = menu(flavors, "Вкусы напитков от Эрика", "Выберите вкус: ") topping = menu(toppings, "Топпинги для напитков от Эрика", "Выберите топпинг: ") print("Ваш заказ: ") print("Основной напиток: ", drink) print("Вкус: ", flavor) print("Топпинг: ", topping) print("Спасибо за заказ!") ТВОЙ ХОД! Задай значения по умолчанию Добавь значения по умолчанию для аргументов title и prompt. Попробуй вызвать функцию без этих аргументов и убедись, что она выводит значения по умолчанию. — Да, теперь программа не говорит пользователю, что перед ним меню напитков, но это лучше, чем выводить пустую строку. Это полезно, когда нужно что-то быстро проверить. Описательные заголовки и подсказки всегда можно добавить позже. Думаю, на сегодня достаточно. Давай вспомним, что ты узнал. С чего мы начали? Глава 3. Функции: не повторяйся! 65 — Мы посмотрели на вчерашнюю программу и нашли код, который повторяется три раза. Ты сказал, что мы должны написать функцию, и рассказал про слово def и аргументы. — Хорошо, — похвалил Саймон. — Продолжай. Что ты запомнил об аргументах? — За аргументы я взял список того, что выбрал пользователь, заголовок и подсказку. — Эрику нравилось говорить о своей функции — ведь он впервые написал ее сам! — Затем ты показал мне функцию REPL в редакторе, — продолжил Эрик. — А потом мы вычислили длину строки. — И как мы ее использовали? — спросил Саймон. — Чтобы вывести счет, теперь он стал красивым. А потом мы попробовали установить значения по умолчанию для аргументов. Это немного скучно, но зато работает. Саймон отметил: — Ты сказал одну очень важную вещь. Хорошие решения в программировании часто выглядят скучно, зато работают. Бˆольшую часть времени разработчики занимаются очень скучными вещами, например проверяют, что ввел пользователь, ищут ошибки и так далее, а не придумывают разные трюки. Но именно благодаря этому программы работают, а значит, время потрачено не зря. Завтра мы узнаем, что нужно, чтобы программа работала правильно, даже если пользователь ввел что-то не то. А теперь отдохни! Ты отлично потрудился! Что мы узнали сегодня Функция — это фрагмент кода, который можно использовать (вызывать) много раз. Ты можешь написать свою функцию или взять ту, которую уже написал кто-то другой. Функции, написанные другими, обычно входят в библиотеку или модуль Python. Аргументы — это переменные, которые мы передаем функции при вызове. Функция принимает аргументы и использует их, чтобы получить результат, который можно вывести на экран или вернуть основной программе. REPL — цикл «чтение — вычисление — вывод» — это способ взаимодействия с Python. Он очень удобен, если нужно быстро проверить функцию. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch03. 4 Ошибки пользователей: ошибаются все В ЭТОЙ ГЛАВЕ 33 Эрик узнает, что пользователи не всегда делают то, что им говорят 33 Эрик учится повторять вопросы к пользователю с помощью циклов 33 Саймон помогает Эрику сделать программу надежнее — Эрик, вчера ты здорово поработал, — сказал Саймон на следующий день. — Ты написал очень хорошую функцию, добавил значения аргументов по умолчанию и все это проверил. — Да, — ответил Эрик. — Кажется, у нас получилась хорошая программа. Пойду покажу ее друзьям. — Подожди, — ответил Саймон. — Она еще не совсем готова. — Почему? По-моему, она работает как надо. — Правда? Давай проверим, — кажется, Саймон что-то задумал. Он снова запустил программу Эрика и в первом разделе меню ввел кофе: Глава 4. Ошибки пользователей: ошибаются все 67 Напитки от Эрика -------------------------------1 горячий шоколад 2 кофе 3 кофе без кофеина Выберите напиток: кофе Traceback (most recent call last): File "/home/erik/mu_code/menu_function.py", line 18, in <module> drink = menu("Напитки Эрика", drinks, "Выберите напиток: ") File "/home/erik/mu_code/menu_function.py", line 9, in menu answer = choices[int(choice) - 1] ValueError: invalid literal for int() with base 10: 'кофе' >>> Сообщение об ошибке от Python — Что ты делаешь? — разозлился Эрик. — Сюда нужно вводить числа, а не слова! — Но ты дал мне список и спросил, что я хочу. Я хочу кофе, вот я и ввел кофе. Что не так? — Саймон попытался сделать невинное лицо, но не смог сдержать улыбку. — Что ж, для таких глупышей, как ты, я напишу большими буквами: «ВВОДИТЕ ЦИФРЫ»! — прорычал Эрик. — Хорошо, хорошо, давай еще раз, — сказал Саймон. Он запустил программу и в первом разделе меню ввел 42: Выберите напиток: 42 Traceback (most recent call last): File "/home/erik/mu_code/menu_function.py", line 18, in <module> drink = menu("Напитки Эрика", drinks, "Выберите напиток: ") File "/home/erik/mu_code/menu_function.py", line 9, in menu answer = choices[int(choice) - 1] IndexError: list index out of range >>> Сообщение об ошибке от Python — Что, опять? Опять сломал?! — Эрик уже был готов ударить Саймона по рукам. — Ты что, не видишь, что тут только три пункта? Зачем ты ввел 42? — Во-первых, потому что 42 — мое любимое число. Во-вторых, да, я ошибся. Пользователи ошибаются. Ну правда. Я хотел показать тебе, что твоя программа должна быть к этому готова. Ты можешь написать что угодно, даже большими буквами, но всегда найдется кто-нибудь, кто не прочитает и сделает ошибку. — И что мне делать? — Эрик все еще злился на брата, но уже понимал, что Саймон прав. Эрик и сам делал подобные ошибки в других программах. — Нужно проверить, что именно ввел пользователь, и, если он допустил ошибку, сказать ему об этом. Давай подумаем, как это сделать. Что нужно ввести в первом разделе меню? — спросил Саймон. — 1, 2 или 3, — ответил Эрик. 68 Поговорим на Python — Хорошо, то есть нужно проверить вот что: если пользователь ввел 1, 2 или 3, то мы передаем это значение функции и выбираем из списка элемент с таким номером, но если пользователь ввел что-то другое, то мы говорим ему, что он ошибся. — Да. Я вспомнил — в Python есть конструкция if-else, — предложил Эрик. — Давай попробуем ее использовать, — сказал Саймон. — Как это сделать? Представь, что я ничего не знаю об if-else, и попробуй объяснить мне. — Я добавлю в функцию условие: «Если (if) пользователь ввел 1, 2 или 3, то использовать это значение. Если пользователь ввел что-то другое (else), то вывести на экран сообщение о том, что можно вводить только 1, 2 или 3». — А как ты проверишь, что пользователь ввел 1, 2 или 3? Как это сделать в Python? — спросил Саймон. — Я не помню… — сказал Эрик. — С помощью оператора сравнения. Он состоит из двойного знака равенства: ==. Например, чтобы проверить, что choice равно 1, нужно написать choice == '1'. — Но я хочу проверить все три цифры, — сказал Эрик. — Мне написать if choice == '1' or '2' or '3'? — Ну почти, — ответил Саймон. — Да, в Python можно использовать оператор or, но в этом случае нужно написать три разных сравнения и поставить оператор or между ними. Вот так, — Саймон написал код на листе бумаги: if choice == '1' or choice == '2' or choice == '3': — Хорошо, я понял, — сказал Эрик. — Прекрасно, давай добавим это в код, — сказал Саймон. Эрик открыл редактор и добавил в функцию четыре строки перед строкой return: def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): print(title) print(len(title) * "-") i = 1 for c in choices: print(i, c) i = i + 1 choice = input(prompt) if choice == '1' or choice == '2' or choice == '3': answer = choices[int(choice) - 1] else: Глава 4. Ошибки пользователей: ошибаются все 69 print("Введите число 1, 2 или 3!") return answer — Теперь давай проверим, — сказал Саймон. Эрик запустил программу и в первом разделе меню ввел 2. — Зачем ты ввел 2? — спросил Саймон. — Потому что я хочу кофе, — ответил Эрик. — Но мы же должны проверить программу на неверные ответы! — сказал Саймон. — Я знаю, что это твоя программа и ты не хочешь ее ломать, но как разработчик ты должен попробовать сломать ее. Ты должен представить все способы, которыми пользователь может сломать программу. Я знаю, это трудно и очень неприятно, но нужно преодолеть себя и ввести все варианты неверных значений, какие только могут быть. — Ну ладно, — сказал Эрик и перезапустил программу. В первом разделе меню он, так же как Саймон, ввел 42 (ввод Эрика выделен жирным шрифтом): Меню кофейни Эрика ---------1 горячий шоколад 2 кофе 3 кофе без кофеина Выберите напиток: 42 Введите число 1, 2 или 3! Traceback (most recent call last): Python File "/home/erik/mu_code/menu_function.py", line 21, in <module> показывает, drink = menu(drinks) где находится File "/home/erik/mu_code/menu_function.py", line 14, in menu ошибка return answer UnboundLocalError: local variable 'answer' referenced before assignment >>> Python сообщает, в чем заключается ошибка ПРИМЕЧАНИЕ Больше информации об ошибках в Python ищите на сайте книги: https://pythonicadventure.com/troubleshooting/index.html. — Давай посмотрим, в чем дело, — сказал Саймон. — Когда ты ввел 42, программа сообщила, что можно вводить только 1, 2 или 3. Это хорошо. Но дальше что-то пошло не так. Смотри, здесь написано, что программе встретилась необъявленная переменная answer. UnboundLocalError: local variable 'answer' referenced before assignment1 1 Сообщение об ошибке: «ссылка на локальную переменную answer до ее объяв­ле­­ния». — Примеч. ред. 70 Поговорим на Python — Проще говоря, это значит, что ты еще не создал переменную answer, но уже пытаешься ее использовать. Причем Python показывает тебе, где именно ты это делаешь — в строке return answer, но в этой строке Python пока ничего не знает о переменной answer. — Почему? — спросил Эрик. — В коде ведь есть строка answer =. — Да, она есть, но важно то, когда эта строка вызывается. Ты создаешь переменную answer только тогда, когда пользователь вводит правильные данные. Если пользователь ошибся, то переменная answer не создается. Другими словами, если пользователь вводит неверное число или слово, ты все равно должен что-то вернуть программе. Это очень важное правило: перед использованием переменных их нужно объявить и присвоить им начальные значения. Какое значение можно присвоить переменной answer, если пользователь допустил ошибку? Например, пустую строку — ''. Добавь это в свою функцию и проверь, что получится. Эрик изменил свою функцию так: def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): print(title) print(len(title) * "-") i = 1 for c in choices: print(i, c) i = i + 1 choice = input(prompt) if choice == '1' or choice == '2' or choice == '3': answer = choices[int(choice) - 1] else: print("Введите число 1, 2 или 3!") answer = '' Переменной answer присваивается пустая строка return answer ТВОЙ ХОД! Добавь в код проверку данных, которые ввел пользователь Добавь в функцию menu() код, который только что добавил Эрик. Убедись, что, если переменная введена неверно, answer присваивается пустая строка. Проверь, что программа на самом деле проверяет ответы пользователя. Эрик проверил программу еще раз, и она не выдала ошибку. Программа вывела сообщение Введите число 1, 2 или 3! и перешла к следующему разделу меню. — Ну как, теперь все правильно? — спросил Саймон. — Она вывела сообщение, что нужно вводить 1, 2 или 3, — ответил Эрик. — Кажется, правильно. Глава 4. Ошибки пользователей: ошибаются все 71 — Но теперь ты не знаешь, что пользователь хочет заказать. Если он ввел неверное число, нужно дать ему возможность ввести верное. Это не школьный тест, в котором есть только один шанс. Нужно спрашивать пользователя, пока он не даст подходящий ответ. — А как это сделать? — спросил Эрик. — Для этого существует другой вид цикла, — пояснил Саймон брату, — это цикл while. Он повторяет действия и при каждом повторении проверяет условие. Если условие истинно, цикл продолжается. Если условие ложно — цикл завершается. Если истинность условия можно определить до начала цикла, то проверку этого условия проводят в начале цикла. В этом случае мы говорим программе: «Пока что-то истинно, делай вот так». Но в нашем случае в начале цикла мы не знаем, истинно условие или ложно, потому что мы еще не задавали пользователю никаких вопросов. Так что мы начнем бесконечный цикл и будем проверять условие внутри этого цикла, то есть после того, как получим данные от пользователя. Если условие станет истинным, мы выйдем из цикла. Это называется прерыванием цикла. Давай покажу на рисунке, — сказал Саймон и начал рисовать, а потом пояснил: — В этом примере мы знаем истинность условия до начала цикла. Мы проверяем условие и решаем, нужно ли начинать цикл. Чтобы цикл запустился, условие должно быть истинно (True), иначе он даже не начнется. Я обозначил условие ромбом; так в программировании принято обозначать точки принятия решений. Если условие истинно, то мы что-то делаем. Главное, чтобы это «что-то» меняло истинность условия. Иначе цикл будет продолжаться вечно, а нам это не нужно. условие Нет условие истинно? Да что-то делать конец ПРИМЕР n=5 while n > 0: n=n-1 1. Мы уже знаем истинность условия 2. Если условие ложно, то цикл даже не начнется Проверка условия Блок «что-то делать» должен менять истинность условия, иначе цикл никогда не закончится! 72 Поговорим на Python — После того как мы что-то сделали, возвращаемся к началу цикла и снова проверяем условие. Если оно все еще истинно, то мы повторяем блок «что-то делать». Если нет, то выходим из цикла и продолжаем основную программу. Вот тебе простой пример: нужно посчитать в обратную сторону от 5 до 0. Сначала мы присваиваем значение 5 переменной n и в начале цикла проверяем, что значение этой переменной больше 0. Оно и правда больше, а значит, можно запустить цикл. Помни, что мы должны менять условие, иначе цикл никогда не закончится. В нашем примере при каждом повторении цикла мы вычитаем 1 из n. В конце концов, переменная n станет равна 0 и цикл остановится. В этом примере важно, что мы знаем значение переменной n до начала цикла, а еще мы знаем, что это значение больше 0. А что, если мы не знаем значения переменной или переменной вообще не существует? Как и в нашем случае, можно проверять ответ пользователя после того, как он выберет какой-то пункт меню. То есть мы должны сделать вот что, — Саймон нарисовал вторую схему и продолжил свой рассказ: — Здесь мы начинаем цикл, не проверяя никакого условия. Мы сначала что-то делаем и только после этого проверяем условие. Иногда такой цикл называют «делать, пока», что означает «делать что-то, пока условие истинно». Если условие истинно, мы выходим из цикла и продолжаем основную программу. В этом примере я использовал твой случай с input(). Ты запрашиваешь ввод, а затем проверяешь его. Вот твое условие: если ввод правильный, выйти из цикла. В Python для этого есть оператор break. начать без условия что-то делать Нет условие истинно? В начале цикла ничего не проверяется Блок «что-то делать» должен менять истинность условия, иначе цикл никогда не закончится Проверка условия Да конец ПРИМЕР while True: answer = input() if answer == '1': break Это длинное объяснение слегка утомило Эрика, но, кажется, теперь он понял, что делать. Он спросил брата: Глава 4. Ошибки пользователей: ошибаются все 73 — То есть, чтобы получить правильный ответ, я должен просто поставить while перед строкой input() и добавить break в конце? — Да, — ответил Саймон. — Именно так. Только не забудь сдвинуть строки, которые входят в цикл, на четыре знака вправо. Не волнуйся, редактор тебе подскажет. Эрик начал дорабатывать программу. Через несколько минут у него получилась вот такая функция: def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): print(title) print(len(title) * "-") i = 1 for c in choices: print(i, c) i = i + 1 while True: choice = input(prompt) if choice == '1' or choice == '2' or choice == '3': answer = choices[int(choice) - 1] break else: print("Введите число 1, 2 или 3!") answer = '' return answer — Правильно? — спросил Эрик у брата. — Говорю же, редактор тебе подскажет. Нажми кнопку Check (Проверить) Эрик нажал Check и увидел это: "Меню кофейни Эрика" ("Введите число 1, 2 или 3!") "Выберите напиток: " Синтаксическая ошибка. Python не понимает эту строку. Проверьте, нет ли пропущенных символов! Ожидается смещенный блок . 74 Поговорим на Python — Видишь? — сказал Саймон. — Ты забыл сдвинуть строки вправо. Вот почему редактор говорит, что ожидает здесь смещенный блок. Сдвинь вправо все строки, которые входят в цикл. Эрик изменил функцию и снова проверил код: def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): print(title) print(len(title) * "-") i = 1 for c in choices: print(i, c) i = i + 1 while True: Здесь начинается цикл while choice = input(prompt) if choice == '1' or choice == '2' or choice == '3': answer = choices[int(choice)- 1] break Если ответ подходящий, выйти из цикла else: print("Введите число 1, 2 или 3!") answer = '' return answer Конец смещенного блока — конец цикла ТВОЙ ХОД! Добавь в функцию цикл while Добавь в свою программу такой же цикл, как у Эрика. Попробуй нажать кнопку Check и найти ошибки в коде. Сделай какие-нибудь ошибки и проверь, найдет ли их редактор. На кнопке Check на секунду появился зеленый значок «большой палец вверх» , и Эрик понял, что все в порядке. Он нажал Run, чтобы запустить программу. В первом разделе меню Эрик, так же как Саймон в прошлый раз, ввел 42. На экране появилось сообщение о том, что можно вводить только 1, 2 или 3, а затем вернулась подсказка. Программа не сломалась, она работала! «Как еще Саймон ломал мою программу? — попытался вспомнить Эрик. — Точно, он вводил ‘кофе’! Надо тоже попробовать». Эрик ввел кофе, и программа снова ответила так, как он ожидал! Наконец Эрик ввел 2 и перешел к следующему разделу меню. В окне редактора он увидел вот это: Меню кофейни Эрика ---------1 горячий шоколад 2 кофе 3 кофе без кофеина Выберите напиток: 42 Глава 4. Ошибки пользователей: ошибаются все 75 Введите число 1, 2 или 3! Выберите напиток: кофе Введите число 1, 2 или 3! Выберите напиток: 2 Вкусы напитков от Эрика -------------------- В следующем разделе меню Эрик решил выбрать малину, он ввел 4 и получил то же самое сообщение: Вкусы напитков от Эрика ------------------1 карамель 2 ваниль 3 мята 4 малина 5 без добавок Выберите вкус: 4 Введите число 1, 2 или 3! Выберите вкус: — Почему так происходит? — спросил Эрик. — Программа работает так, как ты ее написал, — ответил Саймон. Конечно, он уже знал, в чем проблема. — Как ты написал условие? — спросил Саймон. Эрик ответил: — Если ответ равен 1, 2 или 3… а-а-а, я понял! Я ввел 4, и программа думает, что это неправильный ответ! Как это исправить? — Полагаю, для каждого раздела меню нам нужен отдельный список подходящих ответов, — сказал Саймон. — Можно передать его как еще один аргумент. Но мне кажется, ты уже достаточно знаешь, чтобы придумать решение получше. — Какое решение? — спросил Эрик. — Опять что-нибудь с циклами? — Не только, — ответил Саймон. — Сейчас ты кое-что узнаешь и о списках. Я понимаю, что ты уже устал от моих лекций, но нужно разобраться с этой темой сегодня. Программа снова заработает как надо — по-моему, оно того стоит, — подмигнул Саймон брату. Эрик и правда устал, но баг (да, он запомнил это слово!) со вкусами его сильно раздражал, и он хотел поскорее с ним разобраться. — Хорошо, — вздохнул Эрик. — Давай исправим этот баг. Что ты там говорил о списках? — Посмотри на условие, — сказал Саймон. — Ты использовал простую проверку if-else и проверял ввод на допустимые ответы: 1, 2 или 3. А что, если список 76 Поговорим на Python длинный и в нем, например, 20 элементов? if-else тоже получится слишком длинным. В Python есть другой способ. Можно проверить, содержится ли элемент в списке. В нашем случае можно проверить, попадает ли ответ пользователя в список из элементов 1, 2, 3. Давай покажу на первом разделе меню. Саймон взял клавиатуру и изменил строку if choice == … вот так: . . . while True: choice = input(prompt) if choice not in ['1', '2', '3']: answer = choices[int(choice) - 1] break else: print("Введите число 1, 2 или 3!") answer = '' . . . — Тут надо пояснить, — сказал Саймон. — Смотри, пользователь вводит строку со значением 1 или 42. Мы проверяем, находится ли это значение в списке допустимых ответов, который содержит 1, 2 и 3. Если значение не в списке, выводим сообщение об ошибке и продолжаем цикл. Если значение нашлось в списке допустимых ответов, преобразуем его в число и выбираем из меню элемент с таким номером. — Я понял, — сказал Эрик. — Но как насчет второго раздела меню, где была ошибка? Программа не дала мне выбрать номер 4, потому что его не было в if. С твоим списком будет то же самое. У меня пять вкусов, значит, мне нужен другой список ответов, так? — Отличный вопрос! — сказал Саймон. — Я уже хотел сам задать его. Ты прав. У каждого раздела меню должен быть свой список допустимых ответов. Если мы знаем, что находится в нашем меню, то такой список не проблема; мы можем создать его сами. Давай я сначала все напишу, а потом объясню по шагам. Саймон добавил такие строки перед блоком if: . . . while True: Пустой список choice = input(prompt) допустимых ответов allowed_answers = [] for a in range(1, len(choices)+1): allowed_answers.append(str(a)) if choice not in allowed_answers: answer = choices[int(choice) - 1] break else: print("Введите число 1, 2 или 3!") answer = '' . . . Рассчитывает длину списка меню Добавляет допустимый ответ Глава 4. Ошибки пользователей: ошибаются все 77 — Сначала мы создаем пустой список допустимых ответов. Затем вычисляем длину списка раздела меню с помощью функции len(). Потом вызываем функцию range(), чтобы создать последовательность номеров от одного до длины списка. Для напитков мы получим три, а для вкусов — пять. Для напитков последовательность будет 1, 2, 3. Для вкусов — 1, 2, 3, 4, 5. Думаю, ты понял смысл. Только обрати внимание, что в функции range() мы должны использовать не последний элемент последовательности, а элемент, который идет после последнего и не входит в последовательность. Поэтому мы должны прибавить к длине списка раздела меню единицу, вот так: len(choices)+1 — Наконец в цикле for мы преобразуем каждое число последовательности в строку и добавляем эту строку в конец списка допустимых ответов с помощью функции append(). Саймон закончил объяснять и добавил: — Да, на первый взгляд это сложно, но попробуй прочитать код на Python сам, и ты поймешь его, так же как понимаешь обычный язык1. Теперь мы должны заменить список с элементами 1, 2, 3 на список допустимых ответов, который мы только что создали. Саймон внес изменения, посмотрел в код и хлопнул себя по лбу. — Как я не заметил?! — Что? — Эрик думал, что они почти закончили, но, видимо, нашлось что-то еще. — Мы должны изменить подсказку, — сказал Саймон. — Наша функция теперь принимает списки разной длины, а значит, мы должны сказать пользователю что-то вроде: «Введите число от 1 до 6» или «от 1 до 12», в зависимости от длины раздела меню. Помнишь, как мы получали длину списка? — Функцией len()? — спросил Эрик. — Именно! — сказал Саймон и внес последние изменения. Теперь функция стала выглядеть так: def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): print(title) print(len(title) * "-") i = 1 for c in choices: 1 Под «обычным языком» в данном случае имеется в виду английский язык. — Примеч. ред. 78 Поговорим на Python print(i, c) i = i + 1 while True: choice = input(prompt) allowed_answers = [] for a in range(1, len(choices)+1): allowed_answers.append(str(a)) if choice in allowed_answers: answer = choices[int(choice) - 1] break else: print("Введите число от 1 до ", len(choices)) answer = '' Вычисляет количество пунктов в разделе меню с помощью функции len() return answer ТВОЙ ХОД! Добавь список allowed_answers Добавь в свою функцию список допустимых ответов. Проверь, умеет ли твоя функция обрабатывать списки разной длины. — Мы защитили программу от ошибок пользователя! — сказал Саймон. — Проверь. Все работает? Эрик снова запустил программу, ввел 42, кофе и weryiuryt587456, но программа не упала. Каждый раз она выдавала напоминание о том, что нужно ввести число от 1 до 3 или до 5 в зависимости от раздела меню. — Круто! Работает и не падает! — Эрик был невероятно рад, что написал такую надежную программу. — И еще кое-что, — сказал Саймон. — Обещаю, это последнее на сегодня. — Хорошо, — сказал Эрик. Ему уже начало нравиться все это программирование. Особенно то, что теперь его программа выглядит как настоящая — да еще и работает! Пусть она еще не совсем похожа на полноценное приложение, зато она уже работает как чат с кофейней. Эрик представил, как он переписывается со своей любимой кофейней, заказывает напитки, а потом приходит и забирает их. Саймон сказал: — Твоя программа разрешает вводить только числа от 1 до 3 или до 5. А что, если я хочу пропустить что-нибудь? Например, если мне не нужен топпинг? — В меню вкусов есть пункт «без добавок». Можно добавить такой же пункт для топпингов, — ответил Эрик. Глава 4. Ошибки пользователей: ошибаются все 79 — Да, это вариант, — сказал Саймон. — Но обычно пользователю предлагают выйти из меню, например так: «Чтобы выйти из меню, нажмите X». Думаю, нам тоже надо добавить такую функцию. — И как это сделать? — спросил Эрик, но потом немного подумал и сказал: — Знаю! Нужно добавить ‘X’ к списку допустимых ответов. Правильно? — Абсолютно! — Саймон очень обрадовался, что его брат делает такие успехи. — Помнишь, мы добавляли элементы в список с помощью функции append()? Можем применить ее и здесь, как только закончим добавлять числа в список allowed_answers. — Я попробую, — сказал Эрик и начал печатать. — Да, конечно, давай, — подбодрил брата Саймон. — Только проверь, что ты вызываешь функцию append() после цикла for. И добавь перед ней пустую строку; так будет лучше видно, что эта функция не входит в цикл. Вот новая версия функции menu(): def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): print(title) print(len(title) * "-") i = 1 for c in choices: print(i, c) i = i + 1 while True: choice = input(prompt) allowed_answers = [] for a in range(1, len(choices)+1): allowed_answers.append(str(a)) allowed_answers.append('X') Теперь 'X' тоже допустимый ответ if choice in allowed_answers: answer = choices[int(choice) - 1] break else: print("Введите число от 1 до ", len(choices)) answer = '' return answer — Неплохо, — сказал Саймон. — Я еще добавлю маленькую ‘x’, потому что большинство напишет именно ее. А что мы будем делать, если пользователь введет ‘x’? — Выйдем из цикла? — спросил Эрик. 80 Поговорим на Python — Правильно! Но что мы тогда вернем основной программе? Раньше мы возвращали выбор пользователя: кофе, шоколад или что-то еще. А что мы вернем, если пользователь введет ‘x’? — Ничего? — предположил Эрик. — Да, мы вернем пустую строку, — сказал Саймон. — Если пользователь ввел ‘x’, то переменной answer нужно присвоить пустую строку (''), а затем вернуть answer так же, как любой другой ответ пользователя. Главное, добавь проверку на ‘x’ перед преобразованием строки в число, но после проверки на принадлежность к списку allowed_answers. Понимаешь, где это в коде? — Да, сразу после строки if choice in allowed_answers. — Прекрасно! Если ты добавишь проверку на ‘x’, то у тебя получится вложенное ветвление — оператор if внутри оператора if. Оно встречается достаточно часто: иногда бывает три уровня вложенных операторов if и даже больше. Только убедись, что ты правильно сделал отступы. Тогда компьютер поймет, что делать при истинном условии, а что при ложном. Эрик немного доработал код и получил вот это: Листинг 4.1. Финальная версия меню кофейни Эрика def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): print(title) print(len(title) * "-") i = 1 for c in choices: print(i, c) i = i + 1 while True: choice = input(prompt) allowed_answers = [] for a in range(1, len(choices)+1): allowed_answers.append(str(a)) allowed_answers.append('X') allowed_answers.append('x') if choice in allowed_answers: if choice == 'X' or choice == 'x': answer = '' break else: answer = choices[int(choice) - 1] break else: print("Введите число 1 до ", len(choices)) answer = '' return answer Глава 4. Ошибки пользователей: ошибаются все 81 ТВОЙ ХОД! Добавь опцию выхода из меню Добавь опцию 'x' к списку допустимых ответов. Затем реализуй выход из меню с помощью вложенного ветвления. Проверь, что выход из меню работает правильно. Какой заказ ты получишь, если введешь 'x' для всех пунктов меню? Эрик проверил программу: он ввел х для всех трех пунктов меню и получил, что ожидал: Меню кофейни Эрика ---------1 горячий шоколад 2 кофе 3 кофе без кофеина Выберите напиток: x Вкусы напитков от Эрика ------------------1 карамель 2 ваниль 3 мята 4 малина 5 без добавок Выберите вкус: x Топпинги для напитков от Эрика ---------------------1 шоколад 2 корица 3 карамель Выберите топпинг: x Ваш заказ: Основной напиток: Вкус: Топпинг: Спасибо за заказ! — Пустой заказ! — сказал Эрик. — Правильно, так и должно быть, — подтвердил Саймон и продолжил: — Мне нравится твоя программа. Во-первых, она работает, а это самое главное. Вовторых, она дружелюбная, потому что подсказывает пользователю, что делать. В-третьих, она проверяет данные на входе и не дает ввести неверные значения. А теперь давай пройдемся по тому, что ты узнал сегодня. О чем мы говорили в начале? — В начале ты сломал мою программу! — ответил Эрик. Теперь он уже не так сильно злился, потому что Саймон помог ему все исправить. — А еще ты сказал, 82 Поговорим на Python что я всегда должен думать о том, что пользователи могут ввести неверные данные. — Верно, учить упрямых пользователей, которые не хотят следовать инструкциям, — это тоже работа программиста, — сказал Саймон. — Потом мы написали цикл, в котором проверили, что вводит пользователь, и запретили ему вводить неподходящие ответы. Потом ты рассказал мне про функцию append(), которая добавляет элементы в список. Потом я написал проверки с вложенным ветвлением, и теперь мой код выглядит как настоящая программа из фильма. Саймон улыбнулся: — Поверь, программы, которые показывают в фильмах, не очень похожи на настоящие. Но ты прав, твоя программа усложняется. Теперь в ней есть разные операторы Python, а еще циклы, ветвления и списки. — А потом мы добавили в меню вариант ‘x’, и теперь каждый пользователь может получить пустой заказ! — усмехнулся Эрик. — Почему бы и нет? — спросил Саймон. — Нельзя же заставлять пользователей что-то заказывать. Нужно дать им возможность отменить заказ или выйти из меню. Программа действительно неплохая. Завтра попробуем превратить тебя из программиста в менеджера кофейни. — А я еще не менеджер? — спросил Эрик. — Конечно, менеджер, — улыбнулся Саймон. — А теперь представь, что ты — менеджер кофейни — только что получил новый вкус для напитков и хочешь добавить его в меню вкусов. И парочку новых топпингов. Что ты будешь делать? — Добавлю топпинги в список топпингов. Ничего сложного, — ответил Эрик. — Да, но ты просто менеджер, а не программист. Ты ничего не знаешь об этой программе и не знаешь Python, но хочешь добавить новые вкусы и топпинги в меню. Программист должен сделать так, чтобы менеджер мог легко добавлять новые пункты в меню. — И как, по-твоему, это сделать? — спросил Эрик. Он догадывался, что у Саймона уже есть мысли на этот счет. — Думаю, надо поместить меню в файлы и читать списки из этих файлов. — Как из Word? — спросил Эрик. — Да, почти, — сказал Саймон. — Программа откроет файлы и прочитает их. Наверное, лучше завести по одному файлу на каждый раздел меню. В одном Глава 4. Ошибки пользователей: ошибаются все 83 файле мы будем хранить напитки, в другом — вкусы, а в третьем — топпинги. Тогда менеджеру не придется редактировать код на Python, он сможет просто изменить эти файлы и все. Идет? — Да, интересно, — сказал Эрик. Он не мог представить, что его программа на Python будет открывать файлы так же, как это делает Word. — Отлично, — сказал Саймон. — Этим мы займемся завтра. А пока отдохни. Что мы узнали сегодня Пользователи ошибаются. Пользователи не всегда следуют инструкциям, которые мы даем в программе. Нужно быть к этому готовым и проверять ввод на ошибки, неверные типы и так далее. Отступы. Когда ты создаешь блок в Python (например, цикл while), убедись, что все строки кода в блоке имеют отступ, то есть сдвинуты вправо. Выход из меню. Пользователь должен иметь возможность выйти из любого раздела меню. Например, если клиент не хочет заказывать топпинг, у него должна быть возможность пропустить раздел топпингов. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch04. 5 Работа с файлами: будни менеджера кофейни В ЭТОЙ ГЛАВЕ 33 Эрик узнает, что нужно менеджеру кофейни 33 Эрик загружает меню из файла 33 Эрик пишет вторую функцию на Python — В прошлый раз ты что-то говорил про менеджера кофейни, — сказал Эрик на следующий день. — Да, я сказал, что будет неплохо, если менеджер кофейни сможет менять меню, не трогая код на Python, — ответил Саймон. — Да, это хорошая идея, — сказал Эрик. — Не все знают Python. — Я предлагаю создать простые текстовые файлы для каждого раздела меню и назвать эти файлы drinks.txt, flavors.txt и toppings.txt. Твоя программа их прочитает и создаст списки из пунктов меню, записанных в этих файлах. — Почему у всех названий на конце .txt? — спросил Эрик. — Может, лучше выбрать формат .docx, чтобы менеджер редактировал файлы в Microsoft Word? — Ты прав, — сказал Саймон. — Менеджеру проще работать в Word, но нам нужен простой текстовый файл, без шрифтов, заголовков и таблиц. Такой файл похож на твой код на Python — в нем только простой текст, и менеджер будет работать Глава 5. Работа с файлами: будни менеджера кофейни 85 с ним через простой текстовый редактор. Когда я написал в конце .txt, я сказал операционной системе — Windows, macOS, Linux или любой другой, — что этот файл нужно открывать простым текстовым редактором, а не такой сложной программой обработки текста, как Word. Во всех операционных системах есть редактор, с помощью которого можно открыть простой текстовый файл. А еще для чтения таких файлов можно установить стороннее приложение, например редактор Mu. Да и самому Python намного проще читать из простого текстового файла, чем из файла .docx. Давай создадим файлы. Ты можешь сделать это в редакторе Mu. Просто не забудь добавить расширение .txt при сохранении. Иначе редактор автоматически добавит расширение .py. Создай новый файл, напиши в нем названия напитков на отдельных строчках и сохрани его с именем drinks.txt. Потом сделай то же самое для вкусов и топпингов. Эрик начал работать. Через несколько минут у него было готово три файла: Листинг 5.1. Файл drinks.txt кофе горячий шоколад кофе без кофеина Листинг 5.2. Файл flavors.txt карамель ваниль мята малина без добавок Листинг 5.3. Файл toppings.txt шоколад корица карамель ТВОЙ ХОД! Создай свои файлы меню Создай текстовые файлы со своими пунктами меню, как это только что сделал Эрик. Убедись, что это файлы с простым текстом. Используй в меню свои любимые вкусы и топпинги. — Что теперь? — спросил Эрик. — Теперь мы научимся работать с файлами в Python. Давай создадим для этого новую программу. Сначала мы потренируемся, а потом добавим получившийся код 86 Поговорим на Python в основную программу. Я обычно так делаю, когда изучаю что-то новое, — пробую это в отдельной простой программе и только после этого добавляю код в основное приложение. Создай новый файл в редакторе и сохрани его с именем files.py. Эрик уже несколько раз создавал файлы, так что сейчас он сделал это за пару секунд. — Работа с файлами — сложная тема, поэтому я сначала нарисую схему, — сказал Саймон. компьютер "drinks.txt" Python open("drinks.txt") переменная дескриптор файла "f" read() write() close() — Чтобы работать с файлом, тебе нужно знать имя этого файла. Сначала ты просишь редактор открыть файл с именем drinks.txt. Затем редактор читает файл и показывает тебе его содержимое. Потом ты редактируешь файл и сохраняешь его, то есть записываешь файл на жесткий диск компьютера. Пока все понятно? — спросил Саймон. — Да, — сказал Эрик. — Но жесткие диски есть только у очень старых компьютеров. В моем компьютере вместо жесткого диска установлен SSD-накопитель — это не диск. Мне Алекс сказал. — Совершенно верно! — Саймон был рад слышать это от брата. — Да, теперь в большинстве компьютеров используется SSD — это не диск в прямом смысле слова, потому что он не крутится. — Давай вернемся к файлам, — сказал Эрик. — Я вижу, что ты написал «компьютер» и «Python» вверху картинки. Что это значит? — Это значит, — сказал Саймон, — что, если ты хочешь работать с файлом в Python, ты должен создать специальный объект, который называют дескриптором файла (file handle). Этот объект используется для чтения и записи файлов. Чтобы создать такой объект, вызови функцию open() и в качестве аргумента передай ей имя файла. В нашем случае это будет open("drinks.txt"). Эта функция возвращает дескриптор файла, который нужно присвоить какой-то переменной. В моем примере переменная называется f, но ты можешь взять любое имя. Глава 5. Работа с файлами: будни менеджера кофейни 87 — Почему все так сложно? — спросил Эрик. — Почему нельзя просто взять имя файла? — Да, сначала все кажется сложным. Но мы не можем взять имя файла, потому что, как ты помнишь, это просто строка. Если мы хотим читать из файла, то это значит, что мы хотим читать из файла с таким именем, а не из строки. Имя файла и сам файл — разные вещи. Когда мы вызываем функцию open(), мы создаем связь между именем файла и самим файлом. Мы говорим Python: «Найди на компьютере файл с именем drinks.txt и работай с ним как с файлом». Пока не задумывайся об этом. Иногда лучший способ понять что-то — начать применять это на практике. — Хорошо, — сказал Эрик. Он пока немного не понимал объяснение Саймона, но хотел увидеть, как Python прочитает меню из текстовых файлов, которые он только что создал. — Теперь давай напишем простую программу на Python, которая будет работать с файлами, — сказал Саймон. — В редакторе перейди на вкладку, в которой открыта твоя программа files.py. Посмотри на мою схему: ты должен вызвать функцию open(), передать ей имя файла, например drinks.txt, и сохранить результат в переменную f. Сможешь это сделать? — Давай попробую, — и Эрик написал вот это: f = open("drinks.txt") — Хорошо! — сказал Саймон. — Теперь у тебя есть объект типа «файл» с именем f. Чтобы прочитать данные из этого объекта, нужно вызвать метод read(). Методы похожи на функции, они работают почти так же, но применяются к объектам. Об объектах мы поговорим позже, пока тебе нужно запомнить только одно: чтобы вызвать метод объекта, нужно написать имя объекта, поставить точку и потом написать имя метода с круглыми скобками, точно так же, как ты вызываешь функцию, например вот так: f.read(). Методы, так же как и функции, могут возвращать какой-то результат, а значит, этот результат нужно где-то сохранять. Как ты думаешь, что вернет метод read()? — Наверное, он вернет содержимое файла, — ответил Эрик. — Абсолютно верно! — сказал Саймон. — Сохрани это содержимое в переменную drinks и попробуй вывести на экран. Эрик написал следующее: f = open("drinks.txt") drinks = f.read() print(drinks) 88 Поговорим на Python — Теперь запусти, — предложил Саймон. Эрик нажал Run и получил вот такой результат: кофе горячий шоколад кофе без кофеина >>> ТВОЙ ХОД! Прочитай файл Напиши такую же короткую программу и попробуй прочитать данные из файла. Убедись, что на экран выводится все содержимое файла. — Работает! — Эрик был очень доволен. Его программа на Python не только открыла файл, но еще и прочитала его, и даже вывела содержимое файла на экран! — Теперь я знаю, как с помощью Python вывести на экран мое меню. Сейчас напишу то же самое для двух других файлов. — Хорошо, — сказал Саймон. — Но это не совсем то, что нужно. — Почему? — не понял Эрик. — Вспомни, в основной программе ты не просто выводишь меню на экран. Ты еще даешь пользователю возможность выбрать из меню, а потом находишь выбранный элемент в списке, так? — Да, а что, это еще не список? — спросил Эрик. — Похоже на список. — Может, это и похоже на список, но это не список. Это строка, — сказал Саймон. — Когда ты вызвал метод read(), ты скопировал все содержимое файла в переменную drinks. Так что эта переменная — одна большая строка. Если не веришь, можешь проверить прямо здесь. Видишь треугольные скобки в окне вывода? Можешь продолжить программу и написать тут любую команду на Python, так же как мы делали с REPL, помнишь? Напиши type(drinks) — и увидишь, к какому типу относится переменная drinks. Эрик написал, что сказал ему брат, и вот что получилось: >>> type(drinks) <class 'str'> >>> — Видишь, Python говорит, что это строка, — сказал Саймон. — А нам нужен список. Глава 5. Работа с файлами: будни менеджера кофейни 89 — И что нам делать? — спросил Эрик. — К счастью, разработчики Python предвидели, что нам может понадобиться читать файл по строкам, и создали для этого специальный метод. Он называется readlines(). Попробуй поменять read() на readlines() и посмотри, что получится. Не забудь нажать Stop, прежде чем снова запускать программу. Вот как изменил свою программу Эрик: f = open("drinks.txt") drinks = f.readlines() print(drinks) Он нажал Stop, потом Run и получил такой результат: ['кофе\n', 'горячий шоколад\n', 'кофе без кофеина\n'] >>> — Попробуй еще раз проверить тип переменной, — предложил Саймон. Эрик переключился на окно вывода и снова написал type(drinks): >>> type(drinks) <class 'list'> >>> ТВОЙ ХОД! Проверь типы переменных Python Повтори проверку типов, которую сделал Эрик. Понятна ли тебе разница между строкой и списком? — Это список! — сказал Эрик. — Но что это за символы \n? В моем файле с напитками их нет. — Они есть, ты просто их не видишь. Это непечатаемые символы. n с обратным слешем — это один символ, который называется символом перевода строки. Этот символ говорит компьютеру, что следующий пункт меню нужно начинать с новой строки. Без него все твои напитки записались бы вот так: кофегорячий шоколадкофе без кофеина. Тебе же это не нужно, правда? — улыбнулся Саймон. — Конечно нет! — ответил Эрик. — Но в списке нам не нужны эти символы. Списки ведь должны выглядеть так же, как в основной программе. — Ты абсолютно прав. И снова разработчики Python придумали для этого очень полезный метод. Он называется strip() и удаляет непечатаемые символы с обоих концов строки. Нам нужно просто вызвать этот метод для каждого элемента списка. Как думаешь, какая конструкция Python здесь подойдет? 90 Поговорим на Python — Цикл? — предположил Эрик. — Правильно! — сказал Саймон. — Мы пройдем по списку и удалим символы перевода строки методом strip(), — Саймон немного задумался и продолжил. — Удалить лишние символы можно по-разному. Одни способы короче, но их сложнее понять. Мы выберем тот, который проще понять и повторить. Вообще в программировании есть правило хорошего тона: если ты можешь сделать что-то несколькими способами, всегда выбирай тот, который проще понять и повторить. Если кто-то будет читать твой код, он скажет тебе спасибо. Даже если ты сам вернешься к своей программе через три месяца, то будешь рад, что выбрал самый простой способ. Саймон быстро нарисовал схему. drinks.txt файл 'f' temp open('drinks.txt') f.readlines() цикл преобразования drinks — Сначала мы прочитаем данные из файла во временный список (temp, сокращение от temporary). Потом пройдем по этому списку, удаляя символы перевода строки для каждого элемента и добавляя этот элемент в новый список, который мы назовем drinks. Потом повторим те же шаги для вкусов и топпингов. Можно использовать одну временную переменную для всех списков. Давай покажу, — сказал Саймон и начал писать в программе Эрика. Вот что получилось после того, как Саймон закончил: f = open("drinks.txt") temp = f.readlines() drinks = [] for item in temp: new_item = item.strip() drinks.append(new_item) print(drinks) Саймон нажал Run, и братья увидели результат: ['кофе', 'горячий шоколад', 'кофе без кофеина'] >>> Глава 5. Работа с файлами: будни менеджера кофейни 91 ТВОЙ ХОД! Удали символы перевода строки Удали символы перевода строки из элементов меню с помощью метода strip(). — Теперь лучше, правда? — спросил Саймон. — Сделай то же самое для других файлов меню. Эрик начал редактировать программу и, когда файл flavors.txt был почти готов, вдруг воскликнул: — Подожди! Я повторяю код! А ты сказал, что этого делать нельзя. Саймон улыбнулся. Он был рад, что его младший брат так быстро уловил эту идею. — Что нужно сделать, чтобы не повторять код? — спросил Саймон. — Написать функцию? — предложил Эрик. — Да, конечно! Посмотри на код и определи, что повторяется, а что отличается. Что ты сделаешь аргументом? И что вернет твоя функция? Эрик начал рассуждать вслух: — Я открываю разные файлы, значит, имя файла будет аргументом, так? — Верно, — подтвердил Саймон. — Что ты будешь возвращать? — Наверное, список с пунктами меню — конечно, после того как удалю символы перевода строки. А как мне назвать этот список? — Как угодно, потому что он не виден основной программе. Назови его, например, result. Тогда ты сможешь написать return result в конце функции. Если ты вызовешь функцию, то список result нужно будет присвоить какой-то переменной в основной программе, например drinks или flavors, в зависимости от файла, из которого ты читаешь. Внутри функции переменная всегда будет называться result, но вне функции, в основной программе, ты можешь присвоить результат любой переменной. основная программа drinks = function() flavors = function() функция вызов функции return result вызов функции return result 92 Поговорим на Python Саймон подбодрил брата: — Давай, напиши функцию. Помнишь, как это делать? Начни со слова def, придумай какое-нибудь имя, например read_menu, передай в качестве аргумента filename и скопируй код, который мы только что написали. — Хорошо, я попробую, — сказал Эрик. Он начал писать свою вторую функцию на Python. Саймон немного помог брату, и вот что у них получилось: Листинг 5.4. Файл files.py def read_menu(filename): f = open(filename) temp = f.readlines() result = [] for item in temp: new_item = item.strip() result.append(new_item) return result drinks = read_menu("drinks.txt") print(drinks) flavors = read_menu("flavors.txt") print(flavors) toppings = read_menu("toppings.txt") print(toppings) Эрик сохранил файл и нажал Run. Конечно, он получил результат, которого ожидал: ['кофе', 'горячий шоколад', 'кофе без кофеина'] ['карамель', 'ваниль', 'мята', 'малина', 'без добавок'] ['шоколад', 'корица', 'карамель'] >>> ТВОЙ ХОД! Напиши функцию read_menu Напиши функцию read_menu так же, как Эрик. Убедись, что у тебя нет опечаток в именах файлов. Проверь, что будет, если опечатки все-таки есть: измени имя файла и посмотри, какую ошибку выдаст Python. Не забудь потом исправить имя файла, чтобы программа снова заработала правильно. — Это моя вторая функция, и она работает! — с гордостью сказал Эрик. — Да, скоро ты создашь целую библиотеку — это отлично! — заметил Саймон. — Теперь давай скопируем новую функцию в основную программу. Скопируй все, Глава 5. Работа с файлами: будни менеджера кофейни 93 кроме строк с функцией print() — они нужны только для тестирования. Вроде бы файл с основной программой назывался menu_function.py, да? — Ага, — сказал Эрик. — А куда вставить эту функцию? В начало или в конец файла? — По правилам Python, нужно объявлять функции до вызова. Поэтому функции обычно размещают в начале файла перед основной программой. Можешь вставить ее сразу после своей первой функции. — Хорошо, — согласился Эрик и начал работать. Вот что у него получилось: Листинг 5.5. Меню кофейни Эрика с файлами def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): . . . # эта функция не изменилась . . . return answer def read_menu(filename): f = open(filename) temp = f.readlines() result = [] for item in temp: new_item = item.strip() result.append(new_item) return result drinks = read_menu("drinks.txt") flavors = read_menu("flavors.txt") toppings = read_menu("toppings.txt") drink = menu(drinks) flavor = menu(flavors, "Вкусы напитков от Эрика", "Выберите вкус: ") topping = menu(toppings, "Топпинги для напитков от Эрика", "Выберите топпинг: ") print("Ваш заказ: ") print("Основной напиток: ", drink) print("Вкус: ", flavor) print("Топпинг: ", topping) print("Спасибо за заказ!") ТВОЙ ХОД! Скопируй новую функцию в основную программу Скопируй новую функцию read_menu() в основную программу и проверь, все ли работает. 94 Поговорим на Python Эрик проверил программу, она работала так же, как раньше. — Это хорошо, — сказал Саймон. — Теперь попробуй добавить что-нибудь в файл топпингов и посмотри, изменится ли меню. Эрик открыл файл toppings.txt, добавил в конец «ванильная пудра» и сохранил файл. Он снова запустил программу и увидел, что в последнем разделе меню появилась еще одна строка: 4 ванильная пудра. ТВОЙ ХОД! Добавь новый пункт меню Добавь новый пункт к любому разделу меню. Измени какой-нибудь пункт. Не забудь сохранить файлы меню после внесения изменений. Проверь, выводит ли программа изменения в меню. — Отлично, мне нравится! — воскликнул Эрик. — Теперь каждый, кто умеет редактировать текстовый файл, может изменить меню! Подожди… — у Эрика появилась идея. — Я могу добавить в меню все что угодно! Мороженое, сэндвичи или что-то еще! Круто, мне нравится! Надо рассказать об этом Алексу — ему нравятся мини-фигурки LEGO. Может, он будет меняться ими с друзьями в этой программе! — Абсолютно верно! — сказал Саймон. — Здорово, что ты придумал программе новое применение. У меня тоже есть кое-какие мысли, но лучше поговорим о них завтра. А еще завтра мы сделаем главное меню. — Что это значит? — Ну, сейчас, если ты хочешь принять новый заказ, тебе нужно заново запускать программу. Ты принимаешь заказ, выводишь его на экран, и программа завершается. Будет лучше, если программа вернется к началу и снова спросит имя клиента. — Точно, — согласился Эрик. — Это будет похоже на терминал самообслуживания, в котором ты что-то заказываешь, нажимаешь «Готово», и программа возвращается на главный экран с надписью: «Добро пожаловать в наш магазин». Да, давай так и сделаем! — Давай вспомним, что ты узнал сегодня, — предложил Саймон. — Что мы сегодня делали? — Сначала ты сказал, что менеджер кофейни захочет редактировать меню из файла. Потом я создал три файла для разных разделов меню: напитков, вкусов и топпингов. Глава 5. Работа с файлами: будни менеджера кофейни 95 — Очень хорошо, что потом? — Потом я открыл эти файлы и прочитал их. Сначала я прочитал файлы по строкам и получил эти странные символы «n с обратным слешем». Потом мы убрали их с помощью функции strip(). — Хорошо, — сказал Саймон. — А ты запомнил, что я сказал тебе про объекты? — Не совсем. Ты сказал, что в Python файл — это объект и это не то же самое, что имя файла. И ты сказал, что функции объектов называются методами. — Да, верно, — согласился Саймон. — Объекты — это сложно. Пока мы просто будем использовать объекты и их методы, но позже узнаем о них больше. А еще создадим свои объекты и методы, как мы делали с функциями. — Точно, — сказал Эрик. — Ты мне напомнил — я написал вторую функцию на Python, и она работает! — Конечно. Ты становишься серьезным программистом, — заметил Саймон и улыбнулся. — А теперь давай отдохнем. Завтра мы сделаем твою программу еще лучше. Что мы узнали сегодня Что значит «открыть файл». Мы узнали, в чем разница между именем файла и дескриптором файла внутри программы. Что такое \n и как удалить этот символ из строк. Символ \n означает «пе­ рейти на новую строку». Он не нужен в названиях пунктов меню, поэтому мы удалили его с помощью функции strip(). Результат функции нужно присвоить переменной в основной программе. Переменная внутри функции не видна основной программе. Чтобы передать значение этой переменной основной программе, нужно вернуть эту переменную из функции, то есть присвоить ее значение другой переменной в основной программе. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch05. 6 Главное меню: свободная касса! В ЭТОЙ ГЛАВЕ 33 Эрик создает главное меню, чтобы обслуживать много клиентов 33 Эрик узнает о словарях Python 33 Саймон рассказывает о нисходящем подходе к разработке программ — Вчера мы решили создать главное меню, — напомнил Саймон. — Да, ты сказал, что если я хочу обслуживать много клиентов, то я каждый раз должен перезапускать программу — спрашивать имя клиента и что он хочет заказать. — Именно так! — подтвердил Саймон. — Какая конструкция Python тебе здесь пригодится? — Может быть, цикл? Как в меню: повторяй до тех пор, пока пользователь не введет правильный номер или не нажмет х. — Совершенно верно! — Саймон был очень рад, что брат так быстро уловил эту идею. — Мы спросим имя клиента, как в нашей первой программе, а потом запишем его заказ со всеми вкусами и топпингами. Глава 6. Главное меню: свободная касса! 97 — Да, — продолжил Эрик, — а потом клиент сможет выбрать: сделать заказ или выйти из меню. Я видел это на разных сайтах. — Правильно. Если клиент нажмет кнопку «Заказать», то мы сохраним заказ и покажем его бариста. Если клиент выйдет из меню, мы просто забудем о нем. В обоих случаях мы вернемся к главному меню и спросим имя следующего клиента. Саймон взял лист бумаги и начал рисовать. — Для начала нужно нарисовать схему нашего алгоритма. Когда мы согласуем, как алгоритм должен работать, ты начнешь писать код. Но прежде чем заниматься кодом, всегда полезно описать программу простыми словами и схемами. Сначала мы принимаем заказ. Видишь, я нарисовал блок «Принять заказ». Начало Принять заказ Подтвердить заказ Нет Да Сохранить заказ База данных Показать заказ Следующий клиент — А где разделы меню со вкусами и топпингами? Почему ты их не нарисовал? — спросил Эрик. — Блок «Принять заказ» уже включает в себя все разделы меню. Обычно разработчики сначала продумывают крупные блоки программы, а потом прорабатывают детали и составляют схемы каждого блока отдельно. Это еще одна причина, чтобы использовать функции. Представь, если мы добавим все детали программы на одну схему. Основной алгоритм будет просто невозможно понять! 98 Поговорим на Python Но давай продолжим. Мы получили заказ и запрашиваем у клиента его подтверждение. Если клиент отклоняет заказ, мы возвращаемся к началу: «Добро пожаловать в кофейню Эрика». Но если клиент подтверждает заказ, мы сохраняем его и показываем на экране. — Понятно, что заказ нужно вывести на экран, чтобы бариста приготовил напитки, — сказал Эрик. — Но зачем сохранять заказ? И что вообще ты имеешь в виду, когда говоришь «сохранить заказ»? — Прежде всего в конце дня будет полезно узнать, сколько друзей ты обслужил. — Но я и так знаю, — заметил Эрик. — В прошлый раз я приготовил пять напитков. — Не забывай, что мы говорим о настоящей кофейне. А настоящие кофейни работают каждый день и обслуживают десятки и сотни клиентов. Пара моих друзей работают в кофейнях, и я могу тебе точно сказать, что они очень хорошо знают, сколько клиентов обслуживают каждый день. Но есть еще одна причина: менеджер должен знать, какие продукты есть в кофейне, а какие нужно заказать. Помнишь, мы говорили об изменении меню? Представь, что менеджер забыл заказать карамель, и теперь этот вкус нужно удалить из меню. Почему ее не заказали? Потому что не посчитали, сколько порций карамели клиенты уже раскупили. Если мы сохраним все заказы и проанализируем их, то сможем заказывать вкусы и топпинги раньше, чем они закончатся. — Об этом я и не подумал, — согласился Эрик. — Да, сохранять заказы — это хорошо. Но как это сделать? — Всегда есть несколько способов, — сказал Саймон. — Мы можем работать с файлами или базой данных. Конечно, все серьезные приложения используют базы данных. Но мы начнем с файлов, а на базу данных перейдем потом, если смелости хватит. — Конечно, я хочу, чтобы моя программа была как все эти серьезные приложения! — воскликнул Эрик. — И базу данных я тоже хочу! — Хорошо, — сказал Саймон, — но для начала давай закончим с главным меню. Скоро мы будем сохранять заказы. Кстати, о заказах, — сказал Саймон, взяв еще один лист бумаги, — вот что находится внутри блока «Принять заказ». Глава 6. Главное меню: свободная касса! 99 Функция get_order get_name get_drink get_flavor get_topping Вернуть заказ ТВОЙ ХОД! Нарисуй свою схему Если ты вместо кофейни выбрал другой магазин, создай схему своей функции get_order(). — Эту функцию мы уже написали, просто не называли ее функцией. Видишь, мы уже придумали диалоги, чтобы узнать у клиента его имя и какие напиток, вкус и топпинг он хочет заказать. Но мы не сделали одну вещь. Как думаешь, какую? — Не вернули заказ? — предположил Эрик. — Мы этого точно не делали, и я даже не знаю, что это означает. — Твой заказ выглядит вот так, да? — спросил Саймон. Заказ: имя: Эрик напиток: кофе без кофеина вкус: ваниль топпинг: шоколад — Мы получили информацию о заказе с помощью функции get_order(), но эта функция возвращает четыре результата: имя, напиток, вкус и топпинг. Теперь я хочу, чтобы функция возвращала только один результат, который мы назовем заказом. Этот результат будет объединять в себе все свойства заказа. — Я знаю, нам нужен список! — поделился Эрик своей догадкой. 100 Поговорим на Python — Как вариант. Но в Python есть кое-что получше. Например, словари. Вспомни, как выглядит обычный словарь? — Это книга со словами и их значениями, — ответил Эрик. — Или переводами, если это англо-русский словарь. — Верно! — согласился Саймон. — В словаре есть слово и то, что относится к этому слову, — значение или перевод. Вот пример, — Саймон нарисовал схему. Что такое словарь? apple яблоко peach персик dog собака — В словарях Python есть слова, которые называются ключами, и значения, которые связаны с ключами и которые можно получить по ключу. Посмотри на свой заказ. У тебя есть ключ, который называется name, и его значение — Эрик. И еще один ключ, который называется drink, его значение — кофе без кофеина и так далее. Сам словарь называется order, его нам и нужно вернуть как результат выполнения функции. Словари вPython ключ значение Словарь «order» name Эрик drink кофе без кофеина flavor карамель topping ваниль — Давай снова потренируемся в REPL, — предложил Саймон. — Нажми REPL, чтобы открыть интерактивную сессию Python. Эрик нажал на значок REPL, переключился в окно сессии и получил такое сообщение (у вас это сообщение может выглядеть иначе — это нормально): Глава 6. Главное меню: свободная касса! 101 Jupyter QtConsole 4.7.7 Python 3.8.12 (default, Mar 12 2022, 19:58:23) Type 'copyright', 'credits' or 'license' for more information IPython 8.1.1 -- An enhanced Interactive Python. Type '?' for help. In [1]: — Давай для начала создадим пустой словарь и назовем его order. Создать словарь в Python можно с помощью фигурных скобок {}. Фигурные скобки отличают словари от списков, в которых используются квадратные скобки []. Напиши order = {} и нажми Enter. Это будет твой заказ. Эрик напечатал: In [1]: order = {} In [2]: — Теперь давай заполним этот словарь. Начнем с имени. Напиши order['name'] = 'Эрик' . Затем попробуй вывести заказ на экран с помощью той же самой функции print(). Эрик напечатал: In [2]: order['name'] = 'Эрик' In [3]: print(order) {'name': 'Эрик'} In [4]: — Ты же сказал, что в словарях нужно использовать фигурные скобки. Почему здесь мы пишем квадратные? — спросил Эрик. — Хороший вопрос, — Саймон уже так привык к этой особенности Python, что ему пришлось подумать, как объяснить ее брату. — Что ж, фигурные скобки нужны, чтобы создать словарь, а чтобы получить доступ к словарю и найти в нем какойто элемент, нужны квадратные скобки. Этим словари похожи на списки — чтобы получить элемент из списка, нужно использовать квадратные скобки. Разница в том, что в списках ищут по индексам, которые являются целыми числами, а в словарях — по ключам, которые являются строками. Представь, что перед тобой лежит бумажный словарь и ты ищешь значение слова или его перевод по индексу — например, 546, — это же неудобно! Скорее всего, ты возьмешь само слово, например «собака», и быстро найдешь его значение или перевод. — Да, понял, — сказал Эрик. — Теперь нужно добавить напитки, вкусы и топпинги, так? 102 Поговорим на Python — Именно, давай! — сказал Саймон. Эрик продолжил интерактивную сессию: In [4]: order['drink'] = 'кофе без кофеина' In [5]: order['flavor'] = 'ваниль' In [6]: order['topping'] = 'шоколад' In [7]: print(order) {'name': 'Эрик', 'drink': 'кофе без кофеина', 'flavor': 'ваниль', 'topping': 'шоколад'} In [8]: — Обрати внимание, — сказал Саймон, — что ключи и значения всегда идут парами, а между ними стоит двоеточие :. ТВОЙ ХОД! Изучи словари с помощью REPL Открой REPL и поработай со словарями. Можешь повторить команды Эрика или создать свои словари. Попробуй использовать разные ключи и сохранить в словари не строки, а числа. Работает? — Круто, мне нравится! — сказал Эрик. — А можно вывести заказ на экран так же красиво, как раньше? — Конечно, — ответил Саймон. — Думаю, для этого лучше написать новую функцию. Только вернись обратно в редактор. — Это будет уже третья функция, — сказал Эрик. — Ты все еще их считаешь? — улыбнулся Саймон. — Ты быстро собьешься со счета. Давай вернемся в редактор и начнем программу для главного меню. Используем нисходящий подход к разработке. — Что это? — спросил Эрик. — Я только что тебе показал. Сначала мы продумываем алгоритм всей программы: выделяем основные части и решаем, как переходить от одной части к другой, например через отмену или подтверждение заказа. Дальше мы пишем основную программу с помощью функций вроде get_order() или print_order(). Неважно, что у нас еще их нет. Для начала мы заменим их очень простыми функциями, которые будут выводить на экран сообщения типа: «Я — функция Глава 6. Главное меню: свободная касса! 103 print_order()». Такие функции называют заглушками (placeholders). Позже, когда мы убедимся, что главное меню работает и вызывает нужные функции, мы заменим заглушки настоящими функциями. Давай покажу, — Саймон взял клавиатуру. — Сначала создадим новый файл и сохраним его с именем main_menu.py. Затем напишем ключевое слово def и создадим новую функцию main_menu() с круглыми скобками. — Еще одну функцию? — спросил Эрик. — Да, в программировании функции пишут на все подряд. Основная программа обычно очень короткая и вызывает только одну функцию. Потом эта функция вызывает другие функции и так далее. Еще одно правило программирования — делать все в виде функций, даже главное меню. Посмотри на схему еще раз. Видишь стрелки, которые возвращаются к блоку «Начало»? Обычно они означают, что в алгоритме что-то повторяется. Как только мы возвращаемся в «Начало», мы заново проходим весь алгоритм. А чтобы повторить что-то в программе, используем… что? Начало Принять заказ Подтвердить заказ Нет Да Сохранить заказ База данных Показать заказ Следующий клиент — Цикл! — ответил Эрик. — Абсолютно верно! — подтвердил Саймон. — Мы уже применяли два вида циклов: цикл for и цикл while. Какой цикл ты выберешь в этот раз? — Наверное, цикл while, — сказал Эрик. — Главное меню похоже на меню напитков, там мы повторяли вопросы и проверяли, что ответил пользователь. 104 Поговорим на Python — Согласен, — сказал Саймон. — Посмотри на функцию menu(). В ней мы с помощью while True: проверяли, что ввел пользователь. Как думаешь, какое условие нужно проверять здесь? Даю подсказку: условия на схемах обозначены ромбами. Подтвердить заказ — Вижу! — сказал Эрик. — Нужно проверять подтверждение заказа. — Хорошо, давай начнем писать код, — предложил Саймон. Эрик написал: def main_menu(): while True: — Что дальше? — спросил Эрик. — Посмотри на схему, — сказал Саймон. — Принимаем заказ? — спросил Эрик. — Правильно! И помни, что функция get_order() возвращает словарь с заказом. В словаре хранятся имя клиента, напиток, вкус и все такое. В главном меню мы присвоим этот словарь переменной order. Саймон добавил в код Эрика еще одну строку: def main_menu(): while True: order = get_order() — Что дальше? — спросил Саймон у брата. — Теперь мы должны спросить пользователя, подтверждает ли он заказ, — ответил Эрик. — Хорошо. Но прежде чем запросить у пользователя подтверждение, мы должны показать ему заказ, — сказал Саймон и добавил еще несколько строк: def main_menu(): while True: order = get_order() print("Проверьте заказ:") print_order(order) confirm = input("Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: ") Глава 6. Главное меню: свободная касса! 105 — Смотри, — сказал Саймон, — я получил от функции get_order() переменную order и использовал ее в качестве аргумента для следующей функции print_ order(). В программировании это обычное дело: мы вызываем функцию, которая что-то делает и возвращает результат, а потом используем этот результат как входные данные для следующей функции. основная программа get_order() вызов функции order = get_order() return order print_order(order) — Вижу, — сказал Эрик. — Как в кинотеатре: кассир дает тебе билет, ты берешь этот билет и у входа в зал даешь его билетеру, который проверяет билет и решает, пропустить тебя или нет. — Да, это отличное сравнение! Давай его и продолжим. Допустим, мы получили ответ от клиента: подтвердить или отменить заказ. Теперь, в зависимости от ответа, мы должны решить, что делать дальше. Как в кинотеатре, у тебя проверяют билет и решают, пропустить тебя или нет. Давай добавим в код это условие. Посмотри на схему. Что нужно сделать, если пользователь ответил «Да»? Начало Принять заказ Подтвердить заказ Нет Да Сохранить заказ База данных Показать заказ Следующий клиент — Сохранить заказ и вывести его на экран, — ответил Эрик, глядя на рисунок Саймона. 106 Поговорим на Python — Хорошо. А если пользователь решил отменить заказ и ответил «Нет»? — Мы ничего не будем делать, просто вернемся в начало. Только я не знаю, как это сделать. У тебя тут стрелка и все. — Для этого в Python есть простое слово continue (продолжить). Оно означает: «Пропусти оставшиеся действия цикла и начни цикл с начала». Вроде не сложно, — Саймон добавил в функцию новые строки: def main_menu(): while True: order = get_order() print("Проверьте заказ:") print_order(order) confirm = input("Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: ") if confirm == "Y" or confirm == "y": save_order(order) print("Спасибо за заказ:") print_order(order) else: continue — Ты добавил еще две функции: save_order() и print_order(), — сказал Эрик. — Но мы их еще не написали. — Так давай напишем! — воскликнул Саймон. — Сначала напишем очень простые функции, которые будут только выводить на экран сообщения, например «Заказ сохраняется…» Так мы поймем, что функция вызвалась. Потом мы доработаем эти функции, и они станут более полезными. Саймон добавил новые функции после функции main_menu(): def get_order(): return {} def print_order(order): print(order) return def save_order(order): print("Заказ сохраняется...") return И пояснил Эрику: — Функцию get_order() ты уже написал, позже мы перенесем в нее твой старый код, но пока эта функция ничего не делает. В ней нет меню и диалога, но она Глава 6. Главное меню: свободная касса! 107 должна возвращать заказ. Вспомни: заказ — это словарь с ключами, например «имя» и «напиток», но сейчас функция возвращает пустой словарь, то есть пару фигурных скобок. Пока понятно? — Да, — ответил Эрик. — То есть ты хочешь сказать, что мы скопируем мою предыдущую функцию из старого файла в новый, правильно? — Да, — сказал Саймон. — Если программа разрослась, то лучше сгруппировать функции по отдельным файлам. Но в нашем случае проще хранить все в одном файле. Функция print_order() просто выводит заказ, который она ­п олучает в качестве аргумента. Пока что мы вызовем стандартную функцию Python print(), а позже немного украсим вывод. Ты это уже делал, помнишь? — Конечно! Думаю, у нас получится настоящий счет из кофейни. — Отлично, — сказал Саймон. — Функция save_order() ничего не делает, она только выводит на экран надпись: «Заказ сохраняется…» Пока это нормально, мы доработаем ее потом. Теперь мы готовы вызвать функцию main_menu() и проверить наш алгоритм. Добавь вызов функции main_menu() в конце файла и запусти программу. Твоя основная программа будет состоять только из вызова этой функции. Эрик добавил вызов функции, и программа стала выглядеть так: Листинг 6.1. Главное меню с заглушками def main_menu(): while True: order = get_order() print("Проверьте заказ:") print_order(order) confirm = input("Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: ") if confirm == "Y" or confirm == "y": save_order(order) print("Спасибо за заказ:") print_order(order) else: continue def get_order(): return {} def print_order(order): print(order) return 108 Поговорим на Python def save_order(order): print("Заказ сохраняется...") return main_menu() ТВОЙ ХОД! Создай главное меню Напиши функцию главного меню, как у Эрика. Не бойся менять сообщения в диалоге. Эрик нажал Run, и получилось вот что: Проверьте заказ: {} Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: Эрик нажал y, и программа вывела: Заказ сохраняется... Спасибо за заказ: {} Проверьте заказ: {} Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: y — Почему я опять получил сообщение «Проверьте заказ»? — спросил Эрик. — Потому что это цикл! — сказал Саймон. — Когда ты ввел y в подтверждении, программа вернулась к началу цикла и вывела пустой заказ, потому что мы еще не добавили диалог с меню. Все работает как надо. Теперь попробуй ввести n. Эрик ввел n и получил следующее: Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: n Проверьте заказ: {} Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: — Видишь разницу? — спросил Саймон. — Сообщение «Заказ сохраняется…» не появилось. Значит, программа пошла по короткой стрелке в правой части схемы. Глава 6. Главное меню: свободная касса! 109 Начало Принять заказ Подтвердить заказ Нет Да Сохранить заказ База данных Показать заказ Следующий клиент — Отлично! — Саймон был рад, что Эрик так хорошо разобрался в схеме алгоритма. — Кажется, сегодня мы далеко продвинулись — главное меню работает. Завтра мы доработаем функции, чтобы они делали то, что нам нужно. Давай закончим и вспомним, что ты сегодня узнал. — Мы снова использовали цикл while! — сказал Эрик. — Верно! И ты использовал то, что узнал, пока работал над меню, — подтвердил Саймон. — Еще я узнал, что такое словари. Они похожи на обычные словари, но в них можно хранить не только значения слов, но вообще все что угодно. — Да, так и есть! В словаре order мы храним имена, названия напитков и вкусов. Но в более сложных словарях можно хранить числа — например, цены — и даже списки и другие словари. Словари Python — очень полезная вещь, ты будешь ими пользоваться постоянно. — Еще мне понравилось, как ты написал заглушки, чтобы проверить главное меню, — сказал Эрик. — Ты сказал, это называется «нисходящим» подходом. — Абсолютно верно, — сказал Саймон. — Можно догадаться, что есть еще «восходящий» подход, когда сначала пишут функции, тщательно их тестируют, а потом объединяют в большую программу. В некотором смысле этот подход 110 Поговорим на Python мы тоже использовали: ты написал и проверил свою первую функцию menu(), а теперь мы собираемся добавить ее в основную программу. Все, пора отдохнуть. Завтра мы поработаем над заглушками, которые мы оставили в главном меню. Что мы узнали сегодня Нисходящий подход. Сначала создается «крупный план» приложения и вместо функций ставятся заглушки, которые ничего не делают, а только выводят сообщения на экран. Словари. Словари Python хранят пары ключей и значений. Ключу можно присвоить значение, а потом быстро найти это значение по ключу. Условные обозначения блок-схем. Программисты часто используют схемы, чтобы разработать алгоритмы до написания кода. Прямоугольник обычно означает процесс, ромб — точку принятия решения с вопросом, на который можно ответить «да» или «нет». Есть еще обозначения для ввода, использования документов, баз данных и так далее. Мы расскажем об этом позже. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch06. 7 Функции: получение и вывод заказа В ЭТОЙ ГЛАВЕ 33 Эрик дорабатывает функции получения и вывода заказа на экран 33 Эрик сохраняет заказ в словарь и выводит этот словарь на экран 33 Теперь программа Эрика работает как надо! 33 Эрик и Саймон планируют написать функцию сохранения заказов — Вчера мы написали главное меню, — начал Саймон. — И даже проверили, как оно работает. — Да, оно не делает ничего полезного, — сказал Эрик. — Именно так! — сказал Саймон. — Помнишь, мы говорили о «нисходящем» подходе? Мы создали заглушки, чтобы проверить работу главного меню. Пора заменить заглушки реальными функциями. Открой файл Python, в котором мы читали меню из файлов. Он называется menu_files.py. Эрик открыл файл, и теперь в его редакторе было две вкладки: одна с файлом main_menu.py, а другая с файлом menu_files.py. — Открой вкладку с файлом main_menu.py и посмотри, какие функции нам нужно дописать, — сказал Саймон. — В этом файле три функции: get_order(), print_order() и save_order(). Давай начнем с get_order(). Где наша схема? 112 Поговорим на Python Функция get_order get_name get_drink get_flavor get_topping вернуть заказ — Что нужно сделать в get_name? — спросил Саймон. — Спросить: «Как вас зовут?» — ответил Эрик. — Правильно, а потом? — А потом сохранить ответ в переменную name. — Ну почти, — сказал Саймон. — Вспомни, мы решили, что order будет словарем. И в этот словарь мы будем сохранять все, что относится к заказу. Например, чтобы сохранить имя клиента, вместо name = 'Эрик' мы напишем order['name'] = 'Эрик'. И вместо 'Эрик' мы вызовем функцию input(), как ты делал в своей первой программе. — Давай попробую, — сказал Эрик и написал функцию. def get_order(): order['name'] = input("Как вас зовут: ") return {} — Теперь проверь, — сказал Саймон. Эрик нажал Run. Программа спросила его имя, и он ввел 'Эрик', но тут же получил сообщение об ошибке на несколько строк: Как вас зовут: Эрик Traceback (most recent call last): File "/home/erik/mu_code/main_menu.py", line 41, in <module> main_menu() File "/home/erik/mu_code/main_menu.py", line 3, in main_menu order = get_order() Глава 7. Функции: получение и вывод заказа 113 File "/home/erik/mu_code/main_menu.py", line 27, in get_order order['name'] = input("Как вас зовут: ") NameError: name 'order' is not defined >>> — Что это означает? — спросил Эрик. — Python говорит тебе, в каком месте произошла ошибка. Прочитай последнюю строку. — Переменная order не определена (name 'order' is not defined), — прочитал Эрик. — Все очень просто, — пояснил Саймон. — Ты пытаешься сохранить что-то в словарь, который еще не создан. Это легко исправить. Давай создадим пустой словарь. Вспомни, для этого мы использовали фигурные скобки. Просто напиши order = {} перед строкой с input(). Эрик внес изменения и получил такую функцию: def get_order(): order = {} order['name'] = input("Как вас зовут: ") return {} Эрик снова запустил программу, и на этот раз ошибок не было: Как вас зовут: Эрик Проверьте заказ: {} Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: y Заказ сохраняется... Спасибо за заказ: {} Как вас зовут: — Так-то лучше, — сказал Эрик. — Да, лучше, но смотри: программа все еще выводит пустой заказ. Ты создал заказ и даже ввел свое имя, но твоя функция возвращает пустой словарь. Видишь строку return {}? — Но это же ты написал! — Эрик был уверен, что он ни в чем не виноват. — Да, но я написал это, чтобы проверить главное меню. Теперь мы должны вернуть реальный словарь order. Поменяй эту строку на return order, и давай посмотрим, выведет ли программа твое имя. 114 Поговорим на Python Эрик изменил функцию: def get_order(): order = {} order['name'] = input("Как вас зовут: ") return order И снова запустил программу. На этот раз она вывела его имя: Как вас зовут: Эрик Проверьте заказ: {'name': 'Эрик'} Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: — Да, теперь я вижу свое имя! — Поздравляю! — сказал Саймон. — Теперь ты знаешь, как работать со словарями. ТВОЙ ХОД! Напиши функцию get_order() Начни писать свою функцию get_order() в файле main_manu.py. Напиши код для ввода имени клиента. Запусти главное меню и проверь функцию get_order(). Что-нибудь выбрали? — Пойдем дальше, — продолжил Саймон. — Теперь нам надо добавить функцию menu(), чтобы получить от пользователя напиток и вкус. Но нам еще нужна функция read_menu(), которая читает меню из файлов. Скопируй функции menu() и read_menu() из файла menu_files.py и вставь их в файл main_menu.py сразу перед строкой def get_order():. — А что, если я вставлю их после этой строки? — Эрику стало интересно, почему старший брат ему приказывает. — Тогда не будет работать, — ответил Саймон и улыбнулся. — Хорошо, если тебе и правда интересно, мы будем использовать эти функции в функции get_order(). Сначала прочитаем из файлов пункты меню: напитки, вкусы и топпинги. Потом вызовем функцию menu() три раза и получим выбор клиента. Но перед использованием мы должны объявить эти функции. Другими словами, мы должны сказать Python, что функции menu() и read_menu() существуют и делают то-то и то-то. Поэтому их нужно вставить перед строкой def get_order():. Кстати, именно поэтому мы пишем слово def перед именем функции — мы объявляем1 эту функцию. 1 def — от английского слова define (объявить). — Примеч. ред. Глава 7. Функции: получение и вывод заказа 115 — Хорошо, — сказал Эрик и начал копировать функции. Через пару минут его файл main_menu.py стал выглядеть так: Листинг 7.1. Главное меню готово def main_menu(): while True: order = get_order() print("Проверьте заказ:") print_order(order) confirm = input("Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: ") if confirm == "Y" or confirm == "y": save_order(order) print("Спасибо за заказ:") print_order(order) else: continue def menu(choices, title="Меню кофейни Эрика", prompt="Выберите напиток: "): print(title) print(len(title) * "-") i=1 for c in choices: print(i, c) i=i+1 while True: choice = input(prompt) allowed_answers = [] for a in range(1, len(choices) + 1): allowed_answers.append(str(a)) allowed_answers.append("X") allowed_answers.append("x") if choice in allowed_answers: if choice == "X" or choice == "x": answer = "" break else: answer = choices[int(choice) - 1] break else: print("Введите число от 1 до ", len(choices)) answer = "" return answer def read_menu(filename): f = open(filename) temp = f.readlines() result = [] for item in temp: new_item = item.strip() 116 Поговорим на Python result.append(new_item) return result #. . . # Функции get_order() и save_order() остаются без изменений #. . . main_menu() — Все верно! — сказал Саймон. — Теперь ты знаешь еще одно правило: всегда объявляй функции и переменные перед их использованием. Ты уже видел ошибку, которую вызвал необъявленный словарь order. Теперь можно использовать функции menu() и read_menu() в get_order(). Посмотри на программу menu_files.py. Что мы в ней делали? — Читали меню из файлов, — ответил Эрик. — Хорошо. Давай сделаем здесь то же самое, только внутри функции get_order(). Эрик добавил в функцию get_order() три строки: def get_order(): order = {} order["name"] = input("Как вас зовут: ") drinks = read_menu("drinks.txt") flavors = read_menu("flavors.txt") toppings = read_menu("toppings.txt") return order Он также добавил четыре пробела в начале каждой строки, чтобы все строки были выровнены одинаково. — Теперь то же самое с тремя функциями menu()? — спросил Эрик. — Конечно, давай! Эрик изменил функцию вот так: def get_order(): order = {} order["name"] = input("Как вас зовут: ") drinks = read_menu("drinks.txt") flavors = read_menu("flavors.txt") toppings = read_menu("toppings.txt") drink = menu(drinks, "Напитки кофейни Эрика", "Выберите напиток: ") flavor = menu(flavors, "Вкусы напитков от Эрика", "Выберите вкус: ") topping = menu(toppings, "Топпинги для напитков от Эрика", "Выберите топпинг: ") return order Явно довольный своей работой, Эрик посмотрел на Саймона. Глава 7. Функции: получение и вывод заказа 117 — Почти так, — сказал Саймон. — Ты верно все скопировал, но, чтобы сохранить ответы в словарь order, нужно еще немного изменить код. Это просто — уверен, ты все знаешь. — А, понял, — сказал Эрик и изменил функцию. Программа стала выглядеть так: Листинг 7.2. main_manu.py def get_order(): order = {} order["name"] = input("Как вас зовут: ") drinks = read_menu("drinks.txt") flavors = read_menu("flavors.txt") toppings = read_menu("toppings.txt") order["drink"] = menu(drinks, "Напитки кофейни Эрика", "Выберите напиток: ") order["flavor"] = menu(flavors, "Вкусы напитков от Эрика", "Выберите вкус: ") order["topping"] = menu(toppings, "Топпинги для напитков от Эрика", "Выберите топпинг: ") return order — Запускай! — подбодрил Саймон брата. Эрик запустил программу. Как вас зовут: Эрик Напитки кофейни Эрика --------------------1 кофе 2 горячий шоколад 3 кофе без кофеина Выберите напиток: 1 Вкусы напитков от Эрика ------------------1 карамель 2 ваниль 3 мята 4 малина 5 без добавок Выберите вкус: 2 Топпинги для напитков от Эрика ---------------------1 шоколад 2 корица 3 карамель 4 ванильная пудра Выберите топпинг: 3 Проверьте заказ: {'name': 'Эрик', 'drink': 'кофе', 'flavor': 'ваниль', 'topping': 'карамель'} 118 Поговорим на Python Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: y Заказ сохраняется... Спасибо за заказ: {'name': 'Эрик', 'drink': 'кофе', 'flavor': 'ваниль', 'topping': 'карамель'} Как вас зовут: — Вау! — Эрик был по-настоящему счастлив. — Я написал программу больше чем в 70 строк, и она работает! — Так и есть — она правда работает, — подтвердил Саймон и улыбнулся. ТВОЙ ХОД! Добавь в свою программу выбор из меню Добавь в файл main_menu.py функции menu() и read_menu(), как в предыдущей программе. Запусти программу, введи свой выбор и убедись, что все работает. Проверь, что функция menu() не позволяет вводить недопустимые ответы. Покажите заказ! — Но мы еще не все доделали. Заказ выглядит непрофессионально. Он не похож на заказ из настоящей кофейни, — продолжил Саймон. — Да уж… наверное, нужно исправить функцию print_order(), — предложил Эрик. — Верно. Найди функцию print_order() в файле main_menu.py. Функция print_order() выглядела вот так: def print_order(order): print(order) return — Мы использовали стандартную функцию вывода Python, — продолжил Саймон. — Python, конечно, может вывести твой словарь, но это будет некрасиво. Для проверки подойдет, но для настоящих заказов и счетов нужно привести вывод в порядок. Ты уже это делал. — Ты имеешь в виду пунктирные линии? Да, они уж точно были красивее. — Давай сделаем здесь как в конце файла menu_files.py. Можешь скопировать оттуда строки, которые начинаются с print. Только помни, что нужно сохранить отступы справа, и убедись, что ты используешь словарь, а не простые переменные. Глава 7. Функции: получение и вывод заказа 119 И не забудь, что теперь у нас есть имя клиента, используй его в функции. Все понял? — Да, — ответил Эрик и начал работать над функцией. Когда он закончил, у него получилось вот это: def print_order(order): print("Ваш заказ, ", order["name"]) print("Основной напиток: ", order["drink"]) print("Вкус: ", order["flavor"]) print("Топпинг: ", order["topping"]) print("Спасибо за заказ!") return ТВОЙ ХОД! Добавь функцию print_order() Добавь функцию print_order() в файл main_menu.py. Используй украшения, например дефисы (-), нижние подчеркивания (_) или знаки равенства (=), чтобы заказ выглядел как тот, что ты видел в других кофейнях или магазинах. Найди счета из ресторанов, кофеен или кафе-мороженого и попробуй сделать похожий вывод. Эрик запустил программу. Вывод стал намного красивее: Ваш заказ, Эрик Основной напиток: кофе Вкус: ваниль Топпинг: карамель Спасибо за заказ! Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: — Да, так намного лучше! — сказал Саймон. — Теперь можешь добавить украшения, например дефисы или вертикальные черточки — что пожелаешь. Ты проделал большую работу — написал несколько очень полезных функций, правильно их организовал и проверил. Молодец, Эрик. Я тобой горжусь! Эрик подумал, что пора заканчивать и вспомнить все, что он сегодня узнал: — Еще мы изучили словари и попробовали их использовать. — Точно, — подтвердил Саймон. — Словари Python важны. В программах их используют очень часто. Позже я расскажу о них подробнее. — Еще ты сказал, что мы должны сохранять заказы. Мы сделаем это завтра? — спросил Эрик. — Да, конечно, — сказал Саймон. — Ты знаешь про JSON? — спросил он. 120 Поговорим на Python — Джейсона? Да, мы вместе сидим на математике. А что с ним? — Нет, не Джейсона, — рассмеялся Саймон. — JSON — это формат файла, в котором мы будем сохранять заказы из кофейни. Мы поговорим об этом завтра, хорошо? — Хорошо, — сказал Эрик и отправился отдыхать. Что мы узнали сегодня Объявление функций и переменных. Переменные и функции в Python нужно объявлять перед использованием. Переменным просто присваивают пустое значение: словарям order = {}, а строкам — name = "". Функции объявляют с помощью ключевого слова def. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch07. 8 Работа с JSON: сохранение заказа В ЭТОЙ ГЛАВЕ 33 Эрик узнает о формате и файлах JSON 33 Эрик узнает о модулях Python 33 Эрик создает список заказов 33 Эрик и Саймон пишут функцию для сохранения заказов в файл JSON — Вчера ты сказал что-то про Джейсона, — сказал Эрик. — Но оказалось, что это не тот Джейсон, который ходит со мной на математику. — Да, это другой Джейсон, — улыбнулся Саймон. — Это JSON, J-S-O-N — формат файла, в котором мы сохраним данные. — Как файлы, в которых мы сохраняли меню? — Да, вроде того, — ответил Саймон. — Формат JSON хорошо подходит для хранения структурированных данных. — Что это? — спросил Эрик. — Иногда нам нужно сохранить небольшой текст или картинку. У таких данных нет строгой структуры. Текст — это просто текст. А картинка может быть большой 122 Поговорим на Python или маленькой, черно-белой или цветной, но у нее нет никакой структуры — это просто набор пикселей. Поэтому такие данные называются неструктурированными. В твоем случае у каждого заказа есть структура: имя клиента и все компоненты напитка, который ты собираешься для него приготовить, — только это и ничего более. В заказе всегда есть основной напиток, вкус и топпинг. Ты так написал программу меню: заказ сохраняется или выводится на экран только после того, как клиент ответит на все вопросы. К тому же клиент ничего не может добавить в заказ. — Например, еще один топпинг? — спросил Эрик. — Именно, — сказал Саймон. — Твой заказ — это пример структурированных данных. Эрик и не догадывался, что он придумал что-то с таким серьезным названием. — Твой заказ хранится в словаре, — продолжил Саймон, — и ты точно знаешь, что в каждом заказе есть ключи name, drink, flavor и topping (помнишь, что это такое?), — Саймон достал одну из своих схем. Словари вPython ключ значение Словарь «order» name Эрик drink кофе без кофеина flavor карамель topping ваниль — Формат JSON придумали, чтобы хранить структурированные данные. Давай немного потренируемся с ним. Создадим отдельную программу и попробуем несколько простых операций, как мы делали раньше. Затем используем полученные знания в основной программе и научимся сохранять заказы в файл. Для одного дня это большая задача, так что, может быть, оставим что-то на завтра. Открой редактор, создай новый файл и сохрани его, например, с именем dict_json.py. Эрик открыл редактор и нажал кнопку New. Потом он нажал Save, ввел имя файла dict_json.py и снова нажал Save. Эта процедура была ему уже знакома. — Теперь, — сказал Саймон, — создай тестовый заказ. — Что за тестовый заказ? Глава 8. Работа с JSON: сохранение заказа 123 — Заказ — это словарь, — начал объяснять Саймон. — В основной программе ты создавал пустой заказ, а потом заполнял его значениями, полученными от клиента. Теперь мы пропустим этот шаг и представим, что словарь order уже заполнен ответами клиента. Давай покажу, — сказал Саймон и напечатал в редакторе: order = { "name": "Эрик", — Можешь продолжить, — сказал Саймон. — Не забудь закрыть фигурные скобки. Эрик дописал словарь order и закрыл скобку. Словарь стал выглядеть так: order = { "name": "Эрик", "drink": "кофе", "flavor": "карамель", "topping": "шоколад" } — Вижу, ты сдвинул вправо строки словаря, — сказал Эрик брату. — В словарях это обязательно? — Нет, — ответил Саймон. — Сейчас я так сделал, только чтобы лучше смотрелось. И читалось. Я могу написать все в одну строку или без отступов, но мне кажется, что так выглядит лучше. Теперь у нас есть словарь. И мы хотим сохранить его в файл. Наверное, стоит вспомнить об операциях с файлами и словарями. — Да, — сказал Эрик. — Я проходил это так давно. И почти ничего не помню. — Конечно, — сказал Саймон. — А еще я расскажу тебе кое-что новое о файлах. Сначала мы открываем файл. Для этого мы, естественно, должны вызвать функцию open() и передать ей имя файла в качестве аргумента. Ты же помнишь, что такое функции и аргументы? Функция open() возвращает дескриптор файла. Это специальный объект, с помощью которого программа будет работать с файлом. Саймон написал еще одну строку после словаря Эрика: order = { "name": "Эрик", "drink": "кофе", "flavor": "карамель", "topping": "шоколад" } f = open("orders.json", "w") — Вот первое, что я хотел сказать о файлах: видишь w в последней строке? Это значит, что мы открываем файл для записи1. 1 w — первая буква английского слова write (записывать). — Примеч. ред. 124 Поговорим на Python — Когда мы открывали файлы с меню, мы не писали никакие буквы, — напомнил Эрик. — Ты прав! — сказал Саймон. — Мы не писали буквы — кстати, они называются режимы (modes), — потому что, когда мы не указываем никакие буквы, Python открывает файлы только для чтения. Теперь нам нужно записать данные в файл, так что мы должны предупредить об этом Python. — Почему ты назвал файл orders.json? Ты хочешь использовать формат JSON, о котором ты говорил? — Совершенно верно, — ответил Саймон. — Это не обязательно, но есть негласное правило добавлять расширение .json к файлам JSON. Еще одно отличие режима записи в том, что если файл с таким именем не существует, то Python создаст его. — Что дальше? — спросил Эрик. — Как записать что-то в этот файл JSON? В последний раз мы использовали «методы» — они ведь так назывались? — Да, ты правильно вспомнил, — сказал Саймон. — Но в этот раз мы сделаем по-другому. Нам надо записать структурированные данные, а не простой текст, поэтому мы используем модуль Python, который называется json. — А что такое модуль? — тут же спросил Эрик. — Сейчас объясню, — сказал Саймон и улыбнулся. — Вспомни, недавно ты написал несколько функций на Python. Например, ту, которая считывает пункты меню из файла и возвращает их в виде списка. Представь, что кто-то из твоих друзей захотел написать свою программу для кофейни или чего-то еще. — Представил, — сказал Эрик. — Я недавно говорил с Эмили, и она сказала, что хочет написать программу для кафе-мороженого. — Отлично! — сказал Саймон. — Ты, наверное, захочешь ей помочь и поделиться функциями, которые ты написал. Это сэкономит ей время, и ее программа будет готова быстрее. Программисты часто делятся своими разработками, чтобы помочь друг другу. В Python функции, которыми ты хочешь поделиться, можно сгруппировать в файл и передать его Эмили. Она скопирует файл на свой компьютер и импортирует его в свою программу. После импорта она сможет вызывать твои функции в своем приложении. Файл с функциями называется модулем. — А что, если я не хочу делиться своими функциями? — спросил Эрик. — Я потратил на них столько времени! — Да, ты правда потратил много времени, — сказал Саймон. — И проделал большую работу. Но помни: другие люди тоже потратили время, чтобы написать функции Python и даже сам Python. Они поделились своей работой с другими Глава 8. Работа с JSON: сохранение заказа 125 программистами, и теперь ты можешь пользоваться Python и его функциями абсолютно бесплатно. Так мы помогаем друг другу разрабатывать новые проекты. Было бы намного медленнее, если бы нам с тобой пришлось писать весь код с нуля. Поэтому люди используют чужой код и делятся своим кодом с другими. Это называется сообществом разработчиков программ с открытым исходным кодом (open source community). Вернемся к JSON. Будем использовать модуль json, который написали другие программисты. Этот модуль может читать словари Python и преобразовывать их в файлы JSON. Вернись к началу файла и добавь строку: import json. Это должна быть самая первая строка в файле. Эрик так и сделал. Вот его обновленный файл: import json order = { "name": "Эрик", "drink": "кофе", "flavor": "карамель", "topping": "шоколад" } f = open("orders.json", "w") — Теперь нужно привести твой тестовый заказ к формату JSON и записать его в файл, который мы только что открыли, — сказал Саймон. — В модуле json есть функция dump. Но прежде чем вызвать ее, мы должны сказать Python, что эта функция находится в модуле json, поэтому вызов функции будет выглядеть так: json.dump(). Тебе нужно передать в нее два аргумента: словарь и объект файла. Добавь эту функцию в конец файла и передай ей словарь order и объект файла f. Эрик добавил строку в конец программы: json.dump(order, f) Саймон продолжил: — Есть еще кое-что, чего мы еще не делали с файлами. Мы должны закрыть файл. Это важно, поэтому давай посмотрим еще на одну схему. Вот три главные части компьютера: процессор, память (более правильно называть ее «оперативная память») и диск1 (более точно — «долговременная, 1 Для упрощения Саймон говорит «диск», но на самом деле это может быть и флешка, и SSD-накопитель, в общем, любое устройство для постоянного хранения информации. — Примеч. ред. 126 Поговорим на Python или «постоянная», или «внешняя» память). Программа на Python запускается в процессоре. Файл хранится в файловой системе на диске. Файловая система — это то, что ты видишь в Finder на Mac, или папки и файлы в Проводнике на компьютере с Windows. Если ты хочешь работать с файлом в Python, то сначала ты открываешь этот файл. Ты уже это делал. При открытии файла в программе создается файловый объект. Когда ты записываешь что-то в файл, пишешь в оперативную память. Потом, если ты хочешь, чтобы файл по-настоящему записался в файловую систему, ты закрываешь файл. Компьютер Процессор Программа на Python Объект файла Память write() Файл в памяти open() close() Файл на диске — Как сложно! — смутился Эрик. — Почему нельзя записывать сразу на диск? — Ты прав, компьютеры в принципе сложные, — согласился Саймон. — Потому что инженеры постоянно пытаются ускорить работу компьютера. Записывать на диск долго, намного дольше, чем в память. Представь, что ты пишешь программу в текстовом редакторе. Если бы редактор сразу сохранял каждую букву на диск, то программа записывалась бы очень медленно. Тебе, скорее всего, не понравится работать с таким медленным компьютером. — Ненавижу медленные компьютеры! — подтвердил Эрик. — Чтобы компьютеры работали быстрее, — продолжил Саймон, — инженеры решили хранить данные в оперативной памяти и сохранять их на диске, только когда это необходимо. Для ускорения работы компьютера уже придумали много всяких приемов, конечно, я не знаю их все. Может быть, в колледже я узнаю побольше. Но давай вернемся к программе и закроем файл. Вспомни, у файловых объектов есть методы. В нашем случае нужно вызвать метод f.close(). Так заказ будет записан в файл. Добавь вызов метода f.close() в программу после последней строки. Глава 8. Работа с JSON: сохранение заказа 127 Несмотря на такое длинное объяснение, изменения оказались довольно простыми. Эрик быстро добавил нужную строку и получил вот это: import json order = { "name": "Эрик", "drink": "кофе", "flavor": "карамель", "topping": "шоколад" } f = open("orders.json", "w") json.dump(order, f) f.close() — Теперь запусти, — сказал Саймон. Эрик нажал Run и увидел знакомый символ >>> в нижней части окна. — И что теперь? — спросил Эрик. — Ничего не произошло? — улыбнулся Саймон. Он заметил смущение Эрика. — Ты же не просил Python вывести что-то на экран. Но все же за кадром кое-что произошло. Python открыл файл orders.json, записал туда твой заказ и закрыл файл. Давай откроем этот файл и проверим, правильно ли все записалось. Открой файл в простом текстовом редакторе. У тебя Mac, поэтому тебе нужен TextEdit в папке Applications. На Windows это Блокнот, на Linux — gedit или Kate. Запусти редактор и открой файл. Он находится в домашней директории, то есть в папке mu_code, и называется orders.json. Эрик запустил TextEdit, нашел файл и открыл его. Конечно, он увидел свой заказ. ТВОЙ ХОД! Сохрани свой тестовый заказ в файл JSON Напиши программу, как у Эрика. Немного измени тестовый заказ. Запусти программу и с помощью текстового редактора проверь получившийся файл JSON. Попробуй создать совсем другой заказ и снова запусти программу. JSON-файл изменился? (Возможно, придется заново открыть файл в редакторе.) 128 Поговорим на Python — Видишь? — спросил Саймон. — Твой тестовый заказ сохранился в файл. Дай я кое-что добавлю, и ты поймешь, почему файлы JSON идеально подходят для хранения словарей Python. Саймон взял клавиатуру и изменил вызов json.dump() следующим образом: json.dump(order, f, indent=4) Он снова запустил программу и открыл файл orders.json. Содержимое файла что-то напоминало Эрику. { } "name": "Эрик", "drink": "кофе", "flavor": "карамель", "topping": "шоколад" ТВОЙ ХОД! Сделай файл красивым Добавь к предыдущей программе аргумент indent=4, запусти программу снова и проверь, изменился ли файл JSON. — Файл похож на мой словарь! — воскликнул Эрик. — Что я и говорил, — сказал Саймон. — Будем сохранять твои заказы с помощью JSON. Я сказал «заказы», а значит, теперь нужно научиться сохранять несколько заказов и записывать их в файл. Мы уже знаем, как в Python сохранить несколько позиций из заказа — ты делал это в диалоге с меню. — С помощью списка! — сказал Эрик. — Правильно! Список Python может хранить разные типы данных: строки, числа и даже словари. В нашем случае это будет список словарей. Каждый словарь будет содержать заказ, и мы будем добавлять заказы в список по одному — новый клиент, новый заказ, новый словарь. Я нарисую схему. Глава 8. Работа с JSON: сохранение заказа 129 Список заказов name: Эрик drink: кофе flavor: шоколад ... order name: Алекс drink: шоколад flavor: ваниль ... order 1 ... order 2 name: Эмили drink: шоколад flavor: без добавок ... — Давай создадим список заказов, — продолжил Саймон. — Скопируй существующий заказ и назови новый заказ, например order1. Потом измени состав заказа: имя, напиток и все остальное. Эрик занялся кодом и написал второй заказ, сразу после первого: order1 = { "name": "Алекс", "drink": "шоколад", "flavor": "ваниль", "topping": "карамель" } — Хорошо, — сказал Саймон. — Теперь создай пустой список orders. Обрати внимание, что название списка стоит во множественном числе — orders. Пустой список создается почти как пустой словарь — ты это уже делал, — просто вместо фигурных скобок поставь квадратные. Эрик добавил следующую строку после второго заказа: orders = [] — Теперь добавь оба заказа в список, — сказал Саймон. — И знаешь, список заказов, который ты только что создал, — это тоже объект. В Python все является объектами, и у каждого объекта есть методы, которые ты можешь вызывать. Тебе просто нужно знать, какие именно методы есть у объекта. Например, у всех списков есть метод append(). Он добавляет в конец списка элемент, который ты передаешь ему в качестве аргумента. Сейчас я вызову метод append(), чтобы добавить order и order1 в список заказов. Саймон добавил две строки после строки с созданием списка orders: orders.append(order) orders.append(order1) 130 Поговорим на Python — А как ты узнал, что у списков есть метод append()? — спросил Эрик. — Прочитал в документации, когда учил Python, — ответил Саймон с улыбкой. — Набери в Google «методы списков Python», и получишь все, что тебе нужно. Теперь можно попробовать сохранить список в JSON. В функции dump() нам нужно поменять только то, что мы хотим «сбросить»1 в файл. Чтобы записать в файл первый заказ order, его нужно передать функции dump() в качестве аргумента. Дальше мы заменим order на orders (во множественном числе!) и посмотрим, что изменилось. Эрик изменил строку json.dump() на такую: json.dump(orders, f, indent=4) Он запустил программу и снова открыл файл orders.json. [ { }, { ] } "name": "Эрик", "drink": "кофе", "flavor": "карамель", "topping": "шоколад" "name": "Алекс", "drink": "шоколад", "flavor": «ваниль", "topping": "карамель" ТВОЙ ХОД! Сохрани список Добавь еще один тестовый заказ и назови его order2. Создай список заказов и сохрани его в тот же файл JSON. Проверь результат с помощью текстового редактора. Добавь столько заказов, сколько можешь придумать, и запиши их в этот файл. Проверь, есть ли ограничение на количество заказов в файле. — Что скажешь? — спросил Саймон. — Это похоже на твои заказы? 1 Английское слово dump означает «сбрасывать», «разгружать». — Примеч. пер. Глава 8. Работа с JSON: сохранение заказа 131 — Да, прямо как в Python! — сказал Эрик. — Почему мы пишем заказы в файл? Если заказы в файле так похожи на заказы в Python, то почему бы не сохранять их прямо в программе? — Отличный вопрос! — Саймон был очень рад, что Эрик хотел во всем разобраться. — Прежде всего нужно отделять программу от данных. Вспомни, когда ты запускаешь Word, ты не сохраняешь документы в саму программу. Ты сохраняешь их в отдельных файлах. То же самое здесь. Например, программа может хранить заказы за разные дни в разных файлах. Нужно только менять имя выходного файла, например orders.Monday.json, orders.Tuesday.json и так далее. Вторая причина в том, что название формата JSON придумано не просто так. Оно расшифровывается как JavaScript Object Notation (объектная нотация JavaScript). Формат JSON разработали программисты, которые пишут на языке JavaScript, а потом этот формат начали использовать и в других языках. Так что ты можешь сохранять заказы, созданные на языке Python, в файл JSON, а потом, например, кто-то из твоих друзей напишет программу на JavaScript, которая будет читать из этих файлов и выводить заказы на веб-страницу. — Да, в моем классе есть те, кто знает JavaScript, — сказал Эрик. — Хорошо! Вы можете создать команду и работать вместе, — сказал Саймон. — Но пока давай продолжим с нашей программой. Сейчас мы прочитаем заказы из файла JSON и сохраним их в новый список. Давай назовем его saved_orders. — Мы ведь только что записали заказы в файл, зачем нам их читать? — удивился Эрик. — Я, наверное, не очень понятно объяснил, — сказал Саймон. — В этой программе мы только знакомились и пробовали работать с файлами JSON. Но теперь мы готовы перенести их в основную программу. Так часто делают: создают простые программы, чтобы проверить концепции и идеи. Давай я покажу тебе схему основной программы, чтобы тебе было проще понять, куда мы идем. Саймон взял еще один лист бумаги и начал рисовать. — Сначала мы должны проверить, что файл orders.json существует. Если он существует, то мы открываем его и считываем из него предыдущий заказ. — Зачем нам нужен предыдущий заказ? — спросил Эрик. — Мы его уже приготовили. — Да, но мы же говорили, что неплохо бы посчитать, сколько клиентов мы обслужили сегодня, или вчера, или в прошлом месяце. Или сколько порций карамели мы продали, чтобы решить, не пора ли пополнить запас. Если ты 132 Поговорим на Python хочешь по-настоящему управлять кофейней, тебе нужны все эти заказы. Поэтому в любом бизнесе записи заказов хранят достаточно долго. — А что, если у нас нет файла с заказами? — спросил Эрик. — Значит, мы только что запустили свой бизнес, — сказал Саймон. — В этом случае мы создаем пустой список и начинаем принимать заказы. Если мы откроем файл для записи, то он создастся автоматически. Программа orders Файл существует? load() orders.json — Посмотри на эту схему: здесь есть список orders — это пустой список или список заказов за прошлые дни. Как только мы начинаем получать заказы, мы сохраняем их в список orders. В конце дня закрываем файл и сохраняем все заказы на диске. На следующий день мы снова открываем файл и продолжаем принимать заказы. Все новые заказы запишутся после предыдущих. Программа orders dump() orders.json ТВОЙ ХОД! Нарисуй схему своей программы Попробуй нарисовать свою схему, не подсматривая в книгу. Рисуя схемы, ты сможешь лучше понять, как работают программы. — Так работают настоящие кофейни? Даже Starbucks? — спросил Эрик. — Да, почти, — сказал Саймон. — Конечно, для надежности и безопасности они пользуются базами данных. А еще их записи заказов намного сложнее наших. Глава 8. Работа с JSON: сохранение заказа 133 Но общий процесс очень похож. Теперь ты посвящен в наш великий план, и мы можем продолжить работу над нашей простой программой и прочитать данные из файла. Мы прочитаем старые заказы в новый список saved_orders, а потом выведем этот список на экран, чтобы убедиться, что мы все сделали правильно. В модуле json есть функция load(). Она работает так же, как dump(): сначала мы открываем файл, но в этот раз для чтения, а не для записи. Потом вызываем json.load() и передаем объект файла в качестве аргумента. Функция возвращает объект, который она прочитала из файла, и мы присваиваем этот объект переменной. В нашем случае функция вернет список заказов, которые, как ты помнишь, являются словарями. Сложновато? Давай помогу. В Python это выглядит гораздо проще. Саймон начал добавлять строки в код Эрика. Вот что он добавил в конец программы: f = open("orders.json", "r") saved_orders = json.load(f) print(saved_orders) Саймон нажал Run, и Эрик увидел результат. [{'name': 'Эрик', 'drink': 'кофе', 'flavor': 'карамель', 'topping': 'шоколад'}, {'name': 'Алекс', 'drink': 'шоколад', 'flavor': 'ваниль', 'topping': 'карамель'}] >>>| ТВОЙ ХОД! Прочитай файл JSON Добавь в свою программу строки, указанные выше, и попробуй прочитать файл JSON, который ты создал. Ты получил те же заказы, которые были в тестовых данных? — Сегодня ты узнал очень много, — сказал Саймон. — Давай сделаем перерыв до завтра. Потом мы добавим новые функции в основную программу, и она станет настоящим приложением для кофейни. Можешь быстро вспомнить, что мы сегодня делали? — Мы создали файл JSON из словаря Python, и этот файл выглядел почти как словарь. Потом ты рассказал про файлы, оперативную и внешнюю память (диск). Еще мы создали список словарей и тоже сохранили его в файл. 134 Поговорим на Python — И мы узнали, что такое модули Python и как их импортировать, — добавил Саймон. — Пока все хорошо. Давай продолжим завтра. Скоро у нас будет готова первая версия приложения. Что мы узнали сегодня JSON (JavaScript Object Notation) — это формат, который используется для хранения структурированных данных, а также для обмена данными между программами. Модули Python — это группы функций Python, которыми можно делиться с другими разработчиками. Обычно функции группируют в файлы. Перед использованием модули необходимо импортировать. Списки словарей. Списки могут содержать разные типы данных: строки, числа, словари и даже другие списки. Операции с файлами. Файлы можно открывать для чтения или для записи. Если ты записываешь данные в файл, то сначала они сохраняются в оперативную память компьютера. Чтобы сохранить данные на диск, нужно закрыть файл. Сообщество разработчиков ПО с открытым исходным кодом — это программисты, которые делятся своими программами и помогают друг другу писать более качественный код. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch08. 9 Завершение работы над меню: реальная программа В ЭТОЙ ГЛАВЕ 33 Эрик и Саймон дополняют основную программу функциями чтения и сохранения заказов 33 Саймон добавляет в программу выход из главного меню и функцию get_order() 33 Саймон объясняет, почему он считает, что программу Эрика теперь можно назвать настоящей 33 Братья строят планы на будущее — А теперь давай применим новые знания на практике, — сказал Саймон. — Вчера мы потренировались на тестовых заказах. Сегодня используем то, что мы узнали, в основной программе. — Отлично, — сказал Эрик. — Открой файл main_menu.py, в котором ты писал главное меню. Чтобы работать с файлом JSON, в файл main_menu.py нужно добавить пару функций. Давай вспомним, что мы должны сделать сначала. — Саймон достал вчерашнюю схему. 136 Поговорим на Python Программа orders Файл существует? load() orders.json — Сначала нужно написать функцию, которая прочитает список заказов из файла JSON. Но сначала она должна проверить, существует ли этот файл. Если не существует, функция создаст пустой список и вернет его в качестве результата. Если файл существует, то функция прочитает из него данные, преобразует их из формата JSON в список Python и вернет этот список. Давай я тебе помогу, — Саймону показалось, что для Эрика это может быть сложно. — Если функция работает с файлом, то мы обычно передаем ей имя файла в качестве аргумента, — Саймон начал писать функцию в нижней части файла main_menu.py, перед последней строкой с main_menu(): def load_orders(filename): — Теперь нужно проверить, существует ли указанный файл JSON. Для этого есть специальная функция, она находится в модуле os. — Что такое «os»? — спросил Эрик. — OS означает «операционная система» (operating system). В компьютере операционная система управляет всеми файлами и программами. Она работает с монитором, клавиатурой, колонками и видеокамерой. Обычно на компьютере установлена какая-то одна операционная система: Windows, macOS или Linux. Мы спросим у операционной системы, есть ли на компьютере файл с заданным именем, — Саймон добавил еще одну строку: def load_orders(filename): if os.path.exists(filename): — Здесь мы обращаемся к модулю os, а значит, мы должны импортировать этот модуль, так же как мы вчера импортировали модуль json в тренировочной программе. В основной программе мы еще не импортировали ни модуль json, ни модуль os, так что давай сделаем это сейчас. Саймон переместил курсор в начало файла и добавил две строки: import os import json Глава 9. Завершение работы над меню: реальная программа 137 Потом он вернул курсор в конец файла к функции load_orders() и продолжил объяснение: — Если файл существует, мы откроем его для чтения. Потом вызовем функцию json.load(), прочитаем данные из файла в список orders и вернем этот список. Саймон добавил в функцию еще три строки: def load_orders(filename): if os.path.exists(filename): f = open(filename, "r") orders = json.load(f) return orders — Если файл не существует, мы просто создадим пустой список и вернем его в качестве результата. def load_orders(filename): if os.path.exists(filename): f = open(filename, "r") orders = json.load(f) return orders else: orders = [] return orders — Функция чтения из файла готова! — сказал Саймон и посмотрел на Эрика. — Наверное, сам бы я ее не написал, — сказал Эрик. — Конечно, сперва она выглядит сложной. Но ты можешь прочитать ее как обычный текст. Эрик еще раз посмотрел на функцию и попробовал прочитать ее: — Если файл с именем filename существует, то открой этот файл. Сохрани полученный объект в переменную f. Потом загрузи все заказы из файла f в список orders. Хм, я и правда могу прочитать эту функцию, — удивился Эрик. Оказалось, что он умеет читать и понимать программы на Python! — Следующая функция будет проще, — продолжил Саймон. — Ты, наверное, сможешь написать ее сам, на примере файла с тестовыми заказами. Смотри, сразу после двух вызовов метода append() идут три строчки, которые нужно сюда добавить. У нас уже есть функция save_order(), которая ничего не делает, а только выводит на экран сообщение: «Заказ сохраняется…» Давай заменим ее реальной функцией. Назовем эту функцию save_orders() во множественном числе, потому что мы будем сохранять в файл JSON не один заказ, а список заказов. 138 Поговорим на Python Саймон написал первую строку функции: def save_orders(orders, filename): И пояснил: — В качестве аргумента мы передаем список заказов и имя файла, в который необходимо сохранить этот список. Теперь ты можешь добавить в функцию save_orders() три строчки из вчерашней программы. Эрик открыл файл dict_json.py и скопировал оттуда три строчки. Функция save_orders() приняла следующий вид: def save_orders(orders, filename): f = open(filename, "w") json.dump(orders, f, indent=4) return — Теперь можно проверить? — спросил Эрик. — Почти, — ответил Саймон. — Посмотри, что внизу файла. Там мы просто вызываем функцию main_menu(). А вот что мы должны делать на самом деле, — Саймон быстро нарисовал картинку. Программа orders get_orders() order order 1 order 2 — Вот что делает функция main_menu(). Нам нужно немного изменить ее, чтобы мы могли обслуживать несколько клиентов и сохранять заказы в список. Мы должны передать функции main_menu() список, в котором будем сохранять заказы по мере их поступления. Саймон установил курсор в начале файла и добавил аргумент orders к объявлению функции main_menu(): def main_menu(orders): — Теперь, — продолжил Саймон, — каждый новый заказ будет добавляться в список orders сразу после того, как клиент ввел этот заказ. Пока мы не добавили Глава 9. Завершение работы над меню: реальная программа 139 список orders в качестве аргумента, функция main_menu() не знала, куда сохранять новые заказы. Теперь мы можем вызвать метод append() и добавить заказ в список orders сразу после того, как клиент подтвердит заказ. Функция save_order() теперь не нужна. Мы сохраним все заказы при закрытии программы. Саймон отредактировал функцию main_menu() и получил следующее: def main_menu(orders): while True: order = get_order() print("Проверьте заказ:") print_order(order) confirm = input("Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: ") if confirm == "Y" or confirm == "y": orders.append(order) Добавляет новый заказ в список заказов print("Спасибо за заказ:") print_order(order) else: continue — Теперь давай внесем изменения в основную программу. Она должна выполнять три шага: читать старые заказы, получать новые (главное меню) и сохранять заказы в список. Саймон добавил в конец файла еще три строчки: orders = load_orders("orders.json") main_menu(orders) save_orders(orders, "orders.json") — Можно попробовать? — спросил Эрик. — Конечно, давай! — сказал Саймон. Эрик запустил программу, ввел свое имя и выбрал компоненты напитка. Когда программа попросила подтвердить заказ, Эрик напечатал Y. Программа вернулась к сообщению «Как вас зовут?» — Хорошо, — сказал Эрик, — работает. Но как мне проверить, что заказы сохранились в файл? — Сейчас покажу, — сказал Саймон. Эрик и Саймон открыли файл orders.json и обнаружили в нем только вчерашние заказы. Братья очень удивились, даже Саймон задумался. — Давай разбираться, — сказал он. — Мы открыли файл, прочитали данные, получили заказ… Но мы ничего не записали в файл, потому что все еще находимся 140 Поговорим на Python в главном меню и принимаем заказы! Мы никогда не дойдем до функции save_ orders()! Хм, давай подумаем, как это исправить. Эрик улыбнулся — его брат-всезнайка не знает, что делать. Но радость Эрика длилась недолго. Саймон сказал: — Я понял. У пользователя нет возможности выйти из главного меню. Мы продолжаем спрашивать «Как вас зовут?», но не предлагаем пользователю завершить программу. — Я видел, как ты завершал программу клавишами Ctrl-C, — сказал Эрик. — Да, завершал, но это плохой способ. Когда я нажал Ctrl-C, программа прервалась. Обычно в таком случае Python выдает сообщение об ошибке. Если программа прервана, значит, она ничего больше не делает, просто останавливается, в каком бы состоянии она ни была. Она не закроет файл и не сохранит заказы. Завершать программу нажатием Ctrl-C плохо. — Саймон ненадолго остановился и продолжил: — Нужно дать пользователю возможность нормально завершить программу. — Это как нажать значок «Закрыть» в Word? — Да, примерно так. Давай скажем пользователю, что если он хочет выйти из программы, то в ответ на вопрос «Как вас зовут?» он должен ввести Х. Вероятность того, что нам попадется пользователь, которого зовут «Х», невелика, она практически равна нулю. Так что давай так и сделаем: если в функции get_order() пользователь вводит имя Х, то мы больше не задаем ему вопросов и возвращаем пустой заказ, order = {}. Этот заказ попадет в функцию main_menu(), которая решит, что делать дальше. Если заказ пустой, то функция main_menu() сохранит заказ в файл и завершит программу. Если заказ не пустой, она добавит этот заказ в список заказов и продолжит работу. Давай нарисуем схему. get_order() Как вас зовут? name == 'X'? Да Нет продолжить задавать вопросы по заказу вернуть заказ с ответами клиента вернуть пустой заказ Глава 9. Завершение работы над меню: реальная программа 141 — Вот так будет выглядеть новая функция get_order(). Давай помогу ее написать, — сказал Саймон. Он взял у Эрика клавиатуру и начал редактировать функцию get_order(): def get_order(): order = {} name = input("Введите свое имя, для выхода введите 'X': ") if name == "X" or name == "x": Проверить прописную и строчную 'Х' return {} else: order["name"] = name drinks = read_menu("drinks.txt") flavors = read_menu("flavors.txt") toppings = read_menu("toppings.txt") order["drink"] = menu(drinks, "Напитки кофейни Эрика", "Выберите напиток: ") order["flavor"] = menu(flavors, "Вкусы напитков от Эрика", "Выберите вкус: ") order["topping"] = menu(toppings, "Топпинги для напитков от Эрика", "Выберите топпинг: ") return order — В этой функции я просто следовал схеме, которую мы нарисовали вместе. А вот схема для главного меню, — Саймон начал рисовать. main_menu() get_order() заказ пустой? Да Нет добавить в список выйти из main_menu save_orders() — Как мы и говорили, если функция get_order() вернет пустой заказ, то мы выйдем из главного меню. После этого программа сохранит заказы в файл, — Саймон отредактировал функцию main_menu() и получил следующее: def main_menu(orders): while True: order = get_order() if order == {}: Если заказ пустой, завершить работу программы 142 Поговорим на Python print("Вы ввели 'X', программа завершает работу...") return print("Проверьте заказ:") print_order(order) confirm = input( "Все верно? Чтобы подтвердить заказ, нажмите Y, чтобы отклонить заказ, нажмите N: ") if confirm == "Y" or confirm == "y": orders.append(order) print("Спасибо за заказ:") print_order(order) elif confirm == "X" or confirm == "x": return else: continue ТВОЙ ХОД! Отредактируй функцию главного меню Отредактируй свою функцию главного меню так же, как Саймон. Если тебе понадобится помощь, то полный код программы из этой главы можно найти по ссылке: https://github.com/pavelanni/pythonicadventure-code/tree/main/ch09. — Вот и все, — сказал Саймон. — Давай проверим. На этот раз введи другое имя, чтобы было легко понять, сохранился заказ в файл или нет. Эрик запустил программу. Когда она спросила, как его зовут, Эрик ответил: «Джейсон». Потом он ввел заказ и подтвердил его, нажав Y. Программа снова спросила, как его зовут. — Теперь давай введем x и посмотрим, завершится ли программа, — предложил Саймон. Эрик ввел x и нажал Enter. Программа вывела сообщение: Вы ввели 'X', программа завершает работу…, а затем появилась знакомая инструкция Python: >>>. — Теперь проверь файл orders.json, — сказал Саймон. Эрик открыл JSON-файл в текстовом редакторе. В самом конце файла он увидел последний заказ от Джейсона. — Работает! — сказал Эрик. — Теперь все заказы сохранились в файл! — Да, сохранились, — сказал Саймон и улыбнулся. Он был рад видеть законченную работающую программу, которая принимала заказы и сохраняла их в файл. И к тому же была написана его младшим братом! Глава 9. Завершение работы над меню: реальная программа 143 — Поздравляю, Эрик! — сказал Саймон. — Думаю, теперь можно сказать, что ты написал настоящую программу. У нее есть ввод и вывод, хранилище данных, структура данных и алгоритмы. Программа проверяет ввод на ошибки. И самое главное, она работает и очень полезна — она принимает заказы. Нет, я серьезно — это хорошая программа. Я тобой горжусь. Саймон, как обычно, нарисовал схему. В этот раз на схеме было показано, что такое настоящая программа. Настоящая программа Имя ввод вывод Пользователь Заказ структуры данных: - списки - словари алгоритмы: - получение заказа - главное меню хранилище данных JSON — Да, мне тоже нравится моя программа, — сказал Эрик. — Она делает то, что я хочу, и красиво смотрится. Она выводит заказы так же, как в Starbucks. Ну почти… Может, я добавлю линии или звездочки, чтобы заказы смотрелись еще лучше. Но у меня есть еще пара идей, что можно добавить в программу. — Что ты хочешь добавить? — спросил Саймон. — Сначала я хочу переделать программу в веб-приложение с меню и кнопками. Оно должно работать онлайн, а значит, я смогу брать с собой iPad и запускать программу с него. — Отличная идея! Давай начнем работать над этим на следующей неделе. У меня тоже есть пара идей, — сказал Саймон и улыбнулся. — Что ты смеешься? — спросил Эрик. — Я вспомнил, как в первый день ты решил, что мы доделали программу до конца. — А, да, я тоже это помню, — сказал Эрик. — Конечно, тогда программа была еще не готова. Так что ты придумал? — Хочу добавить кое-что в структуру данных, — сказал Саймон. — Например, дату и время получения заказа. Так мы увидим, сколько клиентов мы обслуживаем за день или за месяц. 144 Поговорим на Python — Да, было бы хорошо, — согласился Эрик. — Потом, может быть, лучше сохранять заказ в хранилище сразу после получения. — Ты же сказал, что это замедлит программу, — напомнил Эрик. — Только немного. Но нам важно сохранять заказы сразу после получения — иначе мы рискуем их потерять. Думаю, что нам понадобится база данных. Еще, если мы хотим сделать из твоей программы настоящее приложение для бизнеса, нам будут нужны такие функции, как «вывести все заказы» и «посчитать, сколько мы продали ванили». — Конечно, я тоже все это хочу, — сказал Эрик. — Но сперва я хочу сделать красивое веб-приложение! — Конечно, займемся этим на следующей неделе. Что мы узнали сегодня Как проверить, что файл существует? Используй модуль Python os и метод os.path.exists(). Передай имя файла в качестве аргумента, и функция вернет True или False. Завершать программу нажатием Ctrl-C плохо. Можно завершать работу нажатием комбинации клавиш Ctrl-C, только если программа сбоит. В хороших программах всегда есть возможность корректно завершить работу. Что такое настоящая программа? Это программа, у которой есть ввод и вывод, структура данных, алгоритмы, хранилище данных и защита от ошибок пользователя. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch09. 10 Изучаем Flask: первое веб-приложение В ЭТОЙ ГЛАВЕ 33 Эрик пишет свое первое веб-приложение 33 Саймон рассказывает, что такое Flask и как он работает 33 Эрик и Эмили работают над веб-формой 33 Первое веб-меню готово! — Ты все еще хочешь написать веб-приложение? — спросил Саймон у Эрика на следующей неделе. — Да, конечно! — сказал Эрик. — Иначе как мне работать с программой на iPad? — Хорошо, но имей в виду: веб-приложение — непростая штука. Оно потребует много внимания. Не переживай, если чего-то не поймешь. Я помогу, если будет нужно. — Знаю, ты хороший брат, — ответил Эрик почти без усмешки. — Чтобы создать веб-приложение, воспользуемся помощью старого друга — редактора Mu. У него есть специальный режим для работы с веб. Запусти редактор и нажми кнопку Mode («Режим») в левом верхнем углу. 146 Поговорим на Python Эрик нажал кнопку Mode, и на экране появилось меню. Глава 10. Изучаем Flask: первое веб-приложение 147 — Прокрути вниз, — сказал Саймон. — Найди режим Web и нажми на него. Потом нажми OK. После того как Эрик проделал все, что сказал брат, Саймон показал на нижний правый угол экрана и сказал: — Видишь слово «Web» рядом с шестеренкой? Это значит, что мы переключились в режим Web. Теперь давай посмотрим, что в нем можно делать. Нажми кнопку New (Новый). 148 Поговорим на Python Эрик нажал кнопку New, и в окне редактора тут же появился код на Python. — Интересно, — сказал Эрик. — Редактор что-то написал за меня. Это можно запустить? — Конечно, запусти. Но сначала нужно сохранить файл. Давай назовем его first_app.py. Глава 10. Изучаем Flask: первое веб-приложение 149 Эрик нажал Run, ввел имя first_app.py в окне сохранения файла и увидел результат запуска программы в нижней части окна редактора. — Что это? — спросил Эрик. — Это значит, что твое первое веб-приложение запущено. Видишь сообщение: «Running on http://127.0.0.1:5000/» («Работает по адресу http://127.0.0.1:5000/»)? Открой браузер и введи адрес http://127.0.0.1:5000/. Этот адрес называется URL, или универсальный указатель местонахождения ресурса (Uniform Resource Locator), — ты будешь часто употреблять этот термин при работе с веб-ресурсами. 150 Поговорим на Python Эрик открыл в браузере новую вкладку и ввел адрес. Он очень удивился тому, что увидел. ① ② ③ ④ ① Это очень простое динамическое веб-приложение на языке Python и во фреймворке Flask. ② Попробуй добавить следующий маршрут в свой код на Python: ③ Теперь добавь следующий шаблон и назови его greeting.html ④ Когда ты это сделаешь, перейди по ссылке /hello/yourname (вместо yourname в адресной строке подставь свое имя). :-) — Подожди, это все написал редактор? — спросил Эрик. — Да, но смотри, редактор предлагает тебе дописать кое-что самому, — сказал Саймон и указал на пример кода на странице. — Mu рекомендует скопировать код из первой серой рамки в твою программу. Сделай это. Это просто. Эрик скопировал код и вставил его после уже существующего. Глава 10. Изучаем Flask: первое веб-приложение 151 — Теперь Mu говорит, что нужно создать новый файл, — продолжил Саймон. — Скопируй код из второго окна и вставь его в новый файл, сохрани этот файл с именем greeting.html1. Эрик знал, как это сделать. Он нажал кнопку New в редакторе и удалил программу, которую Mu написал в этом файле. Он скопировал текст из второй серой рамки и вставил его в новый файл. Потом нажал Save. Саймон решил помочь брату: — Открой раскрывающийся список и измени «*.py» на «Other (*.*)» («Другое»). Иначе Mu будет думать, что ты сохраняешь программу на Python. Мы должны предупредить Mu, что нам нужен другой тип файла. Набери greeting.html в поле Save As («Сохранить как»). — Теперь останови программу и запусти ее заново, — сказал Саймон. — Не забудь переключиться на вкладку first_app.py. Эрик переключился на вкладку с приложением, нажал Stop, а потом снова нажал Run. Внизу страницы он увидел тот же результат. — Теперь вернись в браузер и сделай то, что предлагает редактор. Видишь, тут сказано: «Перейдите по адресу /hello/yourname», только вместо yourname нужно подставить свое имя2. Добавь в адресную строку /hello/Erik, сразу после 5000. Эрик почувствовал себя настоящим хакером. Он ввел то, что сказал Саймон, и нажал Enter. Чтобы приветствие было по-русски, надо изменить третью строку в этом коде: вместо «Hello» написать «Привет», вместо «How are you?» — «Как дела?». — Примеч. ред. 2 Имя необходимо вводить латиницей. — Примеч. ред. 1 152 Поговорим на Python — Ого! Программа со мной разговаривает! — удивился Эрик. — С тобой разговаривает твоя программа! — уточнил Саймон. — Это было не сложно, да? — Подожди, — сказал Эрик, — если мы собираемся работать с вебом, я должен позвонить Эмили. Она сказала, что изучала HTML, а это как раз то, что нам нужно для веб, так? — Абсолютно верно, — согласился Саймон. — Позвони ей. Работать в команде всегда интересней. ТВОЙ ХОД! Создай свое первое веб-приложение Переключись в режим Web в редакторе Mu и создай свое первое вебприложение. Скопируй примеры со страницы в браузере, как это сделал Эрик. Запусти приложение со своим именем. Попробуй другие имена. Покажи страницу своим друзьям и попроси их ввести свои имена. Эмили жила по соседству. Она пришла через 15 минут, очень воодушевленная проектом Эрика, и тут же сказала: — Эрик, покажи мне свой HTML! Эрик показал ей файл greeting.html и сказал: — Ну, это не мой код, это написал редактор Mu. — Ух, как интересно, — сказала Эмили. — Я никогда не встречала фигурные скобки в HTML. — Правильно, — сказал Саймон, — потому что это не чистый HTML, а шаблон. Мы используем программу Flask, она включена в редактор Mu и генерирует HTML-код с помощью шаблонов. Глава 10. Изучаем Flask: первое веб-приложение 153 — Понятно, — сказала Эмили. — Но теги <h1> и <p> мне встречались. — Теги? Что такое теги? — спросил Эрик. — Теги — это небольшие фрагменты кода, которые помещают в текст, чтобы изменить его внешний вид. Смотри, у тебя есть <h1> перед «Hello!» и </h1> после, и слово «Hello!» стало больше. В HTML это называется заголовок, как названия глав в книге. — А что значит <p>? — спросил Эрик. — Это значит «абзац», — пояснила Эмили. — В HTML можно писать код как угодно: в одну длинную строку, в несколько коротких строк или по одному слову в строке. Но если фрагмент текста заключен между тегами <p> и </p>, то в браузере этот фрагмент будет отображаться как отдельный абзац. Есть еще много других тегов. Текст можно выделить жирным шрифтом или курсивом, изменить цвет шрифта и так далее. — Эмили, а ты знаешь что-нибудь об HTML-формах? — спросил Саймон. — На уроках нам говорили, что в HTML можно создавать формы, чтобы вводить текст и вызывать меню, — ответила Эмили. — Но я никогда не делала этого сама. — Меню — это то, что нам нужно! — воскликнул Эрик. — Я тебе помогу, — сказал Саймон. — Сначала нам нужно написать обязательные HTML-теги. В начале файла всегда находится тег <html>, а в конце — </html>. Также нам нужно заключить текст в тег <body>. Он открывает блок текста, а тег </body> — закрывает. Поэтому теги со слешем / называются закрывающими. <html> <body> </body> </html> — Теги похожи на скобки списков Python, — сказал Эрик. Он хотел показать Эмили, что уже знает этот язык. — Ты прав, — подтвердил Саймон. — Теги помогают понять, что означает текст между ними: где заголовок, а где абзац. Но сейчас мы создаем меню, и для этого нам понадобится тег <form> и тег <select>, который мы поместим внутри тега <form>. Давайте создадим очень простое меню, — сказал Саймон и начал печатать. 154 Поговорим на Python Листинг 10.1. Первое меню с двумя опциями <html> <body> <form> <select> <option>Кофе</option> <option>Кофе без кофеина</option> </select> <input type="submit" value="Подтвердить"> </form> </body> </html> ТВОЙ ХОД! Создай свою первую веб-форму Создай файл forms.html и сохрани его в папке mu_code/templates. Скопируй предыдущий код и протестируй его в браузере. Попробуй изменить меню: отредактируй существующие пункты меню и добавь новые. Саймон закончил писать, нажал Save и сохранил файл forms.html в папке mu_code/templates. — Сколько элементов, заключенных в теги, вы здесь видите? Наверное, Эмили знает ответ. Эмили начала считать: — Сначала тег <html>, потом тег <body>. Внутри тега <body> есть тег <form>, а внутри тега <form> есть тег <select>. Это же для меню, правильно? А потом внутри <select> у нас есть два элемента <option>. — Правильно. Молодец, Эмили, — сказал Саймон. — Только не забудь элемент <input>, который является частью формы. У него нет закрывающего тега — он сам по себе. Он нужен, чтобы создать кнопку «Подтвердить». Давайте посмотрим, как это выглядит в браузере. — В браузере можно открывать файлы? — спросил Эрик. — Я думал, только веб-страницы. — Конечно, можно, — ответила Эмили. — Мы постоянно открывали файлы на уроках по HTML. Просто вызываешь меню «Файл» в браузере и нажимаешь «Открыть файл». Потом находишь свой файл. И все. Эрик проделал то, что сказала Эмили, и открыл файл forms.html. Он увидел меню, очень похожее на то, что он использовал на других сайтах. Он нажал на меню, и оно раскрылось. Глава 10. Изучаем Flask: первое веб-приложение 155 Кофе Подтвердить Кофе без кофеина — Не знала, что создавать формы так просто, — сказала Эмили. — Да, простые формы довольно легко создавать, но здесь кое-чего не хватает, — ответил Саймон. — Мне нравится, как выглядит наша страница, — заметил Эрик. — А чего тут не хватает? — Меню только выглядит хорошо, но оно ничего не делает, — сказал Саймон. — Мы должны получить данные от пользователя, а потом передать эти данные программе. А как передать данные? — спросил Саймон и сам ответил на свой вопрос: — С помощью переменных, которые очень похожи на переменные в Python. Сейчас я кое-что добавлю в форму: Листинг 10.2. Обновленная форма с переменной и атрибутом action <html> Определяет переменную, <body> которую вернет меню <form action="/order" method="post"> <select name="drink"> </select> Добавляет опцию, которая <input type="submit" value="Подтвердить"> подсказывает пользователю, <option value="">- Выберите напиток -</option> что делать <option value="coffee">Кофе</option> <option value="decaf">Кофе без кофеина</option> Определяет значения </form> </body> </html> переменной, которые может вернуть меню — Первым делом, — сказал Саймон, — мы определим переменную, которую вернет меню. Это будет переменная drink. В первом пункте мы говорим пользователю, что делать. Этот пункт будет работать как подсказка. Он будет отображаться на экране, пока пользователь не выберет напиток. Ты же не можешь ничего приготовить, не зная основной напиток? Наконец, следующие две опции определяют 156 Поговорим на Python значения, которые может вернуть меню. Это похоже на то, что мы делали, когда выбирали элементы из текстового меню, помнишь? — Можно я попробую выбрать что-нибудь из меню? — спросил Эрик. — Конечно, открой файл заново. Или просто обнови страницу в браузере. Эрик заново открыл файл, выбрал в меню «Кофе без кофеина» и нажал «Подтвердить». Нет доступа к файлу Возможно, файл перемещен, переименован или удален — Что это? — спросил Эрик. Он выглядел озадаченным. — А, забыл сказать, — ответил Саймон. — Я немного изменил тег form. Листинг 10.3. Атрибут action <form action="/order" method="post"> — У каждой формы должен быть обработчик (action), — начал объяснять Саймон. — В обработчике описано, что нужно делать с данными после отправки формы на сервер. Когда пользователь сделает выбор — кофе или кофе без кофеина, — его выбор передается некоторой функции, которая должна знать, что делать с полученной информацией: сохранить в файл или в базу данных, вывести на экран и так далее. — Как мы делали в другой программе? — спросил Эрик. — Ты постоянно говоришь о какой-то «другой» программе. Покажи ее, — потребовала Эмили. Глава 10. Изучаем Flask: первое веб-приложение 157 — Прости, Эмили, — сказал Саймон. — Я должен был сразу о ней рассказать. Мы с Эриком работали над программой, которая собирает заказы в кофейне, похожей на Starbucks. Ты можешь выбрать напиток, добавить вкус и топпинг и все такое. Эрик написал программу, которая показывает меню и спрашивает клиента, что он хочет заказать. Клиент выбирает напиток, вкус и топпинг, и программа выводит заказ на экран. Но пока эта программа работает как терминал, в текстовом режиме. Эрику нужна твоя помощь, чтобы сделать из этой программы веб-приложение. — Какой классный проект! — сказала Эмили. — Надеюсь, Эрик тоже научит меня программировать на Python. — Конечно, — сказал Саймон. — Учить других — лучший способ учиться самому. Но вернемся к нашей форме. Атрибут action говорит браузеру: «После того как пользователь отправит форму на сервер, передай информацию из формы по этому адресу». В нашем случае это адрес /order. Не волнуйтесь, это только звучит страшно — я покажу, что делать, — добавил Саймон, заметив сомнения на лицах Эрика и Эмили. — Я все равно не понимаю, — сказал Эрик. — Где находится адрес, о котором ты говоришь? — Посмотри на первое приложение, — сказал Саймон. — Видишь функцию greet? Эту функцию за нас написал редактор Mu, точнее, авторы редактора Mu. Ты уже знаешь объявление функции, которое начинается со слова def, но прямо над ним есть кое-что новое: @app.route('/hello/<name>'). Это декоратор Python, но сегодня мы не будем заниматься декораторами. @app.route('/hello/<name>') def greet(name='Stranger'): return render_template("greeting.html", name=name) — Пока для нас важно только то, что с помощью декоратора можно сказать программе, какой адрес относится к нашей функции. — Ага, понял! Это адрес /hello/Erik, который я вводил в браузере! — сказал Эрик. — Давай покажем Эмили, как это работает. Эрик открыл редактор Mu, перешел на вкладку с файлом first_app.py и нажал Run, а потом Browse (Обзор). Саймон открыл страницу с приветствием от редактора Mu. — Смотри, Эмили, можно набрать адрес сразу после цифр 127.0.0.1:5000. Смотри, что сейчас будет! Эрик набрал /hello/Emily и нажал Enter. Браузер показал вот такой результат: 158 Поговорим на Python — Ого! Класс! — сказала Эмили. — Можно мне попробовать? Она изменила Emily на Erik. Браузер, конечно, показал страницу с приветствием: «Hello, Erik, how are you?». — Интересно, — сказала Эмили. — На уроках HTML мы меняли страницы, но для этого нам приходилось редактировать HTML-код. Ваш способ намного проще! — Верно, — сказал Саймон. — Это называется динамической страницей, такая страница меняется в зависимости от того, что мы вводим. Можно ввести информацию в адресной строке, например /hello/Emily, или с помощью форм. Затем на основе введенной информации генерируется страница. Вы наверняка уже не раз видели такие страницы, например, когда оставляли комментарии или переписывались с кем-то в интернете. Вы нажимали «Отправить» или Enter, и страница обновлялась, верно? Теперь вы узнали, как сделать такие страницы самим. Давайте покажу на схеме, — сказал Саймон и начал рисовать. адрес или URL браузер Python http://127.0.0.1:5000/order - Выберите напиток - Подтвердить @app.route('/order') def order(): код функции HTML <form action="/order" method="post"> <select name="drink"> <option value="">- Выберите напиток -</option> Глава 10. Изучаем Flask: первое веб-приложение 159 — Комбинация букв и цифр в верхней части браузера — это адрес, или URL. Обычно адресом является имя сайта, например google.com. В нашем случае адрес — это цифры, которые являются IP-адресом сайта. В качестве сервера мы сейчас используем компьютер Эрика, а для любого компьютера в мире адрес 127.0.0.1 означает «этот компьютер». Но пока не думайте об этом. Саймон указал на первый овал вокруг слова «order» («заказ») в адресе и сказал: — Вот то, что нам нужно. Вот эта часть адреса. Когда мы переходим по этому адресу, видим форму с меню напитков. Когда мы нажимаем «Подтвердить», — Саймон провел по стрелке, указывающей вниз, — форма понимает, что она должна найти функцию, которая находится по адресу /order. Этот адрес написан в поле form action. А потом с помощью декоратора @app.route('/order'), — Саймон провел по стрелке, указывающей вверх на блок Python, — форма находит функцию, с которой она может работать. Видите, все связано, просто нужно написать одно и то же имя в адресе, форме и программе на Python. — Функция тоже называется order — это четвертое место, в котором мы должны написать то же самое имя? — спросил Эрик. — У тебя зоркий глаз! — Саймон улыбнулся. — Нет, в этом случае функция может называться по-другому. Я мог бы назвать ее new_order или get_order. Давайте теперь напишем саму функцию. Я вам помогу. Сначала может выглядеть страшновато, но не волнуйтесь. Я разбираюсь с Flask самостоятельно и обычно просто беру примеры из уроков в интернете. Не думайте, что я все это помню. Саймон написал функцию, подсматривая в пример, который был открыт у него в браузере. — В первой строке есть слова GET и POST. Это методы, которые используются в веб-серверах. Если нужно получить что-то с сервера, например веб-страницу, используют метод GET. Если нужно отправить информацию на сервер, используют метод POST — как в нашем случае, чтобы отправить на сервер напиток, который выбрал клиент. Говоря иначе: когда мы загружаем страницу в браузере, мы используем GET; когда мы нажимаем «Подтвердить» в форме, мы используем POST. Не волнуйтесь, вы разберетесь, когда начнете практиковаться. Ниже мы используем слово POST: if request.method == 'POST':. Вот что означает эта строка: если указан метод POST, то есть кто-то заполнил форму и нажал «Подтвердить», мы считываем данные, которые пользователь ввел в форме, и выводим их на экран. Помнишь, что в форме мы использовали вот это: <select name="drink">? Это имя раздела меню, которое мы используем в следующей строке, в квадратных скобках: drink = request.form['drink']. Позже мы добавим в меню разделы для вкусов и топпингов. В форме у них будут имена flavor и topping. Здесь, в коде, они будт выглядеть как request.form['flavor'] и request.form['topping']. В строке 160 Поговорим на Python print мы просто выводим на экран все, что получили от формы. Ты увидишь это в редакторе. В последней строке мы просим веб-сервер вывести на экран страницу меню. Это как цикл меню в другой твоей программе — ты получаешь данные от клиента, выводишь их на экран и возвращаешься к меню, чтобы получить следующий заказ. Цикл повторяется, пока заказы не закончатся. В листинге ниже показана функция, которую написал Саймон: Листинг 10.4. Функция для обработки заказа Методы, которые используются в этой форме @app.route('/order', methods=('GET', 'POST')) def order(): if request.method == 'POST': Метод POST означает, что данные отправляются drink = request.form['drink'] Получает напиток, который клиент print("Напиток: ", drink) выбрал в поле drink формы return render_template("forms.html") Отображает шаблон forms.html Выводит на экран напиток, полученный от клиента — Сейчас добавлю еще кое-что, — сказал Саймон и добавил в первую строку с import модуль request. — Модуль request — это часть Flask. Если мы вызываем этот модуль, его нужно импортировать. Первая строка приняла вот такой вид: from flask import Flask, render_template, request ТВОЙ ХОД! Напиши свою функцию order() Добавь в свою программу first_app.py функцию order(), которая описана выше. Не забудь изменить строку import. Попробуй запустить программу. Открой новую вкладку в браузере и перей­ди по адресу http://127.0.0.1:5000/order. Если у тебя возникли проблемы, продолжай читать и сделай то же самое, что сделали Эрик и Эмили. — Теперь можно запустить? — спросил Эрик. Он немного устал от такого длинного объяснения. Эмили, напротив, смотрела на Саймона как на волшебника. Ей нравилась вся эта магия программирования, и она уже очень хотела проверить программу. — А можно я запущу? — спросила Эмили. Глава 10. Изучаем Flask: первое веб-приложение 161 — Конечно, — сказал Эрик. — Просто нажми Save, а потом Run. — Теперь нажми Browse, — сказал Саймон. Эмили сделала все, как ей сказали, и в новой вкладке браузера открылась такая страница: — Теперь нужно добавить к адресу /order, — подсказал Саймон. Эмили изменила адрес в браузере на http://127.0.0.1:5000/order. На странице появилось меню, которое Эрик, Саймон и Эмили только что написали вместе. - Выберите напиток - ПОДТВЕРДИТЬ — Выбери напиток и нажми «Подтвердить», — предложил Саймон. Эмили выбрала «Кофе без кофеина» и нажала кнопку «Подтвердить». На экране отобразилась та же страница с меню «Выберите напиток». — Работает? — спросила Эмили. Она выглядела смущенной. 162 Поговорим на Python — Давай проверим, — сказал Саймон. — Вернись в редактор и посмотри, что написано в нижнем окне. Напиток: кофе без кофеина — Видишь строку Напиток: кофе без кофеина? Это то, что наша программа должна вывести на экран, — сказал Саймон. — Значит, работает! — Но я думала, что программа выведет заказ прямо на страницу, — заметила Эмили. — Выведет, не спеши, — ответил Саймон. — Эту часть мы еще не написали. Но мы к ней уже подбираемся. Он взял клавиатуру, добавил новую строку в файл first_app.py после функции print() и пояснил: — Когда мы впервые переходим по адресу /order в браузере, используем метод GET, потому что сначала мы должны получить страницу. Нам еще нечего отправлять на сервер. Мы используем шаблон forms.html, который отображает меню напитков. Но после того как мы выбрали напиток и нажали «Подтвердить», мы используем метод POST, потому что хотим отправить данные программе. В этом случае мы получаем данные из формы — выбранный напиток — и применяем другой шаблон. Я назвал его print.html, потому что он будет выводить заказ на экран. Функция order() приняла следующий вид: @app.route('/order', methods=('GET', 'POST')) def order(): if request.method == 'POST': drink = request.form['drink'] print("Напиток: ", drink) return render_template("print.html", drink=drink) return render_template("forms.html") — Но у нас нет файла print.html, — сказала Эмили. Выводит заказ на экран с помощью шаблона print.html Глава 10. Изучаем Flask: первое веб-приложение 163 — Верно, я создам его прямо сейчас, — Саймон создал в редакторе новый файл и сохранил его в папке templates с именем print.html. Листинг 10.5. Шаблон для вывода заказа на экран (первая версия) {% extends "base.html" %} {% block content %} <h1>Спасибо за заказ!</h1> <p>Ваш напиток: <strong>{{drink}}</strong></p> {% endblock %} Эмили посмотрела на код Саймона и сказала: — Это я понимаю! Ты выводишь на экран заголовок «Спасибо за заказ!», потом с новой строки выводишь «Ваш напиток», а потом название напитка жирным шрифтом. А drink в двойных фигурных скобках работает так же, как с моим именем, когда программа вывела на экран «Привет, Эмили!», верно? — Абсолютно верно! — воскликнул Саймон. — Ты молодец, Эмили! ТВОЙ ХОД! Отредактируй свою веб-форму и выведи на экран выбранный напиток Скопируй шаблон, описанный выше, в свой файл print.html. Поменяй в шаблоне заголовок и текст. В файл first_app.py добавь строку return. Сохрани изменения. Запусти программу. — Можно запустить? — спросила Эмили. — Конечно, нажми Run, — сказал Саймон. Эмили нажала Run и выбрала «Кофе» в раскрывающемся списке. Страница обновилась. Спасибо за заказ! Ваш напиток: кофе 164 Поговорим на Python — Работает! — воскликнула Эмили. — Это как вызов функции print_order() в предыдущей программе, — сказал Эрик. — Именно так! — ответил Саймон. — Но как вернуться на страницу заказов? — спросил Эрик. — Видишь, в адресной строке браузера осталось слово /order? — спросил Саймон. — Значит, если ты поставишь курсор в адресную строку и нажмешь Enter, страница перезагрузится и ты снова окажешься на странице заказов. Только не нажимай «Обновить» в браузере, а то создашь еще один заказ. Эмили сделала все, что сказал Саймон, и снова увидела страницу заказов. — Но есть способ лучше, — сказал Саймон. — Ты же хотела увидеть на странице что-то вроде кнопки «Вернуться к странице заказов»? — Да, так было бы проще, — согласилась Эмили. — Для этого можно создать отдельную форму. Это очень просто, — сказал Саймон и добавил несколько строк в файл templates/print.html. — Вот, я создал отдельную форму, которая содержит только кнопку отправки. Только теперь я назвал эту кнопку не «Подтвердить», а «Новый заказ». В поле action кнопка ссылается на /order, и значит, после нажатия на эту кнопку снова появится меню напитков. Попробуй! Листинг 10.6. О бновленный шаблон вывода на экран с кнопкой «Новый заказ» {% extends "base.html" %} {% block content %} <h1>Спасибо за заказ!</h1> <p>Ваш напиток: <strong>{{drink}}</strong></p> <form action="/order"> <input type="submit" value="Новый заказ" /> </form> {% endblock %} Глава 10. Изучаем Flask: первое веб-приложение 165 Эмили ввела заказ, нажала «Подтвердить» и увидела страницу с новой кнопкой. Спасибо за заказ! Ваш напиток: кофе без кофеина НОВЫЙ ЗАКАЗ Она нажала кнопку «Новый заказ» и вернулась к странице заказов. — Это как цикл в главном меню, — отметил Эрик. — Заказываешь, подтверждаешь, потом выводишь на экран и возвращаешься к меню заказов. — Ты прав! — подтвердил Саймон. — Давай покажу на схеме. браузер Python @app.route('/order') def order(): drink=request.form['drink'] print(drink) (3) return render_template( "print.html", drink=drink) http://127.0.0.1:5000/order Кофе без кофеина (1) HTML Подтвердить Text <form action="/order" method="post"> <select name="drink"> <option value="decaf">Кофе без кофеина</option> (5) (2) (4) браузер http://127.0.0.1:5000/order Ваш напиток: кофе без кофеина Новый заказ 166 Поговорим на Python — Посмотрите на схему. Шаг первый: вы выбираете «Кофе без кофеина», и переменной drink на форме присваивается значение кофе без кофеина. Шаг второй: значение кофе без кофеина передается программе на Python через шаблон request. form. Переменная drink в программе принимает значение кофе без кофеина. Шаг третий: мы передаем значение переменной drink — кофе без кофеина — из программы на Python в переменную drink из шаблона print.html. Шаг четвертый: мы вызываем функцию render_template() с переменной drink, которая заменяет ее значение на кофе без кофеина. Так кофе без кофеина появляется на странице. И наконец, шаг пятый: мы нажимаем кнопку «Новый заказ» и возвращаемся к странице заказов. ТВОЙ ХОД! Измени шаблон print.html Добавь в шаблон templates/print.html кнопку «Новый заказ». Проверь программу. Получилось ли вернуться к странице заказов? — Эрик, Эмили вы отлично поработали сегодня, — сказал Саймон. — Главное, что вы не заснули, пока я вам так долго объяснял, что происходит. — Я почти заснул, — признался Эрик. — Да, я заметил, — сказал Саймон и улыбнулся. — Но если серьезно, создавать веб-приложения в разы сложнее работы над текстовым меню и диалогами. Я впечатлен вашим терпением! — Оно того стоило, — сказала Эмили. — Программа работает! — Еще нужно добавить вкусы и топпинги, — заметил Эрик. — Правильно! — согласился Саймон. — Нужно создать веб-формы для всех разделов меню. Уверен, Эмили тебе поможет. — Конечно, — подтвердила Эмили. — Кажется, нужно добавить в шаблон еще несколько тегов select. Эрик, покажешь мне первую программу? Можем поработать над веб-приложением завтра. — Конечно, — сказал Эрик. — Давай встретимся завтра и все доделаем. — Буду рад вам помочь, — улыбнулся Саймон. — Позовите меня завтра. Глава 10. Изучаем Flask: первое веб-приложение 167 Что мы узнали сегодня В редакторе Mu есть режим Web. В дополнение к стандартному режиму Python в редакторе Mu есть режим Web. В этом режиме редактор Mu предложит тебе простой пример веб-приложения. HTML-формы — это способ передать данные от пользователя в вебприложение. С помощью форм можно создавать меню, кнопки и текстовые поля. Когда ты нажимаешь «Подтвердить» (кнопку отправки данных на сервер), форма отправляет данные по адресу, который указан в поле action. Это адрес обработчика формы в программе. Flask — программа, которая помогает создавать веб-приложения. Это инструмент с открытым исходным кодом, и для нее можно найти много хороших уроков и примеров. С ее помощью написано много сайтов и веб-приложений. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch10. 11 Веб-форма для заказов: кофейня в интернете В ЭТОЙ ГЛАВЕ 33 Эрик и Эмили дорабатывают веб-форму для кофейни 33 Саймон рассказывает, как передать в программу значения из формы 33 Эрик и Эмили знакомятся с элементами формы input и select 33 Саймон рассказывает, как компьютер сохраняет файлы На следующий день Эрик, Эмили и Саймон собрались вместе, чтобы продолжить работу над приложением для кофейни. — Вы готовы серьезно взяться за дело? — спросил Саймон. — Сегодня наша веб-форма заработает по-настоящему. Вчера мы попробовали сделать очень простое меню. Теперь нужно добавить в него еще два раздела и текстовое поле, в которое клиент введет свое имя. Давайте посмотрим на вчерашний код. Один раздел меню готов — это код между тегами <select> и </select>. В этом разделе уже есть два пункта: кофе и кофе без кофеина. Глава 11. Веб-форма для заказов: кофейня в интернете 169 Листинг 11.1. Первые пункты меню в templates/forms.html {% extends "base.html" %} {% block content %} <form action="/order" method="post"> <select name="drink"> <option value="">- Выберите напиток -</option> Пункт меню «Кофе» <option value="coffee">Кофе</option> <option value="decaf">Кофе без кофеина</option> Пункт меню «Кофе </select> без кофеина» <input type="submit" value="Подтвердить"> </form> {% endblock %} — Теперь нужно добавить в меню напитков остальные пункты. Эрик, у тебя, кажется, еще был горячий шоколад, так? — Да, кофе, горячий шоколад и кофе без кофеина, — подтвердил Эрик. — Хорошо. Что было в разделах вкусов и топпингов? — В разделе вкусов у нас были карамель, ваниль, мята, малина и «без добавок», — сказал Эрик. — В разделе топпингов — шоколад, корица и карамель. — М-м-м, вкусно, — сказала Эмили. — Вот вам задание, — сказал Саймон. — Во-первых, добавьте третий пункт в раздел напитков. Во-вторых, добавьте разделы вкусов и топпингов. Вам понадобится скопировать код, вставить его и немного дописать, а еще вы должны быть внимательны. Кто за клавиатурой? — Я, — сказала Эмили. — Я справлюсь. Я знаю HTML и все эти теги. — Очень хорошо, давай! Эмили начала редактировать файл. Эрик наблюдал и время от времени задавал вопросы. — Почему ты сдвинула строку <option> вправо? В HTML тоже есть правило с отступами, как в Python? — Нет, в HTML не обязательно сдвигать строки, — ответила Эмили. — Можно все писать с начала строки, но с отступами лучше смотрится. Учитель говорил нам, что так код лучше читается. — Да, Саймон говорил то же самое, — сказал Эрик. — В Python обязательно сдвигать строки в блоках, но иногда можно выбирать, как писать код: в одну строку или в разных — и делать ли отступы. Саймон сказал, что нужно писать читаемый код, хотя бы для себя. 170 Поговорим на Python С помощью подсказок Эрика Эмили перечислила все вкусы и топпинги и создала вот такой HTML-шаблон: Листинг 11.2. Дополнительные пункты меню в шаблоне templates/forms.html {% extends "base.html" %} {% block content %} <form action="/order" method="post"> <select name="drink"> <option value="">- Выберите напиток -</option> <option value="coffee">Кофе</option> <option value="chocolate">Горячий шоколад</option> <option value="decaf">Кофе без кофеина</option> </select> <select name="flavor"> <option value="">- Выберите вкус -</option> <option value="caramel">Карамель</option> <option value="vanilla">Ваниль</option> <option value="peppermint">Мята</option> <option value="raspberry">Малина</option> <option value="plain">Без добавок</option> </select> <select name="topping"> <option value="">- Выберите топпинг -</option> <option value="chocolate">Шоколад</option> <option value="cinnamon">Корица</option> <option value="caramel">Карамель</option> </select> <input type="submit" value="Подтвердить"> </form> {% endblock %} ТВОЙ ХОД! Создай свои пункты меню Создай новые пункты меню в шаблоне templates/forms.html. Придумай свои названия. Возьми идеи из местной кофейни или кафе-мороженого. Саймон посмотрел на код и сказал: — Выглядит неплохо. Но прежде чем перейти от HTML к Python, давайте добавим еще кое-что. Помните, мы спрашивали имя клиента? — Как в Starbucks! — сказала Эмили. — Именно, — сказал Саймон. — Я добавлю в код формы еще одну строку. Это тег input с типом text. Мы используем его для ввода имени клиента. Видите, я добавил его сразу после строки form. Глава 11. Веб-форма для заказов: кофейня в интернете 171 Листинг 11.3. Т екстовое поле для ввода имени в шаблоне templates/forms.html {% extends "base.html" %} {% block content %} <form action="/order" method="post"> <input type="text" name="name" placeholder="ВВЕДИТЕ ИМЯ"> <select name="drink"> <option value="">- Выберите напиток -</option> <option value="coffee">Кофе</option> ... ТВОЙ ХОД! Добавь поле input Добавь тег input для ввода имени клиента. Не забудь написать подсказку для пользователя в поле placeholder. — Посмотрите на HTML-код еще раз, — продолжил Саймон. — Все три пункта меню, которые добавила Эмили, и строка input расположены между открывающим тегом <form> и закрывающим тегом </form>. Хорошо, что Эмили сдвинула строки вправо для читаемости. Теперь нам видно, что эти элементы находятся в форме. То есть все они являются частями одной формы, а значит, при нажатии на кнопку «Подтвердить» значения всех этих элементов будут переданы программе одновременно. В программе мы сможем прочитать их с помощью словаря request.form, так же как мы делали в тренировочном файле. — Как request.form['drink']? — спросил Эрик. — Да, верно, — ответил Саймон. — Эмили назвала новые поля flavor и topping, а значит, эти имена можно использовать в качестве ключей словаря. Давай снова откроем файл first_app.py и посмотрим, где нужно внести изменения. Помнишь, что мы делали в этом файле? Мы получали от формы словарь и копировали значение для ключа drink в переменную drink. Например, если клиент выбирает горячий шоколад, то переменной drink будет присвоено значение горячий шоколад. Затем переменная drink будет передана форме print.html для вывода на страницу (или, как иногда говорят, для рендеринга). Листинг 11.4. Первая версия функции order() в файле first_app.py . . . . @app.route("/order", methods=("GET", "POST")) def order(): if request.method == "POST": drink = request.form["drink"] print("Drink: ", drink) 172 Поговорим на Python return render_template("print.html", drink=drink) return render_template("forms.html") Саймон продолжил: — Теперь мы получаем от формы больше значений, а значит, мы должны собрать их вместе и передать в шаблон. Эрик, можешь дописать код? — Думаю, да, — сказал Эрик. Когда Эрик добавлял в код строки для вкусов и топпингов, Саймон напомнил: — Не забудь про поле name! Эрик отредактировал код, и получилось вот что: Листинг 11.5. Ф айл first_app.py: передаем шаблону вывода на экран остальные пункты меню . . . . @app.route("/order", methods=("GET", "POST")) def order(): if request.method == "POST": name=request.form["name"] drink=request.form["drink"] flavor=request.form["flavor"] topping=request.form["topping"] return render_template("print.html", name=name, drink=drink, flavor=flavor, topping=topping) return render_template("forms.html") ТВОЙ ХОД! Отредактируй свое приложение на Flask Добавь в свою программу first_app.py переменные, как сделал Эрик. Пока не запускай программу, потому что у тебя еще нет файла print.html. — Отлично, Эрик! Хотя я, наверное, рано тебя похвалил, мы еще не протестировали, — сказал Саймон и улыбнулся. — Осталось совсем немного: нужно добавить новые переменные в шаблон print.html. Давайте откроем его в редакторе. Смотрите, кроме основного напитка нужно вывести на экран вкус и топпинг. А, да, и не забудьте, что у нас есть имя клиента. Будем вежливы и выведем на экран приветствие. Эмили, можешь отредактировать HTML? Глава 11. Веб-форма для заказов: кофейня в интернете 173 — Конечно, — сказала Эмили и начала редактировать файл, поясняя Эрику свои действия. — Сначала давай заменим надпись «Спасибо за заказ» на «Привет!» плюс имя пользователя. Оставим тег <h1>, чтобы текст выделился крупным и жирным шрифтом. Дальше мы поместим надпись «Ваш заказ» в тег <h2>. Она будет немного меньше. После этого выведем на экран заказ: основной напиток, вкус и топпинг; каждый пункт напечатаем с новой строки. Тег <strong> оставим, чтобы сохранить жирный шрифт. Внизу страницы у нас простая форма всего с одной кнопкой, которая вернет нас на страницу заказов. Скорее всего, ты помнишь, мы вчера про нее говорили. Листинг 11.6. Ф айл templates/print.html: шаблон для вывода заказа на страницу {% extends "base.html" %} {% block content %} <h1>Привет, {{name}}!</h1> <h2>Ваш заказ:</h2> <p>Напиток: <strong>{{drink}}</strong></p> <p>Вкус: <strong>{{flavor}}</strong></p> <p>Топпинг: <strong>{{topping}}</strong></p> <form action="/order"> <input type="submit" value="Новый заказ" /> </form> {% endblock %} Форма с одной кнопкой возвращает обратно на страницу заказов ТВОЙ ХОД! Создай шаблон templates/print.html Создай шаблон для вывода заказа на страницу, как сделала Эмили. Обрати внимание на переменные: ты должен использовать переменные, полученные от программы. Если ты изменил названия переменных в файле first_app.py, измени их и здесь. Попробуй запустить приложение. — Теперь можно запустить? — спросил Эрик. — А ты сохранил файлы? — поинтересовался Саймон. — Посмотри на вкладки в редакторе. Если ты что-то не сохранил, то Mu отметит вкладку красным кружком. — Все в порядке, — сказал Эрик. — Тогда переключись на вкладку Python и нажми Run. 174 Поговорим на Python Эрик нажал Run, а потом Browse. Браузер открыл новую вкладку с уже знакомой страницей «Hello from Mu!». Эрик добавил к адресу /order и увидел меню. ВВЕДИТЕ ИМЯ - Выберите напиток- - Выберите вкус - - Выберите топпинг - ПОДТВЕРДИТЬ — Дай я попробую! — сказала Эмили. Она ввела свое имя и выбрала горячий шоколад, ваниль и карамель. Затем нажала «Подтвердить», и ее заказ появился на странице. Привет, Эмили! Ваш заказ: Напиток: горячий шоколад Вкус: ваниль Топпинг: карамель НОВЫЙ ЗАКАЗ — Ого! Работает! — Эмили была очень рада видеть настоящее веб-приложение, которое они только что написали вместе. — Замечательно, — сказал Саймон. — Теперь нужно добавить функции, которые мы написали в текстовой версии программы. — Какие функции? — удивилась Эмили. Она думала, что все уже сделано. — Эрик, можешь объяснить? — Мы только что написали пользовательский интерфейс для нашего приложения, — начал Эрик. — Само приложение мы создали раньше, только в текстовом виде. Веб-приложение выглядит гораздо лучше. Но мы хотим не только выводить заказы на экран, но и сохранять их в файл, чтобы потом, например, посчитать, сколько раз клиенты заказывали карамель. Глава 11. Веб-форма для заказов: кофейня в интернете 175 — Поняла. Вы хотите знать, когда заказывать новую партию карамели, — догадалась Эмили. — Именно! Эмили, ты прямо настоящий менеджер кофейни, — сказал Саймон. — Мы написали функцию, которая сохраняет заказы в файл, как раз перед тем, как ты к нам присоединилась. Наша программа сохраняет заказы в конце рабочего дня. При следующем запуске программа загружает предыдущие заказы в список и принимает в этот список новые заказы по мере их поступления. — Саймон на секунду остановился и задумался: — Но для веб-приложения нужно немного изменить этот подход. — Почему? — спросил Эрик. — Потому что наше приложение все время работает, нам не нужно его останавливать. Такие приложения называются веб-сервисами. Они не останавливаются. Они работают постоянно. Даже больше: если случается что-то непредвиденное и приложение падает, то его перезапускает другая программа, чтобы приложение всегда могло ответить на запросы пользователей — в нашем случае принять заказ. Это значит, что нужно сохранять заказ сразу после получения. Сейчас я придумаю, как это сделать. Саймон открыл в браузере новую вкладку и пару минут искал что-то в интернете. — Все, знаю, — сказал он наконец. — Эрик, открой нашу первую программу и скопируй функции load_orders() и save_orders() в веб-приложение. — А откуда мы будем их вызывать? Из функции order()? — спросил Эрик. — В прошлый раз ты сказал, что нужно объявлять функции перед вызовом, так? — Да, именно так, — сказал Саймон. — Вставь их в самое начало, даже перед строкой app = Flask(name). Так мы отделим функции для файлов от функций веб-приложения. А еще не забудь добавить import json и import os. Эрик скопировал функции из файла main_menu.py, над которым они с Саймоном работали несколько дней назад. Саймон проверил код Эрика и сказал: — Хорошо. Теперь нам осталось только закрыть файл после загрузки заказов, а главное — после сохранения заказов. — Но раньше мы этого не делали. Зачем нам теперь закрывать файл? И что именно значит закрыть файл? — спросил Эрик. — Я только недавно рассказывал. Сейчас найду схему. Эмили, тебе, наверное, тоже интересно. 176 Поговорим на Python — Это что-то вроде закрытия файла в Word? — спросила Эмили. — Да, но в нашем случае нужно закрыть файл из программы на Python. Важно понимать, что, пока файл открыт, Python хранит заказы в оперативной памяти. И если что-то случится с компьютером, например он выключится, то мы потеряем все заказы. Поэтому нужно закрыть файл, чтобы все заказы сохранились на диске. Посмотрите на схему. Компьютер Процессор Программа на Python Объект файла Память write() Файл в памяти open() close() Файл на диске ТВОЙ ХОД! Попробуй объяснить, как работает сохранение файлов Попробуй объяснить кому-нибудь принцип сохранения файлов с помощью схемы Саймона. Лучше ли ты стал понимать процесс сохранения файлов после того, как сам объяснил его? — Раньше мы так не делали, потому что закрывали все файлы во время завершения программы. В веб-приложении мы будем закрывать файл с заказами сразу после получения нового заказа. — Но ты сказал, что это замедлит работу программы, — сказал Эрик. — Да, немного замедлит. Но разработка — это вечный компромисс. Так нам говорит учитель по робототехнике. Программа становится все реалистичнее, так что придется продумывать, как сохранять важные данные — даже если это замедлит ее работу. Глава 11. Веб-форма для заказов: кофейня в интернете 177 Саймон добавил в обе функции метод f.close(), и вот что получилось: Листинг 11.7. Функции чтения и сохранения заказов в first_app.py import os import json from flask import Flask, render_template, request def save_orders(orders, filename): f = open(filename, "w") json.dump(orders, f, indent=4) f.close() return def load_orders(filename): if os.path.exists(filename): f = open(filename, "r") orders = json.load(f) f.close() return orders else: orders = [] return orders . . . . — Конечно, — добавил Саймон, — лучше использовать базу данных. Может быть, мы добавим ее позже. А теперь нужно прочитать заказы из файла. Саймон добавил строку перед строкой Flask: Листинг 11.8. С оздание списка заказов с помощью функции load_orders() в first_app.py . . . . orders = load_orders("orders.json") app = Flask(name) . . . . — Теперь у нас есть список, в который мы будем добавлять новые заказы. И у меня появилась идея, — Саймон начал печатать, комментируя вслух. — У нас есть список, в котором мы храним заказы, и этот список состоит из словарей Python, так? То есть когда мы получаем из формы новый заказ, то его лучше сразу поместить в словарь, а не в отдельные переменные. Мы добавим этот словарь в список, а потом сохраним в файл. Даже лучше: передадим словарь шаблону HTML, чтобы он вывел этот словарь на страницу. Это упростит программу! 178 Поговорим на Python Саймон закончил печатать, и функция order() приняла вот такой вид: Листинг 11.9. Словарь для нового заказа в first_app.py @app.route("/order", methods=("GET", "POST")) def order(): if request.method == "POST": new_order = {"name": request.form["name"], "drink": request.form["drink"], Создает новый словарь "flavor": request.form["flavor"], на основе данных, "topping": request.form["topping"] полученных из формы } Добавляет в список новый заказ orders.append(new_order) save_orders(orders, "orders.json") Сохраняет список в файл JSON return render_template( "print.html", new_order=new_order ) Передает словарь шаблону return render_template("forms.html") ТВОЙ ХОД! Отредактируй приложение Отредактируй программу в файле first_app.py, используя три предыдущих листинга. Убедись, что понимаешь, что происходит, когда ты создаешь новый заказ и передаешь его шаблону. — Ну и наконец, осталось изменить HTML-шаблон, — сказал Саймон. — Эмили, поможешь? — Конечно, только я не знаю, что делать, — ответила Эмили. — Смотри, нам нужно передать переменные (name, flavor и так далее) шаблону, чтобы он показал их значения на странице. Теперь вместо четырех переменных мы передаем только одну — словарь new_order, — который содержит все четыре значения. В Python такая структура данных называется словарем. Ты вводишь ключ, а словарь возвращает значение. Например, ты вводишь flavor, а словарь возвращает карамель. — Да, Эрик уже рассказывал про словари Python. Но как использовать их в HTML? — Точно так же, как в Python: с квадратными скобками. Давай покажу, — предложил Саймон и изменил name на new_order["name"] в строке с приветствием Привет. — Теперь понятно, — сказала Эмили, исправляя остальные переменные. Когда она закончила, шаблон print.html стал выглядеть так: Глава 11. Веб-форма для заказов: кофейня в интернете 179 Листинг 11.10. Используем словарь в templates/print.html {% extends "base.html" %} {% block content %} <h1>Привет, {{new_order["name"] }}!</h1> <h2>Ваш заказ:</h2> <p>Drink: <strong>{{ new_order["drink"] }}</strong></p> <p>Flavor: <strong>{{ new_order["flavor"] }}</strong></p> <p>Topping: <strong>{{ new_order["topping"] }}</strong></p> <form action="/order"> <input type="submit" value="Новый заказ" /> </form> {% endblock %} ТВОЙ ХОД! Отредактируй файл templates/print.html Замени переменные на словарь в шаблоне вывода на экран. Обрати внимание на ключи словаря — если ты изменил их в программе на Python, то в файле templates/print.html их тоже нужно изменить. — Отлично! — сказал Саймон. — Хотите проверить? — Конечно! — сказал Эрик и перешел по уже знакомому адресу http://127.0.0.1/ order. Эрик ввел имя «Алекс», выбрал горячий шоколад, ваниль и корицу, и его заказ появился на экране. Привет, Алекс! Ваш заказ: Напиток: горячий шоколад Вкус: ваниль Топпинг: корица НОВЫЙ ЗАКАЗ 180 Поговорим на Python — Теперь нужно проверить, сохранился ли заказ в файл, — сказал Саймон. Он нашел файл orders.json в папке mu_code и открыл его в текстовом редакторе. — Да! Заказ сохранился! Смотрите, первый заказ остался с того дня, когда мы тес­ тировали функцию save_order(). А второй только что добавил Эрик. Отлично! { }, { } "name": "Эрик", "drink": "кофе", "flavor": "карамель", "topping": "шоколад" "name": "Алекс", "drink": "горячий шоколад", "flavor": "ваниль", "topping": "корица" — А что с файлами меню? — спросил Эрик. — Какими файлами? — Помнишь, — пояснил Эрик, — ты говорил, что менеджер кофейни, скорее всего, не знает Python. И поэтому мы сохранили меню в простых текстовых файлах. Теперь наше меню хранится в HTML, но менеджер, скорее всего, HTML тоже не знает. Мы можем сделать то же самое с веб-приложением, чтобы менеджер мог редактировать текстовые файлы вместо HTML, если он захочет что-то добавить в меню? — Отличная мысль, Эрик! — ответил Саймон. — Думаю, для шаблонов HTML такое тоже возможно, нужно проверить это в документации Flask. Прежде чем вы уйдете, давайте вспомним, что мы сегодня сделали. — Ты рассказал, как компьютер сохраняет файлы, — вспомнил Эрик. — Я узнала о поле input в формах HTML, — сказала Эмили. — И было очень интересно поработать над настоящим веб-приложением, а не над упражнениями из учебника. — Согласен, — сказал Саймон. — Самый лучший способ что-то изучить — поработать на реальном проекте. Мы много сделали сегодня, так что давайте отдохнем, а завтра займемся идеей Эрика. Глава 11. Веб-форма для заказов: кофейня в интернете 181 Что мы узнали сегодня Веб-сервис — это программа, которая работает постоянно и отвечает на запросы пользователей, например помогает сделать заказ в кофейне. Почему важно закрывать файлы? Когда ты работаешь с файлами, Python сохраняет обновления в оперативной памяти. Если ты хочешь, чтобы данные сохранились на диске, вызови метод f.close(). Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch11. 12 База данных: создаем надежное хранилище В ЭТОЙ ГЛАВЕ 33 Саймон предлагает поместить файлы приложения в отдельную папку 33 Эрик и Эмили создают новую структуру папок и перемещают туда файлы приложения 33 Эрик повторно использует функцию чтения файлов 33 Эрик и Эмили знакомятся с базами данных 33 Саймон помогает Эрику и Эмили перевести приложение с файлов JSON на базу данных — Давайте еще немного улучшим наш проект, — сказал Саймон при следующей встрече. — Хочешь добавить базу данных? — спросил Эрик. — Ты что-то вчера о ней говорил. — И это тоже, — ответил Саймон. — Но сначала предлагаю изменить структуру папок. Давайте наведем порядок в файлах. Посмотрите на наши файлы и папки. Глава 12. База данных: создаем надежное хранилище 183 Саймон взял лист бумаги и начал рисовать и объяснять: — Редактор Mu по умолчанию помещает программы в папке mu_code. При этом шаблоны, необходимые для веб-приложения, находятся в папке templates внутри папки mu_code. Если вы посмотрите на структуру файлов в файловой системе — в Finder на macOS или Проводнике на Windows, — вы увидите, что в папке mu_code есть и другие каталоги, например static, images, fonts и так далее. Вспомните, мы уже создали сами два шаблона, order.html и print.html, и поместили их в папку templates. Если вы откроете папку static, то в ней вы увидите папку css — наверное, Эмили знает, что эта папка нужна для таблиц стилей, — и в ней даже есть несколько файлов. base.html mu_code index.html order.html templates static images fonts print.html css normalize.css skeleton.css cat1.png, etc. ... first_app.py orders.json — Как видите, наше приложение состоит из нескольких файлов, лежащих в разных папках. Я предлагаю создать отдельную папку приложения для кофейни и скопировать в нее все нужные файлы. Это также поможет отделить вебприложение от предыдущей версии программы. Всегда лучше хранить разные программы в разных папках. Давайте создадим папку для всех наших проектов, в том числе будущих. Обычно такую папку называют Projects (Проекты), по аналогии с папками Downloads (Загрузки), Pictures (Изображения) и Documents (Документы), которые уже есть на компьютере. В папке Projects создадим еще одну папку и назовем ее coffeeshop. Перенесем в эту папку все файлы, которые относятся к нашему веб-приложению. Еще я предлагаю переименовать файл first_app.py в просто app.py. Так обычно называют приложения, написанные с помощью Flask. Давайте покажу, что это значит, — сказал Саймон и дополнил свою схему. 184 Поговорим на Python mu_code templates static images fonts base.html index.html order.html print.html css Projects coffeeshop templates normalize.css skeleton.css cat1.png, etc. ... static app.py orders.json first_app.py orders.json — Сможете все это сделать? — спросил Саймон у Эрика и Эмили. — Да, — сказала Эмили, и они начали создавать папки и перемещать файлы. Через некоторое время Эрик и Эмили показали Саймону папку проекта coffeeshop. — Все верно? — спросил Эрик. Глава 12. База данных: создаем надежное хранилище 185 ТВОЙ ХОД! Создай папку для своего приложения Создай папку для своих проектов (можешь назвать ее Projects или как-то иначе). Создай папку для своего приложения. Назови ее в соответствии с выбранной темой приложения, например icecream или pizzaplace. Перемести файлы, которые относятся к веб-приложению, из папки mu_code в новую папку. — Сейчас проверим, — сказал Саймон, посмотрев на созданную папку. — Вы переместили директории static и templates со всем их содержимым, это правильно. Вы даже переместили папки fonts и images — пока они нам не нужны, но ничего страшного. — У меня вопрос, — сказал Эрик. — Почему иногда ты говоришь «директория», а иногда — «папка»? Это же одно и то же, так? — Прости, что не соблюдаю единство терминологии, — сказал Саймон. — Ты прав, эти два слова означают одно и то же: место, где хранятся файлы. Папки нужны, чтобы упорядочить файлы. Мы можем собрать вместе файлы, которые относятся к одному проекту, например к приложению для кофейни, или файлы одного типа, например шаблоны. Изначально в системах Unix и Linux папки назывались директориями. MacOS основана на Unix, а в кружке по робототехнике у нас Linux, так что я привык называть папки директориями1. На Windows их называют папками, но это абсолютно то же самое. Просто привыкните к разным терминам, это не так важно. — А еще вчера ты сказал, что мы можем хранить меню в текстовых файлах, как мы делали в предыдущей версии, — сказал Эрик. — Для менеджеров, которые не знают HTML, помнишь? — Спасибо, что напомнил, Эрик, — ответил Саймон. — Сегодня я хотел показать вам, как работать с базой данных. Но это сложная тема. Давайте разогреемся на файлах с меню. Можешь скопировать их из папки mu_code? — Да, секундочку, — сказал Эрик и скопировал файлы drinks.txt, flavors.txt и toppings.txt в папку coffeeshop. ТВОЙ ХОД! Перемести файлы меню Перемести файлы меню в папку, которую только что создал. 1 По-русски говорят также «каталог». — Примеч. ред. 186 Поговорим на Python — Теперь посмотри, как мы работали с файлами в старой программе, — сказал Саймон. — Открой файл menu_files.py и вспомни. Попробуй объяснить Эмили — это лучший способ понять что-то самому. — Да, мне интересно узнать, что вы делали в старой программе, — сказала Эмили. Эрик посмотрел на код и начал объяснять: — Мы написали функцию read_menu() , которая читает текст из файлов со списком напитков, вкусов и топпингов, и создали список с разделами меню. Мы передавали этот список функции menu(), которая выводила его на экран и просила клиента выбрать напиток, вкус и топпинг. После этого мы показывали клиенту его заказ. Листинг 12.1. menu_files.py: чтение меню из файлов def read_menu(filename): f = open(filename) temp = f.readlines() result = [] for item in temp: new_item = item.strip() result.append(new_item) return result drinks = read_menu("drinks.txt") flavors = read_menu("flavors.txt") toppings = read_menu("toppings.txt") drink = menu(drinks) flavor = menu(flavors, "Вкусы напитков от Эрика", "Выберите вкус: ") topping = menu(toppings, "Топпинги для напитков от Эрика", "Выберите топпинг: ") print("Ваш заказ: ") print("Основной напиток: ", drink) print("Вкус: ", flavor) print("Топпинг: ", topping) print("Спасибо за заказ!") — Я поняла, — сказала Эмили. — А что такое список? Это как массив в JavaScript? — Да, — подтвердил Саймон. — Это то же самое: набор упорядоченных элементов. В разных языках программирования он называется по-разному, но по сути это одно и то же. Главное, что можно получать элементы по номеру в списке, например элемент номер 3 или элемент номер 5. Эти номера называются индексами списка. — А можно опять использовать эту функцию? — спросил Эрик. Глава 12. База данных: создаем надежное хранилище 187 — Конечно! — сказал Саймон. — Функции для того и нужны, чтобы их использовать по нескольку раз! Давай скопируем функцию read_menu() в файл app.py. Вставь ее сразу после функции load_orders(). А еще скопируй три вызова этой функции, которые идут сразу после самой функции. Эрик скопировал из файла menu_files.py функцию read_menu() и три строки после нее и вставил все это в файл app.py, расположенный в директории coffeeshop. — Теперь у нас есть списки разделов меню из этих трех файлов: drinks.txt, flavors.txt и toppings.txt. Нужно показать эти списки на странице заказов. Хорошо, что файл order.html — это шаблон, а не простой HTML-файл. В шаблоне можно использовать переменные и передавать значения, которые мы хотим вывести на страницу. Помнишь, мы делали это в шаблоне print.html, когда передавали словарь new_order? Сейчас покажу. Вот этот код на Python в файле app.py: return render_template( "print.html", new_order=new_order ) — А вот часть шаблона order.html, которая использует словарь new_order, — продолжил Саймон. . . . <h2>Ваш заказ:</h2> <p>Напиток: <strong>{{ new_order["drink"] }}</strong></p> <p>Вкус: <strong>{{ new_order["flavor"] }}</strong></p> <p>Топпинг: <strong>{{ new_order["topping"] }}</strong></p> . . . — Теперь нужно передать списки разделов меню шаблону order.html. На основе этих списков мы построим само меню. Найди вызов функции render_template(), который ссылается на шаблон order.html, и передай эти три списка, так же как ты передал словарь new_order на пару строчек выше. Эрик нашел нужную строчку (это была последняя строчка программы) и изменил ее так: return render_template("order.html", drinks=drinks, flavors=flavors, toppings =toppings) — Хорошо, — сказал Саймон. — Мы передали список шаблону, и теперь нужно изменить сам шаблон. Эмили, ты удивишься. Он будет не похож на простой HTML. Мы добавим в шаблон цикл. — Как в Python? — спросил Эрик. 188 Поговорим на Python — Да, цикл в шаблоне очень похож на цикл Python, да и работают они почти одинаково, — сказал Саймон. — Вместо того чтобы выводить пункты меню на страницу как строки в теге select, мы возьмем названия пунктов из списка. Наши циклы будут похожи на for d in drinks — почти так же, как ты делал в функции menu(). Наверное, потому что шаблонизатор и Flask придумал один и тот же человек — Армин Ронахер (Armin Ronacher). Так что, как видите, шаблонизатор очень похож на Python. Кстати, шаблонизатор еще называют Jinja 1— да, почти как ниндзя. Давайте помогу вам с шаблоном order.html. У меня есть несколько примеров, которые здесь пригодятся. — Ты что, сам все не знаешь? — Эрик подмигнул брату. — Конечно нет, — совершенно серьезно ответил Саймон. — Я учусь. Сам. Когда мне нужно что-то узнать, я ищу примеры и документацию в интернете. К счастью, по Jinja и Flask хорошая и очень полезная документация. А еще много примеров и уроков, которыми делятся другие разработчики. Саймон изменил файл templates/order.html: Листинг 12.2. o rder.html: отображение разделов меню в шаблоне с помощью списков . . . <form action="/order" method="post"> <input type="text" name="name" placeholder="ВВЕДИТЕ ИМЯ"> <select name="drink"> <option value="">- Выберите напиток -</option> Подсказка для пользователя {% for d in drinks -%} Цикл for, который <option value="{{ d }}">{{ d | capitalize }}</option> добавляет в меню {% endfor %} пункты из списка </select> <select name="flavor"> <option value="">- Выберите вкус -</option> {% for f in flavors -%} <option value="{{ f }}">{{ f | capitalize }}</option> {% endfor %} </select> <select name="topping"> <option value="">- Выберите топпинг -</option> {% for t in toppings -%} <option value="{{ t }}">{{ t | capitalize }}</option> {% endfor %} </select> <input type="submit" value="Подтвердить"> </form> . . . 1 Произносится как «дзиндзя» или «джинджя». — Примеч. ред. Глава 12. База данных: создаем надежное хранилище 189 ТВОЙ ХОД! Измени шаблон order.html Измени свой шаблон templates/order.html аналогичным образом. Будь осторожен при копировании — обрати внимание, что в шаблоне использованы двойные фигурные скобки. Не потеряй дефисы и символы процентов. Эрик и Эмили проверили страницу, она работала так же, как и раньше. — Эмили, попробуй добавить что-нибудь в один из файлов меню, — предложил Саймон, — посмотрим, обновится ли страница. Эмили открыла файл toppings.txt и добавила в конец файла ванильную пудру. Она сохранила файл и перезапустила приложение. Конечно, в меню появился новый топпинг. ВВЕДИТЕ ИМЯ - Выберите напиток- - Выберите вкус - - Выберите топпинг - ПОДТВЕРДИТЬ Шоколад Корица Карамель Ванильная пудра ТВОЙ ХОД! Проверь свою страницу Проверь свою страницу с новым шаблоном. Она выглядит так же, как раньше? С помощью текстового редактора добавь в файл пункт меню. Проверь, появился ли новый пункт на странице. — Саймон, ты добавил в шаблон слово capitalize, — сказала Эмили, — и теперь Ванильная пудра начинается с большой буквы «В». Выглядит круто! — Да, — сказал Саймон, — с помощью шаблонов можно сделать много интересного. Это только один пример. Позже сделаем еще что-нибудь, если захочешь. Но сейчас разберемся с базами данных! Знаете, зачем нужны базы данных? Мы ведь уже храним заказы в файле JSON, и вроде это неплохо работает. Но представьте, что мы, например, хотим из списка заказов выбрать все заказы со вкусом ванили. Или все заказы от клиентов с именем Алекс. Или посчитать, в каком месяце у нас ушло больше корицы — в этом или 190 Поговорим на Python в предыдущем. По идее, мы можем и сами написать такие функции. Но оказалось, что это настолько частые вопросы, что люди уже давно начали писать специальные программы, которые не только хранят данные, например заказы и имена клиентов, но и могут давать ответы. Такие вопросы в мире баз данных обычно называют запросами. Много лет назад несколько умных людей создали язык, который называется языком структурированных запросов, или кратко SQL (Structured Query Language). Обычно это название произносят как сиквел (sequel). Но сегодня мы не будем учить SQL, я рассказываю вам о нем только потому, что мы будем использовать базу данных SQLite — можете называть ее сиквелайт. — Теперь для хранения и загрузки заказов вместо файла JSON мы будем использовать базу данных? — спросил Эрик. — Да, — ответил Саймон, — но теперь нам больше не придется загружать заказы. Помнишь, мы загружали заказы в список, чтобы добавлять в него новые заказы, и сохраняли этот список в файл JSON в конце дня? Теперь база данных будет хранить заказы в своем собственном файле. База данных умеет добавлять новые записи в список заказов, так что нам не придется об этом думать. — Мне нравится, — сказал Эрик. — Что для этого нужно сделать? Саймон начал рисовать схему. — Мы будем использовать файл базы данных, который называется orders.db. Функция load_orders() нам больше не нужна, — Саймон перечеркнул функцию load_orders() красным маркером. — И нам не нужен список заказов — база данных все сохранит. Также мы перепишем функцию save_orders(). Теперь нам не нужно сохранять все заказы в файл каждый раз, когда мы получаем новый заказ. Мы просто сохраним новый заказ в базу данных, где уже сохранены все предыдущие заказы. Предлагаю переименовать функцию save_orders() в save_order(). На схеме вы увидите разницу между старым способом хранения данных с помощью файла JSON и новым способом — с помощью базы данных. Хранение вбазе данных Хранение вфайле load_orders() _orde orde rd orders.json orders.db orders[] rders deerrs[ get orders save_orders() get orders save_order() cоединение внесение данных сохранение данных Глава 12. База данных: создаем надежное хранилище 191 — Каждый раз, сохраняя новый заказ, мы устанавливаем соединение (connection) с базой данных. Затем мы даем базе данных команду вставить (insert) данные в базу. После этого мы вызываем функцию commit(), чтобы заказ окончательно сохранился в базу данных. — Похоже на то, что мы делали с файлом, — сказал Эрик. — Да, очень похоже, — согласился Саймон. — Разница в том, что сейчас мы общаемся не с файлом, а с базой данных. Пока у нас очень простая программа, но что, если в нашей кофейне заказы принимает не один бариста, а несколько? Если несколько человек одновременно пытаются записать данные в один файл, как правило, возникает проблема. Но база данных берет ее решение на себя. И если вы научитесь работать с SQLite с помощью языка SQL, то сможете применять этот язык и в других базах данных. Все они отлично понимают SQL. Все, хватит теории, давайте писать код. Прежде всего нужно создать в базе данных таблицу. В нашем случае таблица будет достаточно простой, в ней будут храниться имя клиента и наши привычные варианты выбора: напиток, вкус и топпинг. Саймон начал редактировать файл app.py, объясняя на ходу: — Добавляю строку sqlite3 в начало программы. Удаляю вызов функции orders = load_orders() и саму функцию load_orders(). Прямо перед строкой app = Flask() добавляю три строки, которые установят соединение с базой данных и создадут таблицу orders. . . . import sqlite3 Импортирует пакет sqlite3 . . . con = sqlite3.connect("orders.db") Устанавливает соединение с базой данных cur = con.cursor() cur.execute( "CREATE TABLE IF NOT EXISTS orders(name, drink, flavor, topping);") app = Flask(name) . . . Создает таблицу заказов ТВОЙ ХОД! Подключись к базе данных и создай таблицу Добавь строки Саймона в свой файл app.py. Не забудь добавить import sqlite3 в самое начало файла. — Что такое cur? — спросила Эмили. 192 Поговорим на Python — В базах данных это называется курсором (cursor). Это как курсор мышки — он может указать в определенное место базы данных. Пока мы не будем углубляться в детали — этот код я взял из официальной документации Python, которая говорит, что курсор нужен для выполнения команд SQL. Посмотрите на следующую строчку с cur.execute(). Это наша первая SQL-инструкция, которая создает таблицу orders, со столбцами name, drink, flavor и topping. Таблицы баз данных — это обычные таблицы со строками и столбцами. Каждый новый заказ — это новая строка в таблице. У каждой строки есть четыре столбца, названия которых я уже перечислил. Легко, правда? Саймон нарисовал простую таблицу. заказ 1 name drink flavor topping заказ 2 заказ 3 ... — Теперь давайте перепишем функцию save_orders(). Сначала переименуем ее в save_order(), потому что мы будем сохранять заказы по одному. Потом повторим две строки, которые я уже написал, чтобы установить соединение с базой данных и создать курсор. После этого можно выполнять команды SQL. В этом случае мы будем использовать команду INSERT и передадим ей значения из словаря order[]. Наконец вызовем функцию commit(), которая сохранит новый заказ в базу данных. Листинг 12.3. app.py: добавление заказов в таблицу . . . from flask import Flask, render_template, request def save_order(order): con = sqlite3.connect("orders.db") cur = con.cursor() cur.execute( "INSERT INTO orders(name,drink,flavor,topping) VALUES(?,?,?,?);", (order["name"], order["drink"], order["flavor"], order["topping"]), ) con.commit() return . . . ТВОЙ ХОД! Создай функцию save_order() Скопируй функцию save_order() в свою программу. Обрати внимание на кавычки, запятые, квадратные и круглые скобки. Глава 12. База данных: создаем надежное хранилище 193 — Знаю, выглядит сложно, но если вы просто прочитаете код как текст, то довольно легко его поймете. Вы просите базу данных вставить (INSERT) некоторые значения (VALUES) в таблицу orders. Потом перечисляете эти значения в круглых скобках. Вам уже знакомы словари, и вы знаете, как получить значение по ключу. — Почему некоторые слова ты написал большими буквами? — спросил Эрик. — Это негласное правило языка SQL. Команды и ключевые слова SQL пишутся большими буквами, например INSERT и INTO. Имена таблиц пишутся маленькими буквами, например таблица orders. Я пробовал писать команды маленькими буквами, это тоже работает. Так что большие буквы нужны только для лучшей читаемости, чтобы отделить команды SQL от переменных. — Давайте уже запустим! — сказала Эмили. — Подожди! — сказал Саймон. — Я чуть не забыл — мы же хотели удалить вызов load_orders() из функции order(). И функцию сохранения надо переименовать. Саймон отредактировал функцию order(), и она стала выглядеть так: Листинг 12.4. app.py: удаление функции load_orders() @app.route("/order", methods=("GET", "POST")) def order(): if request.method == "POST": new_order = {"name": request.form["name"], "drink": request.form["drink"], "flavor": request.form["flavor"], "topping": request.form["topping"] } save_order(new_order) return render_template( "print.html", new_order=new_order ) return render_template("order.html", drinks=drinks, flavors=flavors, toppings=toppings) ТВОЙ ХОД! Отредактируй функцию order() Отредактируй функцию order() так же, как Саймон. Не забудь переименовать функцию save_orders() (во множественном числе) в save_order() (в единственном числе). Саймон сохранил файл и сказал: — Теперь готово. Эмили, запускай! Но на этот раз я сам заполню заказ. 194 Поговорим на Python Эмили нажала Run и перезагрузила страницу заказов. Саймон выбрал напиток, вкус и топпинг. Привет, Саймон! Ваш заказ: Напиток: кофе Вкус: карамель Топпинг: шоколад НОВЫЙ ЗАКАЗ ТВОЙ ХОД! Протестируй программу Протестируй свою программу. Теперь программа работает не с файлом JSON, а с базой данных, но интерфейс программы должен остаться прежним. — Работает! — сказал Саймон и выдохнул с облегчением. Он боялся ошибиться и не хотел этого показать. — Мы можем проверить, сохранился ли заказ в базу данных, как мы делали с файлом JSON? — спросил Эрик. — Хороший вопрос! — ответил Саймон. — Нет, файл базы данных отличается от текстового файла, и мы не можем его открыть. Но можно сделать лучше — написать новую функцию, которая выведет все заказы на веб-страницу. — Отлично! — сказал Эрик. — Например, если менеджер кофейни захочет узнать, сколько карамели мы сегодня продали. — А еще можно увидеть, что обычно заказывают клиенты, — добавила Эмили. Глава 12. База данных: создаем надежное хранилище 195 — Отличная идея, — сказал Саймон. — Готовы еще немного поработать? Нам нужно сделать три вещи. Во-первых, вспомните: чтобы открыть страницу заказов, мы добавляем к адресу сайта /order. Во Flask это называется маршрутом. Нам нужно написать функцию для нового маршрута, который мы будем использовать, чтобы вывести список заказов. Назовем этот маршрут /list. Функцию для этого маршрута мы тоже назовем list(). Она будет очень похожа на функцию order(). Чтобы показать список заказов на странице, вызовем функцию render_template() в конце функции list(). Во-вторых, мы напишем функцию, которая обратится к базе данных и вернет функции list() список заказов. Давайте назовем эту функцию get_orders(). И в-третьих, нужно создать шаблон, который покажет заказы на странице. Эмили, умеешь создавать HTML-таблицы? — Таблицы? Да, умею, — сказала Эмили. — Сначала нужно создать таблицу с помощью тега <table>, потом добавить в нее строки и столбцы с помощью тегов <tr> и <td>. — Отлично! — сказал Саймон. — Значит, ты напишешь шаблон для функции list(). Эрик, а ты напишешь функции, хорошо? — Конечно, — сказал Эрик. — Откуда мне начинать? — Сразу после функции save_order(). Создай функцию с именем get_orders(). Начни с той же первой строки, как и в функции save_order(). После нее добавь строку: con.row_factory = sqlite3.Row. Эта инструкция позволяет использовать имена столбцов при работе с результатом запроса. Пока не задумывайся об этом — поймешь позже. Затем создай курсор с помощью строчки, написанной выше: cur = con.cursor(). Покажи код. Эрик показал, что он написал: def get_orders(): con = sqlite3.connect("orders.db") con.row_factory = sqlite3.Row cur = con.cursor() — Хорошо, — сказал Саймон. — Теперь важная часть: мы должны получить все заказы из таблицы orders. Используем самую популярную инструкцию языка SQL, которая попросит базу данных вернуть нам все строки и столбцы таблицы. Она выглядит так: SELECT * FROM orders;. Звездочка (*) означает «все». Мы должны вызывать эту инструкцию внутри функции cur.execute() так же, как мы делали в функции save_order(). Выполнив команду SELECT, мы получим (fetch) данные из базы и сохраним их в списке rows с помощью функции cur. fetchall(). Не волнуйтесь, вот страница в документации, из которой я это узнал. Используйте ее в качестве примера. Саймон показал вкладку в браузере: https://docs.python.org/3.8/library/sqlite3.html. 196 Поговорим на Python Эрик начал печатать. Через несколько минут он показал Саймону, что у него получилось: Листинг 12.5. app.py: функция get_orders() def get_orders(): con = sqlite3.connect("orders.db") con.row_factory = sqlite3.Row cur = con.cursor() cur.execute("SELECT * FROM orders;") rows = cur.fetchall() return rows ТВОЙ ХОД! Напиши функцию get_orders() Напиши функцию get_orders(), как у Эрика. — Выглядит неплохо, — сказал Саймон. — А вот работает ли, поймем, только когда протестируем. Теперь нужно добавить функцию, которая обработает маршрут /list. Ее мы добавим после функции order(), которая обрабатывает маршрут /order. Давайте помогу. Саймон начал писать функцию в конце файла. — Сначала с помощью @app.route() я задам маршрут, который мы хотим обработать. Затем я объявлю функцию обычным способом. Сама функция довольно простая: я вызываю get_orders() и передаю результаты в render_template(). Как вы помните, функция render_template() показывает данные в браузере с помощью шаблона, который мы ей передали. Листинг 12.6. app.py: функция list() @app.route("/list", methods=["GET"]) def list(): orders = get_orders() return render_template("list.html", orders=orders) ТВОЙ ХОД! Напиши функцию list() Напиши функцию list(), как у Эрика. — Почему мы не получаем все заказы из базы данных прямо в функции render_ template()? — спросил Эрик. — Зачем ты попросил меня написать отдельную функцию? Глава 12. База данных: создаем надежное хранилище 197 — Хороший вопрос, — сказал Саймон. — Я хочу отделить функции, которые взаимодействуют с пользователем, от функций, которые обрабатывают данные. Иногда это называют фронтенд (frontend) и бэкенд (backend). Если мы захотим поменять что-то в обработке данных, мы будем менять функцию get_orders(). Например, представьте, что нам нужно выбрать заказы с карамельным вкусом. Мы должны передать этот запрос функции, и она вернет нам список заказов, как раньше. Наша функция list() покажет на странице все, что она получит от функции get_orders(). Функции list() все равно, будет это полный список заказов или только часть этого списка, — она просто показывает заказы. С другой стороны, функции get_orders() все равно, как заказы будут выведены на страницу. Она просто возвращает список заказов. В программировании этот принцип называется разделением ответственности (separation of concerns). Я дам вам ссылку на Википедию, потом почитаете. ПРИМЕЧАНИЕ Статью о разделении ответственности можно прочитать здесь: https:// ru.wikipedia.org/wiki/Разделение_ответственности. — Остался только шаблон для списка, — сказал Саймон. — Эмили, давай создадим новый файл в редакторе. Удали из него пример веб-приложения, который создал редактор Mu. Сохрани этот файл с именем list.html в папке templates. Не забудь изменить тип файла с «Python (*.py)» на «Other (*.*)». 198 Поговорим на Python — Начни со строк, которые мы использовали в шаблоне print.html. Первые две строки и последняя строка останутся такими же, как были. Эмили скопировала три строки: {% extends "base.html" %} {% block content %} {% endblock %} — Теперь создай таблицу, которая состоит из одной пустой строки. У нас будет по четыре ячейки в каждой строке и четыре заголовка столбцов. Это будет первая строка. Знаешь, как сделать заголовки столбцов? — спросил Саймон. — Да, с помощью тега <th>, — ответила Эмили и начала работать над HTMLтаблицей в файле templates/list.html. — Теперь добавь заголовки Имя, Напиток, Вкус и Топпинг, — сказал Саймон. Листинг 12.7. Шаблон templates/list.html с заголовками {% extends "base.html" %} {% block content %} <table> <tr> <th>Имя</th> <th>Напиток</th> <th>Вкус</th> <th>Топпинг</th> </tr> <tr> <td></td> <td></td> <td></td> <td></td> </tr> </table> {% endblock %} — Отлично, — сказал Саймон. — Теперь я добавлю цикл. Это будет тот же цикл for, который мы использовали для вывода меню на экран. Помните, мы передавали список orders шаблону? Цикл пройдет по этому списку вот так: for o in orders. В каждую ячейку между тегами <td> и </td> мы вставим значение для каждого ключа: name, drink, flavor и topping. Саймон изменил шаблон: Листинг 12.8. Шаблон templates/list.html с циклом {% extends "base.html" %} {% block content %} Глава 12. База данных: создаем надежное хранилище 199 <table> <tr> <th>Имя</th> <th>Напиток</th> <th>Вкус</th> <th>Топпинг</th> </tr> {% for o in orders -%} <tr> <td>{{ o['name'] }}</td> <td>{{ o['drink'] }}</td> <td>{{ o['flavor'] }}</td> <td>{{ o['topping'] }}</td> </tr> {% endfor %} </table> {% endblock %} ТВОЙ ХОД! Создай шаблон для списка Создай файл templates/list.html, как это сделала Эмили. Обрати внимание на кавычки, квадратные и двойные фигурные скобки. — Пора проверить функцию отображения списка, — сказал Саймон, сохранив файл. — Запустите приложение и введите два-три заказа. Эрик запустил приложение на вкладке с адресом 127.0.0.1:5000/order и ввел три заказа: для Саймона, Эмили и себя. — Теперь замени /order в адресе на /list, — сказал Саймон. Эрик изменил адрес, и на странице появился результат. Имя Напиток Вкус Топпинг Саймон кофе карамель шоколад Эрик шоколад ваниль корица Эмили кофе без кофеина малина шоколад 200 Поговорим на Python — Отлично смотрится, — сказала Эмили. — Менеджеру очень пригодится! — Да, мне тоже нравится, — сказал Эрик. — Думаю, стоит добавить номера строк, чтобы понять, сколько человек мы обслужили. — Хорошая мысль! — сказал Саймон. — А еще можно добавить дату и время, чтобы понять, сколько клиентов ты обслуживаешь в день. Все это реально — нужно только доработать базу данных, чтобы она сохраняла эти данные. Но это потом. На завтра я приготовил кое-что другое. Эмили, ты знаешь что-нибудь о CSS? — Мы немного проходили CSS на уроках по HTML, — ответила Эмили. — Что такое CSS? — спросил Эрик. — Это способ изменить внешний вид HTML-страницы в браузере, — пояснила Эмили. — Вроде это расшифровывается как каскадные таблицы стилей (Cascading Style Sheets). — Абсолютно верно, — сказал Саймон. — Давайте завтра сделаем наш сайт красивее с помощью CSS. Но прежде чем вы уйдете, вспомним, что мы сегодня сделали. — Мы создали отдельную директорию для проекта и перенесли в нее все файлы, — начала Эмили. — Мы переименовали основное приложение в app.py. — Мы прочитали меню из текстовых файлов так же, как в предыдущей программе, — добавил Эрик. — Теперь менеджеру кофейни не нужно менять программу, он легко отредактирует меню через эти файлы. — А потом мы перевели программу на базу данных, — сказала Эмили. — Это было непросто. — Согласен, — сказал Саймон. — Но оно того стоит. С базой данных наша программа стала более профессиональной. Пришло время сделать ее красивой. Поработаем над этим завтра. Что мы узнали сегодня Папки и директории. В разных операционных системах для одних и тех же понятий используются разные термины. Папки, или директории, — это группы файлов, которые относятся к одному проекту, или группы файлов определенного типа. Шаблоны Jinja. Программа Flask создает HTML-страницы с помощью шаблонов Jinja. Ты можешь передать в эти шаблоны переменные, списки и словари и использовать все это в Python по своему усмотрению. В шаблонах также можно использовать циклы for, похожие на циклы в Python. Глава 12. База данных: создаем надежное хранилище 201 База данных — это программа, которая может хранить данные и выполнять с ними разные операции. Базы данных могут искать записи по заданным критериям или сводить данные (например, подсчитывать, сколько раз в этом месяце заказывали ванильный вкус). SQL — расшифровывается как «язык структурированных запросов» (Structured Query Language) и используется для работы с базами данных. Фронтенд и бэкенд. Фронтенд — это часть приложения, которая взаимодействует с пользователями: отображает страницы, принимает ввод с клавиатуры и выбор из меню. Бэкенд работает с данными: хранит данные и получает их из файлов или баз данных. Крупные приложения разделяют на фронтенд и бэкенд с целью разделения ответственности. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch12. 13 Стили: наводим красоту В ЭТОЙ ГЛАВЕ 33 Эрик и Эмили экспериментируют со стилями, шрифтами и цветом веб-страницы 33 Эрик и Эмили обновляют внешний вид главной страницы 33 Эрик и Эмили добавляют в приложение картинки — Давайте украсим наше приложение! — сказал Саймон при следующей встрече. — Хочешь сказать, применим стили к HTML-файлам? — спросила Эмили. — Мы работали со стилями на уроках по HTML. С их помощью можно изменить цвет и размер шрифта, добавить фон на страницу. Это круто! — Да, давайте поговорим о стилях, — поддержал Саймон. — Эмили, можешь немного пояснить, что это? — Стили называются CSS. Это расшифровывается… — Эмили на секунду задумалась. — Это расшифровывается как каскадные таблицы стилей (Cascading Style Sheets), правильно? — она посмотрела на Саймона. — Но я, честно, не помню почему. — Ты права! — сказал Саймон. — Это каскадные таблицы стилей. Они называются таблицами стилей, потому что это простые текстовые файлы, которые Глава 13. Стили: наводим красоту 203 описывают стили. Стили определяют внешний вид элементов HTML. Например, для тега <h2> (заголовка второго уровня) можно задать размер шрифта, равный 36 пикселям, и красный цвет. Или вообще другой шрифт. Один из принципов веб-дизайна — это отделение содержимого страницы от стилей. Конечно, можно задать стили прямо в HTML-файле. Но это не очень удобно. Если потребуется изменить размер или цвет шрифта, придется пройти по всем HTML-файлам и изменить все заголовки <h2>. Поэтому стили задают в отдельном файле, где прописывают, что у всех заголовков <h2> должен быть такой-то цвет и размер шрифта. Таким образом, если потребуется изменить стиль заголовков <h2>, то достаточно поменять код только в одном месте. Правда, удобно? — Я понял, — сказал Эрик. — Это как функции в Python. Ты пишешь их один раз, а потом используешь в разных местах. — Точно! — ответил Саймон. — Мне нравится эта аналогия. Значит, мы разобрались, откуда берется название «таблицы стилей», но откуда взялось слово «каскадные»? В одном приложении можно сочетать несколько таблиц стилей. На практике разработчики часто используют таблицы стилей, уже созданные кем-то другим, только добавляют туда новые элементы или меняют элементы, которые должны отличаться от остальных. Это еще одно сходство с функциями. В нашем проекте мы будем использовать таблицы стилей, встроенные в редактор Mu; мы немного их настроим, чтобы они лучше подходили к нашей кофейне. Посмотрите на страницу заказов и подумайте, что бы вы хотели изменить. ВВЕДИТЕ ИМЯ - Выберите напиток- - Выберите вкус - - Выберите топпинг - ПОДТВЕРДИТЬ Эрик посмотрел на экран и сказал: — Нам нужен заголовок. Как в текстовом приложении: «Добро пожаловать в кофейню Эрика!» — Подожди! — сказала Эмили. — Теперь это не только твоя кофейня. Думаю, надо назвать ее «Кофейня Эрика и Эмили». Или для краткости «Кофейня Э&Э». Согласен? — Я тоже думаю, что этот проект уже стал вашим общим, а «Кофейня Э&Э» — отличное название, — поддержал Саймон. — Да, давайте назовем ее «Кофейня Э&Э», — согласился Эрик. 204 Поговорим на Python — Я добавлю заголовок, — сказала Эмили и открыла файл order.html в папке templates. Она добавила заголовок, и начало файла стало выглядеть так: {% extends "base.html" %} {% block content %} <h1>Welcome to E&E Coffee Shop!</h1> <form action="/order" method="post"> <input type="text" name="name" placeholder="ВВЕДИТЕ ИМЯ"> . . . Эмили сохранила файл, переключилась на вкладку app.py в редакторе Mu и нажала Run. Она открыла браузер и заново ввела URL: http://127.0.0.1:5000/ order1. ВВЕДИТЕ ИМЯ - Выберите напиток- - Выберите вкус - - Выберите топпинг - ПОДТВЕРДИТЬ — Выглядит неплохо! — сказал Эрик. — Саймон, ты сказал, что мы можем поменять шрифт заголовка, — заметила Эмили. — Этот шрифт какой-то… скучный. Наверное, нам нужно что-то поинтереснее. — Согласен, — сказал Саймон. — К счастью, для веб-страниц есть много бесплатных шрифтов. За некоторые шрифты нужно платить, но для нашего проекта подойдут и бесплатные. Можем посмотреть, например, на Google Fonts — это хороший ресурс бесплатных шрифтов. Саймон открыл в браузере новую вкладку и набрал адрес: fonts.google.com. — Как видите, здесь более 1400 шрифтов. Есть еще шрифты для разных языков. Нажмите кнопку Filter («Фильтр»). Видите поле, в котором написано Type something (Напечатайте что-нибудь)? Введите заголовок страницы, и увидите, как он смотрится с разными шрифтами. 1 Примеры со шрифтами сохранены как у автора, на английском. Ты можешь самостоятельно поэкспериментировать с кириллическими шрифтами (заменив заголовок на «Добро пожаловать в кофейню Э&Э»). — Примеч. ред. Глава 13. Стили: наводим красоту 205 — А что такое Decorative stroke (Тип засечек) и Classification (Классификация)? Что это значит? — спросила Эмили. — Это разные группы шрифтов. Serif (с засечками), Sans Serif (без засечек, или руб­леный), Slab Serif (брусковый, с толстыми засечками). И есть классификация по тому, где применяются шрифты: Display (экранные), Handwriting (рукописные) и Monospace (моноширинные). Моноширинный шрифт обычно используют для программного кода. Если ты хочешь напечатать поздравительную открытку для друга, хорошо подойдет рукописный шрифт. Экранные шрифты применяются только для отображения на экране компьютера, их не используют для печати на бумаге. Serif и Sans Serif звучат странно, но вы их хорошо знаете. Serif — это шрифт, у которого на концах букв есть небольшие засечки. Конечно, на ум сразу приходит Times New Roman, во многих программах он используется как шрифт по умолчанию, а еще он часто встречается в документах. Попробуйте выбрать Serif, и увидите, о чем я говорю. — Мне нравится шрифт Playfair Display! — сказала Эмили. — Хорошо! — сказал Саймон. — Запиши это название, и позже мы к нему вернемся. Я только хотел показать вам, как выглядят шрифты из группы Sans Serif. «Sans» означает «без»; в нашем случае это значит, что у шрифтов из этой группы нет засечек, про которые я говорил. Скорее всего, из этой категории вам встречался шрифт Arial. Посмотрите сюда, — Саймон изменил тип шрифта на Sans Serif. — Такие шрифты применяются в меню и других элементах интерфейса приложений. Просто присмотритесь, и вы начнете определять и различать эти 206 Поговорим на Python две группы шрифтов в интернете и в приложениях. Если вы хотите побольше узнать о шрифтах, перейдите на вкладку Knowledge (Дополнительные сведения). Поверьте, шрифты — это очень интересно! ТВОЙ ХОД! Узнай больше о Google Fonts Открой страницу fonts.google.com и рассмотри ее. Поищи интересные шрифты в разных категориях. Обрати внимание, что можно отсортировать результаты поиска по трендам, популярности, новизне и так далее. Перейди на свои любимые сайты и попробуй определить, используют ли они serif, sans serif или моноширинный шрифт. — Эти шрифты тоже хорошо смотрятся, но Playfair мне нравится больше. Думаю, он хорошо подойдет для заголовка страницы. Эрик, тебе нравится? — спросила Эмили. — Да, мне тоже нравится этот шрифт. Он не скучный, — ответил Эрик. — И мне нравится, как выглядит знак &. Да, этот шрифт годится для заголовка. Саймон вернулся к категории Serif и щелкнул на шрифте Playfair Display. Открылась страница шрифта. — Здесь можно узнать, как использовать этот шрифт на веб-странице или в приложении, — пояснил Саймон. Он прокрутил страницу вниз, пока заголовок, Глава 13. Стили: наводим красоту 207 который они ввели, не расположился прямо под заголовком Styles (Стили). — Здесь можно выбрать начертание шрифта (typeface): курсивом или жирным, и можно выбрать его толщину («жирность»). — А теперь нажмите кнопку Get font (Получить шрифт), а затем кнопку Get embed code (Получить код для встраивания), откроется инструкция для встраи­ вания шрифта. 208 Поговорим на Python — Посмотрите на блок справа, под словами Use On the Web (Как использовать в Web). Google довольно хорошо объясняет, как использовать этот шрифт. Мы просто должны скопировать эти строки в свой файл. Из двух предложенных фрагментов кода рекомендуется использовать первый, с тегом <link>. Второй фрагмент кода, который называется CSS class for a variable style («CSS для изменяющихся стилей»), показывает, как можно использовать шрифт в CSS-файлах. Все это может показаться сложным, но не волнуйтесь, я вам помогу. Хорошо, что во Flask есть шаблоны. Посмотрите на файл order.html: он начинается с {% extends "base.html" %}. Файл print.html начинается с такой же строки. Это значит, что мы повторно используем файл base.html в наших шаб­ лонах. То есть нам не придется изменять каждый файл в проекте, достаточно изменить только файл base.html. Да, Эрик, опять как в функциях, — улыбнулся Саймон. — Эмили, открой, пожалуйста, файл base.html, он находится в папке templates. Эмили открыла файл base.html, и Саймон сказал: — Посмотрите на инструкцию на странице Google Fonts. Скопируйте из серого поля первые три строки, которые начинаются с тега <link>, и вставьте их в файл base.html сразу после закрывающего тега </head>. Эмили скопировала нужные строки в файл base.html, и он стал выглядеть так (показываем только измененную часть): Листинг 13.1. templates/base.html: добавление шрифта . . . <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Playfair+ Display&display=swap" rel="stylesheet"> </head> <body> . . . ТВОЙ ХОД! Добавь ссылку на шрифт в файл base.html Открой в редакторе файл templates/base.html и внеси в него изменения, которые сделала Эмили. — Хорошо. Это был первый шаг, — сказал Саймон. — Второй шаг — создать свою таблицу стилей. Нажми New в редакторе. Редактор создаст пример Глава 13. Стили: наводим красоту 209 веб-приложения, но сейчас он нам не нужен. Удали весь текст и нажми Save. Нужно сохранить файл в директории, в которой хранятся остальные CSS-файлы, то есть в папке static/css. Назови файл coffeeshop.css. Не забудь изменить тип файла с «Python (*.py)» на «Other (*.*)». Саймон убедился, что рядом с остальными CSS-файлами появился файл coffeeshop.css. — Хорошо, — сказал Саймон. — Теперь мы можем задать шрифт для заголовка h1. Посмотрите на блок CSS Rules to Specify Families («Правила определения семейства шрифтов CSS») на странице шрифтов в браузере. Выделите текст в этом блоке и скопируйте его. Теперь напишите h1 { и вставьте скопированный текст после фигурной скобки. Затем закройте фигурные скобки и сохраните файл. Это было легко. Эмили отредактировала файл coffeeshop.css, и он стал выглядеть так: Листинг 13.2. static/css/coffeeshop.css: добавление шрифта в CSS h1 {font-family: 'Playfair Display', serif;} 210 Поговорим на Python ТВОЙ ХОД! Создай файл coffeeshop.css Создай файл static/css/coffeeshop.css в редакторе и добавь строку со стилем для заголовка h1. — Отлично! — похвалил Саймон. — Теперь у нас есть свой CSS-файл. Осталось только сообщить Flask, что мы будем использовать этот файл. Это третий шаг. Эмили, у тебя в редакторе еще открыт файл base.html? Найди две строки со словом stylesheet. Ты узнаешь имена таблиц стилей, которые там указаны. — Да, вижу, — сказала Эмили. — Они называются normalize.css и skeleton.css, я видела эти имена, когда сохраняла таблицу стилей. — Хорошо, теперь скопируй строку с файлом skeleton.css и вставь ее сразу после нее самой. У тебя должны получиться две абсолютно одинаковые строки. Теперь во второй строке измени skeleton на coffeeshop. Эмили сделала все, как сказал Саймон, и файл base.html принял следующий вид: Листинг 13.3. templates/base.html: добавление CSS-файла coffeeshop . . . <!-- CSS –––––––––––––––––––––––––––––––––––––––––––––––––– --> <link rel="stylesheet" href="/static/css/normalize.css"> <link rel="stylesheet" href="/static/css/skeleton.css"> <link rel="stylesheet" href="/static/css/coffeeshop.css"> . . . ТВОЙ ХОД! Добавь таблицу стилей coffeeshop.css в файл base.html Открой файл templates/base.html в редакторе и внеси в него изменения, которые сделала Эмили. — Теперь вы видите, почему таблицы стилей называются каскадными. У нас есть три таблицы стилей. Первая, normalize.css, нужна, чтобы наш сайт хорошо смотрелся во всех браузерах на разных устройствах. — Даже на моем iPad? — спросил Эрик. — Да, конечно, — ответил Саймон. — Позже мы это проверим. Следующий CSSфайл, skeleton.css, добавил редактор Mu. В этом файле собраны украшающие стили для всех элементов HTML. Нам не нужно создавать стили для каждого элемента — элементы уже неплохо смотрятся. Но если мы захотим что-то изменить, например семейство шрифтов для заголовков, нам будет достаточно Глава 13. Стили: наводим красоту 211 добавить стили только в CSS-файл. Поэтому таблицы стилей и называются каскадными. ПРИМЕЧАНИЕ Вы можете узнать больше о файле normalize.css по ссылке https:// nicolasgallagher.com/about-normalize-css/. Файл skeleton.css описан здесь: http:// getskeleton.com/. — Но хватит теории, посмотрим на наш сайт! — сказал Саймон. Эрик и Эмили проверили, что они сохранили все файлы. После этого они переключились на вкладку app.py и нажали кнопку Run. Они открыли вкладку браузера с уже знакомым адресом http://127.0.0.1:5000/order и увидели обновленную страницу. ВВЕДИТЕ ИМЯ - Выберите напиток- - Выберите вкус - - Выберите топпинг - ПОДТВЕРДИТЬ — Ого! Мне нравится! — воскликнул Эрик. — Теперь ты знаешь, как изменить шрифт на странице, — сказал Саймон. — Но почему вкладка называется «Hello from Mu!»? — заметил Эрик. — Может, лучше назвать ее «Привет от Э&Э!» («Hello from E&E»)? — Хорошее замечание! — сказал Саймон. — Это очень легко исправить. Найди эту фразу в файле base.html и измени ее. Она должна быть где-то в начале, внутри тега <title>. Эрик быстро нашел эту фразу, изменил ее и спросил: — Нужно перезапускать приложение на Python? — Нет, просто перезагрузи страницу в браузере и увидишь изменения. Конечно, название вкладки изменилось. 212 Поговорим на Python — Еще можно изменить цвет фона страницы, — сказала Эмили. — Наверное, нам нужен какой-нибудь кофейный цвет, например капучино. — Отличная мысль! — сказал Саймон. — Сначала выберите цвет, который понравится вам обоим. Самый простой способ — загуглить фразу color picker. Саймон проделал это, и на экране появилась программа Color Picker. — Выберите основной цвет с помощью радужной линии, а потом, используя белый круг, выберите понравившийся оттенок. Есть много других сайтов, которые помогут составить красивые сочетания цветов. Это целый мир — так же, как для шрифтов. Его можно долго изучать. Например, — Саймон выбрал один из первых результатов поиска: https://htmlcolorcodes.com/color-picker/, — попробуйте и этот ресурс, тут вы найдете много полезной информации о цвете и о том, как использовать его на веб-страницах. Эрик и Эмили несколько минут поэкспериментировали с Color Picker и, наконец, пришли к согласию по поводу цвета. Глава 13. Стили: наводим красоту 213 ТВОЙ ХОД! Поэкспериментируй с Color Picker Найди Color Picker в Google и поработай с ним. Разберись, как изменить основной цвет и его яркость. Найди свои любимые фоновые цвета. — Хорошо, — сказал Саймон. — Теперь скопируйте эти шесть букв и цифр под надписью HEX, и мы внесем изменения в файл coffeeshop.css. Кто в этот раз за клавиатурой? — Я! — сказал Эрик. — Введи вот это: body {background-color:. Потом вставь то, что ты скопировал из Color Picker. Проверь, что ты не забыл символ «решетка» (#). Затем поставь точку с запятой и закрой фигурные скобки. Эрик добавил описанную Саймоном строку, и CSS-файл стал выглядеть так: Листинг 13.4. static/css/coffeeshop.css: добавление цвета фона в CSS h1 {font-family: 'Playfair Display', serif;} body {background-color: #e8d9b7;} 214 Поговорим на Python ТВОЙ ХОД! Измени цвет фона страницы Измени свой файл static/css/coffeeshop.css, добавь строку body с цветом фона, который ты выбрал. Эрик перезагрузил страницу заказов — она очень сильно изменилась. ВВЕДИТЕ ИМЯ - Выберите напиток- - Выберите вкус - - Выберите топпинг - ПОДТВЕРДИТЬ — Мне все больше нравится, — сказала Эмили. — А можно пункты меню сделать такого же кофейного цвета? — Да, конечно, — ответил Саймон. — Но я предлагаю сделать их не точно такого цвета, а на тон светлее. Поищите что-нибудь подходящее. Это оказалось еще проще. Эрик и Эмили договорились о новом цвете шрифта в меню. Глава 13. Стили: наводим красоту 215 — Хорошо. Давайте помогу добавить этот цвет в таблицу стилей, — сказал Саймон. — Нам нужно применить этот цвет к элементам формы: меню select и полю input[type="text"], в которое клиенты вводят свое имя. Добавим эти элементы в таблицу стилей и зададим им цвет фона, который вы выбрали. Листинг 13.5. static/css/coffeeshop.css: изменение цвета меню input[type="text"], select {background-color: #ede9df;} — А что это за буквы и цифры? Что они означают? — спросил Эрик. — Любой цвет можно задать комбинацией из шести букв и цифр, — пояснила Эмили. — Эта шестерка состоит из трех пар цифр для трех цветов: красного, зеленого и синего. Каждая пара задает количество соответствующего ей цвета в оттенке, который мы выбрали. Это как в рисовании: можно смешать красный и голубой и получить сиреневый. Каждая пара символов — это число в шестнадцатеричной системе, это могут быть цифры или буквы от a до f. Я только не помню, почему шестнадцатеричную систему называют «hex». — Потому что это сокращение от hexadecimal, что значит «шестнадцатеричная», — объяснил Саймон. — Это способ представления числа, похожий на привычный нам десятичный способ, только он короче и понятнее для компьютеров. Наша система счисления называется десятичной, в ней есть цифры от 0 до 9, то есть всего 10 цифр. Одной цифрой мы можем задать число от 0 до 9. Двумя цифрами — число от 0 до 99. Шестнадцатеричная система похожа на десятичную, только у нее не 10 цифр, а 16. Это цифры от 0 до f: мы добавили буквы от a до f и используем их как цифры. То есть цифра a означает 10, цифра b — 11 и так далее. Цифра f означает 15. Одной шестнадцатеричной цифрой мы можем задать число от 0 до 15, двумя цифрами — от 0 до 255. Поэтому шестнадцатеричная запись числа короче: для 255 в десятичной системе нужны три цифры, а в шестнадцатеричной системе это просто ff. Но давайте вернемся к цвету. Как сказала Эмили, шестизначное шестнадцатеричное число говорит нам, сколько красного, зеленого и синего в заданном цвете. Например, цвет вашего меню #ede9df состоит из ed красного, e9 зеленого и df синего. А в десятичной системе это… — Саймон посмотрел на страницу Color Picker и нашел на ней числа: — 237 красного, 233 зеленого и 223 синего. Числа могут возрастать до 255. Если все числа будут равны 255, мы получим белый цвет. — А если все три числа будут нулями, то мы получим черный цвет, — добавила Эмили. — Абсолютно верно, — подтвердил Саймон. — Если вы хотите узнать больше о цвете в веб-приложениях, почитайте Википедию. 216 Поговорим на Python Саймон быстро нашел страницу о цвете и открыл ее в новой вкладке: https:// ru.wikipedia.org/wiki/HTML-цвета. — Хочу заказать что-нибудь на странице с новым дизайном, — сказала Эмили и ввела свой заказ. Она нажала кнопку «Подтвердить» и увидела обновленную страницу. Привет от Э&Э! Привет, Эмили! Ваш заказ: Напиток: кофе Вкус: мята Топпинг: карамель НОВЫЙ ЗАКАЗ Но Эмили была не очень довольна результатом: — Почему во второй строке шрифт не поменялся? — Может быть, потому что мы задали шрифт только для h1? — предположил Эрик. — Да, Эрик, ты прав, — сказал Саймон. — Это очень легко исправить. Открой файл skeleton.css и посмотри, как в нем определены стили. Из таблиц стилей, написанных другими разработчиками, можно многое узнать. Найди раздел Typography. И ты поймешь, как стилизовать остальные уровни заголовков. Эрик открыл файл skeleton.css и прокрутил его вниз до раздела Typography. Вот что он увидел: Глава 13. Стили: наводим красоту 217 Листинг 13.6. static/css/skeleton.css: раздел Typography /* Typography –––––––––––––––––––––––––––––––––––––––––––––––––– */ h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 2rem; font-weight: 300; } h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } — Не буду углубляться, — сказал Саймон, — но вы видите, что здесь через запятую можно перечислить разные уровни заголовков — h1, h2, h3 и так далее — и применить стиль к ним всем. А еще для каждого уровня заголовка можно задать свой размер шрифта и другие параметры. Можете скопировать эту часть в свой файл coffeeshop.css и попробовать изменить разные параметры. Но пока самое главное — добавить уровни h2, h3 и так далее к стилю заголовка h1. — Я понял, — сказал Эрик и отредактировал первую строку файла coffeeshop.css: Листинг 13.7. s tatic/css/coffeeshop.css: изменение шрифта для всех заголовков h1, h2, h3, h4, h5, h6 {font-family: 'Playfair Display', serif;} Эрик нажал Reload (Обновить), и браузер попросил подтвердить повторную отправку формы. — Нажми «Продолжить», — сказал Саймон. — Заказ Эмили сохранится второй раз. Пока мы тестируем, это не страшно, но с реальным приложением лучше быть аккуратным, потому что, нажав «Продолжить», мы создадим новую запись в базе данных, а значит — еще один заказ. 218 Поговорим на Python Теперь шрифт заголовка изменился. Привет от Э&Э! Привет, Эмили! Ваш заказ: Напиток: кофе Вкус: мята Топпинг: карамель НОВЫЙ ЗАКАЗ ТВОЙ ХОД! Измени шрифт остальных заголовков Измени шрифт всех заголовков в файле static/css/coffeeshop.css. — А еще можно добавить изображение, — сказала Эмили. — Мы делали это на уроках по HTML. Нужно просто скачать красивую картинку. — Хорошая мысль, — сказал Саймон. — Но помните, что не любую картинку можно использовать. В интернете много классных изображений, но у большинства из них есть правообладатель, например фотограф или художник, который обычно не любит, когда его фото или рисунки размещают на других сайтах без разрешения и указания ссылки на источник. Есть много разных правил размещения картинок, к тому же у разных правообладателей разные требования. Для нас будет безопаснее поискать среди бесплатных изображений, чтобы точно не нарушить чье-то авторское право. Наберите в поисковике, например, картинки с кофе скачать бесплатно, и посмотрим, что получится. Эрик и Эмили несколько минут искали в интернете, они открывали разные картинки и спорили. Наконец Эрик сказал: Глава 13. Стили: наводим красоту 219 — Мне нравится эта картинка. На ней разные виды кофе с топпингами и кружки симпатичные, — он показал картинку Саймону: https://www.freepik.com/free-vector/ colorful-coffee-cup-set-flat-style_1544839.htm. — Что ж, давай посмотрим, — сказал Саймон. — Взгляни на этот блок на странице. ① ② ③ ① Бесплатная лицензия — Подробнее ② Указание авторства обязательно — Как указать автора? ③ Тип файла: AI, EPS, JPG — Как изменить тип файла? Саймон нажал More info (Подробнее) и проверил условия лицензии. ① ② ③ ④ ⑤ ⑥ ⑦ ① Краткое описание лицензии ② Лицензия позволяет использовать выбранный контент ③ Для коммерческих и личных проектов ④ В цифровых и печатных средствах массовой информации ⑤ Неограниченное количество раз и без ограничений по времени ⑥ В любой точке мира ⑦ Вносить изменения и создавать производные материалы 220 Поговорим на Python — Выглядит неплохо. Мы можем использовать эту картинку, но мы должны указать автора. Видите фразу «Attribution is required» («Указание авторства обязательно»)? Давайте посмотрим, что нужно сделать. Саймон нажал на ссылку «How to attribute?» («Как указать автора?»). ① ② ③ ④ ⑤ ⑥ ① Как указать автора? ② Создание контента отнимает много времени и сил, поэтому мы просим указать ссылку на источник. Где вы будете использовать изображение? ③ Веб Печатные материалы Видео Игры и приложения ④ Например: веб-сайты, социальные сети, блоги, электронные книги, интернетрассылки и т. д. ⑤ Скопируйте эту ссылку и поместите ее на видном месте рядом с изображени- ем. Если это невозможно, то поместите ссылку в нижнем колонтитуле веб-сайта, блога или рассылки или в разделе ссылок на источники информации. ⑥ Изображение от Freepik — Мы должны скопировать текст и поместить его на нашей странице рядом с картинкой. Не проблема, — сказал Саймон и нажал кнопку Copy (Копировать). — Теперь нужно поместить эту ссылку на странице заказов, — Саймон открыл файл order.html и вставил текст внизу страницы, перед строкой {% endblock %}. — Мы переместим ссылку позже, когда вставим картинку, — пояснил Саймон и обратился к Эрику: — Теперь скачай картинку. Глава 13. Стили: наводим красоту 221 Эрик нажал зеленую кнопку Download (Скачать), а потом в выпадающем меню Free download (Скачать бесплатно) выбрал JPG. Указание авторства обязательно Бесплатная загрузка Тип файла Загрузить ZIP-архив В нижней части браузера появилось имя изображения 1544839_222805-P1KRC2559.jpg. ПРИМЕЧАНИЕ Имя твоего изображения, скорее всего, будет отличаться. В дальнейшем используй свое имя изображения. — Теперь перемести скачанное изображение из папки Downloads в папку проекта, точнее в папку coffeeshop/static/img/. Сможешь ее найти? — Думаю, да, — сказал Эрик и открыл файловый менеджер (у Эрика на MacBook это Finder). Он нашел нужную директорию в несколько щелчков мышью, и в папке проекта появилась картинка. 222 Поговорим на Python — Отлично, — сказал Саймон. — Теперь нужно поместить картинку на нашей странице. Открой файл order.html, и я помогу с картинкой, — Саймон добавил новую строку кода перед ссылкой на автора изображения. Конец файла order. html принял следующий вид: . . . <img src="/static/img/1544839_222805-P1KRC2-559.jpg" alt="Coffee drinks" width="100%"> Image by <a href="https://www.freepik.com/free-vector/ colorful-coffee-cup-set-flat-style_1544839.htm">Freepik</a> {% endblock %} Саймон сохранил файл и обратился к Эрику и Эмили: — Перезагрузите страницу, посмотрим, появилась ли картинка. Эмили нажала Reload, и страница обновилась. ТВОЙ ХОД! Добавь картинку на свою страницу Найди картинку, которая тебе понравится, и добавь ее на страницу заказов. Не забудь проверить условия лицензии и укажи автора изображения. Глава 13. Стили: наводим красоту 223 ВВЕДИТЕ ИМЯ - Выберите напиток- - Выберите вкус - - Выберите топпинг - ПОДТВЕРДИТЬ — Отличная работа! — сказал Саймон. — Теперь наше приложение похоже на сайт настоящей кофейни. Но главное, что оно не только красиво выглядит, но еще и работает! Эрик и Эмили были очень рады видеть результат своей упорной работы. Было нелегко собрать вместе отдельные кусочки, но результат получился потрясающий — настоящее веб-приложение, которое они написали сами. — Ну как? Вспомним, что мы узнали сегодня? — спросил Саймон. — Мне понравилось менять шрифт и цвет. Это проще, чем работать с базой данных, — сказала Эмили. — И хорошо, что ты рассказал про шестнадцатеричную систему счисления — я многого не знала. 224 Поговорим на Python — Мне понравилась страница Google Fonts, — сказал Эрик. — Там так много красивых шрифтов! А их можно использовать в документах? Я хотел сделать постер. — Конечно! — ответил Саймон. — Ты можешь установить эти шрифты на компьютер и использовать их в Word. А для Google Docs их даже не нужно устанавливать. Можно просто нажать More Fonts (Больше шрифтов) в меню Fonts (Шрифт) и выбрать нужный шрифт. — А еще мы узнали о лицензиях для картинок и о ссылках на источник, — добавила Эмили. — Саймон, ты прав, там столько всего, целый мир. — Хорошее замечание, ты молодец, Эмили, — сказал Саймон. — У меня есть еще одна мысль. Вы же знаете, что такое искусственный интеллект, или ИИ? — Да. Все эти странные чат-боты и программы, которые создают смешные картинки, — ответил Эрик. — Мы с друзьями уже пробовали. — Да, но я говорю об ИИ, который поможет нам писать программу, — сказал Саймон. — Некоторые программы ИИ помогают писать функции, улучшать код, расшифровывать сообщения об ошибках. — Да, я видела несколько демок, — сказала Эмили. — Искусственный интеллект писал целые программы, и они работали. Волшебство какое-то. ИИ правда может написать приложение, как наше? — Возможно, — сказал Саймон. — Но он не сможет сделать этого без помощи человека. Во-первых, нужно придумать идею приложения, как это сделал Эрик несколько недель назад. Во-вторых, объяснить ИИ, что вы от него хотите. Причем объяснить очень точно, иначе ИИ сделает что-нибудь не то. В-третьих, нужно проверить код и убедиться, что он работает так, как вы задумали. И конечно, никакой ИИ не выберет за вас цвет, шрифт и картинки, которые понравятся именно вам. Это дело вкуса, и, насколько я знаю, ИИ не может выбрать за вас. ИИ хороший помощник, но пока он не способен полностью заменить дизайнеров и разработчиков. Давайте поработаем с ним завтра и посмотрим, чем он может быть нам полезен. Что мы узнали сегодня Типы шрифтов. Serif (шрифт с засечками), Sans serif (без засечек, или рубленый), моноширинный — это типы шрифтов, которые можно использовать в приложении. Serif хорошо подходит для заголовков, книг и документации. Sans serif используется в меню, а также в заголовках и в тексте веб-страниц и приложений. Моноширинный шрифт используется, чтобы выделить в тексте программный код, имена файлов и примеры команд. Глава 13. Стили: наводим красоту 225 Кодировка цвета. Цвет можно задать с помощью чисел, записанных в шестнадцатеричной системе счисления, например #a1d4c5. Любой цвет состоит из трех основных цветов: красного, зеленого и синего. Кодировку цвета можно использовать, чтобы задать цвет фона, меню, заголовка и текста на веб-странице. У изображений есть лицензия и правообладатель. Важно знать, что не все картинки, которые есть в интернете, можно бесплатно использовать на вебстранице. У картинок и фотографий есть правообладатель, который решает, на каких условиях можно использовать его работу. Часто картинки можно брать бесплатно, просто указав на своей странице ссылку на источник. Так мы благодарим автора за работу. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch13. 14 Помощь искусственного интеллекта: улучшаем код В ЭТОЙ ГЛАВЕ 33 Эрик и Эмили просят помощи у искусственного интеллекта 33 ИИ делает код-ревью 33 ИИ пишет комментарии и поясняет сообщения об ошибках 33 ИИ рассказывает о себе 33 ИИ дает советы по использованию ИИ — Готовы позвать на помощь искусственный интеллект? — спросил Саймон. — Конечно, — ответил Эрик. — Есть какой-то специальный сайт ИИ для разработчиков? — Для разработчиков есть несколько инструментов на основе ИИ, — сказал Саймон. — Для некоторых нужно зайти на сайт, авторизоваться и сделать запрос. Например, можно попросить ИИ провести код-ревью1 и дать пояснения к коду. Другие инструменты можно установить прямо в редактор кода, и они помогут писать программу. Такие инструменты вы попробуете позже, 1 Код-ревью (code review) — проверка и анализ кода. — Примеч. пер. Глава 14. Помощь искусственного интеллекта: улучшаем код 227 а пока давайте начнем с сайта. У меня есть учетная запись в ChatGPT — используем его. Саймон открыл сайт https://chat.openai.com и ввел свой логин и пароль. На экране появилась незамысловатая страница с надписью Send a message… (Отправьте сообщение…) в нижней части. — Помнишь, вчера я говорил, — начал Саймон, — что нужно подробно объяснить ИИ, что мы от него хотим? Это не чат-бот, у которого можно спросить «Как дела?» Нам нужен код-ревью одной из наших функций, и мы должны четко объяснить это ИИ. Рекомендуют говорить ИИ, что он должен действовать как кто-либо. В нашем случае мы попросим его действовать как код-ревьювер. Потом мы объясним ему, чего мы от него хотим1. Представь, что ты код-ревьювер. Твоя задача – принять от меня функцию и дать мне обратную связь о структуре кода, синтаксисе, возможных ошибках и улучшениях. Вот первая функция на ревью: Саймон остановился и сказал: — Давайте начнем с простой функции. Например, с функции read_menu(). Он скопировал функцию из редактора: def read_menu(filename): f = open(filename) temp = f.readlines() result = [] for item in temp: new_item = item.strip() result.append(new_item) return result 1 Искусственный интеллект постоянно развивается. Если ты задашь ему точно такие же вопросы, ответы могут немного отличаться в деталях. — Примеч. ред. 228 Поговорим на Python ИИ почти сразу начал писать ответ: Спасибо, что предоставил код. Вот код-ревью: Я проанализировал функцию read_menu, и вот что я думаю: Структура кода. Общая структура кода проста и понятна. Отличная работа! Синтаксис. Синтаксис верный и соответствует соглашениям языка Python. Потенциальные ошибки. В коде отсутствует надлежащая обработка ошибок, особенно в случае операций чтения/записи файлов. Если файл не существует или у тебя нет прав на чтение или запись этого файла, то твой код выдаст ошибку. Улучшения: • Для открытия файла ты можешь использовать оператор with, кото­ рый автоматически закрывает файл даже в случае ошибок. Это гаран­ тирует корректное закрытие файла после его использования. • Ты можешь упростить цикл for с помощью спискового включения, это сократит код и сделает его более питоническим. Вот обновленная версия функции с предложенными улучшениями: ИИ создал отдельное окно с кодом на Python: def read_menu(filename): try: with open(filename) as f: result = [item.strip() for item in f.readlines()] except FileNotFoundError: print( f"Error: The file '{filename}' could not be found.") result = [] except PermissionError: print(f"Error: You do not have permission to read \ the file '{filename}'.") result = [] except Exception as e: print(f"An unexpected error occurred: {e}") result = [] return result Затем ИИ добавил пояснения: Эта версия функции включает в себя обработку ошибок при операциях с файлами, использует оператор with для обработки файла и генерирует результат с помощью спискового включения. Глава 14. Помощь искусственного интеллекта: улучшаем код 229 — Круто! — одновременно сказали Эрик и Эмили. — Все по делу, — отметил Саймон. — Да, обычно при работе с файлами используют конструкцию with open. Я не предложил этот вариант, потому что для него нужны дополнительные пояснения, к которым вы были еще не готовы. Конструкция try - except тоже часто используется. В нашем случае, если файл меню не найден или у вас нет прав на чтение этого файла, программа не упадет с ошибкой, а вежливо сообщит, что случилось. Это очень хорошая практика. — А что такое списковое включение? — спросил Эрик. — Это особый синтаксис языка Python, с помощью которого можно сделать цикл for короче. Посмотрите на код, который написал ИИ: result = [item.strip() for item in f.readlines()] — Этот код заменяет следующий цикл: temp = f.readlines() result = [] for item in temp: new_item = item.strip() result.append(new_item) — Да, код ИИ короче, — согласился Эрик, — но наш старый цикл понятнее. — Верно! — сказал Саймон. — Поэтому мы и начали с циклов. Они понятнее. Теперь ты знаешь, как создавать списки в цикле, и можешь использовать более короткую версию кода. Сначала я тоже не понимал списковые включения. Но я начал практиковаться и теперь читаю их так же легко, как обычные циклы. ИИ верно заметил, что код со списковыми включениями более питонический, то есть более соответствует стилю Python. А еще можно попросить ИИ объяснить это понятие. Саймон напечатал следующее: Расскажи, что такое списковые включения в Python. ИИ ответил: Списковые включения (list comprehensions) — это компактный способ создания списков в языке Python. Эта синтаксическая конструкция помогает создать список, не перечисляя его элементы, а основываясь на существующей последовательности (списке, кортеже, диапазоне). Списковые включения короче и обычно быстрее, чем традиционный цикл for в сочетании с методом append(). 230 Поговорим на Python Вот базовая структура спискового включения: [выражение for элемент in последовательность if условие], где: выражение определяет значение каждого элемента нового списка. Оно обычно включает в себя переменную элемент; элемент — временная переменная, представляющая каждый элемент последовательности; последовательность — объект последовательности, по которому требуется пройти циклом, например список, кортеж, диапазон; условие — необязательное условие, которое фильтрует элементы последовательности. В список будут включены только те элементы, для которых данное условие истинно. Вот простой пример использования спискового включения: # Традиционный способ создания списка квадратов числа с помощью цикла for squares = [] for i in range(1, 6): squares.append(i**2) print(squares) # Вывод: [1, 4, 9, 16, 25] # Создание такого же списка квадратов с помощью спискового включения squares = [i**2 for i in range(1, 6)] print(squares) # Вывод: [1, 4, 9, 16, 25] Также можно использовать списковое включение с условием. Вот пример, в котором создается список четных чисел от 1 до 10: even_numbers = [x for x in range(1, 11) if x % 2 == 0] print(even_numbers) # Вывод: [2, 4, 6, 8, 10] Списковые включения могут сделать код более компактным и читаемым, особенно при простых операциях. Однако при более сложных операциях или вложенных циклах код, наоборот, может стать менее читаемым, поэтому важно соблюдать баланс между краткостью и читаемостью. — Давайте спросим ИИ о чем-нибудь еще. Когда мы писали код, мы не оставили комментариев. Это не очень хорошо. Нужно обязательно добавлять комментарии, чтобы пояснить, что делает код. Может быть, кто-то будет его читать. Или, что гораздо вероятнее, ты сам будешь читать его месяца через три. Я гарантирую, что если ты не напишешь комментарии, то не вспомнишь, что делает код, почему ты написал его именно так и что ты имел в виду в такой-то строке. Поверь моему опыту, — сказал Саймон и подмигнул. — Так что лучше комментировать Глава 14. Помощь искусственного интеллекта: улучшаем код 231 код. Обычно разработчики делают это сами, но давайте проверим, сможет ли ИИ с этим справиться. В поле запроса ИИ Саймон написал: Пожалуйста, добавь комментарии к функции, которую ты только что написал. ИИ ответил мгновенно: Конечно! Вот обновленная версия функции с комментариями, поясняющими каждый шаг: def read_menu(filename): """ Функция читает меню из файла и возвращает список пунктов меню. :param filename: имя файла, в котором хранится меню. :return: список пунктов меню, для каждого элемента списка удалены начальные и конечные пробелы. """ try: # Открывает файл с помощью операции 'with', которая # автоматически закрывает файл после использования with open(filename) as f: # С помощью спискового включения # читает все строки из файла и удаляет пробелы result = [item.strip() for item in f.readlines()] except FileNotFoundError: # Обрабатывает случай, когда файл не существует print(f"Error: The file '{filename}' could not be found.") result = [] except PermissionError: # Обрабатывает случай, когда права на чтение файла отсутствуют print(f"Error: You do not have permission to read \ the file '{filename}'.") result = [] except Exception as e: # Обрабатывает остальные непредвиденные ошибки print(f"An unexpected error occurred: {e}") result = [] return result — Очень хорошо! — сказал Саймон. — Смотрите, в начале функции ИИ добавил строку документации. Это специальная строка, которую потом можно включить в документацию к функции. Потом ИИ добавил пояснения для каждой ошибки. Сам бы я так не написал! Запомните этот пример и всегда добавляйте в свой код комментарии и строки документации! — Да? А мы думали, что теперь ИИ будет это делать за нас, — ответил Эрик и улыбнулся. 232 Поговорим на Python — ИИ может комментировать простой код, например для обработки ошибок, — ответил Саймон, — но я абсолютно уверен, что скоро ты начнешь писать такие сложные функции, что никакой даже самый умный ИИ не сможет объяснить, как они работают. Так что если ты хочешь писать нестандартный код, то будь готов сам его комментировать. — ИИ может пояснить сообщения об ошибках? — спросила Эмили. — Отличная мысль! — сказал Саймон. — Давайте попробуем. Уверен, какие-то сообщения он сможет пояснить. Эрик, помнишь то сообщение об ошибке, которое ты получил в самом начале? — Я помню, что ты ему очень обрадовался, — проворчал Эрик. — Сейчас найду его, я сохранил его в своих записях. Вот оно, — сказал Саймон и ввел в поле запрос и сообщение об ошибке: Пожалуйста, поясни следующее сообщение об ошибке от Python: Traceback (most recent call last): File "/home/erik/mu_code/menu.py", line 30, in <module> print("Основной напиток: ", drinks[drink]) TypeError: list indices must be integers or slices, not str ИИ ответил следующее: Это сообщение об ошибке означает, что ты пытаешься использовать строку в качестве индекса элемента списка, что недопустимо в Python. В данном случае похоже, что ты пытаешься получить доступ к элементу списка drinks с помощью переменной drink, содержащей строку. Вот пример кода, который мог вызвать подобную ошибку: drinks = ["кофе", "чай", "содовая"] drink = "кофе" print("Основной напиток: ", drinks[drink]) В этом коде переменная drink является строкой, которая содержит значение "кофе". При попытке получить доступ к элементу списка drinks[drink] Python выдает ошибку несоответствия типов, потому что индекс должен быть целым числом, а не строкой. Чтобы исправить эту ошибку, для доступа к элементу списка используй целочисленный индекс. Например, если нужен первый элемент списка, используй drinks[0]: drinks = ["кофе", "чай", "содовая"] drink = 0 print("Основной напиток: ", drinks[drink]) Глава 14. Помощь искусственного интеллекта: улучшаем код 233 Этот код выведет на экран строку "Основной напиток: кофе", что является первым элементом списка drinks. — ИИ даже знает, что делает наше приложение! — воскликнул Эрик. — Да, — сказал Саймон. — Мы дали ИИ только одно сообщение об ошибке, а он предположил, что делает наша программа. Это возможно благодаря принципам, на которых основана его работа. — А как он работает? Можешь рассказать? — спросила Эмили. — Ну… — начал Саймон. — Я не очень-то разбираюсь в искусственном интеллекте. Только прочитал пару статей. Но я попытаюсь объяснить своими словами. Люди в компаниях по разработке ИИ — таких, как OpenAI, которая создала ChatGPT, — знают, как обучить компьютеры, чтобы они понимали человеческий язык и программы. Для этого компьютерам дают огромное количество текстов и кода. Потом специальная программа, которая называется моделью, — кстати, обычно это программа на Python — анализирует все эти тексты и определяет, какие слова чаще всего сочетаются друг с другом. Как в нашем случае: модель искусственного интеллекта обнаружила, что вместе со словом «напиток» люди часто употребляют слова «кофе», «чай», «содовая». Вот почему ИИ выдал нам пример, который очень похож на наш код. К тому же модели ИИ могут рассчитывать так называемое сходство текстов (text similarity) и сравнивать разные слова и словосочетания. Они используют свои расчеты, чтобы отвечать на такие вопросы, как: «Что связано с Испанией так же, как Париж связан с Францией?» Модель рассчитает «расстояние» между «Парижем» и «Францией», а потом выполнит вот такую математическую операцию: ? = Spain + (Paris - France) — И получит ответ «Мадрид». — Можно попробовать? — спросила Эмили. — Конечно, попробуй! — сказал Саймон. — Я читал про это в нескольких статьях, но сам никогда не проверял. Эмили взяла клавиатуру и напечатала запрос: Что связано с Испанией так же, как Париж связан с Францией? ИИ ответил: Мадрид так же связан с Испанией, как Париж связан с Францией. Париж и Мадрид являются столицами Франции и Испании соответственно. 234 Поговорим на Python — И правда, работает! — сказал Саймон. — Давайте спросим, как он догадался. Эмили напечатала: Можешь рассказать, как ты это узнал? ИИ сказал: Конечно! Я узнал это, поняв, как именно Париж связан с Францией. Париж — столица Франции. То есть вопрос в том, чтобы найти что-то, что связано с Испанией таким же образом. Учитывая, что столица Испании — Мадрид, я определил, что Мадрид так же связан с Испанией, как Париж с Францией. Следовательно, ответом на вопрос является Мадрид. Тип рассуждений, при котором нужно найти сходство между парами элементов, основываясь на связи между ними, называется аналогией. Саймон задал еще один вопрос: Ты использовал сходство текста, чтобы найти эту связь? ИИ ответил: Как языковая модель ИИ, я не использовал сходство текстов, чтобы найти связь между Парижем и Францией, а затем между Мадридом и Испанией. Вместо этого я полагался на уже имеющиеся знания и понимание взаимосвязей между странами и их столицами, которые я приобрел в процессе обучения на большом наборе текстовых данных. Однако такие техники сходства текста, как векторное представление слов, могут помочь установить отношения между словами, основываясь на их контексте и употреблении. В данном случае, чтобы установить отношения и дать ответ, я использовал свои знания и рассуждения по принципу аналогии. — Как круто! — сказал Саймон. — Можно узнать про ИИ у самого ИИ! Я знаю, что в программировании искусственный интеллект используют и для многих других задач. Мы попробовали написать комментарии, сделать код-ревью и пояснить сообщения об ошибке. Интересно, чем еще ИИ может нам помочь? Давайте у него и спросим! Саймон ввел следующий вопрос: Мы попросили ChatGPT помочь нам с проектом на Python. Мы вместе с ним написали комментарии, сделали код-ревью и пояснили сообщения об ошибке. Чем еще ChatGPT может нам помочь? Глава 14. Помощь искусственного интеллекта: улучшаем код 235 ИИ ответил: Помимо написания комментариев, код-ревью и пояснения сообщений об ошибках ChatGPT может помочь со многими другими задачами в рамках вашего проекта на Python, например: Генерация примеров кода. ChatGPT может создать для вас примеры фрагментов кода, которые показывают, как использовать определенные функции, библиотеки и особенности языка. Отладка программы. Вы можете описать проблему, с которой вы столкнулись, и ChatGPT предложит потенциальное решение или способы выявления источника проблемы. Рекомендация инструментов и библиотек. Основываясь на знаниях из популярных и широко используемых источников информации о Python, ChatGPT может порекомендовать библиотеки и инструменты, подходящие для конкретной задачи. Лучшие практики и соглашения в области разработки. ChatGPT может дать рекомендации по применению стандартов и лучших практик программирования, а также рекомендации по стилю, чтобы ваш код на Python стал более чистым и эффективным и его было проще поддерживать. Разработка алгоритмов. ChatGPT может помочь понять или разработать алгоритмы, пояснить их логику и привести примеры их применения в Python. Структура проекта. ChatGPT может дать совет по организации проекта на Python, например, как построить структуру папок, файлов и модулей, а также дать рекомендации по управлению пакетами и зависимостями. ChatGPT может вам помочь, но вы должны помнить, что он не может полностью заменить тщательное исследование и личное понимание задачи. К тому же важно проверять сгенерированный код или предположения ИИ, чтобы убедиться, что они верны и соответствуют конкретным требованиям проекта. — Отличные мысли! — сказал Саймон. — Думаю, мы вместе должны все это попробовать. Давайте завтра обсудим все возможные способы улучшить наше приложение и займемся планированием на будущее. Мне кажется, что в ответе ИИ очень важно последнее. Когда люди впервые видят код, написанный ИИ, им кажется, что искусственный интеллект может делать за них абсолютно все. Но я экспериментировал с ИИ и не раз замечал, что не любой сгенерированный им код работает. Вы всегда должны проверять код и тестировать его. 236 Поговорим на Python Вернемся к нашему приложению. Как его можно улучшить? — Ты говорил, что я могу открывать приложение на iPad, — сказал Эрик. — Да, это возможно, но для этого нужно внести пару небольших изменений. Ты прав, от приложения мало толку, если оно работает только на компьютере. Завтра я покажу вам, как запустить его на iPad или на смартфоне. — Мы разместим его в облаке? — спросила Эмили. — Почти, — сказал Саймон. — Пока это будет наше собственное «облако» внутри домашней сети. Но чтобы приложение стало доступно из любой точки, придется разместить его в настоящем облаке. Давайте поговорим об этом завтра. Что мы узнали сегодня ИИ может помочь написать качественный код. ИИ может помочь улучшить код, пояснить ошибки, исправить баги и написать комментарии. ИИ также может предложить идеи структуры программы и полезные инструменты и библиотеки. ИИ не может заменить разработчика. ИИ может делать много интересного, но, чтобы создать работающее и полезное приложение, ему все еще нужна помощь человека. Мы всегда должны проверять и тестировать код, полученный от ИИ. ИИ использует специальные программы, которые называются моделями. Чтобы отвечать на вопросы, ИИ использует языковые модели, которые обучаются на большом количестве текстовой информации, в том числе на материалах Википедии и других статьях в интернете, а также на программном коде и многом другом. Модели могут устанавливать связи между словами и предложениями и строить ответы, основываясь на этих связях. 15 Что дальше: планы на будущее В ЭТОЙ ГЛАВЕ 33 Эрик и Эмили разрабатывают домашнюю страницу приложения 33 Саймон показывает, как открыть приложение на разных устройствах 33 Эрик и Эмили тестируют приложение на iPad и Android 33 Саймон, Эрик и Эмили строят планы на будущее — Осталось совсем немного, — сказал Саймон, — и наше приложение будет готово. Помните, при запуске приложения мы видели страницу «Hello from Mu!»? Думаю, нужно заменить ее нашей домашней страницей. — Конечно! — согласилась Эмили. — Мы уже выбрали цвет и нашли красивую картинку. Думаю, на домашней странице достаточно заголовка «Добро пожаловать в кофейню Э&Э!» и картинки, которую мы использовали на странице заказов. — Неплохо, — сказал Саймон. — Но не забывайте, для чего нужна домашняя страница. — Для чего? — спросил Эрик. 238 Поговорим на Python — На любом сайте или в веб-приложении домашняя страница нужна для навигации, — ответил Саймон. — Когда пользователи заходят на сайт, им сразу должно быть понятно, что этот сайт умеет и как им пользоваться. Посмотрите на сайты ваших любимых кофеен и скажите, что на них есть. Поищите, что у них общего. Эрик и Эмили поискали знакомые компании и открыли их сайты в разных вкладках. Некоторое время они изучали сайты, а потом Эрик сказал: — У всех есть страница «Меню». А еще страница «Как нас найти?» или «Найти кофейню», на которой показано, где они находятся. — Если вы откроете несколько кофеен, такая страница вам тоже понадобится, — отметил Саймон. — Да, — сказал Эрик и продолжил: — Еще у них есть бонусные карты и подарочные сертификаты. Думаю, нам это тоже пригодится. Внизу страницы обычно расположено много маленьких ссылок и разделы «О нас», «Контакты» и другие. — Ты неплохо изучил сайты, — сказал Саймон. — Теперь ты понимаешь, что я имел в виду под навигацией. Пока кроме домашней страницы у нас есть только страница заказов. Думаю, на домашней странице можно поместить кнопку, которая откроет страницу заказов. — Как мы делали на странице «Ваш заказ»? — спросила Эмили. — Именно, — сказал Саймон. — Можешь просто скопировать эти три строки из файла print.html. Открой файл templates/index.html, удали из него всё между второй строкой с block content и последней строкой с endblock. Затем вставь в файл элементы, которые мы обсудили: заголовок, кнопку и картинку. — Да, теперь я знаю, как это сделать, — сказала Эмили. Эрик и Эмили начали работать над домашней страницей и через несколько минут показали ее Саймону: Листинг 15.1. templates/index.html: домашняя страница {% extends "base.html" %} {% block content %} <h1>Welcome to E&E Coffee Shop!</h1> <form action="/order"> <input type="submit" value="Новый заказ" /> </form> <img src="/static/img/8507631_3925457.jpg" alt="Coffee drinks" width="100%"> Image by <a href="https://www.freepik.com/free-vector/ take-away-coffee-coffee-mugs_8507631.htm">Freepik</a> {% endblock %} Глава 15. Что дальше: планы на будущее 239 ТВОЙ ХОД! Отредактируй свою домашнюю страницу Открой файл templates/index.html и внеси изменения, как у Эмили. Не бойся менять заголовок и картинку. — Мне нравится, — сказал Саймон. — Давайте проверим! НОВЫЙ ЗАКАЗ — Хорошо. Теперь попробуй нажать кнопку «Новый заказ». Должна открыться страница заказов, — сказал Саймон. Эрик нажал кнопку и увидел знакомую страницу заказов с меню. — Работает! — сказал Эрик. — Наше приложение готово! 240 Поговорим на Python — Саймон, — начала Эмили, — вчера ты сказал, что мы можем запустить это приложение и на других устройствах. Покажешь, как это сделать? — Да, я хочу попробовать запустить его на своем iPad, — сказал Эрик. — Конечно, — ответил Саймон. — Эрик, помнишь, что мы начали наш проект, когда ты пошел собирать заказы с помощью iPad? Я еще сказал тебе, что ты можешь написать веб-приложение и использовать iPad не только как блокнот? — Да, я помню. Это было всего пару недель назад, — сказал Эрик и улыбнулся. — Хорошо, тогда давайте сделаем так, чтобы приложение заработало на iPad, — сказал Саймон. — Сейчас объясню, что нам для этого нужно. Саймон начал рисовать схему и объяснять: — Если мы запускаем приложение из редактора Mu, то по умолчанию оно доступно только на том компьютере, на котором запущено. Поэтому, когда мы открываем приложение в браузере, мы используем localhost или адрес 127.0.0.1. Flask и редактор Mu делают так, чтобы приложение было недоступно другим компьютерам и пользователям, пока оно еще не готово. — Это важно, — сказал Эрик. — Без картинок и шрифтов наше приложение было не очень-то красивым. — Верно, — сказал Саймон. — Но теперь мы готовы открыть его для других устройств, и нам нужно изменить область видимости приложения. К счастью, для этого в редакторе Mu нужно отредактировать всего одну строчку. После этих изменений все устройства в нашей домашней сети получат доступ к приложению — конечно, если на них есть браузер. — Я смогу запустить приложение на телефоне? — спросила Эмили. — Да, но для этого ты должна подключиться к нашей домашней сети. Давай я введу пароль от нашего Wi-Fi. — Саймон ввел пароль на телефоне Эмили и продолжил свой рассказ: — Слева находится конфигурация по умолчанию. Мы можем открыть приложение только на том компьютере, на котором оно запущено. А справа мы видим конфигурацию, при которой приложение доступно другим устройствам. Глава 15. Что дальше: планы на будущее 241 Пользователь Пользователь Устройство (iPad, компьютер, телефон) Welcome http://127.0.0.1:5000 Welcome http://192.168.1.85:5000 адрес: 192.168.1.85 — У каждого компьютера есть сетевой адрес. Ноутбук Эрика подключен к нашей домашней сети и имеет адрес 192.168.1.85. Любое другое устройство, которое хочет взаимодействовать с компьютером Эрика, должно обращаться по этому адресу. Если компьютер хочет взаимодействовать с самим собой, то он должен обращаться по адресу 127.0.0.1. — Можно мне попробовать? — спросил Эрик. — Подожди, нужно изменить настройки. Ты помнишь, что приложение еще не доступно для других устройств в нашей сети? Конечно, к сети подключено не так много устройств, но мы должны быть уверены, что никто не увидит 242 Поговорим на Python приложение, пока оно не готово. Позже мы загрузим приложение в облако, и тогда его можно будет запустить с любого компьютера в мире. Но сначала мы должны проверить, что приложение корректно работает и хорошо выглядит. Эрик, видишь вот эту маленькую шестеренку в нижнем правом углу редактора Mu? Обычно шестеренка означает настройки или конфигурацию. Нажми на нее, пожалуйста. — Теперь открой вкладку Python 3 Environment и добавь вот такую строку после всего текста: FLASK_RUN_HOST=0.0.0.0 Эрик добавил строку и увидел результат. Глава 15. Что дальше: планы на будущее 243 — Теперь нажми OK и перезапусти приложение. Эрик сохранил настройки, нажав ОK. Потом он нажал Stop и Run в редакторе. — Теперь посмотри на результат в нижнем окне. Видишь строку Running on all addresses? Это именно то, что нам нужно. На две строки ниже указан адрес, по которому мы можем открыть приложение. Первый адрес, 127.0.0.1, мы используем постоянно. Второй адрес — это адрес твоего компьютера в нашей домашней сети. Нужно ввести этот адрес в браузере на iPad. Эмили, можешь ввести этот адрес на телефоне. В нашем случае это адрес http://192.168.1.85:5000. Но помните, что этот адрес может меняться. Например, если Эрик придет домой к Эмили и подключится к ее домашней сети, то адрес ноутбука Эрика изменится. Вы увидите новый адрес в окне вывода, и у Эмили дома нужно будет использовать этот новый адрес. 244 Поговорим на Python — Хорошо, теперь перейдите по указанному адресу со своих устройств и покажите, как у вас выглядит страница заказов. Эрик и Эмили взяли свои устройства и начали печатать. — Получилось! — воскликнула Эмили и показала Саймону экран телефона. Эмили Шоколад Карамель Ваниль ПОДТВЕРДИТЬ — Отлично! — кивнул Саймон. — Эрик, а что на iPad? — Смотри! — показал Эрик. Глава 15. Что дальше: планы на будущее 245 Эрик Кофе Шоколад ПОДТВЕРДИТЬ Малина — Работает на обоих устройствах и выглядит здорово, — сказал Саймон. — Помните, я говорил, что мы используем таблицы стилей, предустановленные в редакторе Mu? Эти таблицы стилей работают на разных устройствах. Вот — мы 246 Поговорим на Python открыли приложение на MacBook, iPad и Android, и оно везде отлично смотрится. Более того, вы заметили, что меню подстроилось под размер страницы? Вот почему выгодно использовать таблицы стилей normalize.css и skeleton. css. Мы просто добавили пару небольших изменений в свою таблицу стилей coffeeshop.css. — Как хорошо, что клиенты нашей кофейни смогут заказывать напитки прямо с телефона, — сказала Эмили. — Но для этого они должны прийти в нашу кофейню и подключиться к нашей сети, верно? Саймон, мы можем разместить приложение в настоящем облаке, чтобы клиенты могли открывать его где угодно? Я бы хотела показать приложение друзьям в школе. — Конечно, это возможно. Само собой, это немного сложнее, чем просто добавить одну строку, как мы только что сделали. Обычно облачные сервисы стоят денег, но для такого простого приложения, как наше, можно найти бесплатный сервис. PythonAnywhere — как раз один из таких. Я думаю, что на нем можно разместить ваше приложение, но я никогда им не пользовался. Мне нужно немного времени, чтобы его протестировать, а потом я все вам объясню. ПРИМЕЧАНИЕ Инструкции по использованию сервиса PythonAnywhere есть на веб-сайте книги по адресу https://pythonicadventure.com/. — Прежде чем мы сделаем приложение доступным для всего мира, давайте обсудим, что можно улучшить. Эрик, ты помнишь, про что мы говорили, когда работали над базой данных? — Ты сказал, что в таблицу базы данных хорошо бы добавить дату и время получения заказа, — сказал Эрик. — Да, верно! Давайте составим список будущих улучшений, — сказал Саймон и начал печатать в текстовом редакторе: * Добавить дату и время заказа — Мы можем запоминать имена клиентов, — предложила Эмили, — и показывать им, что они заказали в последний раз. — Хорошая мысль, — сказал Саймон. — Но в этом случае нужно создать для каждого пользователя уникальное имя. Надо не перепутать двух клиентов с именем Алекс. — А что насчет меню? — спросил Эрик. — Когда мы запускаем приложение на моем ноутбуке, мы можем редактировать меню через текстовые файлы. Но если приложение будет работать в облаке, то мы не сможем редактировать файлы, или сможем? Глава 15. Что дальше: планы на будущее 247 — Да, ты прав, Эрик, — сказал Саймон. — Если мы перейдем в облако, то все придется делать через браузер. Нам потребуется отдельная страница для редактирования меню. И не забудь, что эта страница должна быть доступна только менеджеру кофейни. — Я поняла, о чем ты, — сказала Эмили. — Нам нужны пароли! — Верно, — подтвердил Саймон. — Нам нужны учетные записи и роли: клиент, бариста, который получает заказы, и менеджер со своими менеджерскими задачами: списком заказов, редактированием меню и так далее. — Нам нужно добавить еще картинок! — сказала Эмили. — Я имею в виду фотографии наших напитков с разными вкусами и топпингами. — А как насчет промоакций? — спросил Эрик. — Вкус недели или что-то в этом роде. Саймон, ты сказал, что мы должны знать, сколько ингредиентов мы потратили за день или за месяц. Можно показать это на веб-странице? — Так, так, помедленнее. Я не успеваю записывать, — сказал Саймон и улыбнулся. — А если серьезно, мне нравится наш мозговой штурм. Мы часто используем этот метод на кружке по робототехнике. Главное правило — не критиковать предложенные идеи. Каждый высказывает свои самые сумасшедшие мысли. И никто не говорит: «А, ну это нельзя сделать». Мы просто записываем все идеи, а обсуждаем потом. — Тогда я бы хотел добавить в приложение голосовые команды, — сказал Эрик. — И роборуку, которая будет наливать кофе и добавлять вкусы и топпинги. — Записано, — сказал Саймон. — Наверное, вместо паролей лучше использовать распознавание лиц, — сказала Эмили. — Пароли — это скучно. Представьте: клиент входит в нашу кофейню, а мы приветствуем его и говорим: «Эй, Макс, тебе как обычно ваниль и карамель?» — Отлично, тоже записано. Вот что у нас получилось, — сказал Саймон и показал свой список Эрику и Эмили: * Добавить дату и время заказа * Запоминать клиентов и их заказы (добавить учетные записи) * Редактировать меню в браузере * Учетные записи для менеджера, бариста, кассира и клиентов * Выводить список заказов за день, неделю и месяц; список заказов каждого клиента * Добавить фотографии напитков * Добавить промоакции * Отчеты: количество потраченных ингредиентов за день, за неделю, за месяц * Голосовые команды * Роботизированная рука для подачи кофе, вкусов и топпингов * Распознавание лиц клиентов 248 Поговорим на Python — А еще мы вчера получили предложения от ИИ. Давайте запишем их в отдельный список, — сказал Саймон и скопировал ответы от ChatGPT. * Примеры кода: попросить ИИ сделать ревью всех функций и дать предложения по улучшению * Отладка: попросить ИИ проанализировать функции на предмет потенциальных ошибок * Библиотеки и инструменты: попросить ИИ дать рекомендации библиотек и инструментов * Разработка алгоритмов: попросить ИИ проанализировать алгоритмы (пока у нас их немного) * Структура проекта: попросить ИИ сделать предложения по улучшению структуры проекта * Написать тесты для функций (ИИ не сказал об этом, но я знаю, что многие используют его для написания тестов) — Тут работы на год! — сказал Саймон. — Боюсь, в книге не хватит места, чтобы все это описать. — В какой книге? — удивился Эрик. — А ты разве не знаешь? — спросил Саймон. — Папа пишет про нас книгу — про тебя, меня и Эмили. Книга о том, как мы разрабатывали проект для кофейни. Он сказал, что не может добавить больше глав, так что боюсь, нам придется продолжить проект онлайн. Предлагаю создать сайт и выкладывать там наши обновления и функции, которые мы разработаем. Что думаете? — Мне нравится, — сказала Эмили. — Это будет что-то типа блога? — Точно, что-то типа блога! Может быть, мы еще добавим репозиторий на GitHub, где будем хранить все наши программы и примеры. — Отлично! — сказал Эрик. — Я слышал про GitHub — там хранят код большинство разработчиков. — А если мы выложим наш код на GitHub, — продолжил Саймон, — то другие смогут использовать его, учиться на нем и, может, даже добавлять новые функции в наше приложение. Может быть, они поделятся своими приложениями, и мы тоже сможем им помочь. Сейчас так разрабатывается большинство проектов. — Отлично! Я в деле, — сказала Эмили. — Я тоже, — добавил Эрик. — Хорошо, тогда давайте продолжим на сайте. Я поговорю с папой, и мы вместе напишем сайт. Увидимся завтра, — сказал Саймон. Ему понравилось работать над этим проектом с Эриком и Эмили. Он и сам узнал кое-что новое. «Пора разобраться в учетных записях, роборуках и распознавании лиц, чтобы я смог рассказать об этом ребятам», — подумал он. Глава 15. Что дальше: планы на будущее 249 ПРИМЕЧАНИЕ Вот сайт, который создали Саймон, Эрик и Эмили: https:// pythonicadventure.com/. Заходите, там вас ждут идеи новых проектов, советы по исправлению ошибок и многое другое! Что мы узнали сегодня Домашняя страница — это первая страница, которую мы видим на веб-сайте. Обычно она сообщает пользователю, что на этом сайте можно делать. Сетевой адрес. Каждый компьютер, подключенный к сети, имеет адрес, который позволяет другим компьютерам в этой же сети найти его. Если мы хотим открыть веб-приложение на ноутбуке Эрика, то нам нужно знать сетевой адрес этого ноутбука. Код из этой главы Код из этой главы можно найти здесь: https://github.com/pavelanni/pythonicadventurecode/tree/main/ch15. A Идеи для первого приложения Приложение для кофейни тебя не увлекает? Создай что-то свое! Все принципы и методы программирования, описанные в этой книге, можно применить и к другим проектам. Просто осмотрись вокруг — и ты найдешь подходящие идеи для приложений. Вот несколько примеров: Пиццерия Приложение для пиццерии будет очень похоже на приложение для кофейни. Ты можешь спросить у клиента: Какую начинку вы предпочитаете? Какой соус? Какой напиток? Для каждого вопроса ты можешь предложить варианты ответов, и клиент выберет их из меню. Кроме этого можно спросить: Вам на тонком тесте или на толстом? Какого размера — маленькую, среднюю, большую? Поострее или не очень? Какую пиццу вы хотите — Маргариту, Пепперони, вегетарианскую? (Загляни в свою любимую пиццерию и посмотри, какие варианты у них есть.) Кафе-мороженое Зайди в свое любимое кафе-мороженое и понаблюдай, как готовится твой заказ. Что у тебя спрашивают? Что предлагают на выбор? Разработай меню на основе полученных ответов. Приложение A. Идеи для первого приложения 251 Скорее всего, у тебя спросят: Вам рожок или стаканчик? Сколько шариков? Каких вкусов? Добавить топпинг? Кафе-мороженое немного отличается от кофейни. После вопроса, сколько шариков, нужно спросить о вкусе каждого шарика. Подумай, как это реализовать на Python. Подсказка: в Python есть функция range(), которую можно вызвать в цикле for. Мы уже использовали ее в нашем меню. Попробуй спросить о вкусе мороженого нужное количество раз с помощью этой функции. Фигурки LEGO Может быть, у тебя большая коллекция LEGO-человечков и их частей и ты хочешь помочь друзьям собрать нового человечка. Что ты спросишь у друзей и какие предложишь варианты ответов? Вот несколько примеров: Выберите голову — улыбающееся лицо, темные очки, борода. Выберите, что на голове — темные волосы, светлые волосы, каска, полицейская фуражка. Выберите туловище — механик, полицейский, рубашка и галстук, футболка. Выберите ноги — синие джинсы, зеленые шорты, коричневые брюки-карго. Выберите аксессуары — меч, радио, молоток, увеличительное стекло. Ты можешь добавить условия. Например, если твой друг выбрал полицейскую фуражку, то в качестве аксессуара он не сможет выбрать бейсбольную биту. Подумай, какие еще условия можно добавить в меню. А как насчет случайного выбора деталей? Так может получиться что-то забавное. Как добавить в меню случайный выбор? Как его реализовать? Подсказка: в Python есть модуль random. Импортируй его с помощью ключевого слова import в начале программы и вызови функцию choice(). Ты можешь дать этой функции список возможных вариантов, а она выберет один вариант случайным образом. При следующем вызове функция choice() выберет что-то другое (или, может быть, то же самое — это же случайный выбор!). Вот пример короткой программы. Напиши и запусти ее. В этой программе мы попросим Python выбрать цвет волос из трех вариантов пять раз. 252 Поговорим на Python Листинг А.1. choise.py import random for _ in range(5): print(random.choice(["брюнет", "блондин", "рыжий"])) Запусти программу как python choice.py, и ты увидишь примерно это: блондин рыжий блондин блондин брюнет Конечно, твой список будет отличаться, скорее всего, в нем будут другие пять вариантов и в другом порядке. Другие идеи для проектов Есть еще идеи? Поделись ими на форуме liveBook: https://livebook.manning.com/ book/a-pythonic-adventure/discussion. Б Установка редактора Mu и окружения для Python Здесь я расскажу, как установить Python на компьютер. Самый простой способ — установить программный редактор, который содержит Python. Я рекомендую установить редактор Mu, который используется в этой книге. Если у тебя будет такой же редактор, то тебе будет проще следить за диалогами и выполнять инструкции из книги. Я расскажу и про другие способы установки Python — попробуй их тоже. Mu Для установки редактора Mu: 1. Открой страницу редактора Mu в браузере: https://codewith.mu/. 254 Поговорим на Python 2. Нажми Download (Загрузить) (зеленая кнопка). Откроется страница загрузки редактора Mu. 3. Нажми Download рядом с названием твоей операционной системы. Браузер загрузит установочный файл, который для нее подходит: • .msi — файл для Windows; • .dmg — файл для macOS; • .AppImage — файл для Linux. 4. Нажми Instructions (Инструкции) рядом с названием твоей операционной системы и следуй инструкциям. 5. Открой редактор Mu так же, как ты открываешь все приложения в своей операционной системе. Теперь ты готов работать над проектом! Редактор Mu также можно использовать для программирования микроконтроллеров и создания роботов, но это тема отдельной книги. Приложение Б. Установка редактора Mu и окружения для Python 255 Thonny Thonny — еще один отличный редактор кода на Python, разработанный с расчетом на новичков. Он доступен по ссылке https://thonny.org/. На первой странице ты найдешь установочные файлы для Windows, macOS и Linux. Для Windows загрузи и запусти файл.exe. Для macOS загрузи файл .pkg и установи его. Для Linux выполни команды, описанные в инструкции. После установки запусти приложение и открой его настройки. Можешь выбрать цветовую схему, шрифт редактора и терминала и многое другое из десятка предложенных вариантов. 256 Поговорим на Python В редакторе Thonny есть очень полезная функция Assistant (Помощник). В настройках можно задать, чтобы она запускалась каждый раз, когда в коде появляется ошибка или предупреждение о возможной ошибке. Assistant может предположить, что не так с твоим кодом. Попробуй сделать небольшую ошибку (например, опечатку в имени переменной), а потом запустить программу, и ты увидишь Assistant в действии. Python Все редакторы кода, про которые я рассказал, включают Python в свои установочные пакеты. Но по разным причинам тебе может потребоваться установить Python отдельно. В операционных системах macOS и Linux Python уже установлен. Скорее всего, не последней версии, но это не проблема: все программы, которые мы разработали в этой книге, будут работать в Python, начиная с версии 3.5. Ничего дополнительно устанавливать не нужно — по крайней мере для этой книги. Если у тебя Windows, перейди на официальный сайт Python https://www.python. org/downloads/windows/ и загрузи установочные файлы. Внимательно прочти описание и правильно подбери версию Python к своей версии Windows.